summaryrefslogtreecommitdiffstats
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/amzn/amzn-nvme.c54
-rw-r--r--plugins/amzn/amzn-nvme.h18
-rw-r--r--plugins/dell/dell-nvme.c54
-rw-r--r--plugins/dell/dell-nvme.h18
-rw-r--r--plugins/dera/dera-nvme.c205
-rw-r--r--plugins/dera/dera-nvme.h18
-rw-r--r--plugins/fdp/fdp.c541
-rw-r--r--plugins/fdp/fdp.h24
-rw-r--r--plugins/huawei/huawei-nvme.c383
-rw-r--r--plugins/huawei/huawei-nvme.h19
-rw-r--r--plugins/innogrit/innogrit-nvme.c466
-rw-r--r--plugins/innogrit/innogrit-nvme.h20
-rw-r--r--plugins/innogrit/typedef.h79
-rw-r--r--plugins/inspur/inspur-nvme.c233
-rw-r--r--plugins/inspur/inspur-nvme.h18
-rw-r--r--plugins/inspur/inspur-utils.h175
-rw-r--r--plugins/intel/intel-nvme.c1738
-rw-r--r--plugins/intel/intel-nvme.h25
-rw-r--r--plugins/memblaze/memblaze-nvme.c1842
-rw-r--r--plugins/memblaze/memblaze-nvme.h29
-rw-r--r--plugins/memblaze/memblaze-utils.h217
-rw-r--r--plugins/meson.build35
-rw-r--r--plugins/micron/micron-nvme.c3390
-rw-r--r--plugins/micron/micron-nvme.h36
-rw-r--r--plugins/nbft/nbft-plugin.c563
-rw-r--r--plugins/nbft/nbft-plugin.h18
-rw-r--r--plugins/netapp/netapp-nvme.c654
-rw-r--r--plugins/netapp/netapp-nvme.h19
-rw-r--r--plugins/nvidia/nvidia-nvme.c53
-rw-r--r--plugins/nvidia/nvidia-nvme.h18
-rw-r--r--plugins/ocp/meson.build8
-rw-r--r--plugins/ocp/ocp-clear-features.c93
-rw-r--r--plugins/ocp/ocp-clear-features.h12
-rw-r--r--plugins/ocp/ocp-fw-activation-history.c225
-rw-r--r--plugins/ocp/ocp-fw-activation-history.h17
-rw-r--r--plugins/ocp/ocp-nvme.c2971
-rw-r--r--plugins/ocp/ocp-nvme.h38
-rw-r--r--plugins/ocp/ocp-smart-extended-log.c352
-rw-r--r--plugins/ocp/ocp-smart-extended-log.h18
-rw-r--r--plugins/ocp/ocp-utils.c32
-rw-r--r--plugins/ocp/ocp-utils.h18
-rw-r--r--plugins/scaleflux/sfx-nvme.c1687
-rw-r--r--plugins/scaleflux/sfx-nvme.h26
-rw-r--r--plugins/seagate/seagate-diag.h388
-rw-r--r--plugins/seagate/seagate-nvme.c1968
-rw-r--r--plugins/seagate/seagate-nvme.h51
-rw-r--r--plugins/sed/meson.build4
-rw-r--r--plugins/sed/sed.c178
-rw-r--r--plugins/sed/sed.h19
-rw-r--r--plugins/sed/sedopal_cmd.c512
-rw-r--r--plugins/sed/sedopal_cmd.h55
-rw-r--r--plugins/sed/sedopal_spec.h71
-rw-r--r--plugins/shannon/shannon-nvme.c381
-rw-r--r--plugins/shannon/shannon-nvme.h21
-rw-r--r--plugins/solidigm/meson.build16
-rw-r--r--plugins/solidigm/solidigm-garbage-collection.c138
-rw-r--r--plugins/solidigm/solidigm-garbage-collection.h8
-rw-r--r--plugins/solidigm/solidigm-get-drive-info.c79
-rw-r--r--plugins/solidigm/solidigm-get-drive-info.h8
-rw-r--r--plugins/solidigm/solidigm-id-ctrl.c73
-rw-r--r--plugins/solidigm/solidigm-id-ctrl.h10
-rw-r--r--plugins/solidigm/solidigm-internal-logs.c657
-rw-r--r--plugins/solidigm/solidigm-internal-logs.h8
-rw-r--r--plugins/solidigm/solidigm-latency-tracking.c474
-rw-r--r--plugins/solidigm/solidigm-latency-tracking.h9
-rw-r--r--plugins/solidigm/solidigm-log-page-dir.c286
-rw-r--r--plugins/solidigm/solidigm-log-page-dir.h17
-rw-r--r--plugins/solidigm/solidigm-market-log.c63
-rw-r--r--plugins/solidigm/solidigm-market-log.h8
-rw-r--r--plugins/solidigm/solidigm-nvme.c109
-rw-r--r--plugins/solidigm/solidigm-nvme.h40
-rw-r--r--plugins/solidigm/solidigm-ocp-version.c25
-rw-r--r--plugins/solidigm/solidigm-ocp-version.h8
-rw-r--r--plugins/solidigm/solidigm-smart.c271
-rw-r--r--plugins/solidigm/solidigm-smart.h8
-rw-r--r--plugins/solidigm/solidigm-telemetry.c189
-rw-r--r--plugins/solidigm/solidigm-telemetry.h8
-rw-r--r--plugins/solidigm/solidigm-telemetry/cod.c190
-rw-r--r--plugins/solidigm/solidigm-telemetry/cod.h9
-rw-r--r--plugins/solidigm/solidigm-telemetry/config.c76
-rw-r--r--plugins/solidigm/solidigm-telemetry/config.h19
-rw-r--r--plugins/solidigm/solidigm-telemetry/data-area.c472
-rw-r--r--plugins/solidigm/solidigm-telemetry/data-area.h10
-rw-r--r--plugins/solidigm/solidigm-telemetry/header.c223
-rw-r--r--plugins/solidigm/solidigm-telemetry/header.h9
-rw-r--r--plugins/solidigm/solidigm-telemetry/meson.build7
-rw-r--r--plugins/solidigm/solidigm-telemetry/nlog.c131
-rw-r--r--plugins/solidigm/solidigm-telemetry/nlog.h11
-rw-r--r--plugins/solidigm/solidigm-telemetry/telemetry-log.h31
-rw-r--r--plugins/solidigm/solidigm-temp-stats.c108
-rw-r--r--plugins/solidigm/solidigm-temp-stats.h8
-rw-r--r--plugins/solidigm/solidigm-util.c20
-rw-r--r--plugins/solidigm/solidigm-util.h12
-rw-r--r--plugins/toshiba/toshiba-nvme.c573
-rw-r--r--plugins/toshiba/toshiba-nvme.h21
-rw-r--r--plugins/transcend/transcend-nvme.c86
-rw-r--r--plugins/transcend/transcend-nvme.h21
-rw-r--r--plugins/virtium/virtium-nvme.c1051
-rw-r--r--plugins/virtium/virtium-nvme.h22
-rw-r--r--plugins/wdc/wdc-nvme.c12486
-rw-r--r--plugins/wdc/wdc-nvme.h91
-rw-r--r--plugins/wdc/wdc-utils.c196
-rw-r--r--plugins/wdc/wdc-utils.h81
-rw-r--r--plugins/ymtc/ymtc-nvme.c161
-rw-r--r--plugins/ymtc/ymtc-nvme.h25
-rw-r--r--plugins/ymtc/ymtc-utils.h81
-rw-r--r--plugins/zns/zns.c1274
-rw-r--r--plugins/zns/zns.h32
108 files changed, 40156 insertions, 0 deletions
diff --git a/plugins/amzn/amzn-nvme.c b/plugins/amzn/amzn-nvme.c
new file mode 100644
index 0000000..d359cc3
--- /dev/null
+++ b/plugins/amzn/amzn-nvme.c
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+
+#define CREATE_CMD
+#include "amzn-nvme.h"
+
+struct nvme_vu_id_ctrl_field {
+ __u8 bdev[32];
+ __u8 reserved0[992];
+};
+
+static void json_amzn_id_ctrl(struct nvme_vu_id_ctrl_field *id,
+ char *bdev,
+ struct json_object *root)
+{
+ json_object_add_value_string(root, "bdev", bdev);
+}
+
+static void amzn_id_ctrl(__u8 *vs, struct json_object *root)
+{
+ struct nvme_vu_id_ctrl_field *id = (struct nvme_vu_id_ctrl_field *)vs;
+
+ char bdev[32] = { 0 };
+
+ int len = 0;
+
+ while (len < 31) {
+ if (id->bdev[++len] == ' ')
+ break;
+ }
+ snprintf(bdev, len+1, "%s", id->bdev);
+
+ if (root) {
+ json_amzn_id_ctrl(id, bdev, root);
+ return;
+ }
+
+ printf("bdev : %s\n", bdev);
+}
+
+static int id_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return __id_ctrl(argc, argv, cmd, plugin, amzn_id_ctrl);
+}
diff --git a/plugins/amzn/amzn-nvme.h b/plugins/amzn/amzn-nvme.h
new file mode 100644
index 0000000..f6c4f8b
--- /dev/null
+++ b/plugins/amzn/amzn-nvme.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/amzn/amzn-nvme
+
+#if !defined(AMZN_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define AMZN_NVME
+
+#include "cmd.h"
+
+PLUGIN(NAME("amzn", "Amazon vendor specific extensions", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("id-ctrl", "Send NVMe Identify Controller", id_ctrl)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/dell/dell-nvme.c b/plugins/dell/dell-nvme.c
new file mode 100644
index 0000000..8ed10e7
--- /dev/null
+++ b/plugins/dell/dell-nvme.c
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// Copyright © 2022 Dell Inc. or its subsidiaries. All Rights Reserved.
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+
+#define CREATE_CMD
+#include "dell-nvme.h"
+
+#define ARRAY_NAME_LEN 80
+
+struct nvme_vu_id_ctrl_field {
+ __u16 dell_mjr;
+ __u16 dell_mnr;
+ __u16 dell_ter;
+ __u8 reserved0[1018];
+};
+
+static void dell_id_ctrl(__u8 *vs, struct json_object *root)
+{
+ struct nvme_vu_id_ctrl_field *id = (struct nvme_vu_id_ctrl_field *)vs;
+ char array_ver[16] = { 0 };
+ char array_name[ARRAY_NAME_LEN + 1] = {0};
+
+ snprintf(array_ver, sizeof(array_ver), "0x%04x%04x%04x",
+ le16_to_cpu(id->dell_mjr),
+ le16_to_cpu(id->dell_mnr),
+ le16_to_cpu(id->dell_ter));
+
+ memcpy(array_name, vs + sizeof(array_ver), ARRAY_NAME_LEN);
+
+ if (root) {
+ json_object_add_value_string(root, "array_name", strlen(array_name) > 1 ? array_name : "NULL");
+ json_object_add_value_string(root, "array_ver", array_ver);
+ return;
+ }
+
+ printf("array_name : %s\n", strlen(array_name) > 1 ? array_name : "NULL");
+ printf("array_ver : %s\n", array_ver);
+}
+
+static int id_ctrl(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ return __id_ctrl(argc, argv, cmd, plugin, dell_id_ctrl);
+}
diff --git a/plugins/dell/dell-nvme.h b/plugins/dell/dell-nvme.h
new file mode 100644
index 0000000..aaf0de1
--- /dev/null
+++ b/plugins/dell/dell-nvme.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/dell/dell-nvme
+
+#if !defined(DELL_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define DELL_NVME
+
+#include "cmd.h"
+
+PLUGIN(NAME("dell", "DELL vendor specific extensions", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("id-ctrl", "Send NVMe Identify Controller", id_ctrl)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/dera/dera-nvme.c b/plugins/dera/dera-nvme.c
new file mode 100644
index 0000000..ca4b53d
--- /dev/null
+++ b/plugins/dera/dera-nvme.c
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "nvme-print.h"
+
+#define CREATE_CMD
+#include "dera-nvme.h"
+
+static int char4_to_int(__u8 *data)
+{
+ int i;
+ int result = 0;
+
+ for (i = 0; i < 4; i++) {
+ result = result << 8;
+ result += data[3 - i];
+ }
+ return result;
+}
+
+struct nvme_dera_smart_info_log
+{
+ __u8 quick_rebuild_cnt0[4];
+ __u8 quick_rebuild_cnt1[4];
+ __u8 full_rebuild_cnt0[4];
+ __u8 full_rebuild_cnt1[4];
+ __u8 raw_rebuild_cnt0[4];
+ __u8 raw_rebuild_cnt1[4];
+ __u8 cap_aged;
+ __u8 cap_aged_ratio;
+ __u8 cap_status;
+ __u8 cap_voltage[4];
+ __u8 cap_charge_ctrl_en;
+ __u8 cap_charge_ctrl_val[2];
+ __u8 cap_charge_max_thr[2];
+ __u8 cap_charge_min_thr[2];
+ __u8 dev_status;
+ __u8 dev_status_up;
+ __u8 nand_erase_err_cnt[4];
+ __u8 nand_program_err_cnt[4];
+ __u8 ddra_1bit_err[2];
+ __u8 ddra_2bit_err[2];
+ __u8 ddrb_1bit_err[2];
+ __u8 ddrb_2bit_err[2];
+ __u8 ddr_err_bit;
+ __u8 pcie_corr_err[2];
+ __u8 pcie_uncorr_err[2];
+ __u8 pcie_fatal_err[2];
+ __u8 pcie_err_bit;
+ __u8 power_level;
+ __u8 current_power[2];
+ __u8 nand_init_fail[2];
+ __u8 fw_loader_version[8];
+ __u8 uefi_driver_version[8];
+ __u8 gpio0_err[2];
+ __u8 gpio5_err[2];
+ __u8 gpio_err_bit[2];
+ __u8 rebuild_percent;
+ __u8 pcie_volt_status;
+ __u8 current_pcie_volt[2];
+ __u8 init_pcie_volt_thr[2];
+ __u8 rt_pcie_volt_thr[2];
+ __u8 init_pcie_volt_low[2];
+ __u8 rt_pcie_volt_low[2];
+ __u8 temp_sensor_abnormal[2];
+ __u8 nand_read_retry_fail_cnt[4];
+ __u8 fw_slot_version[8];
+ __u8 rsved[395];
+};
+
+enum dera_device_status
+{
+ DEVICE_STATUS_READY = 0x00,
+ DEVICE_STATUS_QUICK_REBUILDING = 0x01,
+ DEVICE_STATUS_FULL_REBUILDING = 0x02,
+ DEVICE_STATUS_RAW_REBUILDING = 0x03,
+ DEVICE_STATUS_CARD_READ_ONLY = 0x04,
+ DEVICE_STATUS_FATAL_ERROR = 0x05,
+ DEVICE_STATUS_BUSY = 0x06,
+ DEVICE_STAUTS_LOW_LEVEL_FORMAT = 0x07,
+ DEVICE_STAUTS_FW_COMMITING = 0x08,
+ DEVICE_STAUTS__OVER_TEMPRATURE = 0x09,
+};
+
+static int nvme_dera_get_device_status(int fd, enum dera_device_status *result)
+{
+ int err = 0;
+
+ struct nvme_passthru_cmd cmd = {
+ .opcode = 0xc0,
+ .addr = (__u64)(uintptr_t)NULL,
+ .data_len = 0,
+ .cdw10 = 0,
+ .cdw12 = 0x104,
+ };
+
+ err = nvme_submit_admin_passthru(fd, &cmd, NULL);
+ if (!err && result)
+ *result = cmd.result;
+
+ return err;
+}
+
+static int get_status(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ struct nvme_dera_smart_info_log log;
+ enum dera_device_status state = DEVICE_STATUS_FATAL_ERROR;
+ char *desc = "Get the Dera device status";
+ struct nvme_dev *dev;
+ int err;
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = nvme_get_log_simple(dev_fd(dev), 0xc0, sizeof(log), &log);
+ if (err)
+ goto exit;
+
+ static const char *dev_status[] = {
+ "Normal",
+ "Quick Rebuilding",
+ "Full Rebuilding",
+ "Raw Rebuilding",
+ "Card Read Only",
+ "Fatal Error",
+ "Busy",
+ "Low Level Format",
+ "Firmware Committing",
+ "Over Temperature" };
+
+ static const char *volt_status[] = {
+ "Normal",
+ "Initial Low",
+ "Runtime Low",
+ };
+
+ err = nvme_dera_get_device_status(dev_fd(dev), &state);
+ if (!err) {
+ if (state > 0 && state < 4)
+ printf("device_status : %s %d%% completed\n", dev_status[state], log.rebuild_percent);
+ else
+ printf("device_status : %s\n", dev_status[state]);
+ } else {
+ goto exit;
+ }
+
+ printf("dev_status_up : %s\n", dev_status[log.dev_status_up]);
+ printf("cap_aged : %s\n", log.cap_aged == 1 ? "True" : "False");
+ printf("cap_aged_ratio : %d%%\n", log.cap_aged_ratio < 100 ? log.cap_aged_ratio : 100);
+ printf("cap_status : %s\n", log.cap_status == 0 ? "Normal" : (log.cap_status == 1 ? "Warning" : "Critical"));
+ printf("cap_voltage : %d mV\n", char4_to_int(log.cap_voltage));
+ printf("nand_erase_err_cnt : %d\n", char4_to_int(log.nand_erase_err_cnt));
+ printf("nand_program_err_cnt : %d\n", char4_to_int(log.nand_program_err_cnt));
+ printf("ddra_1bit_err : %d\n", log.ddra_1bit_err[1] << 8 | log.ddra_1bit_err[0]);
+ printf("ddra_2bit_err : %d\n", log.ddra_2bit_err[1] << 8 | log.ddra_2bit_err[0]);
+ printf("ddrb_1bit_err : %d\n", log.ddrb_1bit_err[1] << 8 | log.ddrb_1bit_err[0]);
+ printf("ddrb_2bit_err : %d\n", log.ddrb_2bit_err[1] << 8 | log.ddrb_2bit_err[0]);
+ printf("ddr_err_bit : %d\n", log.ddr_err_bit);
+ printf("pcie_corr_err : %d\n", log.pcie_corr_err[1] << 8 | log.pcie_corr_err[0]);
+ printf("pcie_uncorr_err : %d\n", log.pcie_uncorr_err[1] << 8 | log.pcie_uncorr_err[0]);
+ printf("pcie_fatal_err : %d\n", log.pcie_fatal_err[1] << 8 | log.pcie_fatal_err[0]);
+ printf("power_level : %d W\n", log.power_level);
+ printf("current_power : %d mW\n", log.current_power[1] << 8 | log.current_power[0]);
+ printf("nand_init_fail : %d\n", log.nand_init_fail[1] << 8 | log.nand_init_fail[0]);
+ printf("fw_loader_version : %.*s\n", 8, log.fw_loader_version);
+ printf("uefi_driver_version : %.*s\n", 8, log.uefi_driver_version);
+
+ if (log.pcie_volt_status < sizeof(volt_status) / sizeof(const char *))
+ printf("pcie_volt_status : %s\n", volt_status[log.pcie_volt_status]);
+ else
+ printf("pcie_volt_status : Unknown\n");
+
+ printf("current_pcie_volt : %d mV\n", log.current_pcie_volt[1] << 8 | log.current_pcie_volt[0]);
+ printf("init_pcie_volt_low_cnt : %d\n", log.init_pcie_volt_low[1] << 8 | log.init_pcie_volt_low[0]);
+ printf("rt_pcie_volt_low_cnt : %d\n", log.rt_pcie_volt_low[1] << 8 | log.rt_pcie_volt_low[0]);
+ printf("temp_sensor_abnormal_cnt : %d\n", log.temp_sensor_abnormal[1] << 8 | log.temp_sensor_abnormal[0]);
+ printf("nand_read_retry_fail_cnt : %d\n", char4_to_int(log.nand_read_retry_fail_cnt));
+ printf("fw_slot_version : %.*s\n", 8, log.fw_slot_version);
+
+exit:
+ if (err > 0)
+ nvme_show_status(err);
+
+ dev_close(dev);
+ return err;
+}
+
diff --git a/plugins/dera/dera-nvme.h b/plugins/dera/dera-nvme.h
new file mode 100644
index 0000000..a5bb0ae
--- /dev/null
+++ b/plugins/dera/dera-nvme.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/dera/dera-nvme
+
+#if !defined(DERA_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define DERA_NVME
+
+#include "cmd.h"
+
+PLUGIN(NAME("dera", "Dera vendor specific extensions", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("smart-log-add", "Retrieve Dera SMART Log, show it", get_status, "stat")
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/fdp/fdp.c b/plugins/fdp/fdp.c
new file mode 100644
index 0000000..2a221f8
--- /dev/null
+++ b/plugins/fdp/fdp.c
@@ -0,0 +1,541 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <linux/fs.h>
+#include <sys/stat.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "nvme-print.h"
+
+#define CREATE_CMD
+#include "fdp.h"
+
+static int fdp_configs(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ const char *desc = "Get Flexible Data Placement Configurations";
+ const char *egid = "Endurance group identifier";
+ const char *human_readable = "show log in readable format";
+ const char *raw = "use binary output";
+
+ enum nvme_print_flags flags;
+ struct nvme_dev *dev;
+ struct nvme_fdp_config_log hdr;
+ void *log = NULL;
+ int err;
+
+ struct config {
+ __u16 egid;
+ char *output_format;
+ bool human_readable;
+ bool raw_binary;
+ };
+
+ struct config cfg = {
+ .egid = 0,
+ .output_format = "normal",
+ .raw_binary = false,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("endgrp-id", 'e', &cfg.egid, egid),
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_FLAG("human-readable", 'H', &cfg.human_readable, human_readable),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = validate_output_format(cfg.output_format, &flags);
+ if (flags < 0)
+ goto out;
+
+ if (cfg.raw_binary)
+ flags = BINARY;
+
+ if (cfg.human_readable)
+ flags |= VERBOSE;
+
+ if (!cfg.egid) {
+ fprintf(stderr, "endurance group identifier required\n");
+ err = -EINVAL;
+ goto out;
+ }
+
+ err = nvme_get_log_fdp_configurations(dev->direct.fd, cfg.egid, 0,
+ sizeof(hdr), &hdr);
+ if (err) {
+ nvme_show_status(errno);
+ goto out;
+ }
+
+ log = malloc(hdr.size);
+ if (!log) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ err = nvme_get_log_fdp_configurations(dev->direct.fd, cfg.egid, 0,
+ hdr.size, log);
+ if (err) {
+ nvme_show_status(errno);
+ goto out;
+ }
+
+ nvme_show_fdp_configs(log, hdr.size, flags);
+
+out:
+ dev_close(dev);
+ free(log);
+
+ return err;
+}
+
+static int fdp_usage(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Get Flexible Data Placement Reclaim Unit Handle Usage";
+ const char *egid = "Endurance group identifier";
+ const char *raw = "use binary output";
+
+ enum nvme_print_flags flags;
+ struct nvme_dev *dev;
+ struct nvme_fdp_ruhu_log hdr;
+ size_t len;
+ void *log = NULL;
+ int err;
+
+ struct config {
+ __u16 egid;
+ char *output_format;
+ bool raw_binary;
+ };
+
+ struct config cfg = {
+ .egid = 0,
+ .output_format = "normal",
+ .raw_binary = false,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("endgrp-id", 'e', &cfg.egid, egid),
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = validate_output_format(cfg.output_format, &flags);
+ if (flags < 0)
+ goto out;
+
+ if (cfg.raw_binary)
+ flags = BINARY;
+
+ err = nvme_get_log_reclaim_unit_handle_usage(dev->direct.fd, cfg.egid,
+ 0, sizeof(hdr), &hdr);
+ if (err) {
+ nvme_show_status(err);
+ goto out;
+ }
+
+ len = sizeof(hdr) + le16_to_cpu(hdr.nruh) * sizeof(struct nvme_fdp_ruhu_desc);
+ log = malloc(len);
+ if (!log) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ err = nvme_get_log_reclaim_unit_handle_usage(dev->direct.fd, cfg.egid,
+ 0, len, log);
+ if (err) {
+ nvme_show_status(err);
+ goto out;
+ }
+
+ nvme_show_fdp_usage(log, len, flags);
+
+out:
+ dev_close(dev);
+ free(log);
+
+ return err;
+}
+
+static int fdp_stats(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Get Flexible Data Placement Statistics";
+ const char *egid = "Endurance group identifier";
+ const char *raw = "use binary output";
+
+ enum nvme_print_flags flags;
+ struct nvme_dev *dev;
+ struct nvme_fdp_stats_log stats;
+ int err;
+
+ struct config {
+ __u16 egid;
+ char *output_format;
+ bool raw_binary;
+ };
+
+ struct config cfg = {
+ .egid = 0,
+ .output_format = "normal",
+ .raw_binary = false,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("endgrp-id", 'e', &cfg.egid, egid),
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = validate_output_format(cfg.output_format, &flags);
+ if (flags < 0)
+ goto out;
+
+ if (cfg.raw_binary)
+ flags = BINARY;
+
+ memset(&stats, 0x0, sizeof(stats));
+
+ err = nvme_get_log_fdp_stats(dev->direct.fd, cfg.egid, 0, sizeof(stats), &stats);
+ if (err) {
+ nvme_show_status(err);
+ goto out;
+ }
+
+ nvme_show_fdp_stats(&stats, flags);
+
+out:
+ dev_close(dev);
+
+ return err;
+}
+
+static int fdp_events(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Get Flexible Data Placement Events";
+ const char *egid = "Endurance group identifier";
+ const char *host_events = "Get host events";
+ const char *raw = "use binary output";
+
+ enum nvme_print_flags flags;
+ struct nvme_dev *dev;
+ struct nvme_fdp_events_log events;
+ int err;
+
+ struct config {
+ __u16 egid;
+ bool host_events;
+ char *output_format;
+ bool raw_binary;
+ };
+
+ struct config cfg = {
+ .egid = 0,
+ .host_events = false,
+ .output_format = "normal",
+ .raw_binary = false,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("endgrp-id", 'e', &cfg.egid, egid),
+ OPT_FLAG("host-events", 'E', &cfg.host_events, host_events),
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = validate_output_format(cfg.output_format, &flags);
+ if (flags < 0)
+ goto out;
+
+ if (cfg.raw_binary)
+ flags = BINARY;
+
+ memset(&events, 0x0, sizeof(events));
+
+ err = nvme_get_log_fdp_events(dev->direct.fd, cfg.egid,
+ cfg.host_events, 0, sizeof(events), &events);
+ if (err) {
+ nvme_show_status(err);
+ goto out;
+ }
+
+ nvme_show_fdp_events(&events, flags);
+
+out:
+ dev_close(dev);
+
+ return err;
+}
+
+static int fdp_status(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Reclaim Unit Handle Status";
+ const char *namespace_id = "Namespace identifier";
+ const char *raw = "use binary output";
+
+ enum nvme_print_flags flags;
+ struct nvme_dev *dev;
+ struct nvme_fdp_ruh_status hdr;
+ size_t len;
+ void *buf = NULL;
+ int err = -1;
+
+ struct config {
+ __u32 namespace_id;
+ char *output_format;
+ bool raw_binary;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ .raw_binary = false,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = validate_output_format(cfg.output_format, &flags);
+ if (flags < 0)
+ goto out;
+
+ if (cfg.raw_binary)
+ flags = BINARY;
+
+ if (!cfg.namespace_id) {
+ err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id);
+ if (err < 0) {
+ perror("get-namespace-id");
+ goto out;
+ }
+ }
+
+ err = nvme_fdp_reclaim_unit_handle_status(dev_fd(dev),
+ cfg.namespace_id, sizeof(hdr), &hdr);
+ if (err) {
+ nvme_show_status(err);
+ goto out;
+ }
+
+ len = sizeof(struct nvme_fdp_ruh_status) +
+ le16_to_cpu(hdr.nruhsd) * sizeof(struct nvme_fdp_ruh_status_desc);
+ buf = malloc(len);
+ if (!buf) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ err = nvme_fdp_reclaim_unit_handle_status(dev_fd(dev),
+ cfg.namespace_id, len, buf);
+ if (err) {
+ nvme_show_status(err);
+ goto out;
+ }
+
+ nvme_show_fdp_ruh_status(buf, len, flags);
+
+out:
+ free(buf);
+ dev_close(dev);
+
+ return err;
+}
+
+static int fdp_update(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Reclaim Unit Handle Update";
+ const char *namespace_id = "Namespace identifier";
+ const char *_pids = "Comma-separated list of placement identifiers to update";
+
+ struct nvme_dev *dev;
+ unsigned short pids[256];
+ __u16 buf[256];
+ int npids;
+ int err = -1;
+
+ struct config {
+ __u32 namespace_id;
+ char *pids;
+ };
+
+ struct config cfg = {
+ .pids = "",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_LIST("pids", 'p', &cfg.pids, _pids),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ npids = argconfig_parse_comma_sep_array_short(cfg.pids, pids, ARRAY_SIZE(pids));
+ if (npids < 0) {
+ perror("could not parse pids");
+ err = -EINVAL;
+ goto out;
+ } else if (npids == 0) {
+ fprintf(stderr, "no placement identifiers set\n");
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (!cfg.namespace_id) {
+ err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id);
+ if (err < 0) {
+ perror("get-namespace-id");
+ goto out;
+ }
+ }
+
+ for (unsigned int i = 0; i < npids; i++)
+ buf[i] = cpu_to_le16(pids[i]);
+
+ err = nvme_fdp_reclaim_unit_handle_update(dev_fd(dev), cfg.namespace_id, npids, buf);
+ if (err) {
+ nvme_show_status(err);
+ goto out;
+ }
+
+ printf("update: Success\n");
+
+out:
+ dev_close(dev);
+
+ return err;
+}
+
+static int fdp_set_events(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Enable or disable FDP events";
+ const char *namespace_id = "Namespace identifier";
+ const char *enable = "Enable/disable event";
+ const char *event_types = "Comma-separated list of event types";
+ const char *ph = "Placement Handle";
+ const char *save = "specifies that the controller shall save the attribute";
+
+ struct nvme_dev *dev;
+ int err = -1;
+ unsigned short evts[255];
+ int nev;
+ __u8 buf[255];
+
+ struct config {
+ __u32 namespace_id;
+ __u16 ph;
+ char *event_types;
+ bool enable;
+ bool save;
+ };
+
+ struct config cfg = {
+ .enable = false,
+ .save = false,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_SHRT("placement-handle", 'p', &cfg.ph, ph),
+ OPT_FLAG("enable", 'e', &cfg.enable, enable),
+ OPT_FLAG("save", 's', &cfg.save, save),
+ OPT_LIST("event-types", 't', &cfg.event_types, event_types),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ nev = argconfig_parse_comma_sep_array_short(cfg.event_types, evts, ARRAY_SIZE(evts));
+ if (nev < 0) {
+ perror("could not parse event types");
+ err = -EINVAL;
+ goto out;
+ } else if (nev == 0) {
+ fprintf(stderr, "no event types set\n");
+ err = -EINVAL;
+ goto out;
+ } else if (nev > 255) {
+ fprintf(stderr, "too many event types (max 255)\n");
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (!cfg.namespace_id) {
+ err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id);
+ if (err < 0) {
+ if (errno != ENOTTY) {
+ fprintf(stderr, "get-namespace-id: %s\n", nvme_strerror(errno));
+ goto out;
+ }
+
+ cfg.namespace_id = NVME_NSID_ALL;
+ }
+ }
+
+ for (unsigned int i = 0; i < nev; i++)
+ buf[i] = (__u8)evts[i];
+
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = NVME_FEAT_FID_FDP_EVENTS,
+ .save = cfg.save,
+ .nsid = cfg.namespace_id,
+ .cdw11 = (nev << 16) | cfg.ph,
+ .cdw12 = cfg.enable ? 0x1 : 0x0,
+ .data_len = sizeof(buf),
+ .data = buf,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ };
+
+ err = nvme_set_features(&args);
+ if (err) {
+ nvme_show_status(err);
+ goto out;
+ }
+
+ printf("set-events: Success\n");
+
+out:
+ dev_close(dev);
+
+ return err;
+}
diff --git a/plugins/fdp/fdp.h b/plugins/fdp/fdp.h
new file mode 100644
index 0000000..f162b32
--- /dev/null
+++ b/plugins/fdp/fdp.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/fdp/fdp
+
+#if !defined(FDP_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define FDP_NVME
+
+#include "cmd.h"
+
+PLUGIN(NAME("fdp", "Manage Flexible Data Placement enabled devices", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("configs", "List configurations", fdp_configs)
+ ENTRY("usage", "Show reclaim unit handle usage", fdp_usage)
+ ENTRY("stats", "Show statistics", fdp_stats)
+ ENTRY("events", "List events affecting reclaim units and media usage", fdp_events)
+ ENTRY("status", "Show reclaim unit handle status", fdp_status)
+ ENTRY("update", "Update a reclaim unit handle", fdp_update)
+ ENTRY("set-events", "Enabled or disable events", fdp_set_events)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/huawei/huawei-nvme.c b/plugins/huawei/huawei-nvme.c
new file mode 100644
index 0000000..0272dea
--- /dev/null
+++ b/plugins/huawei/huawei-nvme.c
@@ -0,0 +1,383 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2017-2019 Huawei Corporation or its affiliates.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Author: Zou Ming<zouming.zouming@huawei.com>,
+ * Yang Feng <philip.yang@huawei.com>
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <dirent.h>
+
+#include <sys/stat.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+
+#include "util/suffix.h"
+
+#define CREATE_CMD
+#include "huawei-nvme.h"
+
+#define HW_SSD_PCI_VENDOR_ID 0x19E5
+#define ARRAY_NAME_LEN 80
+#define NS_NAME_LEN 40
+
+#define MIN_ARRAY_NAME_LEN 16
+#define MIN_NS_NAME_LEN 16
+
+struct huawei_list_item {
+ char node[1024];
+ struct nvme_id_ctrl ctrl;
+ unsigned int nsid;
+ struct nvme_id_ns ns;
+ unsigned int block;
+ char ns_name[NS_NAME_LEN];
+ char array_name[ARRAY_NAME_LEN];
+ bool huawei_device;
+};
+
+struct huawei_list_element_len {
+ unsigned int node;
+ unsigned int ns_name;
+ unsigned int nguid;
+ unsigned int ns_id;
+ unsigned int usage;
+ unsigned int array_name;
+};
+
+static int huawei_get_nvme_info(int fd, struct huawei_list_item *item, const char *node)
+{
+ int err;
+ int len;
+ struct stat nvme_stat_info;
+
+ memset(item, 0, sizeof(*item));
+
+ err = nvme_identify_ctrl(fd, &item->ctrl);
+ if (err)
+ return err;
+
+ /*identify huawei device*/
+ if (strstr(item->ctrl.mn, "Huawei") == NULL &&
+ le16_to_cpu(item->ctrl.vid) != HW_SSD_PCI_VENDOR_ID) {
+ item->huawei_device = false;
+ return 0;
+ }
+
+ item->huawei_device = true;
+ err = nvme_get_nsid(fd, &item->nsid);
+ err = nvme_identify_ns(fd, item->nsid, &item->ns);
+ if (err)
+ return err;
+
+ err = fstat(fd, &nvme_stat_info);
+ if (err < 0)
+ return err;
+
+ strncpy(item->node, node, sizeof(item->node));
+ item->node[sizeof(item->node) - 1] = '\0';
+ item->block = S_ISBLK(nvme_stat_info.st_mode);
+
+ if (item->ns.vs[0] == 0) {
+ len = snprintf(item->ns_name, NS_NAME_LEN, "%s", "----");
+ if (len < 0)
+ return -EINVAL;
+ } else {
+ memcpy(item->ns_name, item->ns.vs, NS_NAME_LEN);
+ item->ns_name[NS_NAME_LEN - 1] = '\0';
+ }
+
+ if (item->ctrl.vs[0] == 0) {
+ len = snprintf(item->array_name, ARRAY_NAME_LEN, "%s", "----");
+ if (len < 0)
+ return -EINVAL;
+ } else {
+ memcpy(item->array_name, item->ctrl.vs, ARRAY_NAME_LEN);
+ item->array_name[ARRAY_NAME_LEN - 1] = '\0';
+ }
+ return 0;
+}
+
+static void format(char *formatter, size_t fmt_sz, char *tofmt, size_t tofmtsz)
+{
+ fmt_sz = snprintf(formatter, fmt_sz, "%-*.*s", (int)tofmtsz, (int)tofmtsz, tofmt);
+
+ /* trim() the obnoxious trailing white lines */
+ while (fmt_sz) {
+ if (formatter[fmt_sz - 1] != ' ' && formatter[fmt_sz - 1] != '\0') {
+ formatter[fmt_sz] = '\0';
+ break;
+ }
+ fmt_sz--;
+ }
+}
+
+static void huawei_json_print_list_items(struct huawei_list_item *list_items,
+ unsigned int len)
+{
+ struct json_object *root;
+ struct json_object *devices;
+ struct json_object *device_attrs;
+ char formatter[128] = { 0 };
+ int index, i = 0;
+
+ root = json_create_object();
+ devices = json_create_array();
+ for (i = 0; i < len; i++) {
+ device_attrs = json_create_object();
+
+ json_object_add_value_string(device_attrs,
+ "DevicePath",
+ list_items[i].node);
+
+ if (sscanf(list_items[i].node, "/dev/nvme%d", &index) == 1)
+ json_object_add_value_int(device_attrs,
+ "Index",
+ index);
+
+ format(formatter, sizeof(formatter),
+ list_items[i].ns_name,
+ sizeof(list_items[i].ns_name));
+
+ json_object_add_value_string(device_attrs,
+ "NS Name",
+ formatter);
+
+ format(formatter, sizeof(formatter),
+ list_items[i].array_name,
+ sizeof(list_items[i].array_name));
+
+ json_object_add_value_string(device_attrs,
+ "Array Name",
+ formatter);
+
+ json_array_add_value_object(devices, device_attrs);
+ }
+ json_object_add_value_array(root, "Devices", devices);
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+}
+
+static void huawei_print_list_head(struct huawei_list_element_len element_len)
+{
+ char dash[128];
+ int i;
+
+ for (i = 0; i < 128; i++)
+ dash[i] = '-';
+ dash[127] = '\0';
+
+ printf("%-*.*s %-*.*s %-*.*s %-*.*s %-*.*s %-*.*s\n",
+ element_len.node, element_len.node, "Node",
+ element_len.ns_name, element_len.ns_name, "NS Name",
+ element_len.nguid, element_len.nguid, "Nguid",
+ element_len.ns_id, element_len.ns_id, "NS ID",
+ element_len.usage, element_len.usage, "Usage",
+ element_len.array_name, element_len.array_name, "Array Name");
+
+ printf("%-.*s %-.*s %-.*s %-.*s %-.*s %-.*s\n",
+ element_len.node, dash, element_len.ns_name, dash,
+ element_len.nguid, dash, element_len.ns_id, dash,
+ element_len.usage, dash, element_len.array_name, dash);
+}
+
+static void huawei_print_list_item(struct huawei_list_item *list_item,
+ struct huawei_list_element_len element_len)
+{
+ __u8 lba_index;
+
+ nvme_id_ns_flbas_to_lbaf_inuse(list_item->ns.flbas, &lba_index);
+ unsigned long long lba = 1ULL << list_item->ns.lbaf[lba_index].ds;
+ double nsze = le64_to_cpu(list_item->ns.nsze) * lba;
+ double nuse = le64_to_cpu(list_item->ns.nuse) * lba;
+
+ const char *s_suffix = suffix_si_get(&nsze);
+ const char *u_suffix = suffix_si_get(&nuse);
+
+ char usage[128];
+ char nguid_buf[2 * sizeof(list_item->ns.nguid) + 1];
+ char *nguid = nguid_buf;
+ int i;
+
+ sprintf(usage, "%6.2f %2sB / %6.2f %2sB", nuse, u_suffix, nsze, s_suffix);
+
+ memset(nguid, 0, sizeof(nguid_buf));
+ for (i = 0; i < sizeof(list_item->ns.nguid); i++)
+ nguid += sprintf(nguid, "%02x", list_item->ns.nguid[i]);
+
+ printf("%-*.*s %-*.*s %-*.*s %-*d %-*.*s %-*.*s\n",
+ element_len.node, element_len.node, list_item->node,
+ element_len.ns_name, element_len.ns_name, list_item->ns_name,
+ element_len.nguid, element_len.nguid, nguid_buf,
+ element_len.ns_id, list_item->nsid,
+ element_len.usage, element_len.usage, usage,
+ element_len.array_name, element_len.array_name,
+ list_item->array_name);
+
+}
+
+static unsigned int choose_len(unsigned int old_len, unsigned int cur_len, unsigned int default_len)
+{
+ unsigned int temp_len;
+
+ temp_len = (cur_len > default_len) ? cur_len : default_len;
+ if (temp_len > old_len)
+ return temp_len;
+ return old_len;
+}
+
+static unsigned int huawei_get_ns_len(struct huawei_list_item *list_items, unsigned int len,
+ unsigned int default_len)
+{
+ int i;
+ unsigned int min_len = default_len;
+
+ for (i = 0 ; i < len ; i++)
+ min_len = choose_len(min_len, strlen(list_items->ns_name), default_len);
+
+ return min_len;
+}
+
+static int huawei_get_array_len(struct huawei_list_item *list_items, unsigned int len,
+ unsigned int default_len)
+{
+ int i;
+ int min_len = default_len;
+
+ for (i = 0 ; i < len ; i++)
+ min_len = choose_len(min_len, strlen(list_items->array_name), default_len);
+
+ return min_len;
+}
+
+static void huawei_print_list_items(struct huawei_list_item *list_items, unsigned int len)
+{
+ unsigned int i;
+ struct huawei_list_element_len element_len;
+
+ element_len.node = 16;
+ element_len.nguid = 2 * sizeof(list_items->ns.nguid) + 1;
+ element_len.ns_id = 9;
+ element_len.usage = 26;
+ element_len.ns_name = huawei_get_ns_len(list_items, len, MIN_NS_NAME_LEN);
+ element_len.array_name = huawei_get_array_len(list_items, len, MIN_ARRAY_NAME_LEN);
+
+ huawei_print_list_head(element_len);
+
+ for (i = 0 ; i < len ; i++)
+ huawei_print_list_item(&list_items[i], element_len);
+}
+
+static int huawei_list(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ char path[264];
+ struct dirent **devices;
+ struct huawei_list_item *list_items;
+ unsigned int i, n, ret;
+ unsigned int huawei_num = 0;
+ enum nvme_print_flags fmt;
+ const char *desc = "Retrieve basic information for the given huawei device";
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"),
+ OPT_END()
+ };
+
+ ret = argconfig_parse(argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ ret = validate_output_format(cfg.output_format, &fmt);
+ if (ret < 0 || (fmt != JSON && fmt != NORMAL))
+ return ret;
+
+ n = scandir("/dev", &devices, nvme_namespace_filter, alphasort);
+ if (n <= 0)
+ return n;
+
+ list_items = calloc(n, sizeof(*list_items));
+ if (!list_items) {
+ fprintf(stderr, "can not allocate controller list payload\n");
+ ret = ENOMEM;
+ goto out_free_devices;
+ }
+
+ for (i = 0; i < n; i++) {
+ int fd;
+
+ snprintf(path, sizeof(path), "/dev/%s", devices[i]->d_name);
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "Cannot open device %s: %s\n",
+ path, strerror(errno));
+ continue;
+ }
+ ret = huawei_get_nvme_info(fd, &list_items[huawei_num], path);
+ if (ret) {
+ close(fd);
+ goto out_free_list_items;
+ }
+ if (list_items[huawei_num].huawei_device == true)
+ huawei_num++;
+ close(fd);
+ }
+
+ if (huawei_num > 0) {
+ if (fmt == JSON)
+ huawei_json_print_list_items(list_items, huawei_num);
+ else
+ huawei_print_list_items(list_items, huawei_num);
+ }
+out_free_list_items:
+ free(list_items);
+out_free_devices:
+ for (i = 0; i < n; i++)
+ free(devices[i]);
+ free(devices);
+
+ return ret;
+}
+
+static void huawei_do_id_ctrl(__u8 *vs, struct json_object *root)
+{
+ char array_name[ARRAY_NAME_LEN + 1] = {0};
+
+ memcpy(array_name, vs, ARRAY_NAME_LEN);
+ if (root)
+ json_object_add_value_string(root, "array name", strlen(array_name) > 1 ? array_name : "NULL");
+ else
+ printf("array name : %s\n", strlen(array_name) > 1 ? array_name : "NULL");
+}
+
+static int huawei_id_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return __id_ctrl(argc, argv, cmd, plugin, huawei_do_id_ctrl);
+}
diff --git a/plugins/huawei/huawei-nvme.h b/plugins/huawei/huawei-nvme.h
new file mode 100644
index 0000000..f49e6fd
--- /dev/null
+++ b/plugins/huawei/huawei-nvme.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/huawei/huawei-nvme
+
+#if !defined(HUAWEI_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define HUAWEI_NVME
+
+#include "cmd.h"
+
+PLUGIN(NAME("huawei", "Huawei vendor specific extensions", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("list", "List all Huawei NVMe devices and namespaces on machine", huawei_list)
+ ENTRY("id-ctrl", "Huawei identify controller", huawei_id_ctrl)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/innogrit/innogrit-nvme.c b/plugins/innogrit/innogrit-nvme.c
new file mode 100644
index 0000000..cd47efa
--- /dev/null
+++ b/plugins/innogrit/innogrit-nvme.c
@@ -0,0 +1,466 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <time.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "nvme-print.h"
+#include "typedef.h"
+
+#define CREATE_CMD
+#include "innogrit-nvme.h"
+
+static int innogrit_smart_log_additional(int argc, char **argv,
+ struct command *command,
+ struct plugin *plugin)
+{
+ struct nvme_smart_log smart_log = { 0 };
+ struct vsc_smart_log *pvsc_smart = (struct vsc_smart_log *)smart_log.rsvd232;
+ const char *desc = "Retrieve additional SMART log for the given device ";
+ const char *namespace = "(optional) desired namespace";
+ struct nvme_dev *dev;
+ int err, i, iindex;
+
+ struct config {
+ __u32 namespace_id;
+ };
+
+ struct config cfg = {
+ .namespace_id = NVME_NSID_ALL,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ nvme_get_log_smart(dev_fd(dev), cfg.namespace_id, false, &smart_log);
+ nvme_show_smart_log(&smart_log, cfg.namespace_id, dev->name, NORMAL);
+
+ printf("DW0[0-1] Defect Cnt : %u\n", pvsc_smart->defect_cnt);
+ printf("DW0[2-3] Slc Spb Cnt : %u\n", pvsc_smart->slc_spb_cnt);
+ printf("DW1 Slc Total Ec Cnt : %u\n", pvsc_smart->slc_total_ec_cnt);
+ printf("DW2 Slc Max Ec Cnt : %u\n", pvsc_smart->slc_max_ec_cnt);
+ printf("DW3 Slc Min Ec Cnt : %u\n", pvsc_smart->slc_min_ec_cnt);
+ printf("DW4 Slc Avg Ec Cnt : %u\n", pvsc_smart->slc_avg_ec_cnt);
+ printf("DW5 Total Ec Cnt : %u\n", pvsc_smart->total_ec_cnt);
+ printf("DW6 Max Ec Cnt : %u\n", pvsc_smart->max_ec_cnt);
+ printf("DW7 Min Ec Cnt : %u\n", pvsc_smart->min_ec_cnt);
+ printf("DW8 Avg Ec Cnt : %u\n", pvsc_smart->avg_ec_cnt);
+ printf("DW9 Mrd Rr Good Cnt : %u\n", pvsc_smart->mrd_rr_good_cnt);
+ printf("DW10 Ard Rr Good Cnt : %u\n", pvsc_smart->ard_rr_good_cnt);
+ printf("DW11 Preset Cnt : %u\n", pvsc_smart->preset_cnt);
+ printf("DW12 Nvme Reset Cnt : %u\n", pvsc_smart->nvme_reset_cnt);
+ printf("DW13 Low Pwr Cnt : %u\n", pvsc_smart->low_pwr_cnt);
+ printf("DW14 Wa : %u\n", pvsc_smart->wa);
+ printf("DW15 Ps3 Entry Cnt : %u\n", pvsc_smart->ps3_entry_cnt);
+ printf("DW16[0] highest_temp[0] : %u\n", pvsc_smart->highest_temp[0]);
+ printf("DW16[1] highest_temp[1] : %u\n", pvsc_smart->highest_temp[1]);
+ printf("DW16[2] highest_temp[2] : %u\n", pvsc_smart->highest_temp[2]);
+ printf("DW16[3] highest_temp[3] : %u\n", pvsc_smart->highest_temp[3]);
+ printf("DW17 weight_ec : %u\n", pvsc_smart->weight_ec);
+ printf("DW18 slc_cap_mb : %u\n", pvsc_smart->slc_cap_mb);
+ printf("DW19-20 nand_page_write_cnt : %llu\n", pvsc_smart->nand_page_write_cnt);
+ printf("DW21 program_error_cnt : %u\n", pvsc_smart->program_error_cnt);
+ printf("DW22 erase_error_cnt : %u\n", pvsc_smart->erase_error_cnt);
+ printf("DW23[0] flash_type : %u\n", pvsc_smart->flash_type);
+ printf("DW24 hs_crc_err_cnt : %u\n", pvsc_smart->hs_crc_err_cnt);
+ printf("DW25 ddr_ecc_err_cnt : %u\n", pvsc_smart->ddr_ecc_err_cnt);
+ iindex = 26;
+ for (i = 0; i < (sizeof(pvsc_smart->reserved3)/4); i++) {
+ if (pvsc_smart->reserved3[i] != 0)
+ printf("DW%-37d : %u\n", iindex, pvsc_smart->reserved3[i]);
+ iindex++;
+ }
+
+ return 0;
+}
+
+static int sort_eventlog_fn(const void *a, const void *b)
+{
+ const struct eventlog_addindex *l = a;
+ const struct eventlog_addindex *r = b;
+ int rc;
+
+ if (l->ms > r->ms) {
+ rc = 1;
+ } else if (l->ms < r->ms) {
+ rc = -1;
+ } else {
+ if (l->iindex < r->iindex)
+ rc = -1;
+ else
+ rc = 1;
+ }
+
+ return rc;
+}
+
+static void sort_eventlog(struct eventlog *data16ksrc, unsigned int icount)
+{
+ struct eventlog_addindex peventlogadd[512];
+ unsigned int i;
+
+ for (i = 0; i < icount; i++) {
+ memcpy(&peventlogadd[i], &data16ksrc[i], sizeof(struct eventlog));
+ peventlogadd[i].iindex = i;
+ }
+
+ qsort(peventlogadd, icount, sizeof(struct eventlog_addindex), sort_eventlog_fn);
+
+ for (i = 0; i < icount; i++)
+ memcpy(&data16ksrc[i], &peventlogadd[i], sizeof(struct eventlog));
+}
+
+static unsigned char setfilecontent(char *filenamea, unsigned char *buffer,
+ unsigned int buffersize)
+{
+ FILE *fp = NULL;
+ int rc;
+
+ if (buffersize == 0)
+ return true;
+ fp = fopen(filenamea, "a+");
+ rc = fwrite(buffer, 1, buffersize, fp);
+ fclose(fp);
+ if (rc != buffersize)
+ return false;
+ return true;
+}
+
+static int nvme_vucmd(int fd, unsigned char opcode, unsigned int cdw12,
+ unsigned int cdw13, unsigned int cdw14,
+ unsigned int cdw15, char *data, int data_len)
+{
+ struct nvme_passthru_cmd cmd;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.opcode = opcode;
+ cmd.cdw2 = IGVSC_SIG;
+ cmd.cdw10 = data_len / 4;
+ cmd.cdw12 = cdw12;
+ cmd.cdw13 = cdw13;
+ cmd.cdw14 = cdw14;
+ cmd.cdw15 = cdw15;
+ cmd.nsid = 0xffffffff;
+ cmd.addr = (__u64)(__u64)(uintptr_t)data;
+ cmd.data_len = data_len;
+ return nvme_submit_admin_passthru(fd, &cmd, NULL);
+}
+
+static int innogrit_vsc_geteventlog(int argc, char **argv,
+ struct command *command,
+ struct plugin *plugin)
+{
+ time_t timep;
+ struct tm *logtime;
+ int icount, ioffset16k, iblock, ivsctype;
+ char currentdir[128], filename[512];
+ unsigned char data[4096], data16k[SIZE_16K], zerob[32];
+ unsigned int *pcheckdata;
+ unsigned int isize, icheck_stopvalue, iend;
+ unsigned char bSortLog = false, bget_nextlog = true;
+ struct evlg_flush_hdr *pevlog = (struct evlg_flush_hdr *)data;
+ const char *desc = "Recrieve event log for the given device ";
+ const char *clean_opt = "(optional) 1 for clean event log";
+ struct nvme_dev *dev;
+ int ret = -1;
+
+ struct config {
+ __u32 clean_flg;
+ };
+
+ struct config cfg = {
+ .clean_flg = 0,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("clean_flg", 'c', &cfg.clean_flg, clean_opt),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+
+ if (getcwd(currentdir, 128) == NULL)
+ return -1;
+
+ ret = nvme_vucmd(dev_fd(dev), 0xFE, 0x82, 0x03, 0x00, 0x00, (char *)data, 4096);
+ if (ret == -1)
+ return ret;
+
+ if (data[0] == 0x5A)
+ ivsctype = 1;
+ else
+ ivsctype = 0;
+
+ time(&timep);
+ logtime = localtime(&timep);
+ sprintf(filename, "%s/eventlog_%02d%02d-%02d%02d%02d.elog", currentdir, logtime->tm_mon+1,
+ logtime->tm_mday, logtime->tm_hour, logtime->tm_min, logtime->tm_sec);
+
+ iblock = 0;
+ ioffset16k = 0;
+ memset(data16k, 0, SIZE_16K);
+ memset(zerob, 0, 32);
+
+ icount = 0;
+ while (bget_nextlog) {
+ if (icount % 100 == 0) {
+ printf("\rWait for Dump EventLog " XCLEAN_LINE);
+ fflush(stdout);
+ icount = 0;
+ } else if (icount % 5 == 0) {
+ printf(".");
+ fflush(stdout);
+ }
+ icount++;
+
+ memset(data, 0, 4096);
+ if (ivsctype == 1)
+ ret = nvme_vucmd(dev_fd(dev), 0xFE, 0x60, 0x00, 0x00, 0x00, (char *)data,
+ 4096);
+ else
+ ret = nvme_vucmd(dev_fd(dev), NVME_VSC_GET_EVENT_LOG, 0, 0,
+ (SRB_SIGNATURE >> 32),
+ (SRB_SIGNATURE & 0xFFFFFFFF),
+ (char *)data, 4096);
+ if (ret == -1)
+ return ret;
+
+ pcheckdata = (unsigned int *)&data[4096 - 32];
+ icheck_stopvalue = pcheckdata[1];
+
+ if (icheck_stopvalue == 0xFFFFFFFF) {
+ isize = pcheckdata[0];
+ if (isize == 0) {
+ /* Finish Log */
+ bget_nextlog = false;
+ } else if (bSortLog) {
+ /* No Full 4K Package */
+ for (iend = 0; iend < isize - 32; iend += sizeof(struct eventlog)) {
+ if (memcmp(&data[iend], zerob, sizeof(struct eventlog)) != 0) {
+ memcpy(&data16k[ioffset16k], &data[iend], sizeof(struct eventlog));
+ ioffset16k += sizeof(struct eventlog);
+ }
+ }
+ } else {
+ setfilecontent(filename, data, isize);
+ }
+ } else {
+ /* Full 4K Package */
+ if ((pevlog->signature == EVLOG_SIG) && (pevlog->log_type == 1))
+ bSortLog = true;
+
+ if (bSortLog) {
+ for (iend = 0; iend < SIZE_4K; iend += sizeof(struct eventlog)) {
+ if (memcmp(&data[iend], zerob, sizeof(struct eventlog)) != 0) {
+ memcpy(&data16k[ioffset16k], &data[iend], sizeof(struct eventlog));
+ ioffset16k += sizeof(struct eventlog);
+ }
+ }
+
+ iblock++;
+ if (iblock == 4) {
+ sort_eventlog((struct eventlog *)(data16k + sizeof(struct evlg_flush_hdr)),
+ (ioffset16k - sizeof(struct evlg_flush_hdr))/sizeof(struct eventlog));
+ setfilecontent(filename, data16k, ioffset16k);
+ ioffset16k = 0;
+ iblock = 0;
+ memset(data16k, 0, SIZE_16K);
+ }
+ } else {
+ setfilecontent(filename, data, SIZE_4K);
+ }
+
+ }
+ }
+
+ if (bSortLog) {
+ if (ioffset16k > 0) {
+ sort_eventlog((struct eventlog *)(data16k + sizeof(struct evlg_flush_hdr)),
+ (ioffset16k - sizeof(struct evlg_flush_hdr))/sizeof(struct eventlog));
+ setfilecontent(filename, data16k, ioffset16k);
+ }
+ }
+
+ printf("\r" XCLEAN_LINE "Dump eventLog finish to %s\n", filename);
+ chmod(filename, 0666);
+
+ if (cfg.clean_flg == 1) {
+ printf("Clean eventlog\n");
+ nvme_vucmd(dev_fd(dev), NVME_VSC_CLEAN_EVENT_LOG, 0, 0,
+ (SRB_SIGNATURE >> 32),
+ (SRB_SIGNATURE & 0xFFFFFFFF), (char *)NULL, 0);
+ }
+
+ dev_close(dev);
+
+ return ret;
+}
+
+static int innogrit_vsc_getcdump(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ time_t timep;
+ struct tm *logtime;
+ char currentdir[128], filename[512], fname[128];
+ unsigned int itotal, icur, ivsctype;
+ unsigned char data[4096];
+ struct cdumpinfo cdumpinfo;
+ unsigned char busevsc = false;
+ unsigned int ipackcount, ipackindex;
+ char fwvera[32];
+ const char *desc = "Recrieve cdump data for the given device ";
+ struct nvme_dev *dev;
+ int ret = -1;
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ if (getcwd(currentdir, 128) == NULL)
+ return -1;
+
+ time(&timep);
+ logtime = localtime(&timep);
+
+ ivsctype = 0;
+ ipackindex = 0;
+ memset(data, 0, 4096);
+
+ ret = nvme_vucmd(dev_fd(dev), 0xFE, 0x82, 0x03, 0x00, 0x00, (char *)data, 4096);
+ if (ret == -1)
+ return ret;
+
+ if (data[0] == 0x5A) {
+ ivsctype = 1;
+ ret = nvme_vucmd(dev_fd(dev), 0xFE, 0x82, 0x08, 0x00, 0x00, (char *)data, 4096);
+ } else {
+ ivsctype = 0;
+ ret = nvme_vucmd(dev_fd(dev), NVME_VSC_GET, VSC_FN_GET_CDUMP, 0x00,
+ (SRB_SIGNATURE >> 32), (SRB_SIGNATURE & 0xFFFFFFFF),
+ (char *)data, 4096);
+ }
+ if (ret == -1)
+ return ret;
+
+ memcpy(&cdumpinfo, &data[3072], sizeof(cdumpinfo));
+ if (cdumpinfo.sig == 0x5a5b5c5d) {
+ busevsc = true;
+ ipackcount = cdumpinfo.ipackcount;
+ if (ipackcount == 0) {
+ itotal = 0;
+ } else {
+ itotal = cdumpinfo.cdumppack[ipackindex].ilenth;
+ memset(fwvera, 0, sizeof(fwvera));
+ memcpy(fwvera, cdumpinfo.cdumppack[ipackindex].fwver, 8);
+ sprintf(fname, "cdump_%02d%02d-%02d%02d%02d_%d_%s.cdp", logtime->tm_mon+1,
+ logtime->tm_mday, logtime->tm_hour, logtime->tm_min, logtime->tm_sec,
+ ipackindex, fwvera);
+ sprintf(filename, "%s/%s", currentdir, fname);
+ }
+ }
+
+
+ if (busevsc == false) {
+ memset(data, 0, 4096);
+ ret = nvme_get_nsid_log(dev_fd(dev), true, 0x07,
+ NVME_NSID_ALL,
+ 4096, data);
+ if (ret != 0)
+ return ret;
+
+ ipackcount = 1;
+ memcpy(&itotal, &data[4092], 4);
+ sprintf(fname, "cdump_%02d%02d-%02d%02d%02d.cdp", logtime->tm_mon+1, logtime->tm_mday,
+ logtime->tm_hour, logtime->tm_min, logtime->tm_sec);
+ sprintf(filename, "%s/%s", currentdir, fname);
+ }
+
+ if (itotal == 0) {
+ printf("no cdump data\n");
+ return 0;
+ }
+
+ while (ipackindex < ipackcount) {
+ memset(data, 0, 4096);
+ strcpy((char *)data, "cdumpstart");
+ setfilecontent(filename, data, strlen((char *)data));
+ for (icur = 0; icur < itotal; icur += 4096) {
+ memset(data, 0, 4096);
+ if (busevsc) {
+ if (ivsctype == 1)
+ ret = nvme_vucmd(dev_fd(dev), 0xFE, 0x82, 0x08, 0x00, 0x00,
+ (char *)data, 4096);
+ else
+ ret = nvme_vucmd(dev_fd(dev), NVME_VSC_GET, VSC_FN_GET_CDUMP, 0x00,
+ (SRB_SIGNATURE >> 32), (SRB_SIGNATURE & 0xFFFFFFFF),
+ (char *)data, 4096);
+ } else {
+ ret = nvme_get_nsid_log(dev_fd(dev), true,
+ 0x07,
+ NVME_NSID_ALL, 4096, data);
+ }
+ if (ret != 0)
+ return ret;
+
+ setfilecontent(filename, data, 4096);
+
+ printf("\rWait for dump data %d%%" XCLEAN_LINE, ((icur+4096) * 100/itotal));
+ }
+ memset(data, 0, 4096);
+ strcpy((char *)data, "cdumpend");
+ setfilecontent(filename, data, strlen((char *)data));
+ printf("\r%s\n", fname);
+ ipackindex++;
+ if (ipackindex != ipackcount) {
+ memset(data, 0, 4096);
+ if (busevsc) {
+ if (ivsctype == 1)
+ ret = nvme_vucmd(dev_fd(dev), 0xFE, 0x82, 0x08, 0x00, 0x00,
+ (char *)data, 4096);
+ else
+ ret = nvme_vucmd(dev_fd(dev), NVME_VSC_GET, VSC_FN_GET_CDUMP, 0x00,
+ (SRB_SIGNATURE >> 32), (SRB_SIGNATURE & 0xFFFFFFFF),
+ (char *)data, 4096);
+ } else {
+ ret = nvme_get_nsid_log(dev_fd(dev), true,
+ 0x07,
+ NVME_NSID_ALL, 4096,
+ data);
+ }
+ if (ret != 0)
+ return ret;
+
+ itotal = cdumpinfo.cdumppack[ipackindex].ilenth;
+ memset(fwvera, 0, sizeof(fwvera));
+ memcpy(fwvera, cdumpinfo.cdumppack[ipackindex].fwver, 8);
+ sprintf(fname, "cdump_%02d%02d-%02d%02d%02d_%d_%s.cdp", logtime->tm_mon+1,
+ logtime->tm_mday, logtime->tm_hour, logtime->tm_min, logtime->tm_sec,
+ ipackindex, fwvera);
+ sprintf(filename, "%s/%s", currentdir, fname);
+ }
+
+ }
+
+ printf("\n");
+ dev_close(dev);
+ return ret;
+}
diff --git a/plugins/innogrit/innogrit-nvme.h b/plugins/innogrit/innogrit-nvme.h
new file mode 100644
index 0000000..2de0502
--- /dev/null
+++ b/plugins/innogrit/innogrit-nvme.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/innogrit/innogrit-nvme
+
+#if !defined(INNOGRIT_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define INNOGRIT_NVME
+
+#include "cmd.h"
+
+PLUGIN(NAME("innogrit", "innogrit vendor specific extensions", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("smart-log-add", "Retrieve innogrit SMART Log, show it", innogrit_smart_log_additional)
+ ENTRY("get-eventlog", "get event log", innogrit_vsc_geteventlog)
+ ENTRY("get-cdump", "get cdump data", innogrit_vsc_getcdump)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/innogrit/typedef.h b/plugins/innogrit/typedef.h
new file mode 100644
index 0000000..f2a59b4
--- /dev/null
+++ b/plugins/innogrit/typedef.h
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#define SIZE_4K 4096
+#define SIZE_16K 16384
+
+#define NVME_VSC_GET_EVENT_LOG 0xC2
+#define NVME_VSC_CLEAN_EVENT_LOG 0xD8
+#define NVME_VSC_GET 0xE6
+#define VSC_FN_GET_CDUMP 0x08
+#define EVLOG_SIG 0x65766C67
+#define IGVSC_SIG 0x69677673
+#define SRB_SIGNATURE 0x544952474F4E4E49ULL
+#define XCLEAN_LINE "\033[K"
+
+struct evlg_flush_hdr {
+ unsigned int signature;
+ unsigned int fw_ver[2];
+ unsigned int fw_type : 8;
+ unsigned int log_type : 8;
+ unsigned int project : 16;
+ unsigned int trace_cnt;
+ unsigned int sout_crc;
+ unsigned int reserved[2];
+};
+
+struct eventlog {
+ unsigned int ms;
+ unsigned int param[7];
+};
+
+struct eventlog_addindex {
+ unsigned int ms;
+ unsigned int param[7];
+ unsigned int iindex;
+};
+
+#pragma pack(push)
+#pragma pack(1)
+struct vsc_smart_log {
+ unsigned short defect_cnt;
+ unsigned short slc_spb_cnt;
+ unsigned int slc_total_ec_cnt;
+ unsigned int slc_max_ec_cnt;
+ unsigned int slc_min_ec_cnt;
+ unsigned int slc_avg_ec_cnt;
+ unsigned int total_ec_cnt;
+ unsigned int max_ec_cnt;
+ unsigned int min_ec_cnt;
+ unsigned int avg_ec_cnt;
+ unsigned int mrd_rr_good_cnt;
+ unsigned int ard_rr_good_cnt;
+ unsigned int preset_cnt;
+ unsigned int nvme_reset_cnt;
+ unsigned int low_pwr_cnt;
+ unsigned int wa;
+ unsigned int ps3_entry_cnt;
+ u_char highest_temp[4];
+ unsigned int weight_ec;
+ unsigned int slc_cap_mb;
+ unsigned long long nand_page_write_cnt;
+ unsigned int program_error_cnt;
+ unsigned int erase_error_cnt;
+ u_char flash_type;
+ u_char reserved2[3];
+ unsigned int hs_crc_err_cnt;
+ unsigned int ddr_ecc_err_cnt;
+ unsigned int reserved3[44];
+};
+#pragma pack(pop)
+
+struct cdump_pack {
+ unsigned int ilenth;
+ char fwver[8];
+};
+
+struct cdumpinfo {
+ unsigned int sig;
+ unsigned int ipackcount;
+ struct cdump_pack cdumppack[32];
+};
diff --git a/plugins/inspur/inspur-nvme.c b/plugins/inspur/inspur-nvme.c
new file mode 100644
index 0000000..cda3507
--- /dev/null
+++ b/plugins/inspur/inspur-nvme.c
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "nvme-print.h"
+#include "util/suffix.h"
+
+#define CREATE_CMD
+#include "inspur-nvme.h"
+#include "inspur-utils.h"
+
+void show_r1_vendor_log(r1_cli_vendor_log_t *vendorlog)
+{
+ int i = 0;
+
+ if (vendorlog->device_state == 0)
+ printf("device_state : [healthy]\n");
+ else
+ printf("device_state : [warning]\n");
+
+ printf("commit id : %s\n", vendorlog->commit_id);
+ printf("mcu data id(mcu) : 0x%x\n", le32_to_cpu(vendorlog->mcu_data_id));
+ printf("power_info(mcu) : %u mW\n", le32_to_cpu(vendorlog->power_info));
+ printf("voltage_info(mcu) : %u mV\n", le32_to_cpu(vendorlog->voltage_info));
+ printf("current_info(mcu) : %u mA\n", le32_to_cpu(vendorlog->current_info));
+ printf("history max_power(mcu) : %u mW\n", le32_to_cpu(vendorlog->max_power));
+ printf("disk_max_temper(mcu) : %d C\n", le32_to_cpu(vendorlog->disk_max_temper) - 273);
+ printf("disk_overtemper_cout(mcu) : %u\n", le32_to_cpu(vendorlog->disk_overtemper_cout));
+ printf("ctrl_max_temper(mcu) : %d C\n", le32_to_cpu(vendorlog->ctrl_max_temper) - 273);
+ printf("ctrl_overtemper_cout(mcu) : %u\n", le32_to_cpu(vendorlog->ctrl_overtemper_cout));
+ printf("nand_max_temper(mcu) : %d C\n", le32_to_cpu(vendorlog->nand_max_temper) - 273);
+ printf("nand_overtemper_cout(mcu) : %u\n", le32_to_cpu(vendorlog->nand_overtemper_cout));
+
+ for (i = 0; i < 4; i++)
+ printf("temperature[%d](mcu) : %d C\n", i, le32_to_cpu(vendorlog->current_temp[i]) - 273);
+
+ printf("CAP Time from 32v to 27v(mcu) : %u ms\n", le32_to_cpu(vendorlog->cap_transtime.cap_trans_time1));
+ printf("CAP Time from 27v to 10v(mcu) : %u ms\n", le32_to_cpu(vendorlog->cap_transtime.cap_trans_time2));
+ printf("cap_health_state(mcu) : %u\n", le32_to_cpu(vendorlog->cap_health_state));
+ printf("warning bit(mcu) : 0x%x%08x\n", le32_to_cpu(vendorlog->detail_warning[1]),
+ le32_to_cpu(vendorlog->detail_warning[0]));
+ printf("-->high_format_fail : %x\n", vendorlog->detail_warning_bit.high_format_fail);
+ printf("-->low_format_fail : %x\n", vendorlog->detail_warning_bit.low_format_fail);
+ printf("-->current sensor : %x\n", vendorlog->detail_warning_bit.self_test_fail1);
+ printf("-->nand temp sensor : %x\n", vendorlog->detail_warning_bit.self_test_fail2);
+ printf("-->board temp sensor : %x\n", vendorlog->detail_warning_bit.self_test_fail3);
+ printf("-->cntl temp sensor : %x\n", vendorlog->detail_warning_bit.self_test_fail4);
+ printf("-->cap_timer_test_fail : %x\n", vendorlog->detail_warning_bit.capacitance_test_fail);
+ printf("-->readOnly_after_rebuild : %x\n", vendorlog->detail_warning_bit.readOnly_after_rebuild);
+ printf("-->firmware_loss : %x\n", vendorlog->detail_warning_bit.firmware_loss);
+ printf("-->cap_self_test : %x\n", vendorlog->detail_warning_bit.cap_unsupply);
+ printf("-->spare_space_warning : %x\n", vendorlog->detail_warning_bit.spare_space_warning);
+ printf("-->lifetime_warning : %x\n", vendorlog->detail_warning_bit.lifetime_warning);
+ printf("-->temp_high_warning : %x\n", vendorlog->detail_warning_bit.temp_high_warning);
+ printf("-->temp_low_warning : %x\n", vendorlog->detail_warning_bit.temp_low_warning);
+ printf("-->mcu_disable(mcu) : %x\n", vendorlog->detail_warning_bit.mcu_disable);
+ printf("warning history bit(mcu) : 0x%x%08x\n", le32_to_cpu(vendorlog->detail_warning_his[1]),
+ le32_to_cpu(vendorlog->detail_warning_his[0]));
+ printf("-->high_format_fail : %x\n", vendorlog->detail_warning_his_bit.high_format_fail);
+ printf("-->low_format_fail : %x\n", vendorlog->detail_warning_his_bit.low_format_fail);
+ printf("-->current sensor : %x\n", vendorlog->detail_warning_his_bit.self_test_fail1);
+ printf("-->nand temp sensor : %x\n", vendorlog->detail_warning_his_bit.self_test_fail2);
+ printf("-->board temp sensor : %x\n", vendorlog->detail_warning_his_bit.self_test_fail3);
+ printf("-->cntl temp sensor : %x\n", vendorlog->detail_warning_his_bit.self_test_fail4);
+ printf("-->cap_timer_test_fail : %x\n", vendorlog->detail_warning_his_bit.capacitance_test_fail);
+ printf("-->readOnly_after_rebuild : %x\n", vendorlog->detail_warning_his_bit.readOnly_after_rebuild);
+ printf("-->firmware_loss : %x\n", vendorlog->detail_warning_his_bit.firmware_loss);
+ printf("-->cap_self_test : %x\n", vendorlog->detail_warning_his_bit.cap_unsupply);
+ printf("-->spare_space_warning : %x\n", vendorlog->detail_warning_his_bit.spare_space_warning);
+ printf("-->lifetime_warning : %x\n", vendorlog->detail_warning_his_bit.lifetime_warning);
+ printf("-->temp_high_warning : %x\n", vendorlog->detail_warning_his_bit.temp_high_warning);
+ printf("-->temp_low_warning : %x\n", vendorlog->detail_warning_his_bit.temp_low_warning);
+ printf("-->mcu_disable(mcu) : %x\n", vendorlog->detail_warning_his_bit.mcu_disable);
+
+ for (i = 0; i < 4; i++)
+ printf("[%d]nand_bytes_written : %" PRIu64 " GB\n", i, le64_to_cpu(vendorlog->nand_bytes_written[i]));
+
+ for (i = 0; i < 4; i++) {
+ printf("[%d]io_apptag_err : %u\n", i, le32_to_cpu(vendorlog->io_err[i].io_apptag_err));
+ printf("[%d]io_guard_err : %u\n", i, le32_to_cpu(vendorlog->io_err[i].io_guard_err));
+ printf("[%d]io_reftag_err : %u\n", i, le32_to_cpu(vendorlog->io_err[i].io_reftag_err));
+ printf("[%d]io_read_fail_cout : %u\n", i, le32_to_cpu(vendorlog->io_err[i].io_read_fail_cout));
+ printf("[%d]io_write_fail_cout : %u\n", i, le32_to_cpu(vendorlog->io_err[i].io_write_fail_cout));
+ printf("[%d]io_dma_disable_err : %u\n", i, le32_to_cpu(vendorlog->io_err[i].io_dma_disable_err));
+ printf("[%d]io_dma_fatal_err : %u\n", i, le32_to_cpu(vendorlog->io_err[i].io_dma_fatal_err));
+ printf("[%d]io_dma_linkdown_err : %u\n", i, le32_to_cpu(vendorlog->io_err[i].io_dma_linkdown_err));
+ printf("[%d]io_dma_timeout_err : %u\n", i, le32_to_cpu(vendorlog->io_err[i].io_dma_timeout_err));
+ printf("[%d]lba_err[0] : %u\n", i, le32_to_cpu(vendorlog->io_err[i].lba_err[0]));
+ printf("[%d]lba_err[1] : %u\n", i, le32_to_cpu(vendorlog->io_err[i].lba_err[1]));
+ printf("[%d]lba_err[2] : %u\n", i, le32_to_cpu(vendorlog->io_err[i].lba_err[2]));
+ printf("[%d]lba_err[3] : %u\n", i, le32_to_cpu(vendorlog->io_err[i].lba_err[3]));
+ printf("[%d]lba_err[4] : %u\n", i, le32_to_cpu(vendorlog->io_err[i].lba_err[4]));
+ printf("[%d]lba_err[5] : %u\n", i, le32_to_cpu(vendorlog->io_err[i].lba_err[5]));
+ }
+
+ printf("temp_throttle_per : %u\n", le32_to_cpu(vendorlog->temp_throttle_per));
+ printf("port0_flreset_cnt : %" PRIu64 "\n", le64_to_cpu(vendorlog->port0_fundamental_reset_cnt));
+ printf("port0_hot_reset_cnt : %" PRIu64 "\n", le64_to_cpu(vendorlog->port0_hot_reset_cnt));
+ printf("port0_func_reset_cnt : %" PRIu64 "\n", le64_to_cpu(vendorlog->port0_func_reset_cnt));
+ printf("port0_linkdown_cnt : %" PRIu64 "\n", le64_to_cpu(vendorlog->port0_linkdown_cnt));
+ printf("port0_ctrl_reset_cnt : %" PRIu64 "\n", le64_to_cpu(vendorlog->port0_ctrl_reset_cnt));
+ printf("ces_RcvErr_cnt : %u\n", le32_to_cpu(vendorlog->ces_RcvErr_cnt));
+ printf("ces_BadTlp_cnt : %u\n", le32_to_cpu(vendorlog->ces_BadTlp_cnt));
+ printf("ces_BadDllp_cnt : %u\n", le32_to_cpu(vendorlog->ces_BadDllp_cnt));
+ printf("ces_Rplyover_cnt : %u\n", le32_to_cpu(vendorlog->ces_Rplyover_cnt));
+ printf("ces_RplyTo_cnt : %u\n", le32_to_cpu(vendorlog->ces_RplyTo_cnt));
+ printf("ces_Hlo_cnt : %u\n", le32_to_cpu(vendorlog->ces_Hlo_cnt));
+ printf("scan doorbell err cnt : %u\n", le32_to_cpu(vendorlog->scan_db_err_cnt));
+ printf("doorbell interrupt err cnt : %u\n", le32_to_cpu(vendorlog->db_int_err_cnt));
+
+ printf("------------ncm-----------------------\n");
+ for (i = 0; i < 4; i++) {
+ printf("------------part%d-----------------------\n", i);
+ printf("[%d]nand_rd_unc_count : %u\n", i,
+ le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].nand_rd_unc_cnt));
+ printf("[%d]nand_rd_srr_count : %u\n", i,
+ le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].nand_rd_srr_cnt));
+ printf("[%d]nand_rd_sdecode_count : %u\n", i,
+ le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].nand_rd_soft_decode_cnt));
+ printf("[%d]nand_rd_rb_fail_count : %u\n", i,
+ le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].nand_rd_rebuild_fail_cnt));
+ printf("[%d]nand_prg_fail_count : %u\n", i,
+ le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].nand_prg_fail_cnt));
+ printf("[%d]nand_eras_fail_count : %u\n", i,
+ le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].nand_eras_fail_cnt));
+ printf("[%d]nand_rd_count : %" PRIu64 "\n", i,
+ le64_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].nand_rd_cnt));
+ printf("[%d]nand_prg_count : %" PRIu64 "\n", i,
+ le64_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].nand_prg_cnt));
+ printf("[%d]nand_eras_count : %" PRIu64 "\n", i,
+ le64_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].nand_eras_cnt));
+ printf("[%d]BE_scan_unc_count : %u\n", i,
+ le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].BE_scan_unc_cnt));
+ printf("[%d]rebuild_req_cnt : %u\n", i,
+ le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].rebuild_req_cnt));
+ printf("[%d]retry_req_cnt : %u\n", i,
+ le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].retry_req_cnt));
+ printf("[%d]retry_success_cnt : %u\n", i,
+ le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].retry_success_cnt));
+ printf("[%d]prg_badblk_num : %u\n", i,
+ le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].prg_badblk_num));
+ printf("[%d]eras_badblk_num : %u\n", i,
+ le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].eras_badblk_num));
+ printf("[%d]read_badblk_num : %u\n", i,
+ le32_to_cpu(vendorlog->vendor_log_nandctl_cnt[i].unc_badblk_num));
+ }
+
+ printf("[%d]temp_ctrl_limit_count : %u\n", i, le32_to_cpu(vendorlog->temp_ctrl_limit_cnt));
+ printf("[%d]temp_ctrl_stop_count : %u\n", i, le32_to_cpu(vendorlog->temp_ctrl_stop_cnt));
+ printf("------------wlm-----------------------\n");
+ for (i = 0; i < 4; i++) {
+ printf("------------part%d-----------------------\n", i);
+ printf("[%d]fbb_count : %u\n", i,
+ le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].fbb_count));
+ printf("[%d]ebb_count : %u\n", i,
+ le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].ebb_count));
+ printf("[%d]lbb_count : %u\n", i,
+ le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].lbb_count));
+ printf("[%d]gc_read_count : %u\n", i,
+ le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].gc_read_count));
+ printf("[%d]gc_write_count : %u\n", i,
+ le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].gc_write_count));
+ printf("[%d]gc_write_fail_count : %u\n", i,
+ le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].gc_write_fail_count));
+ printf("[%d]force_gc_count : %u\n", i,
+ le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].force_gc_count));
+ printf("[%d]avg_pe_count : %u\n", i,
+ le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].avg_pe_count));
+ printf("[%d]max_pe_count : %u\n", i,
+ le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].max_pe_count));
+ printf("[%d]free_blk_num1 : %u\n", i,
+ le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].free_blk_num1));
+ printf("[%d]free_blk_num2 : %u\n", i,
+ le32_to_cpu(vendorlog->wearlvl_vendor_log_count[i].free_blk_num2));
+ }
+
+ printf("------------lkm-----------------------\n");
+ printf("[%d]e2e_check_err_count1 : %u\n", i, le32_to_cpu(vendorlog->e2e_check_err_cnt1));
+ printf("[%d]e2e_check_err_count2 : %u\n", i, le32_to_cpu(vendorlog->e2e_check_err_cnt2));
+ printf("[%d]e2e_check_err_count3 : %u\n", i, le32_to_cpu(vendorlog->e2e_check_err_cnt3));
+ printf("[%d]e2e_check_err_count4 : %u\n", i, le32_to_cpu(vendorlog->e2e_check_err_cnt4));
+}
+
+void show_r1_media_err_log(r1_cli_vendor_log_t *vendorlog)
+{
+ int i, j;
+
+ for (i = 0; i < 4; i++) {
+ printf("DM%d read err lba:\n", i);
+ for (j = 0; j < 10; j++)
+ printf("[%d]lba : %" PRIu64 "\n", j, le64_to_cpu(vendorlog->media_err[i].lba_err[j]));
+ }
+}
+
+static int nvme_get_vendor_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ __u8 local_mem[BYTE_OF_4K];
+ char *desc = "Get the Inspur vendor log";
+ struct nvme_dev *dev;
+ int err;
+
+ OPT_ARGS(opts) = { OPT_END() };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ memset(local_mem, 0, BYTE_OF_4K);
+ err = nvme_get_log_simple(dev_fd(dev),
+ (enum nvme_cmd_get_log_lid)VENDOR_SMART_LOG_PAGE,
+ sizeof(r1_cli_vendor_log_t), local_mem);
+ if (!err) {
+ show_r1_vendor_log((r1_cli_vendor_log_t *)local_mem);
+ show_r1_media_err_log((r1_cli_vendor_log_t *)local_mem);
+ } else {
+ nvme_show_status(err);
+ }
+
+ dev_close(dev);
+ return err;
+}
diff --git a/plugins/inspur/inspur-nvme.h b/plugins/inspur/inspur-nvme.h
new file mode 100644
index 0000000..14a5e76
--- /dev/null
+++ b/plugins/inspur/inspur-nvme.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/inspur/inspur-nvme
+
+#if !defined(INSPUR_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define INSPUR_NVME
+
+#include "cmd.h"
+
+PLUGIN(NAME("inspur", "Inspur vendor specific extensions", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("nvme-vendor-log", "Retrieve Inspur Vendor Log, show it", nvme_get_vendor_log)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/inspur/inspur-utils.h b/plugins/inspur/inspur-utils.h
new file mode 100644
index 0000000..d411bf0
--- /dev/null
+++ b/plugins/inspur/inspur-utils.h
@@ -0,0 +1,175 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __INSPUR_UTILS_H__
+#define __INSPUR_UTILS_H__
+
+#define BYTE_OF_64K 65536UL
+#define BYTE_OF_32K 32768UL
+#define BYTE_OF_16K 16384UL
+#define BYTE_OF_4K 4096UL
+#define BYTE_OF_512 512UL
+#define BYTE_OF_256 256UL
+#define BYTE_OF_128 128UL
+
+/* Inspur specific LOG_PAGE_ID */
+typedef enum {
+ VENDOR_SMART_LOG_PAGE = 0xc0,
+} vendor_sepc_log_page_id_e;
+
+#pragma pack(push, 1)
+typedef struct r1_am_cap_transtime {
+ __u32 cap_trans_time1 : 16;
+ __u32 cap_trans_time2 : 16;
+} r1_cap_transtime_t;
+
+typedef struct vendor_warning_bit {
+ __u32 high_format_fail : 1;
+ __u32 low_format_fail : 1;
+ __u32 rebuild_fail1 : 1;
+ __u32 rebuild_fail2 : 1;
+ __u32 rebuild_fail3 : 1;
+ __u32 rebuild_fail4 : 1;
+ __u32 rebuild_fail5 : 1;
+ __u32 rebuild_fail6 : 1;
+ __u32 self_test_fail1 : 1;
+ __u32 self_test_fail2 : 1;
+ __u32 self_test_fail3 : 1;
+ __u32 self_test_fail4 : 1;
+ __u32 internal_err1 : 1;
+ __u32 internal_err2 : 1;
+ __u32 internal_err3 : 1;
+ __u32 internal_err4 : 1;
+ __u32 internal_err5 : 1;
+ __u32 internal_err6 : 1;
+ __u32 internal_err7 : 1;
+ __u32 internal_err8 : 1;
+ __u32 internal_err9 : 1;
+ __u32 internal_err10 : 1;
+ __u32 internal_err11 : 1;
+ __u32 internal_err12 : 1;
+ __u32 internal_err13 : 1;
+ __u32 internal_err14 : 1;
+ __u32 internal_err15 : 1;
+ __u32 internal_err16 : 1;
+ __u32 capacitance_test_fail : 1;
+ __u32 IO_read_fail : 1;
+ __u32 IO_write_fail : 1;
+ __u32 readOnly_after_rebuild : 1;
+ __u32 firmware_loss : 1;
+ __u32 cap_unsupply : 1;
+ __u32 spare_space_warning : 1;
+ __u32 lifetime_warning : 1;
+ __u32 temp_high_warning : 1;
+ __u32 temp_low_warning : 1;
+ __u32 mcu_disable : 1;
+ __u32 rsv : 25;
+} vendor_warning_str;
+
+typedef struct r1_vendor_log_ncm_cout {
+ __u32 nand_rd_unc_cnt;
+ __u32 nand_rd_srr_cnt;
+ __u32 nand_rd_soft_decode_cnt;
+ __u32 nand_rd_rebuild_fail_cnt;
+ __u32 nand_prg_fail_cnt;
+ __u32 nand_eras_fail_cnt;
+ __u64 nand_rd_cnt;
+ __u64 nand_prg_cnt;
+ __u64 nand_eras_cnt;
+ __u32 BE_scan_unc_cnt;
+ __u32 rebuild_req_cnt;
+ __u16 retry_req_cnt;
+ __u16 retry_success_cnt;
+ __u32 prg_badblk_num;
+ __u32 eras_badblk_num;
+ __u32 unc_badblk_num;
+} r1_vendor_log_nandctl_count_t;
+
+typedef struct r1_wearlvl_vendor_log_count {
+ __u32 fbb_count;
+ __u32 ebb_count;
+ __u32 lbb_count;
+ __u32 gc_read_count;
+ __u32 gc_write_count;
+ __u32 gc_write_fail_count;
+ __u32 force_gc_count;
+ __u32 avg_pe_count;
+ __u32 max_pe_count;
+ __u32 free_blk_num1;
+ __u32 free_blk_num2;
+} r1_wearlvl_vendor_log_count_t;
+
+typedef struct vendor_media_err {
+ __u64 lba_err[10];
+} vendor_media_err_t;
+
+typedef struct r1_vendor_log_io_err {
+ __u32 io_guard_err;
+ __u32 io_apptag_err;
+ __u32 io_reftag_err;
+ __u32 io_dma_linkdown_err;
+ __u32 io_dma_disable_err;
+ __u32 io_dma_timeout_err;
+ __u32 io_dma_fatal_err;
+ __u32 io_write_fail_cout;
+ __u32 io_read_fail_cout;
+ __u32 lba_err[6];
+} r1_vendor_log_io_err_t;
+
+typedef struct r1_vendor_log_s {
+ __u32 max_power;
+ __u32 disk_max_temper;
+ __u32 disk_overtemper_cout;
+ __u32 ctrl_max_temper;
+ __u32 ctrl_overtemper_cout;
+ r1_cap_transtime_t cap_transtime;
+ __u32 cap_health_state;
+ __u32 device_state;
+ r1_vendor_log_io_err_t io_err[4];
+ union {
+ vendor_warning_str detail_warning_bit;
+ __u32 detail_warning[2];
+ };
+ union {
+ vendor_warning_str detail_warning_his_bit;
+ __u32 detail_warning_his[2];
+ };
+ __u32 ddr_bit_err_cout;
+ __u32 temp_throttle_per;
+ __u64 port0_fundamental_reset_cnt;
+ __u64 port0_hot_reset_cnt;
+ __u64 port0_func_reset_cnt;
+ __u64 port0_linkdown_cnt;
+ __u64 port0_ctrl_reset_cnt;
+ __u64 nand_bytes_written[4];
+ __u32 power_info;
+ __u32 voltage_info;
+ __u32 current_info;
+ __u32 current_temp[4];
+ __u32 nand_max_temper;
+ __u32 nand_overtemper_cout;
+ __u32 mcu_data_id;
+ __u8 commit_id[16];
+ __u32 ces_RcvErr_cnt;
+ __u32 ces_BadTlp_cnt;
+ __u32 ces_BadDllp_cnt;
+ __u32 ces_Rplyover_cnt;
+ __u32 ces_RplyTo_cnt;
+ __u32 ces_Hlo_cnt;
+ __u32 scan_db_err_cnt;
+ __u32 db_int_err_cnt;
+ __u8 rsvFE[56];
+ r1_vendor_log_nandctl_count_t vendor_log_nandctl_cnt[4];
+ __u32 temp_ctrl_limit_cnt;
+ __u32 temp_ctrl_stop_cnt;
+ __u8 rsvncm[216];
+ r1_wearlvl_vendor_log_count_t wearlvl_vendor_log_count[4];
+ __u8 rsvwlm[512 - sizeof(r1_wearlvl_vendor_log_count_t) * 4 % 512];
+ __u32 e2e_check_err_cnt1;
+ __u32 e2e_check_err_cnt2;
+ __u32 e2e_check_err_cnt3;
+ __u32 e2e_check_err_cnt4;
+ vendor_media_err_t media_err[4];
+ __u8 rsvlkm[176];
+} r1_cli_vendor_log_t;
+#pragma pack(pop)
+
+#endif // __INSPUR_UTILS_H__
diff --git a/plugins/intel/intel-nvme.c b/plugins/intel/intel-nvme.c
new file mode 100644
index 0000000..378ecc0
--- /dev/null
+++ b/plugins/intel/intel-nvme.c
@@ -0,0 +1,1738 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "nvme-print.h"
+
+#define CREATE_CMD
+#include "intel-nvme.h"
+
+struct __packed nvme_additional_smart_log_item {
+ __u8 key;
+ __u8 _kp[2];
+ __u8 norm;
+ __u8 _np;
+ union __packed {
+ __u8 raw[6];
+ struct __packed wear_level {
+ __le16 min;
+ __le16 max;
+ __le16 avg;
+ } wear_level;
+ struct __packed thermal_throttle {
+ __u8 pct;
+ __u32 count;
+ } thermal_throttle;
+ };
+ __u8 _rp;
+};
+
+struct nvme_additional_smart_log {
+ struct nvme_additional_smart_log_item program_fail_cnt;
+ struct nvme_additional_smart_log_item erase_fail_cnt;
+ struct nvme_additional_smart_log_item wear_leveling_cnt;
+ struct nvme_additional_smart_log_item e2e_err_cnt;
+ struct nvme_additional_smart_log_item crc_err_cnt;
+ struct nvme_additional_smart_log_item timed_workload_media_wear;
+ struct nvme_additional_smart_log_item timed_workload_host_reads;
+ struct nvme_additional_smart_log_item timed_workload_timer;
+ struct nvme_additional_smart_log_item thermal_throttle_status;
+ struct nvme_additional_smart_log_item retry_buffer_overflow_cnt;
+ struct nvme_additional_smart_log_item pll_lock_loss_cnt;
+ struct nvme_additional_smart_log_item nand_bytes_written;
+ struct nvme_additional_smart_log_item host_bytes_written;
+ struct nvme_additional_smart_log_item host_ctx_wear_used;
+ struct nvme_additional_smart_log_item perf_stat_indicator;
+ struct nvme_additional_smart_log_item re_alloc_sectr_cnt;
+ struct nvme_additional_smart_log_item soft_ecc_err_rate;
+ struct nvme_additional_smart_log_item unexp_power_loss;
+ struct nvme_additional_smart_log_item media_bytes_read;
+ struct nvme_additional_smart_log_item avail_fw_downgrades;
+};
+
+struct nvme_vu_id_ctrl_field { /* CDR MR5 */
+ __u8 rsvd1[3];
+ __u8 ss;
+ __u8 health[20];
+ __u8 cls;
+ __u8 nlw;
+ __u8 scap;
+ __u8 sstat;
+ __u8 bl[8];
+ __u8 rsvd2[38];
+ __u8 ww[8]; /* little endian */
+ __u8 mic_bl[4];
+ __u8 mic_fw[4];
+};
+
+static void json_intel_id_ctrl(struct nvme_vu_id_ctrl_field *id,
+ char *health, char *bl, char *ww, char *mic_bl, char *mic_fw,
+ struct json_object *root)
+{
+ json_object_add_value_int(root, "ss", id->ss);
+ json_object_add_value_string(root, "health", health);
+ json_object_add_value_int(root, "cls", id->cls);
+ json_object_add_value_int(root, "nlw", id->nlw);
+ json_object_add_value_int(root, "scap", id->scap);
+ json_object_add_value_int(root, "sstat", id->sstat);
+ json_object_add_value_string(root, "bl", bl);
+ json_object_add_value_string(root, "ww", ww);
+ json_object_add_value_string(root, "mic_bl", mic_bl);
+ json_object_add_value_string(root, "mic_fw", mic_fw);
+}
+
+static void intel_id_ctrl(__u8 *vs, struct json_object *root)
+{
+ struct nvme_vu_id_ctrl_field *id = (struct nvme_vu_id_ctrl_field *)vs;
+
+ char health[21] = { 0 };
+ char bl[9] = { 0 };
+ char ww[19] = { 0 };
+ char mic_bl[5] = { 0 };
+ char mic_fw[5] = { 0 };
+
+ if (id->health[0] == 0)
+ snprintf(health, 21, "%s", "healthy");
+ else
+ snprintf(health, 21, "%s", id->health);
+
+ snprintf(bl, 9, "%s", id->bl);
+ snprintf(ww, 19, "%02X%02X%02X%02X%02X%02X%02X%02X", id->ww[7],
+ id->ww[6], id->ww[5], id->ww[4], id->ww[3], id->ww[2],
+ id->ww[1], id->ww[0]);
+ snprintf(mic_bl, 5, "%s", id->mic_bl);
+ snprintf(mic_fw, 5, "%s", id->mic_fw);
+
+ if (root) {
+ json_intel_id_ctrl(id, health, bl, ww, mic_bl, mic_fw, root);
+ return;
+ }
+
+ printf("ss : %d\n", id->ss);
+ printf("health : %s\n", health);
+ printf("cls : %d\n", id->cls);
+ printf("nlw : %d\n", id->nlw);
+ printf("scap : %d\n", id->scap);
+ printf("sstat : %d\n", id->sstat);
+ printf("bl : %s\n", bl);
+ printf("ww : %s\n", ww);
+ printf("mic_bl : %s\n", mic_bl);
+ printf("mic_fw : %s\n", mic_fw);
+}
+
+static int id_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return __id_ctrl(argc, argv, cmd, plugin, intel_id_ctrl);
+}
+
+static void show_intel_smart_log_jsn(struct nvme_additional_smart_log *smart,
+ unsigned int nsid, const char *devname)
+{
+ struct json_object *root, *entry_stats, *dev_stats, *multi;
+
+ root = json_create_object();
+ json_object_add_value_string(root, "Intel Smart log", devname);
+
+ dev_stats = json_create_object();
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->program_fail_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->program_fail_cnt.raw));
+ json_object_add_value_object(dev_stats, "program_fail_count", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->erase_fail_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->erase_fail_cnt.raw));
+ json_object_add_value_object(dev_stats, "erase_fail_count", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->wear_leveling_cnt.norm);
+ multi = json_create_object();
+ json_object_add_value_int(multi, "min", le16_to_cpu(smart->wear_leveling_cnt.wear_level.min));
+ json_object_add_value_int(multi, "max", le16_to_cpu(smart->wear_leveling_cnt.wear_level.max));
+ json_object_add_value_int(multi, "avg", le16_to_cpu(smart->wear_leveling_cnt.wear_level.avg));
+ json_object_add_value_object(entry_stats, "raw", multi);
+ json_object_add_value_object(dev_stats, "wear_leveling", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->e2e_err_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->e2e_err_cnt.raw));
+ json_object_add_value_object(dev_stats, "end_to_end_error_detection_count", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->crc_err_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->crc_err_cnt.raw));
+ json_object_add_value_object(dev_stats, "crc_error_count", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->timed_workload_media_wear.norm);
+ json_object_add_value_double(entry_stats, "raw", ((long double)int48_to_long(smart->timed_workload_media_wear.raw)) / 1024);
+ json_object_add_value_object(dev_stats, "timed_workload_media_wear", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->timed_workload_host_reads.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->timed_workload_host_reads.raw));
+ json_object_add_value_object(dev_stats, "timed_workload_host_reads", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->timed_workload_timer.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->timed_workload_timer.raw));
+ json_object_add_value_object(dev_stats, "timed_workload_timer", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->thermal_throttle_status.norm);
+ multi = json_create_object();
+ json_object_add_value_int(multi, "pct", smart->thermal_throttle_status.thermal_throttle.pct);
+ json_object_add_value_int(multi, "cnt", smart->thermal_throttle_status.thermal_throttle.count);
+ json_object_add_value_object(entry_stats, "raw", multi);
+ json_object_add_value_object(dev_stats, "thermal_throttle_status", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->retry_buffer_overflow_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->retry_buffer_overflow_cnt.raw));
+ json_object_add_value_object(dev_stats, "retry_buffer_overflow_count", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->pll_lock_loss_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->pll_lock_loss_cnt.raw));
+ json_object_add_value_object(dev_stats, "pll_lock_loss_count", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->nand_bytes_written.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->nand_bytes_written.raw));
+ json_object_add_value_object(dev_stats, "nand_bytes_written", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->host_bytes_written.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->host_bytes_written.raw));
+ json_object_add_value_object(dev_stats, "host_bytes_written", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->host_ctx_wear_used.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->host_ctx_wear_used.raw));
+ json_object_add_value_object(dev_stats, "host_ctx_wear_used", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->perf_stat_indicator.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->perf_stat_indicator.raw));
+ json_object_add_value_object(dev_stats, "perf_stat_indicator", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->re_alloc_sectr_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->re_alloc_sectr_cnt.raw));
+ json_object_add_value_object(dev_stats, "re_alloc_sectr_cnt", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->soft_ecc_err_rate.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->soft_ecc_err_rate.raw));
+ json_object_add_value_object(dev_stats, "soft_ecc_err_rate", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->unexp_power_loss.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->unexp_power_loss.raw));
+ json_object_add_value_object(dev_stats, "unexp_power_loss", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->media_bytes_read.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->media_bytes_read.raw));
+ json_object_add_value_object(dev_stats, "media_bytes_read", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->avail_fw_downgrades.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->avail_fw_downgrades.raw));
+ json_object_add_value_object(dev_stats, "avail_fw_downgrades", entry_stats);
+
+ json_object_add_value_object(root, "Device stats", dev_stats);
+
+ json_print_object(root, NULL);
+ json_free_object(root);
+}
+
+static char *id_to_key(__u8 id)
+{
+ switch (id) {
+ case 0xAB:
+ return "program_fail_count";
+ case 0xAC:
+ return "erase_fail_count";
+ case 0xAD:
+ return "wear_leveling_count";
+ case 0xB8:
+ return "e2e_error_detect_count";
+ case 0xC7:
+ return "crc_error_count";
+ case 0xE2:
+ return "media_wear_percentage";
+ case 0xE3:
+ return "host_reads";
+ case 0xE4:
+ return "timed_work_load";
+ case 0xEA:
+ return "thermal_throttle_status";
+ case 0xF0:
+ return "retry_buff_overflow_count";
+ case 0xF3:
+ return "pll_lock_loss_counter";
+ case 0xF4:
+ return "nand_bytes_written";
+ case 0xF5:
+ return "host_bytes_written";
+ case 0xF6:
+ return "host_context_wear_used";
+ case 0xF7:
+ return "performance_status_indicator";
+ case 0xF8:
+ return "media_bytes_read";
+ case 0xF9:
+ return "available_fw_downgrades";
+ case 0x05:
+ return "re-allocated_sector_count";
+ case 0x0D:
+ return "soft_ecc_error_rate";
+ case 0xAE:
+ return "unexpected_power_loss";
+ default:
+ return "Invalid ID";
+ }
+}
+
+static void print_intel_smart_log_items(struct nvme_additional_smart_log_item *item)
+{
+ if (!item->key)
+ return;
+
+ printf("%#x %-45s %3d %"PRIu64"\n",
+ item->key, id_to_key(item->key),
+ item->norm, int48_to_long(item->raw));
+}
+
+static void show_intel_smart_log(struct nvme_additional_smart_log *smart,
+ unsigned int nsid, const char *devname)
+{
+ struct nvme_additional_smart_log_item *iter = &smart->program_fail_cnt;
+ int num_items = sizeof(struct nvme_additional_smart_log) /
+ sizeof(struct nvme_additional_smart_log_item);
+
+ printf("Additional Smart Log for NVME device:%s namespace-id:%x\n",
+ devname, nsid);
+ printf("ID KEY Normalized Raw\n");
+
+ for (int i = 0; i < num_items; i++, iter++)
+ print_intel_smart_log_items(iter);
+}
+
+static int get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc =
+ "Get Intel vendor specific additional smart log (optionally, for the specified namespace), and show it.";
+ const char *namespace = "(optional) desired namespace";
+ const char *raw = "Dump output in binary format";
+ const char *json = "Dump output in json format";
+
+ struct nvme_additional_smart_log smart_log;
+ struct nvme_dev *dev;
+ int err;
+
+ struct config {
+ __u32 namespace_id;
+ bool raw_binary;
+ bool json;
+ };
+
+ struct config cfg = {
+ .namespace_id = NVME_NSID_ALL,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace),
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_FLAG("json", 'j', &cfg.json, json),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = nvme_get_log_simple(dev_fd(dev), 0xca, sizeof(smart_log),
+ &smart_log);
+ if (!err) {
+ if (cfg.json)
+ show_intel_smart_log_jsn(&smart_log, cfg.namespace_id,
+ dev->name);
+ else if (!cfg.raw_binary)
+ show_intel_smart_log(&smart_log, cfg.namespace_id,
+ dev->name);
+ else
+ d_raw((unsigned char *)&smart_log, sizeof(smart_log));
+ } else if (err > 0) {
+ nvme_show_status(err);
+ }
+ dev_close(dev);
+ return err;
+}
+
+static int get_market_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Get Intel Marketing Name log and show it.";
+ const char *raw = "dump output in binary format";
+ struct nvme_dev *dev;
+ char log[512];
+ int err;
+
+ struct config {
+ bool raw_binary;
+ };
+
+ struct config cfg = {
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = nvme_get_log_simple(dev_fd(dev), 0xdd, sizeof(log), log);
+ if (!err) {
+ if (!cfg.raw_binary)
+ printf("Intel Marketing Name Log:\n%s\n", log);
+ else
+ d_raw((unsigned char *)&log, sizeof(log));
+ } else if (err > 0)
+ nvme_show_status(err);
+ dev_close(dev);
+ return err;
+}
+
+struct intel_temp_stats {
+ __le64 curr;
+ __le64 last_overtemp;
+ __le64 life_overtemp;
+ __le64 highest_temp;
+ __le64 lowest_temp;
+ __u8 rsvd[40];
+ __le64 max_operating_temp;
+ __le64 min_operating_temp;
+ __le64 est_offset;
+};
+
+static void show_temp_stats(struct intel_temp_stats *stats)
+{
+ printf(" Intel Temperature Statistics\n");
+ printf("--------------------------------\n");
+ printf("Current temperature : %"PRIu64"\n", le64_to_cpu(stats->curr));
+ printf("Last critical overtemp flag : %"PRIu64"\n", le64_to_cpu(stats->last_overtemp));
+ printf("Life critical overtemp flag : %"PRIu64"\n", le64_to_cpu(stats->life_overtemp));
+ printf("Highest temperature : %"PRIu64"\n", le64_to_cpu(stats->highest_temp));
+ printf("Lowest temperature : %"PRIu64"\n", le64_to_cpu(stats->lowest_temp));
+ printf("Max operating temperature : %"PRIu64"\n", le64_to_cpu(stats->max_operating_temp));
+ printf("Min operating temperature : %"PRIu64"\n", le64_to_cpu(stats->min_operating_temp));
+ printf("Estimated offset : %"PRIu64"\n", le64_to_cpu(stats->est_offset));
+}
+
+static int get_temp_stats_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ struct intel_temp_stats stats;
+ struct nvme_dev *dev;
+ int err;
+
+ const char *desc = "Get Temperature Statistics log and show it.";
+ const char *raw = "dump output in binary format";
+ struct config {
+ bool raw_binary;
+ };
+
+ struct config cfg = {
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = nvme_get_log_simple(dev_fd(dev), 0xc5, sizeof(stats), &stats);
+ if (!err) {
+ if (!cfg.raw_binary)
+ show_temp_stats(&stats);
+ else
+ d_raw((unsigned char *)&stats, sizeof(stats));
+ } else if (err > 0)
+ nvme_show_status(err);
+ dev_close(dev);
+ return err;
+}
+
+struct intel_lat_stats {
+ __u16 maj;
+ __u16 min;
+ __u32 data[1216];
+};
+
+struct __packed optane_lat_stats {
+ __u16 maj;
+ __u16 min;
+ __u64 data[9];
+};
+
+#define MEDIA_MAJOR_IDX 0
+#define MEDIA_MINOR_IDX 1
+#define MEDIA_MAX_LEN 2
+#define OPTANE_V1000_BUCKET_LEN 8
+#define OPTANE_V1000_BUCKET_LEN 8
+#define NAND_LAT_STATS_LEN 4868
+
+
+static struct intel_lat_stats stats;
+static struct optane_lat_stats v1000_stats;
+
+struct v1000_thresholds {
+ __u32 read[OPTANE_V1000_BUCKET_LEN];
+ __u32 write[OPTANE_V1000_BUCKET_LEN];
+};
+
+static struct v1000_thresholds v1000_bucket;
+static __u16 media_version[MEDIA_MAX_LEN] = {0};
+
+enum FormatUnit {
+ US,
+ MS,
+ S
+};
+
+/*
+ * COL_WIDTH controls width of columns in human-readable output.
+ * BUFSIZE is for local temp char[]
+ * US_IN_S and US_IN_MS are for unit conversions when printing.
+ */
+#define COL_WIDTH 12
+#define BUFSIZE 10
+#define US_IN_S 1000000
+#define US_IN_MS 1000
+
+static enum FormatUnit get_seconds_magnitude(__u32 microseconds)
+{
+ if (microseconds > US_IN_S)
+ return S;
+ else if (microseconds > US_IN_MS)
+ return MS;
+ else
+ return US;
+}
+
+static float convert_seconds(__u32 microseconds)
+{
+ float divisor = 1.0;
+
+ if (microseconds > US_IN_S)
+ divisor = US_IN_S;
+ else if (microseconds > US_IN_MS)
+ divisor = US_IN_MS;
+ return microseconds / divisor;
+}
+
+/*
+ * For control over whether a string will format to +/-INF or
+ * print out ####.##US normally.
+ */
+enum inf_bound_type {
+ NEGINF,
+ POSINF,
+ NOINF
+};
+
+/*
+ * Edge buckets may have range [#s, inf) or (-inf, #US] in some
+ * latency statistics formats.
+ * Passing in NEGINF to POSINF to bound_type overrides the string to
+ * either of "-INF" or "+INF", respectively.
+ */
+static void set_unit_string(char *buffer, __u32 microseconds,
+ enum FormatUnit unit,
+ enum inf_bound_type bound_type)
+{
+ char *string;
+
+ if (bound_type != NOINF) {
+ snprintf(buffer, BUFSIZE, "%s", bound_type ? "+INF" : "-INF");
+ return;
+ }
+
+ switch (unit) {
+ case US:
+ string = "us";
+ break;
+ case MS:
+ string = "ms";
+ break;
+ case S:
+ string = "s";
+ break;
+ default:
+ string = "_s";
+ break;
+ }
+
+ snprintf(buffer, BUFSIZE, "%4.2f%s", convert_seconds(microseconds),
+ string);
+}
+
+static void init_buffer(char *buffer, size_t size)
+{
+ size_t i;
+
+ for (i = 0; i < size; i++)
+ buffer[i] = i + '0';
+}
+
+static void show_lat_stats_bucket(struct intel_lat_stats *stats,
+ __u32 lower_us, enum inf_bound_type start_type,
+ __u32 upper_us, enum inf_bound_type end_type, int i)
+{
+ enum FormatUnit fu = S;
+ char buffer[BUFSIZE];
+
+ init_buffer(buffer, BUFSIZE);
+ printf("%-*d", COL_WIDTH, i);
+
+ fu = get_seconds_magnitude(lower_us);
+ set_unit_string(buffer, lower_us, fu, start_type);
+ printf("%-*s", COL_WIDTH, buffer);
+
+ fu = get_seconds_magnitude(upper_us);
+ set_unit_string(buffer, upper_us, fu, end_type);
+ printf("%-*s", COL_WIDTH, buffer);
+
+ printf("%-*d\n", COL_WIDTH, stats->data[i]);
+}
+
+static void show_optane_lat_stats_bucket(struct optane_lat_stats *stats,
+ __u32 lower_us, enum inf_bound_type start_type,
+ __u32 upper_us, enum inf_bound_type end_type, int i)
+{
+ enum FormatUnit fu = S;
+ char buffer[BUFSIZE];
+
+ init_buffer(buffer, BUFSIZE);
+ printf("%-*d", COL_WIDTH, i);
+
+ fu = get_seconds_magnitude(lower_us);
+ set_unit_string(buffer, lower_us, fu, start_type);
+ printf("%-*s", COL_WIDTH, buffer);
+
+ fu = get_seconds_magnitude(upper_us);
+ set_unit_string(buffer, upper_us, fu, end_type);
+ printf("%-*s", COL_WIDTH, buffer);
+
+ printf("%-*lu\n", COL_WIDTH, (unsigned long)stats->data[i]);
+}
+
+
+static void show_lat_stats_linear(struct intel_lat_stats *stats,
+ __u32 start_offset, __u32 end_offset, __u32 bytes_per,
+ __u32 us_step, bool nonzero_print)
+{
+ for (int i = (start_offset / bytes_per) - 1;
+ i < end_offset / bytes_per; i++) {
+ if (nonzero_print && stats->data[i] == 0)
+ continue;
+ show_lat_stats_bucket(stats, us_step * i, NOINF,
+ us_step * (i + 1), NOINF, i);
+ }
+}
+
+/*
+ * For 4.0-4.5 revision.
+ */
+#define LATENCY_STATS_V4_BASE_BITS 6
+#define LATENCY_STATS_V4_BASE_VAL (1 << LATENCY_STATS_V4_BASE_BITS)
+
+static int lat_stats_log_scale(int i)
+{
+ // if (i < 128)
+ if (i < (LATENCY_STATS_V4_BASE_VAL << 1))
+ return i;
+
+ int error_bits = (i >> LATENCY_STATS_V4_BASE_BITS) - 1;
+ int base = 1 << (error_bits + LATENCY_STATS_V4_BASE_BITS);
+ int k = i % LATENCY_STATS_V4_BASE_VAL;
+
+ return base + ((k + 0.5) * (1 << error_bits));
+}
+
+/*
+ * Creates a subroot in the following manner:
+ * {
+ * "latstats" : {
+ * "type" : "write" or "read",
+ * "values" : {
+ */
+static void lat_stats_make_json_root(
+ struct json_object *root, struct json_object *bucket_list,
+ int write)
+{
+ struct json_object *subroot = json_create_object();
+
+ json_object_add_value_object(root, "latstats", subroot);
+ json_object_add_value_string(subroot, "type", write ? "write" : "read");
+ json_object_add_value_object(subroot, "values", bucket_list);
+}
+
+/*
+ * Creates a bucket under the "values" json_object. Format is:
+ * "values" : {
+ * "bucket" : {
+ * "id" : #,
+ * "start" : string,
+ * "end" : string,
+ * "value" : 0,
+ * },
+ */
+static void json_add_bucket(struct intel_lat_stats *stats,
+ struct json_object *bucket_list, __u32 id,
+ __u32 lower_us, enum inf_bound_type start_type,
+ __u32 upper_us, enum inf_bound_type end_type, __u32 val)
+{
+ char buffer[BUFSIZE];
+ struct json_object *bucket = json_create_object();
+
+ init_buffer(buffer, BUFSIZE);
+
+ json_object_array_add(bucket_list, bucket);
+ json_object_add_value_int(bucket, "id", id);
+
+ set_unit_string(buffer, lower_us,
+ get_seconds_magnitude(lower_us), start_type);
+ json_object_add_value_string(bucket, "start", buffer);
+
+ set_unit_string(buffer, upper_us,
+ get_seconds_magnitude(upper_us), end_type);
+ json_object_add_value_string(bucket, "end", buffer);
+
+ json_object_add_value_int(bucket, "value", val);
+}
+
+static void json_add_bucket_optane(struct json_object *bucket_list, __u32 id,
+ __u32 lower_us, enum inf_bound_type start_type,
+ __u32 upper_us, enum inf_bound_type end_type, __u32 val)
+{
+ char buffer[BUFSIZE];
+ struct json_object *bucket = json_create_object();
+
+ init_buffer(buffer, BUFSIZE);
+
+ json_object_array_add(bucket_list, bucket);
+ json_object_add_value_int(bucket, "id", id);
+
+ set_unit_string(buffer, lower_us,
+ get_seconds_magnitude(lower_us), start_type);
+ json_object_add_value_string(bucket, "start", buffer);
+
+ set_unit_string(buffer, upper_us,
+ get_seconds_magnitude(upper_us), end_type);
+ json_object_add_value_string(bucket, "end", buffer);
+
+ json_object_add_value_uint(bucket, "value", val);
+
+}
+
+static void json_lat_stats_linear(struct intel_lat_stats *stats,
+ struct json_object *bucket_list, __u32 start_offset,
+ __u32 end_offset, __u32 bytes_per,
+ __u32 us_step, bool nonzero_print)
+{
+ for (int i = (start_offset / bytes_per) - 1;
+ i < end_offset / bytes_per; i++) {
+ if (nonzero_print && stats->data[i] == 0)
+ continue;
+
+ json_add_bucket(stats, bucket_list,
+ i, us_step * i, NOINF, us_step * (i + 1),
+ NOINF, stats->data[i]);
+ }
+}
+
+static void json_lat_stats_3_0(struct intel_lat_stats *stats,
+ int write)
+{
+ struct json_object *root = json_create_object();
+ struct json_object *bucket_list = json_object_new_array();
+
+ lat_stats_make_json_root(root, bucket_list, write);
+
+ json_lat_stats_linear(stats, bucket_list, 4, 131, 4, 32, false);
+ json_lat_stats_linear(stats, bucket_list, 132, 255, 4, 1024, false);
+ json_lat_stats_linear(stats, bucket_list, 256, 379, 4, 32768, false);
+ json_lat_stats_linear(stats, bucket_list, 380, 383, 4, 32, true);
+ json_lat_stats_linear(stats, bucket_list, 384, 387, 4, 32, true);
+ json_lat_stats_linear(stats, bucket_list, 388, 391, 4, 32, true);
+
+ json_print_object(root, NULL);
+ json_free_object(root);
+}
+
+static void json_lat_stats_4_0(struct intel_lat_stats *stats,
+ int write)
+{
+ struct json_object *root = json_create_object();
+ struct json_object *bucket_list = json_object_new_array();
+
+ lat_stats_make_json_root(root, bucket_list, write);
+
+ __u32 lower_us = 0;
+ __u32 upper_us = 1;
+ bool end = false;
+ int max = 1216;
+
+ for (int i = 0; i < max; i++) {
+ lower_us = lat_stats_log_scale(i);
+ if (i >= max - 1)
+ end = true;
+ else
+ upper_us = lat_stats_log_scale(i + 1);
+
+ json_add_bucket(stats, bucket_list, i,
+ lower_us, NOINF, upper_us,
+ end ? POSINF : NOINF, stats->data[i]);
+ }
+ json_print_object(root, NULL);
+ json_free_object(root);
+}
+
+static void show_lat_stats_3_0(struct intel_lat_stats *stats)
+{
+ show_lat_stats_linear(stats, 4, 131, 4, 32, false);
+ show_lat_stats_linear(stats, 132, 255, 4, 1024, false);
+ show_lat_stats_linear(stats, 256, 379, 4, 32768, false);
+ show_lat_stats_linear(stats, 380, 383, 4, 32, true);
+ show_lat_stats_linear(stats, 384, 387, 4, 32, true);
+ show_lat_stats_linear(stats, 388, 391, 4, 32, true);
+}
+
+static void show_lat_stats_4_0(struct intel_lat_stats *stats)
+{
+ int lower_us = 0;
+ int upper_us = 1;
+ bool end = false;
+ int max = 1216;
+
+ for (int i = 0; i < max; i++) {
+ lower_us = lat_stats_log_scale(i);
+ if (i >= max - 1)
+ end = true;
+ else
+ upper_us = lat_stats_log_scale(i + 1);
+
+ show_lat_stats_bucket(stats, lower_us, NOINF,
+ upper_us, end ? POSINF : NOINF, i);
+ }
+}
+
+static void json_lat_stats_v1000_0(struct optane_lat_stats *stats, int write)
+{
+ int i;
+ struct json_object *root = json_create_object();
+ struct json_object *bucket_list = json_object_new_array();
+
+ lat_stats_make_json_root(root, bucket_list, write);
+
+ if (write) {
+ for (i = 0; i < OPTANE_V1000_BUCKET_LEN - 1; i++)
+ json_add_bucket_optane(bucket_list, i,
+ v1000_bucket.write[i], NOINF,
+ v1000_bucket.write[i + 1] - 1,
+ NOINF,
+ stats->data[i]);
+
+ json_add_bucket_optane(bucket_list,
+ OPTANE_V1000_BUCKET_LEN - 1,
+ v1000_bucket.write[i],
+ NOINF,
+ v1000_bucket.write[i],
+ POSINF, stats->data[i]);
+ } else {
+ for (i = 0; i < OPTANE_V1000_BUCKET_LEN - 1; i++)
+ json_add_bucket_optane(bucket_list, i,
+ v1000_bucket.read[i], NOINF,
+ v1000_bucket.read[i + 1] - 1,
+ NOINF,
+ stats->data[i]);
+
+ json_add_bucket_optane(bucket_list,
+ OPTANE_V1000_BUCKET_LEN - 1,
+ v1000_bucket.read[i],
+ NOINF,
+ v1000_bucket.read[i],
+ POSINF, stats->data[i]);
+ }
+
+ struct json_object *subroot = json_create_object();
+
+ json_object_add_value_object(root, "Average latency since last reset", subroot);
+
+ json_object_add_value_uint(subroot, "value in us", stats->data[8]);
+
+ json_print_object(root, NULL);
+ json_free_object(root);
+
+}
+
+static void show_lat_stats_v1000_0(struct optane_lat_stats *stats, int write)
+{
+ int i;
+
+ if (write) {
+ for (i = 0; i < OPTANE_V1000_BUCKET_LEN - 1; i++)
+ show_optane_lat_stats_bucket(stats,
+ v1000_bucket.write[i],
+ NOINF,
+ v1000_bucket.write[i + 1] - 1,
+ NOINF, i);
+
+ show_optane_lat_stats_bucket(stats, v1000_bucket.write[i],
+ NOINF, v1000_bucket.write[i],
+ POSINF, i);
+ } else {
+ for (i = 0; i < OPTANE_V1000_BUCKET_LEN - 1; i++)
+ show_optane_lat_stats_bucket(stats,
+ v1000_bucket.read[i],
+ NOINF,
+ v1000_bucket.read[i + 1] - 1,
+ NOINF, i);
+
+ show_optane_lat_stats_bucket(stats, v1000_bucket.read[i],
+ NOINF, v1000_bucket.read[i],
+ POSINF, i);
+ }
+
+ printf("Average latency since last reset: %lu us\n", (unsigned long)stats->data[8]);
+
+}
+
+static void json_lat_stats(int write)
+{
+ switch (media_version[MEDIA_MAJOR_IDX]) {
+ case 3:
+ json_lat_stats_3_0(&stats, write);
+ break;
+ case 4:
+ switch (media_version[MEDIA_MINOR_IDX]) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ json_lat_stats_4_0(&stats, write);
+ break;
+ default:
+ printf("Unsupported minor revision (%u.%u)\n",
+ stats.maj, stats.min);
+ break;
+ }
+ break;
+ case 1000:
+ switch (media_version[MEDIA_MINOR_IDX]) {
+ case 0:
+ json_lat_stats_v1000_0(&v1000_stats, write);
+ break;
+ default:
+ printf("Unsupported minor revision (%u.%u)\n",
+ stats.maj, stats.min);
+ break;
+ }
+ break;
+ default:
+ printf("Unsupported revision (%u.%u)\n",
+ stats.maj, stats.min);
+ break;
+ }
+ printf("\n");
+}
+
+static void print_dash_separator(int count)
+{
+ for (int i = 0; i < count; i++)
+ putchar('-');
+ putchar('\n');
+}
+
+static void show_lat_stats(int write)
+{
+ static const int separator_length = 50;
+
+ printf("Intel IO %s Command Latency Statistics\n",
+ write ? "Write" : "Read");
+ printf("Major Revision : %u\nMinor Revision : %u\n",
+ media_version[MEDIA_MAJOR_IDX], media_version[MEDIA_MINOR_IDX]);
+ print_dash_separator(separator_length);
+ printf("%-12s%-12s%-12s%-20s\n", "Bucket", "Start", "End", "Value");
+ print_dash_separator(separator_length);
+
+ switch (media_version[MEDIA_MAJOR_IDX]) {
+ case 3:
+ show_lat_stats_3_0(&stats);
+ break;
+ case 4:
+ switch (media_version[MEDIA_MINOR_IDX]) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ show_lat_stats_4_0(&stats);
+ break;
+ default:
+ printf("Unsupported minor revision (%u.%u)\n",
+ stats.maj, stats.min);
+ break;
+ }
+ break;
+ case 1000:
+ switch (media_version[MEDIA_MINOR_IDX]) {
+ case 0:
+ show_lat_stats_v1000_0(&v1000_stats, write);
+ break;
+ default:
+ printf("Unsupported minor revision (%u.%u)\n",
+ stats.maj, stats.min);
+ break;
+ }
+ break;
+ default:
+ printf("Unsupported revision (%u.%u)\n",
+ stats.maj, stats.min);
+ break;
+ }
+}
+
+static int get_lat_stats_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ __u8 data[NAND_LAT_STATS_LEN];
+ struct nvme_dev *dev;
+ int err;
+
+ const char *desc = "Get Intel Latency Statistics log and show it.";
+ const char *raw = "Dump output in binary format";
+ const char *json = "Dump output in json format";
+ const char *write = "Get write statistics (read default)";
+
+ struct config {
+ bool raw_binary;
+ bool json;
+ bool write;
+ };
+
+ struct config cfg = {
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("write", 'w', &cfg.write, write),
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_FLAG("json", 'j', &cfg.json, json),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ /* For optate, latency stats are deleted every time their LID is pulled.
+ * Therefore, we query the longest lat_stats log page first.
+ */
+ err = nvme_get_log_simple(dev_fd(dev), cfg.write ? 0xc2 : 0xc1,
+ sizeof(data), &data);
+
+ media_version[0] = (data[1] << 8) | data[0];
+ media_version[1] = (data[3] << 8) | data[2];
+
+ if (err)
+ goto close_dev;
+
+ if (media_version[0] == 1000) {
+ __u32 thresholds[OPTANE_V1000_BUCKET_LEN] = {0};
+ __u32 result;
+
+ struct nvme_get_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = 0xf7,
+ .nsid = 0,
+ .sel = 0,
+ .cdw11 = cfg.write ? 0x1 : 0x0,
+ .uuidx = 0,
+ .data_len = sizeof(thresholds),
+ .data = thresholds,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+ err = nvme_get_features(&args);
+ if (err) {
+ fprintf(stderr, "Querying thresholds failed. ");
+ nvme_show_status(err);
+ goto close_dev;
+ }
+
+ /* Update bucket thresholds to be printed */
+ if (cfg.write) {
+ for (int i = 0; i < OPTANE_V1000_BUCKET_LEN; i++)
+ v1000_bucket.write[i] = thresholds[i];
+ } else {
+ for (int i = 0; i < OPTANE_V1000_BUCKET_LEN; i++)
+ v1000_bucket.read[i] = thresholds[i];
+
+ }
+
+ /* Move counter values to optate stats struct which has a
+ * smaller size
+ */
+ memcpy(&v1000_stats, (struct optane_lat_stats *)data,
+ sizeof(struct optane_lat_stats));
+ } else {
+ memcpy(&stats, (struct intel_lat_stats *)data,
+ sizeof(struct intel_lat_stats));
+ }
+
+ if (cfg.json)
+ json_lat_stats(cfg.write);
+ else if (!cfg.raw_binary)
+ show_lat_stats(cfg.write);
+ else {
+ if (media_version[0] == 1000)
+ d_raw((unsigned char *)&v1000_stats,
+ sizeof(v1000_stats));
+ else
+ d_raw((unsigned char *)&stats,
+ sizeof(stats));
+ }
+
+close_dev:
+ dev_close(dev);
+ return err;
+}
+
+struct intel_assert_dump {
+ __u32 coreoffset;
+ __u32 assertsize;
+ __u8 assertdumptype;
+ __u8 assertvalid;
+ __u8 reserved[2];
+};
+
+struct intel_event_dump {
+ __u32 numeventdumps;
+ __u32 coresize;
+ __u32 coreoffset;
+ __u32 eventidoffset[16];
+ __u8 eventIdValidity[16];
+};
+
+struct intel_vu_version {
+ __u16 major;
+ __u16 minor;
+};
+
+struct intel_event_header {
+ __u32 eventidsize;
+ struct intel_event_dump edumps[0];
+};
+
+struct intel_vu_log {
+ struct intel_vu_version ver;
+ __u32 header;
+ __u32 size;
+ __u32 numcores;
+ __u8 reserved[4080];
+};
+
+struct intel_vu_nlog {
+ struct intel_vu_version ver;
+ __u32 logselect;
+ __u32 totalnlogs;
+ __u32 nlognum;
+ __u32 nlogname;
+ __u32 nlogbytesize;
+ __u32 nlogprimarybuffsize;
+ __u32 tickspersecond;
+ __u32 corecount;
+ __u32 nlogpausestatus;
+ __u32 selectoffsetref;
+ __u32 selectnlogpause;
+ __u32 selectaddedoffset;
+ __u32 nlogbufnum;
+ __u32 nlogbufnummax;
+ __u32 coreselected;
+ __u32 reserved[3];
+};
+
+struct intel_cd_log {
+ union {
+ struct {
+ __u32 selectLog : 3;
+ __u32 selectCore : 2;
+ __u32 selectNlog : 8;
+ __u8 selectOffsetRef : 1;
+ __u32 selectNlogPause : 2;
+ __u32 reserved2 : 16;
+ } fields;
+ __u32 entireDword;
+ } u;
+};
+
+static void print_intel_nlog(struct intel_vu_nlog *intel_nlog)
+{
+ printf("Version Major %u\n"
+ "Version Minor %u\n"
+ "Log_select %u\n"
+ "totalnlogs %u\n"
+ "nlognum %u\n"
+ "nlogname %u\n"
+ "nlogbytesze %u\n"
+ "nlogprimarybuffsize %u\n"
+ "tickspersecond %u\n"
+ "corecount %u\n"
+ "nlogpausestatus %u\n"
+ "selectoffsetref %u\n"
+ "selectnlogpause %u\n"
+ "selectaddedoffset %u\n"
+ "nlogbufnum %u\n"
+ "nlogbufnummax %u\n"
+ "coreselected %u\n",
+ intel_nlog->ver.major, intel_nlog->ver.minor,
+ intel_nlog->logselect, intel_nlog->totalnlogs, intel_nlog->nlognum,
+ intel_nlog->nlogname, intel_nlog->nlogbytesize,
+ intel_nlog->nlogprimarybuffsize, intel_nlog->tickspersecond,
+ intel_nlog->corecount, intel_nlog->nlogpausestatus,
+ intel_nlog->selectoffsetref, intel_nlog->selectnlogpause,
+ intel_nlog->selectaddedoffset, intel_nlog->nlogbufnum,
+ intel_nlog->nlogbufnummax, intel_nlog->coreselected);
+}
+
+static int read_entire_cmd(struct nvme_passthru_cmd *cmd, int total_size,
+ const size_t max_tfer, int out_fd, int ioctl_fd,
+ __u8 *buf)
+{
+ int err = 0;
+ size_t dword_tfer = 0;
+
+ dword_tfer = min(max_tfer, total_size);
+ while (total_size > 0) {
+ err = nvme_submit_admin_passthru(ioctl_fd, cmd, NULL);
+ if (err) {
+ fprintf(stderr,
+ "failed on cmd.data_len %u cmd.cdw13 %u cmd.cdw12 %x cmd.cdw10 %u err %x remaining size %d\n",
+ cmd->data_len, cmd->cdw13, cmd->cdw12,
+ cmd->cdw10, err, total_size);
+ goto out;
+ }
+
+ if (out_fd > 0) {
+ err = write(out_fd, buf, cmd->data_len);
+ if (err < 0) {
+ perror("write failure");
+ goto out;
+ }
+ err = 0;
+ }
+ total_size -= dword_tfer;
+ cmd->cdw13 += dword_tfer;
+ cmd->cdw10 = dword_tfer = min(max_tfer, total_size);
+ cmd->data_len = (min(max_tfer, total_size)) * 4;
+ }
+
+ out:
+ return err;
+}
+
+static int write_header(__u8 *buf, int fd, size_t amnt)
+{
+ if (write(fd, buf, amnt) < 0)
+ return 1;
+ return 0;
+}
+
+static int read_header(struct nvme_passthru_cmd *cmd, __u8 *buf, int ioctl_fd,
+ __u32 dw12, int nsid)
+{
+ memset(cmd, 0, sizeof(*cmd));
+ memset(buf, 0, 4096);
+ cmd->opcode = 0xd2;
+ cmd->nsid = nsid;
+ cmd->cdw10 = 0x400;
+ cmd->cdw12 = dw12;
+ cmd->data_len = 0x1000;
+ cmd->addr = (unsigned long)(void *)buf;
+ return read_entire_cmd(cmd, 0x400, 0x400, -1, ioctl_fd, buf);
+}
+
+static int setup_file(char *f, char *file, int fd, int type)
+{
+ struct nvme_id_ctrl ctrl;
+ int err = 0, i = sizeof(ctrl.sn) - 1;
+
+ err = nvme_identify_ctrl(fd, &ctrl);
+ if (err)
+ return err;
+
+ /* Remove trailing spaces from the name */
+ while (i && ctrl.sn[i] == ' ') {
+ ctrl.sn[i] = '\0';
+ i--;
+ }
+
+ sprintf(f, "%s_%-.*s.bin", type == 0 ? "Nlog" :
+ type == 1 ? "EventLog" : "AssertLog",
+ (int)sizeof(ctrl.sn), ctrl.sn);
+ return err;
+}
+
+static int get_internal_log_old(__u8 *buf, int output, int fd,
+ struct nvme_passthru_cmd *cmd)
+{
+ struct intel_vu_log *intel;
+ int err = 0;
+ const int dwmax = 0x400;
+ const int dmamax = 0x1000;
+
+ intel = (struct intel_vu_log *)buf;
+
+ printf("Log major:%d minor:%d header:%d size:%d\n",
+ intel->ver.major, intel->ver.minor, intel->header, intel->size);
+
+ err = write(output, buf, 0x1000);
+ if (err < 0) {
+ perror("write failure");
+ goto out;
+ }
+ intel->size -= 0x400;
+ cmd->opcode = 0xd2;
+ cmd->cdw10 = min(dwmax, intel->size);
+ cmd->data_len = min(dmamax, intel->size);
+ err = read_entire_cmd(cmd, intel->size, dwmax, output, fd, buf);
+ if (err)
+ goto out;
+
+ err = 0;
+ out:
+ return err;
+}
+
+static int get_internal_log(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ __u8 buf[0x2000];
+ char f[0x100];
+ int err, output, i, j, count = 0, core_num = 1;
+ struct nvme_passthru_cmd cmd;
+ struct intel_cd_log cdlog;
+ struct intel_vu_log *intel = malloc(sizeof(struct intel_vu_log));
+ struct intel_vu_nlog *intel_nlog = (struct intel_vu_nlog *)buf;
+ struct intel_assert_dump *ad = (struct intel_assert_dump *) intel->reserved;
+ struct intel_event_header *ehdr = (struct intel_event_header *)intel->reserved;
+ struct nvme_dev *dev;
+
+ const char *desc = "Get Intel Firmware Log and save it.";
+ const char *log = "Log type: 0, 1, or 2 for nlog, event log, and assert log, respectively.";
+ const char *core = "Select which region log should come from. -1 for all";
+ const char *nlognum = "Select which nlog to read. -1 for all nlogs";
+ const char *file = "Output file; defaults to device name provided";
+ const char *verbose = "To print out verbose nlog info";
+ const char *namespace_id = "Namespace to get logs from";
+
+ struct config {
+ __u32 namespace_id;
+ __u32 log;
+ int core;
+ int lnum;
+ char *file;
+ bool verbose;
+ };
+
+ struct config cfg = {
+ .namespace_id = -1,
+ .file = NULL,
+ .lnum = -1,
+ .core = -1
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("log", 'l', &cfg.log, log),
+ OPT_INT("region", 'r', &cfg.core, core),
+ OPT_INT("nlognum", 'm', &cfg.lnum, nlognum),
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_FILE("output-file", 'o', &cfg.file, file),
+ OPT_FLAG("verbose-nlog", 'v', &cfg.verbose, verbose),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err) {
+ free(intel);
+ return err;
+ }
+
+ if (cfg.log > 2 || cfg.core > 4 || cfg.lnum > 255) {
+ err = -EINVAL;
+ goto out_free;
+ }
+
+ if (!cfg.file) {
+ err = setup_file(f, cfg.file, dev_fd(dev), cfg.log);
+ if (err)
+ goto out_free;
+ cfg.file = f;
+ }
+
+ cdlog.u.entireDword = 0;
+
+ cdlog.u.fields.selectLog = cfg.log;
+ cdlog.u.fields.selectCore = cfg.core < 0 ? 0 : cfg.core;
+ cdlog.u.fields.selectNlog = cfg.lnum < 0 ? 0 : cfg.lnum;
+
+ output = open(cfg.file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (output < 0) {
+ err = output;
+ goto out_free;
+ }
+
+ err = read_header(&cmd, buf, dev_fd(dev), cdlog.u.entireDword,
+ cfg.namespace_id);
+ if (err)
+ goto out;
+ memcpy(intel, buf, sizeof(*intel));
+
+ /* for 1.1 Fultondales will use old nlog, but current assert/event */
+ if ((intel->ver.major < 1 && intel->ver.minor < 1) ||
+ (intel->ver.major <= 1 && intel->ver.minor <= 1 && cfg.log == 0)) {
+ cmd.addr = (unsigned long)(void *)buf;
+ err = get_internal_log_old(buf, output, dev_fd(dev), &cmd);
+ goto out;
+ }
+
+ if (cfg.log == 2) {
+ if (cfg.verbose)
+ printf("Log major:%d minor:%d header:%d size:%d numcores:%d\n",
+ intel->ver.major, intel->ver.minor,
+ intel->header, intel->size, intel->numcores);
+
+ err = write_header(buf, output, 0x1000);
+ if (err) {
+ perror("write failure");
+ goto out;
+ }
+
+ count = intel->numcores;
+ } else if (cfg.log == 0) {
+ if (cfg.lnum < 0)
+ count = intel_nlog->totalnlogs;
+ else
+ count = 1;
+ if (cfg.core < 0)
+ core_num = intel_nlog->corecount;
+ } else if (cfg.log == 1) {
+ core_num = intel->numcores;
+ count = 1;
+ err = write_header(buf, output, sizeof(*intel));
+ if (err)
+ goto out;
+ }
+
+ for (j = (cfg.core < 0 ? 0 : cfg.core);
+ j < (cfg.core < 0 ? core_num : cfg.core + 1);
+ j++) {
+ cdlog.u.fields.selectCore = j;
+ for (i = 0; i < count; i++) {
+ if (cfg.log == 2) {
+ if (!ad[i].assertvalid)
+ continue;
+ cmd.cdw13 = ad[i].coreoffset;
+ cmd.cdw10 = 0x400;
+ cmd.data_len = min(0x400, ad[i].assertsize) * 4;
+ err = read_entire_cmd(&cmd, ad[i].assertsize,
+ 0x400, output,
+ dev_fd(dev),
+ buf);
+ if (err)
+ goto out;
+
+ } else if (cfg.log == 0) {
+ /* If the user selected to read the entire nlog */
+ if (count > 1)
+ cdlog.u.fields.selectNlog = i;
+
+ err = read_header(&cmd, buf, dev_fd(dev),
+ cdlog.u.entireDword,
+ cfg.namespace_id);
+ if (err)
+ goto out;
+ err = write_header(buf, output, sizeof(*intel_nlog));
+ if (err)
+ goto out;
+ if (cfg.verbose)
+ print_intel_nlog(intel_nlog);
+ cmd.cdw13 = 0x400;
+ cmd.cdw10 = 0x400;
+ cmd.data_len = min(0x1000, intel_nlog->nlogbytesize);
+ err = read_entire_cmd(&cmd, intel_nlog->nlogbytesize / 4,
+ 0x400, output,
+ dev_fd(dev),
+ buf);
+ if (err)
+ goto out;
+ } else if (cfg.log == 1) {
+ cmd.cdw13 = ehdr->edumps[j].coreoffset;
+ cmd.cdw10 = 0x400;
+ cmd.data_len = 0x400;
+ err = read_entire_cmd(&cmd, ehdr->edumps[j].coresize,
+ 0x400, output,
+ dev_fd(dev),
+ buf);
+ if (err)
+ goto out;
+ }
+ }
+ }
+ err = 0;
+out:
+ if (err > 0) {
+ nvme_show_status(err);
+ } else if (err < 0) {
+ perror("intel log");
+ err = EIO;
+ } else
+ printf("Successfully wrote log to %s\n", cfg.file);
+ close(output);
+out_free:
+ free(intel);
+ dev_close(dev);
+ return err;
+}
+
+static int enable_lat_stats_tracking(int argc, char **argv,
+ struct command *command, struct plugin *plugin)
+{
+ const char *desc = (
+ "Enable/Disable Intel Latency Statistics Tracking.\n"
+ "No argument prints current status.");
+ const char *enable_desc = "Enable LST";
+ const char *disable_desc = "Disable LST";
+ const __u32 nsid = 0;
+ const __u8 fid = 0xe2;
+ const __u8 sel = 0;
+ const __u32 cdw11 = 0x0;
+ const __u32 cdw12 = 0x0;
+ const __u32 data_len = 32;
+ const __u32 save = 0;
+ struct nvme_dev *dev;
+ void *buf = NULL;
+ __u32 result;
+ int err;
+
+ struct config {
+ bool enable, disable;
+ };
+
+ struct config cfg = {
+ .enable = false,
+ .disable = false,
+ };
+
+ struct argconfig_commandline_options command_line_options[] = {
+ {"enable", 'e', "", CFG_FLAG, &cfg.enable, no_argument, enable_desc},
+ {"disable", 'd', "", CFG_FLAG, &cfg.disable, no_argument, disable_desc},
+ {NULL}
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, command_line_options);
+
+ enum Option {
+ None = -1,
+ True = 1,
+ False = 0,
+ };
+
+ enum Option option = None;
+
+ if (cfg.enable && cfg.disable)
+ printf("Cannot enable and disable simultaneously.");
+ else if (cfg.enable || cfg.disable)
+ option = cfg.enable;
+
+ if (err)
+ return err;
+
+ struct nvme_get_features_args args_get = {
+ .args_size = sizeof(args_get),
+ .fd = dev_fd(dev),
+ .fid = fid,
+ .nsid = nsid,
+ .sel = sel,
+ .cdw11 = cdw11,
+ .uuidx = 0,
+ .data_len = data_len,
+ .data = buf,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ struct nvme_set_features_args args_set = {
+ .args_size = sizeof(args_set),
+ .fd = dev_fd(dev),
+ .fid = fid,
+ .nsid = nsid,
+ .cdw11 = option,
+ .cdw12 = cdw12,
+ .save = save,
+ .uuidx = 0,
+ .cdw15 = 0,
+ .data_len = data_len,
+ .data = buf,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ switch (option) {
+ case None:
+ err = nvme_get_features(&args_get);
+ if (!err) {
+ printf(
+ "Latency Statistics Tracking (FID 0x%X) is currently (%i).\n",
+ fid, result);
+ } else {
+ printf("Could not read feature id 0xE2.\n");
+ dev_close(dev);
+ return err;
+ }
+ break;
+ case True:
+ case False:
+ err = nvme_set_features(&args_set);
+ if (err > 0) {
+ nvme_show_status(err);
+ } else if (err < 0) {
+ perror("Enable latency tracking");
+ fprintf(stderr, "Command failed while parsing.\n");
+ } else {
+ printf("Successfully set enable bit for FID (0x%X) to %i.\n",
+ fid, option);
+ }
+ break;
+ default:
+ printf("%d not supported.\n", option);
+ return -EINVAL;
+ }
+ dev_close(dev);
+ return err;
+}
+
+static int set_lat_stats_thresholds(int argc, char **argv,
+ struct command *command, struct plugin *plugin)
+{
+ const char *desc = "Write Intel Bucket Thresholds for Latency Statistics Tracking";
+ const char *bucket_thresholds = "Bucket Threshold List, comma separated list: 0, 10, 20 ...";
+ const char *write = "Set write bucket Thresholds for latency tracking (read default)";
+
+ const __u32 nsid = 0;
+ const __u8 fid = 0xf7;
+ const __u32 cdw12 = 0x0;
+ const __u32 save = 0;
+ struct nvme_dev *dev;
+ __u32 result;
+ int err, num;
+
+ struct config {
+ bool write;
+ char *bucket_thresholds;
+ };
+
+ struct config cfg = {
+ .write = false,
+ .bucket_thresholds = "",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("write", 'w', &cfg.write, write),
+ OPT_LIST("bucket-thresholds", 't', &cfg.bucket_thresholds,
+ bucket_thresholds),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+
+ if (err)
+ return err;
+
+ /* Query maj and minor version first to figure out the amount of
+ * valid buckets a user is allowed to modify. Read or write doesn't
+ * matter
+ */
+ err = nvme_get_log_simple(dev_fd(dev), 0xc2,
+ sizeof(media_version), media_version);
+ if (err) {
+ fprintf(stderr, "Querying media version failed. ");
+ nvme_show_status(err);
+ goto close_dev;
+ }
+
+ if (media_version[0] == 1000) {
+ int thresholds[OPTANE_V1000_BUCKET_LEN] = {0};
+
+ num = argconfig_parse_comma_sep_array(cfg.bucket_thresholds,
+ thresholds,
+ sizeof(thresholds));
+ if (num == -1) {
+ fprintf(stderr, "ERROR: Bucket list is malformed\n");
+ goto close_dev;
+
+ }
+
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = fid,
+ .nsid = nsid,
+ .cdw11 = cfg.write ? 0x1 : 0x0,
+ .cdw12 = cdw12,
+ .save = save,
+ .uuidx = 0,
+ .cdw15 = 0,
+ .data_len = sizeof(thresholds),
+ .data = thresholds,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+ err = nvme_set_features(&args);
+
+ if (err > 0) {
+ nvme_show_status(err);
+ } else if (err < 0) {
+ perror("Enable latency tracking");
+ fprintf(stderr, "Command failed while parsing.\n");
+ }
+ } else {
+ fprintf(stderr, "Unsupported command\n");
+ }
+
+close_dev:
+ dev_close(dev);
+ return err;
+}
+
diff --git a/plugins/intel/intel-nvme.h b/plugins/intel/intel-nvme.h
new file mode 100644
index 0000000..165048a
--- /dev/null
+++ b/plugins/intel/intel-nvme.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/intel/intel-nvme
+
+#if !defined(INTEL_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define INTEL_NVME
+
+#include "cmd.h"
+
+PLUGIN(NAME("intel", "Intel vendor specific extensions", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("id-ctrl", "Send NVMe Identify Controller", id_ctrl)
+ ENTRY("internal-log", "Retrieve Intel internal firmware log, save it", get_internal_log)
+ ENTRY("lat-stats", "Retrieve Intel IO Latency Statistics log, show it", get_lat_stats_log)
+ ENTRY("set-bucket-thresholds", "Set Latency Stats Bucket Values, save it", set_lat_stats_thresholds)
+ ENTRY("lat-stats-tracking", "Enable and disable Latency Statistics logging.", enable_lat_stats_tracking)
+ ENTRY("market-name", "Retrieve Intel Marketing Name log, show it", get_market_log)
+ ENTRY("smart-log-add", "Retrieve Intel SMART Log, show it", get_additional_smart_log)
+ ENTRY("temp-stats", "Retrieve Intel Temperature Statistics log, show it", get_temp_stats_log)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/memblaze/memblaze-nvme.c b/plugins/memblaze/memblaze-nvme.c
new file mode 100644
index 0000000..b215125
--- /dev/null
+++ b/plugins/memblaze/memblaze-nvme.c
@@ -0,0 +1,1842 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "nvme.h"
+#include "common.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "nvme-print.h"
+
+#define CREATE_CMD
+#include "memblaze-nvme.h"
+#include "memblaze-utils.h"
+
+enum {
+ /* feature id */
+ MB_FEAT_POWER_MGMT = 0x02,
+ MB_FEAT_HIGH_LATENCY = 0xE1,
+ /* log id */
+ GLP_ID_VU_GET_READ_LATENCY_HISTOGRAM = 0xC1,
+ GLP_ID_VU_GET_WRITE_LATENCY_HISTOGRAM = 0xC2,
+ GLP_ID_VU_GET_HIGH_LATENCY_LOG = 0xC3,
+ MB_FEAT_CLEAR_ERRORLOG = 0xF7,
+};
+
+#define LOG_PAGE_SIZE (0x1000)
+#define DO_PRINT_FLAG (1)
+#define NOT_PRINT_FLAG (0)
+#define FID_C1_LOG_FILENAME "log_c1.csv"
+#define FID_C2_LOG_FILENAME "log_c2.csv"
+#define FID_C3_LOG_FILENAME "log_c3.csv"
+
+/*
+ * Return -1 if @fw1 < @fw2
+ * Return 0 if @fw1 == @fw2
+ * Return 1 if @fw1 > @fw2
+ */
+static int compare_fw_version(const char *fw1, const char *fw2)
+{
+ while (*fw1 != '\0') {
+ if (*fw2 == '\0' || *fw1 > *fw2)
+ return 1;
+ if (*fw1 < *fw2)
+ return -1;
+ fw1++;
+ fw2++;
+ }
+
+ if (*fw2 != '\0')
+ return -1;
+
+ return 0;
+}
+
+/**********************************************************
+ * input: firmware version string
+ * output:
+ * 1: new intel format
+ * 0: old memblaze format
+ * *******************************************************/
+#define MEMBLAZE_FORMAT (0)
+#define INTEL_FORMAT (1)
+
+/* 2.13 = papaya */
+#define IS_PAPAYA(str) (!strcmp(str, "2.13"))
+/* 2.83 = raisin */
+#define IS_RAISIN(str) (!strcmp(str, "2.83"))
+/* 2.94 = kumquat */
+#define IS_KUMQUAT(str) (!strcmp(str, "2.94"))
+/* 0.60 = loquat */
+#define IS_LOQUAT(str) (!strcmp(str, "0.60"))
+
+#define STR_VER_SIZE (5)
+
+int getlogpage_format_type(char *model_name)
+{
+ int logpage_format_type = INTEL_FORMAT;
+ const char *boundary_model_name1 = "P"; /* MEMBLAZE P7936DT0640M00 */
+ const char *boundary_model_name2 = "P5920"; /* Use INTEL_FORMAT from Raisin P5920. */
+
+ if (!strncmp(model_name, boundary_model_name1, strlen(boundary_model_name1))) {
+ if (strncmp(model_name, boundary_model_name2, strlen(boundary_model_name2)) < 0)
+ logpage_format_type = MEMBLAZE_FORMAT;
+ }
+ return logpage_format_type;
+}
+
+static __u32 item_id_2_u32(struct nvme_memblaze_smart_log_item *item)
+{
+ __le32 __id = 0;
+
+ memcpy(&__id, item->id, 3);
+ return le32_to_cpu(__id);
+}
+
+static __u64 raw_2_u64(const __u8 *buf, size_t len)
+{
+ __le64 val = 0;
+
+ memcpy(&val, buf, len);
+ return le64_to_cpu(val);
+}
+
+static void get_memblaze_new_smart_info(struct nvme_p4_smart_log *smart, int index, __u8 *nm_val, __u8 *raw_val)
+{
+ memcpy(nm_val, smart->itemArr[index].nmVal, NM_SIZE);
+ memcpy(raw_val, smart->itemArr[index].rawVal, RAW_SIZE);
+}
+
+static void show_memblaze_smart_log_new(struct nvme_memblaze_smart_log *s,
+ unsigned int nsid, const char *devname)
+{
+ struct nvme_p4_smart_log *smart = (struct nvme_p4_smart_log *)s;
+ __u8 *nm = malloc(NM_SIZE * sizeof(__u8));
+ __u8 *raw = malloc(RAW_SIZE * sizeof(__u8));
+
+ if (!nm) {
+ if (raw)
+ free(raw);
+ return;
+ }
+ if (!raw) {
+ free(nm);
+ return;
+ }
+
+ printf("%s:%s %s:%x\n", "Additional Smart Log for NVME device", devname, "namespace-id", nsid);
+ printf("%-34s%-11s%s\n", "key", "normalized", "raw");
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_PROGRAM_FAIL, nm, raw);
+ printf("%-32s: %3d%% %"PRIu64"\n", "program_fail_count", *nm, int48_to_long(raw));
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_ERASE_FAIL, nm, raw);
+ printf("%-32s: %3d%% %"PRIu64"\n", "erase_fail_count", *nm, int48_to_long(raw));
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_WEARLEVELING_COUNT, nm, raw);
+ printf("%-31s : %3d%% %s%u%s%u%s%u\n", "wear_leveling", *nm,
+ "min: ", *(__u16 *)raw, ", max: ", *(__u16 *)(raw+2), ", avg: ", *(__u16 *)(raw+4));
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_E2E_DECTECTION_COUNT, nm, raw);
+ printf("%-31s: %3d%% %"PRIu64"\n", "end_to_end_error_detection_count", *nm, int48_to_long(raw));
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_PCIE_CRC_ERR_COUNT, nm, raw);
+ printf("%-32s: %3d%% %"PRIu64"\n", "crc_error_count", *nm, int48_to_long(raw));
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TIMED_WORKLOAD_MEDIA_WEAR, nm, raw);
+ printf("%-32s: %3d%% %.3f%%\n", "timed_workload_media_wear", *nm, ((float)int48_to_long(raw))/1000);
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TIMED_WORKLOAD_HOST_READ, nm, raw);
+ printf("%-32s: %3d%% %"PRIu64"%%\n", "timed_workload_host_reads", *nm, int48_to_long(raw));
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TIMED_WORKLOAD_TIMER, nm, raw);
+ printf("%-32s: %3d%% %"PRIu64"%s\n", "timed_workload_timer", *nm, int48_to_long(raw), " min");
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_THERMAL_THROTTLE_STATUS, nm, raw);
+ printf("%-32s: %3d%% %u%%%s%"PRIu64"\n", "thermal_throttle_status", *nm,
+ *raw, ", cnt: ", int48_to_long(raw+1));
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_RETRY_BUFF_OVERFLOW_COUNT, nm, raw);
+ printf("%-32s: %3d%% %"PRIu64"\n", "retry_buffer_overflow_count", *nm, int48_to_long(raw));
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_PLL_LOCK_LOSS_COUNT, nm, raw);
+ printf("%-32s: %3d%% %"PRIu64"\n", "pll_lock_loss_count", *nm, int48_to_long(raw));
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TOTAL_WRITE, nm, raw);
+ printf("%-32s: %3d%% %s%"PRIu64"\n", "nand_bytes_written", *nm, "sectors: ", int48_to_long(raw));
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_HOST_WRITE, nm, raw);
+ printf("%-32s: %3d%% %s%"PRIu64"\n", "host_bytes_written", *nm, "sectors: ", int48_to_long(raw));
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_SYSTEM_AREA_LIFE_LEFT, nm, raw);
+ printf("%-32s: %3d%% %"PRIu64"\n", "system_area_life_left", *nm, int48_to_long(raw));
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TOTAL_READ, nm, raw);
+ printf("%-32s: %3d%% %"PRIu64"\n", "total_read", *nm, int48_to_long(raw));
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TEMPT_SINCE_BORN, nm, raw);
+ printf("%-32s: %3d%% %s%u%s%u%s%u\n", "tempt_since_born", *nm,
+ "max: ", *(__u16 *)raw, ", min: ", *(__u16 *)(raw+2), ", curr: ", *(__u16 *)(raw+4));
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_POWER_CONSUMPTION, nm, raw);
+ printf("%-32s: %3d%% %s%u%s%u%s%u\n", "power_consumption", *nm,
+ "max: ", *(__u16 *)raw, ", min: ", *(__u16 *)(raw+2), ", curr: ", *(__u16 *)(raw+4));
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_TEMPT_SINCE_BOOTUP, nm, raw);
+ printf("%-32s: %3d%% %s%u%s%u%s%u\n", "tempt_since_bootup", *nm, "max: ", *(__u16 *)raw,
+ ", min: ", *(__u16 *)(raw+2), ", curr: ", *(__u16 *)(raw+4));
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_READ_FAIL, nm, raw);
+ printf("%-32s: %3d%% %"PRIu64"\n", "read_fail_count", *nm, int48_to_long(raw));
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_THERMAL_THROTTLE_TIME, nm, raw);
+ printf("%-32s: %3d%% %"PRIu64"\n", "thermal_throttle_time", *nm, int48_to_long(raw));
+
+ get_memblaze_new_smart_info(smart, RAISIN_SI_VD_FLASH_MEDIA_ERROR, nm, raw);
+ printf("%-32s: %3d%% %"PRIu64"\n", "flash_media_error", *nm, int48_to_long(raw));
+
+ free(nm);
+ free(raw);
+}
+
+static void show_memblaze_smart_log_old(struct nvme_memblaze_smart_log *smart,
+ unsigned int nsid, const char *devname, const char *fw_ver)
+{
+ char fw_ver_local[STR_VER_SIZE + 1];
+ struct nvme_memblaze_smart_log_item *item;
+
+ strncpy(fw_ver_local, fw_ver, STR_VER_SIZE);
+ *(fw_ver_local + STR_VER_SIZE) = '\0';
+
+ printf("Additional Smart Log for NVME device:%s namespace-id:%x\n", devname, nsid);
+
+ printf("Total write in GB since last factory reset : %"PRIu64"\n",
+ int48_to_long(smart->items[TOTAL_WRITE].rawval));
+ printf("Total read in GB since last factory reset : %"PRIu64"\n",
+ int48_to_long(smart->items[TOTAL_READ].rawval));
+
+ printf("Thermal throttling status[1:HTP in progress] : %u\n",
+ smart->items[THERMAL_THROTTLE].thermal_throttle.on);
+ printf("Total thermal throttling minutes since power on : %u\n",
+ smart->items[THERMAL_THROTTLE].thermal_throttle.count);
+
+ printf("Maximum temperature in kelvins since last factory reset : %u\n",
+ le16_to_cpu(smart->items[TEMPT_SINCE_RESET].temperature.max));
+ printf("Minimum temperature in kelvins since last factory reset : %u\n",
+ le16_to_cpu(smart->items[TEMPT_SINCE_RESET].temperature.min));
+ if (compare_fw_version(fw_ver, "0.09.0300") != 0) {
+ printf("Maximum temperature in kelvins since power on : %u\n",
+ le16_to_cpu(smart->items[TEMPT_SINCE_BOOTUP].temperature_p.max));
+ printf("Minimum temperature in kelvins since power on : %u\n",
+ le16_to_cpu(smart->items[TEMPT_SINCE_BOOTUP].temperature_p.min));
+ }
+ printf("Current temperature in kelvins : %u\n",
+ le16_to_cpu(smart->items[TEMPT_SINCE_RESET].temperature.curr));
+
+ printf("Maximum power in watt since power on : %u\n",
+ le16_to_cpu(smart->items[POWER_CONSUMPTION].power.max));
+ printf("Minimum power in watt since power on : %u\n",
+ le16_to_cpu(smart->items[POWER_CONSUMPTION].power.min));
+ printf("Current power in watt : %u\n",
+ le16_to_cpu(smart->items[POWER_CONSUMPTION].power.curr));
+
+ item = &smart->items[POWER_LOSS_PROTECTION];
+ if (item_id_2_u32(item) == 0xEC)
+ printf("Power loss protection normalized value : %u\n",
+ item->power_loss_protection.curr);
+
+ item = &smart->items[WEARLEVELING_COUNT];
+ if (item_id_2_u32(item) == 0xAD) {
+ printf("Percentage of wearleveling count left : %u\n",
+ le16_to_cpu(item->nmval));
+ printf("Wearleveling count min erase cycle : %u\n",
+ le16_to_cpu(item->wearleveling_count.min));
+ printf("Wearleveling count max erase cycle : %u\n",
+ le16_to_cpu(item->wearleveling_count.max));
+ printf("Wearleveling count avg erase cycle : %u\n",
+ le16_to_cpu(item->wearleveling_count.avg));
+ }
+
+ item = &smart->items[HOST_WRITE];
+ if (item_id_2_u32(item) == 0xF5)
+ printf("Total host write in GiB since device born : %llu\n",
+ (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval)));
+
+ item = &smart->items[THERMAL_THROTTLE_CNT];
+ if (item_id_2_u32(item) == 0xEB)
+ printf("Thermal throttling count since device born : %u\n",
+ item->thermal_throttle_cnt.cnt);
+
+ item = &smart->items[CORRECT_PCIE_PORT0];
+ if (item_id_2_u32(item) == 0xED)
+ printf("PCIE Correctable Error Count of Port0 : %llu\n",
+ (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval)));
+
+ item = &smart->items[CORRECT_PCIE_PORT1];
+ if (item_id_2_u32(item) == 0xEE)
+ printf("PCIE Correctable Error Count of Port1 : %llu\n",
+ (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval)));
+
+ item = &smart->items[REBUILD_FAIL];
+ if (item_id_2_u32(item) == 0xEF)
+ printf("End-to-End Error Detection Count : %llu\n",
+ (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval)));
+
+ item = &smart->items[ERASE_FAIL];
+ if (item_id_2_u32(item) == 0xF0)
+ printf("Erase Fail Count : %llu\n",
+ (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval)));
+
+ item = &smart->items[PROGRAM_FAIL];
+ if (item_id_2_u32(item) == 0xF1)
+ printf("Program Fail Count : %llu\n",
+ (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval)));
+
+ item = &smart->items[READ_FAIL];
+ if (item_id_2_u32(item) == 0xF2)
+ printf("Read Fail Count : %llu\n",
+ (unsigned long long)raw_2_u64(item->rawval, sizeof(item->rawval)));
+
+ if (IS_PAPAYA(fw_ver_local)) {
+ struct nvme_p4_smart_log *s = (struct nvme_p4_smart_log *)smart;
+ __u8 *nm = malloc(NM_SIZE * sizeof(__u8));
+ __u8 *raw = malloc(RAW_SIZE * sizeof(__u8));
+
+ if (!nm) {
+ if (raw)
+ free(raw);
+ return;
+ }
+ if (!raw) {
+ free(nm);
+ return;
+ }
+ get_memblaze_new_smart_info(s, PROGRAM_FAIL, nm, raw);
+ printf("%-32s : %3d%% %"PRIu64"\n",
+ "program_fail_count", *nm, int48_to_long(raw));
+
+ get_memblaze_new_smart_info(s, ERASE_FAIL, nm, raw);
+ printf("%-32s : %3d%% %"PRIu64"\n",
+ "erase_fail_count", *nm, int48_to_long(raw));
+
+ get_memblaze_new_smart_info(s, WEARLEVELING_COUNT, nm, raw);
+ printf("%-31s : %3d%% %s%u%s%u%s%u\n",
+ "wear_leveling", *nm, "min: ", *(__u16 *)raw, ", max: ", *(__u16 *)(raw+2), ", avg: ", *(__u16 *)(raw+4));
+
+ get_memblaze_new_smart_info(s, TOTAL_WRITE, nm, raw);
+ printf("%-32s : %3d%% %"PRIu64"\n",
+ "nand_bytes_written", *nm, 32*int48_to_long(raw));
+
+ get_memblaze_new_smart_info(s, HOST_WRITE, nm, raw);
+ printf("%-32s : %3d%% %"PRIu64"\n",
+ "host_bytes_written", *nm, 32*int48_to_long(raw));
+
+ free(nm);
+ free(raw);
+ }
+}
+
+static int show_memblaze_smart_log(int fd, __u32 nsid, const char *devname,
+ struct nvme_memblaze_smart_log *smart)
+{
+ struct nvme_id_ctrl ctrl;
+ char fw_ver[10];
+ int err = 0;
+
+ err = nvme_identify_ctrl(fd, &ctrl);
+ if (err)
+ return err;
+
+ snprintf(fw_ver, sizeof(fw_ver), "%c.%c%c.%c%c%c%c",
+ ctrl.fr[0], ctrl.fr[1], ctrl.fr[2], ctrl.fr[3],
+ ctrl.fr[4], ctrl.fr[5], ctrl.fr[6]);
+
+ if (getlogpage_format_type(ctrl.mn)) /* Intel Format & new format */
+ show_memblaze_smart_log_new(smart, nsid, devname);
+ else /* Memblaze Format & old format */
+ show_memblaze_smart_log_old(smart, nsid, devname, fw_ver);
+ return err;
+}
+
+int parse_params(char *str, int number, ...)
+{
+ va_list argp;
+ int *param;
+ char *c;
+ int value;
+
+ va_start(argp, number);
+
+ while (number > 0) {
+ c = strtok(str, ",");
+ if (!c) {
+ printf("No enough parameters. abort...\n");
+ va_end(argp);
+ return 1;
+ }
+
+ if (!isalnum((int)*c)) {
+ printf("%s is not a valid number\n", c);
+ va_end(argp);
+ return 1;
+ }
+ value = atoi(c);
+ param = va_arg(argp, int *);
+ *param = value;
+
+ if (str) {
+ str = strchr(str, ',');
+ if (str)
+ str++;
+ }
+ number--;
+ }
+ va_end(argp);
+
+ return 0;
+}
+
+static int mb_get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ struct nvme_memblaze_smart_log smart_log;
+ char *desc =
+ "Get Memblaze vendor specific additional smart log (optionally, for the specified namespace), and show it.";
+ const char *namespace = "(optional) desired namespace";
+ const char *raw = "dump output in binary format";
+ struct nvme_dev *dev;
+ struct config {
+ __u32 namespace_id;
+ bool raw_binary;
+ };
+ int err;
+
+ struct config cfg = {
+ .namespace_id = NVME_NSID_ALL,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace),
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = nvme_get_nsid_log(dev_fd(dev), false, 0xca, cfg.namespace_id,
+ sizeof(smart_log), &smart_log);
+ if (!err) {
+ if (!cfg.raw_binary)
+ err = show_memblaze_smart_log(dev_fd(dev), cfg.namespace_id, dev->name,
+ &smart_log);
+ else
+ d_raw((unsigned char *)&smart_log, sizeof(smart_log));
+ }
+ if (err > 0)
+ nvme_show_status(err);
+
+ dev_close(dev);
+ return err;
+}
+
+static char *mb_feature_to_string(int feature)
+{
+ switch (feature) {
+ case MB_FEAT_POWER_MGMT:
+ return "Memblaze power management";
+ case MB_FEAT_HIGH_LATENCY:
+ return "Memblaze high latency log";
+ case MB_FEAT_CLEAR_ERRORLOG:
+ return "Memblaze clear error log";
+ default:
+ return "Unknown";
+ }
+}
+
+static int mb_get_powermanager_status(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Get Memblaze power management ststus\n (value 0 - 25w, 1 - 20w, 2 - 15w)";
+ __u32 result;
+ __u32 feature_id = MB_FEAT_POWER_MGMT;
+ struct nvme_dev *dev;
+ int err;
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ struct nvme_get_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = feature_id,
+ .nsid = 0,
+ .sel = 0,
+ .cdw11 = 0,
+ .uuidx = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+ err = nvme_get_features(&args);
+ if (err < 0)
+ perror("get-feature");
+ if (!err)
+ printf("get-feature:0x%02x (%s), %s value: %#08x\n", feature_id,
+ mb_feature_to_string(feature_id), nvme_select_to_string(0), result);
+ else if (err > 0)
+ nvme_show_status(err);
+ dev_close(dev);
+ return err;
+}
+
+static int mb_set_powermanager_status(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Set Memblaze power management status\n (value 0 - 25w, 1 - 20w, 2 - 15w)";
+ const char *value = "new value of feature (required)";
+ const char *save = "specifies that the controller shall save the attribute";
+ struct nvme_dev *dev;
+ __u32 result;
+ int err;
+
+ struct config {
+ __u32 feature_id;
+ __u32 value;
+ bool save;
+ };
+
+ struct config cfg = {
+ .feature_id = MB_FEAT_POWER_MGMT,
+ .value = 0,
+ .save = 0,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("value", 'v', &cfg.value, value),
+ OPT_FLAG("save", 's', &cfg.save, save),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = cfg.feature_id,
+ .nsid = 0,
+ .cdw11 = cfg.value,
+ .cdw12 = 0,
+ .save = cfg.save,
+ .uuidx = 0,
+ .cdw15 = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+ err = nvme_set_features(&args);
+ if (err < 0)
+ perror("set-feature");
+ if (!err)
+ printf("set-feature:%02x (%s), value:%#08x\n", cfg.feature_id,
+ mb_feature_to_string(cfg.feature_id), cfg.value);
+ else if (err > 0)
+ nvme_show_status(err);
+
+ dev_close(dev);
+ return err;
+}
+
+#define P2MIN (1)
+#define P2MAX (5000)
+#define MB_FEAT_HIGH_LATENCY_VALUE_SHIFT (15)
+static int mb_set_high_latency_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Set Memblaze high latency log\n"
+ " input parameter p1,p2\n"
+ " p1 value: 0 is disable, 1 is enable\n"
+ " p2 value: 1 .. 5000 ms";
+ const char *param = "input parameters";
+ int param1 = 0, param2 = 0;
+ struct nvme_dev *dev;
+ __u32 result;
+ int err;
+
+ struct config {
+ __u32 feature_id;
+ char *param;
+ __u32 value;
+ };
+
+ struct config cfg = {
+ .feature_id = MB_FEAT_HIGH_LATENCY,
+ .param = "0,0",
+ .value = 0,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_LIST("param", 'p', &cfg.param, param),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ if (parse_params(cfg.param, 2, &param1, &param2)) {
+ printf("setfeature: invalid formats %s\n", cfg.param);
+ dev_close(dev);
+ return -EINVAL;
+ }
+ if ((param1 == 1) && (param2 < P2MIN || param2 > P2MAX)) {
+ printf("setfeature: invalid high io latency threshold %d\n", param2);
+ dev_close(dev);
+ return -EINVAL;
+ }
+ cfg.value = (param1 << MB_FEAT_HIGH_LATENCY_VALUE_SHIFT) | param2;
+
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = cfg.feature_id,
+ .nsid = 0,
+ .cdw11 = cfg.value,
+ .cdw12 = 0,
+ .save = false,
+ .uuidx = 0,
+ .cdw15 = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+ err = nvme_set_features(&args);
+ if (err < 0)
+ perror("set-feature");
+ if (!err)
+ printf("set-feature:0x%02X (%s), value:%#08x\n", cfg.feature_id,
+ mb_feature_to_string(cfg.feature_id), cfg.value);
+ else if (err > 0)
+ nvme_show_status(err);
+
+ dev_close(dev);
+ return err;
+}
+
+static int glp_high_latency_show_bar(FILE *fdi, int print)
+{
+ fPRINT_PARAM1("Memblaze High Latency Log\n");
+ fPRINT_PARAM1("---------------------------------------------------------------------------------------------\n");
+ fPRINT_PARAM1("Timestamp Type QID CID NSID StartLBA NumLBA Latency\n");
+ fPRINT_PARAM1("---------------------------------------------------------------------------------------------\n");
+ return 0;
+}
+
+/*
+ * High latency log page definition
+ * Total 32 bytes
+ */
+struct log_page_high_latency {
+ __u8 port;
+ __u8 revision;
+ __u16 rsvd;
+ __u8 opcode;
+ __u8 sqe;
+ __u16 cid;
+ __u32 nsid;
+ __u32 latency;
+ __u64 sLBA;
+ __u16 numLBA;
+ __u16 timestampH;
+ __u32 timestampL;
+}; /* total 32 bytes */
+
+static int find_deadbeef(char *buf)
+{
+ if (((*(buf + 0) & 0xff) == 0xef) && ((*(buf + 1) & 0xff) == 0xbe) &&
+ ((*(buf + 2) & 0xff) == 0xad) && ((*(buf + 3) & 0xff) == 0xde))
+ return 1;
+ return 0;
+}
+
+#define TIME_STR_SIZE (44)
+static int glp_high_latency(FILE *fdi, char *buf, int buflen, int print)
+{
+ struct log_page_high_latency *logEntry;
+ char string[TIME_STR_SIZE];
+ int i, entrySize;
+ __u64 timestamp;
+ time_t tt = 0;
+ struct tm *t = NULL;
+ int millisec = 0;
+
+ if (find_deadbeef(buf))
+ return 0;
+
+ entrySize = sizeof(struct log_page_high_latency);
+ for (i = 0; i < buflen; i += entrySize) {
+ logEntry = (struct log_page_high_latency *)(buf + i);
+
+ if (logEntry->latency == 0 && logEntry->revision == 0)
+ return 1;
+
+ if (!logEntry->timestampH) { /* generate host time string */
+ snprintf(string, sizeof(string), "%d", logEntry->timestampL);
+ } else { /* sort */
+ timestamp = logEntry->timestampH;
+ timestamp = timestamp << 32;
+ timestamp += logEntry->timestampL;
+ tt = timestamp / 1000;
+ millisec = timestamp % 1000;
+ t = gmtime(&tt);
+ snprintf(string, sizeof(string), "%4d%02d%02d--%02d:%02d:%02d.%03d UTC",
+ 1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday, t->tm_hour,
+ t->tm_min, t->tm_sec, millisec);
+ }
+
+ if (fdi)
+ fprintf(fdi, "%-32s %-7x %-6x %-6x %-8x %4x%08x %-8x %-d\n",
+ string, logEntry->opcode, logEntry->sqe,
+ logEntry->cid, logEntry->nsid,
+ (__u32)(logEntry->sLBA >> 32),
+ (__u32)logEntry->sLBA, logEntry->numLBA,
+ logEntry->latency);
+ if (print)
+ printf("%-32s %-7x %-6x %-6x %-8x %4x%08x %-8x %-d\n",
+ string, logEntry->opcode, logEntry->sqe, logEntry->cid,
+ logEntry->nsid, (__u32)(logEntry->sLBA >> 32), (__u32)logEntry->sLBA,
+ logEntry->numLBA, logEntry->latency);
+ }
+ return 1;
+}
+
+static int mb_high_latency_log_print(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Get Memblaze high latency log";
+ char buf[LOG_PAGE_SIZE];
+ struct nvme_dev *dev;
+ FILE *fdi = NULL;
+ int err;
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ fdi = fopen(FID_C3_LOG_FILENAME, "w+");
+
+ glp_high_latency_show_bar(fdi, DO_PRINT_FLAG);
+ err = nvme_get_log_simple(dev_fd(dev), GLP_ID_VU_GET_HIGH_LATENCY_LOG,
+ sizeof(buf), &buf);
+
+ while (1) {
+ if (!glp_high_latency(fdi, buf, LOG_PAGE_SIZE, DO_PRINT_FLAG))
+ break;
+ err = nvme_get_log_simple(dev_fd(dev), GLP_ID_VU_GET_HIGH_LATENCY_LOG,
+ sizeof(buf), &buf);
+ if (err) {
+ nvme_show_status(err);
+ break;
+ }
+ }
+
+ if (fdi)
+ fclose(fdi);
+ dev_close(dev);
+ return err;
+}
+
+static int memblaze_fw_commit(int fd, int select)
+{
+ struct nvme_passthru_cmd cmd = {
+ .opcode = nvme_admin_fw_commit,
+ .cdw10 = 8,
+ .cdw12 = select,
+ };
+
+ return nvme_submit_admin_passthru(fd, &cmd, NULL);
+}
+
+static int mb_selective_download(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc =
+ "This performs a selective firmware download, which allows the user to\n"
+ "select which firmware binary to update for 9200 devices. This requires a power cycle once the\n"
+ "update completes. The options available are:\n\n"
+ "OOB - This updates the OOB and main firmware\n"
+ "EEP - This updates the eeprom and main firmware\n"
+ "ALL - This updates the eeprom, OOB, and main firmware";
+ const char *fw = "firmware file (required)";
+ const char *select = "FW Select (e.g., --select=OOB, EEP, ALL)";
+ int xfer = 4096;
+ void *fw_buf;
+ int selectNo, fw_fd, fw_size, err, offset = 0;
+ struct nvme_dev *dev;
+ struct stat sb;
+ int i;
+
+ struct config {
+ char *fw;
+ char *select;
+ };
+
+ struct config cfg = {
+ .fw = "",
+ .select = "\0",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_STRING("fw", 'f', "FILE", &cfg.fw, fw),
+ OPT_STRING("select", 's', "flag", &cfg.select, select),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ if (strlen(cfg.select) != 3) {
+ fprintf(stderr, "Invalid select flag\n");
+ err = EINVAL;
+ goto out;
+ }
+
+ for (i = 0; i < 3; i++)
+ cfg.select[i] = toupper(cfg.select[i]);
+
+ if (!strncmp(cfg.select, "OOB", 3)) {
+ selectNo = 18;
+ } else if (!strncmp(cfg.select, "EEP", 3)) {
+ selectNo = 10;
+ } else if (!strncmp(cfg.select, "ALL", 3)) {
+ selectNo = 26;
+ } else {
+ fprintf(stderr, "Invalid select flag\n");
+ err = EINVAL;
+ goto out;
+ }
+
+ fw_fd = open(cfg.fw, O_RDONLY);
+ if (fw_fd < 0) {
+ fprintf(stderr, "no firmware file provided\n");
+ err = EINVAL;
+ goto out;
+ }
+
+ err = fstat(fw_fd, &sb);
+ if (err < 0) {
+ perror("fstat");
+ err = errno;
+ goto out_close;
+ }
+
+ fw_size = sb.st_size;
+ if (fw_size & 0x3) {
+ fprintf(stderr, "Invalid size:%d for f/w image\n", fw_size);
+ err = EINVAL;
+ goto out_close;
+ }
+
+ if (posix_memalign(&fw_buf, getpagesize(), fw_size)) {
+ fprintf(stderr, "No memory for f/w size:%d\n", fw_size);
+ err = ENOMEM;
+ goto out_close;
+ }
+
+ if (read(fw_fd, fw_buf, fw_size) != ((ssize_t)(fw_size))) {
+ err = errno;
+ goto out_free;
+ }
+
+ while (fw_size > 0) {
+ xfer = min(xfer, fw_size);
+
+ struct nvme_fw_download_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .offset = offset,
+ .data_len = xfer,
+ .data = fw_buf,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ };
+ err = nvme_fw_download(&args);
+ if (err < 0) {
+ perror("fw-download");
+ goto out_free;
+ } else if (err != 0) {
+ nvme_show_status(err);
+ goto out_free;
+ }
+ fw_buf += xfer;
+ fw_size -= xfer;
+ offset += xfer;
+ }
+
+ err = memblaze_fw_commit(dev_fd(dev), selectNo);
+
+ if (err == 0x10B || err == 0x20B) {
+ err = 0;
+ fprintf(stderr, "Update successful! Please power cycle for changes to take effect\n");
+ }
+
+out_free:
+ free(fw_buf);
+out_close:
+ close(fw_fd);
+out:
+ dev_close(dev);
+ return err;
+}
+
+static void ioLatencyHistogramOutput(FILE *fd, int index, int start, int end, char *unit0,
+ char *unit1, unsigned int *pHistogram, int print)
+{
+ int len;
+ char string[64], subString0[12], subString1[12];
+
+ snprintf(subString0, sizeof(subString0), "%d%s", start, unit0);
+ if (end != 0x7FFFFFFF)
+ snprintf(subString1, sizeof(subString1), "%d%s", end, unit1);
+ else
+ snprintf(subString1, sizeof(subString1), "%s", "+INF");
+ len = snprintf(string, sizeof(string), "%-11d %-11s %-11s %-11u\n",
+ index, subString0, subString1,
+ pHistogram[index]);
+ fwrite(string, 1, len, fd);
+ if (print)
+ printf("%s", string);
+}
+
+int io_latency_histogram(char *file, char *buf, int print, int logid)
+{
+ FILE *fdi = fopen(file, "w+");
+ int i, index;
+ char unit[2][3];
+ unsigned int *revision = (unsigned int *)buf;
+
+ if (logid == GLP_ID_VU_GET_READ_LATENCY_HISTOGRAM)
+ fPRINT_PARAM1("Memblaze IO Read Command Latency Histogram\n");
+ else if (logid == GLP_ID_VU_GET_WRITE_LATENCY_HISTOGRAM)
+ fPRINT_PARAM1("Memblaze IO Write Command Latency Histogram\n");
+ fPRINT_PARAM2("Major Revision : %d\n", revision[1]);
+ fPRINT_PARAM2("Minor Revision : %d\n", revision[0]);
+ buf += 8;
+
+ if (revision[1] == 1 && revision[0] == 0) {
+ fPRINT_PARAM1("--------------------------------------------------\n");
+ fPRINT_PARAM1("Bucket Start End Value\n");
+ fPRINT_PARAM1("--------------------------------------------------\n");
+ index = 0;
+ strcpy(unit[0], "us");
+ strcpy(unit[1], "us");
+ for (i = 0; i < 32; i++, index++) {
+ if (i == 31) {
+ strcpy(unit[1], "ms");
+ ioLatencyHistogramOutput(fdi, index, i * 32, 1, unit[0], unit[1],
+ (unsigned int *)buf, print);
+ } else {
+ ioLatencyHistogramOutput(fdi, index, i * 32, (i + 1) * 32, unit[0],
+ unit[1], (unsigned int *)buf, print);
+ }
+ }
+
+ strcpy(unit[0], "ms");
+ strcpy(unit[1], "ms");
+ for (i = 1; i < 32; i++, index++)
+ ioLatencyHistogramOutput(fdi, index, i, i + 1, unit[0], unit[1], (unsigned int *)buf, print);
+
+ for (i = 1; i < 32; i++, index++) {
+ if (i == 31) {
+ strcpy(unit[1], "s");
+ ioLatencyHistogramOutput(fdi, index, i * 32, 1, unit[0], unit[1],
+ (unsigned int *)buf, print);
+ } else {
+ ioLatencyHistogramOutput(fdi, index, i * 32, (i + 1) * 32, unit[0],
+ unit[1], (unsigned int *)buf, print);
+ }
+ }
+
+ strcpy(unit[0], "s");
+ strcpy(unit[1], "s");
+ for (i = 1; i < 4; i++, index++)
+ ioLatencyHistogramOutput(fdi, index, i, i + 1, unit[0], unit[1], (unsigned int *)buf, print);
+
+ ioLatencyHistogramOutput(fdi, index, i, 0x7FFFFFFF, unit[0], unit[1], (unsigned int *)buf, print);
+ } else {
+ fPRINT_PARAM1("Unsupported io latency histogram revision\n");
+ }
+
+ if (fdi)
+ fclose(fdi);
+ return 1;
+}
+
+static int mb_lat_stats_log_print(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ char stats[LOG_PAGE_SIZE];
+ char f1[] = FID_C1_LOG_FILENAME;
+ char f2[] = FID_C2_LOG_FILENAME;
+ struct nvme_dev *dev;
+ int err;
+
+ const char *desc = "Get Latency Statistics log and show it.";
+ const char *write = "Get write statistics (read default)";
+
+ struct config {
+ bool write;
+ };
+ struct config cfg = {
+ .write = 0,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("write", 'w', &cfg.write, write),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = nvme_get_log_simple(dev_fd(dev), cfg.write ? 0xc2 : 0xc1,
+ sizeof(stats), &stats);
+ if (!err)
+ io_latency_histogram(cfg.write ? f2 : f1, stats, DO_PRINT_FLAG,
+ cfg.write ? GLP_ID_VU_GET_WRITE_LATENCY_HISTOGRAM :
+ GLP_ID_VU_GET_READ_LATENCY_HISTOGRAM);
+ else
+ nvme_show_status(err);
+
+ dev_close(dev);
+ return err;
+}
+
+static int memblaze_clear_error_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ char *desc = "Clear Memblaze devices error log.";
+ struct nvme_dev *dev;
+ int err;
+
+ __u32 result;
+
+ struct config {
+ __u32 feature_id;
+ __u32 value;
+ int save;
+ };
+
+ struct config cfg = {
+ .feature_id = 0xf7,
+ .value = 0x534d0001,
+ .save = 0,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = cfg.feature_id,
+ .nsid = 0,
+ .cdw11 = cfg.value,
+ .cdw12 = 0,
+ .save = cfg.save,
+ .uuidx = 0,
+ .cdw15 = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+ err = nvme_set_features(&args);
+ if (err < 0)
+ perror("set-feature");
+ if (!err)
+ printf("set-feature:%02x (%s), value:%#08x\n", cfg.feature_id, mb_feature_to_string(cfg.feature_id), cfg.value);
+ else if (err > 0)
+ nvme_show_status(err);
+
+ dev_close(dev);
+ return err;
+}
+
+static int mb_set_lat_stats(int argc, char **argv,
+ struct command *command, struct plugin *plugin)
+{
+ const char *desc = (
+ "Enable/Disable Latency Statistics Tracking.\n"
+ "No argument prints current status.");
+ const char *enable_desc = "Enable LST";
+ const char *disable_desc = "Disable LST";
+ const __u32 nsid = 0;
+ const __u8 fid = 0xe2;
+ const __u8 sel = 0;
+ const __u32 cdw11 = 0x0;
+ const __u32 cdw12 = 0x0;
+ const __u32 data_len = 32;
+ const __u32 save = 0;
+ struct nvme_dev *dev;
+ void *buf = NULL;
+ __u32 result;
+ int err;
+
+ struct config {
+ bool enable, disable;
+ };
+
+ struct config cfg = {
+ .enable = false,
+ .disable = false,
+ };
+
+ struct argconfig_commandline_options command_line_options[] = {
+ {"enable", 'e', "", CFG_FLAG, &cfg.enable, no_argument, enable_desc},
+ {"disable", 'd', "", CFG_FLAG, &cfg.disable, no_argument, disable_desc},
+ {NULL}
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, command_line_options);
+
+ enum Option {
+ None = -1,
+ True = 1,
+ False = 0,
+ };
+ enum Option option = None;
+
+ if (cfg.enable && cfg.disable)
+ printf("Cannot enable and disable simultaneously.");
+ else if (cfg.enable || cfg.disable)
+ option = cfg.enable;
+
+ struct nvme_get_features_args args_get = {
+ .args_size = sizeof(args_get),
+ .fd = dev_fd(dev),
+ .fid = fid,
+ .nsid = nsid,
+ .sel = sel,
+ .cdw11 = cdw11,
+ .uuidx = 0,
+ .data_len = data_len,
+ .data = buf,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ struct nvme_set_features_args args_set = {
+ .args_size = sizeof(args_set),
+ .fd = dev_fd(dev),
+ .fid = fid,
+ .nsid = nsid,
+ .cdw11 = option,
+ .cdw12 = cdw12,
+ .save = save,
+ .uuidx = 0,
+ .cdw15 = 0,
+ .data_len = data_len,
+ .data = buf,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ if (err)
+ return err;
+ switch (option) {
+ case None:
+ err = nvme_get_features(&args_get);
+ if (!err) {
+ printf(
+ "Latency Statistics Tracking (FID 0x%X) is currently (%i).\n",
+ fid, result);
+ } else {
+ printf("Could not read feature id 0xE2.\n");
+ dev_close(dev);
+ return err;
+ }
+ break;
+ case True:
+ case False:
+ err = nvme_set_features(&args_set);
+ if (err > 0) {
+ nvme_show_status(err);
+ } else if (err < 0) {
+ perror("Enable latency tracking");
+ fprintf(stderr, "Command failed while parsing.\n");
+ } else {
+ printf("Successfully set enable bit for FID (0x%X) to %i.\n",
+ 0xe2, option);
+ }
+ break;
+ default:
+ printf("%d not supported.\n", option);
+ err = EINVAL;
+ }
+ dev_close(dev);
+ return err;
+}
+
+// Global definitions
+
+static inline int K2C(int k) // KELVINS_2_CELSIUS
+{
+ return (k - 273);
+};
+
+// Global ID definitions
+
+enum {
+ // feature ids
+ FID_LATENCY_FEATURE = 0xd0,
+
+ // log ids
+ LID_SMART_LOG_ADD = 0xca,
+ LID_LATENCY_STATISTICS = 0xd0,
+ LID_HIGH_LATENCY_LOG = 0xd1,
+ LID_PERFORMANCE_STATISTICS = 0xd2,
+};
+
+// smart-log-add
+
+struct smart_log_add_item {
+ uint32_t index;
+ char *attr;
+};
+
+struct __packed wear_level {
+ __le16 min;
+ __le16 max;
+ __le16 avg;
+};
+
+struct __packed smart_log_add_item_12 {
+ uint8_t id;
+ uint8_t rsvd[2];
+ uint8_t norm;
+ uint8_t rsvd1;
+ union {
+ struct wear_level wear_level; // 0xad
+ struct temp_since_born { // 0xe7
+ __le16 max;
+ __le16 min;
+ __le16 curr;
+ } temp_since_born;
+ struct power_consumption { // 0xe8
+ __le16 max;
+ __le16 min;
+ __le16 curr;
+ } power_consumption;
+ struct temp_since_power_on { // 0xaf
+ __le16 max;
+ __le16 min;
+ __le16 curr;
+ } temp_since_power_on;
+ uint8_t raw[6];
+ };
+ uint8_t rsvd2;
+};
+
+struct __packed smart_log_add_item_10 {
+ uint8_t id;
+ uint8_t norm;
+ union {
+ struct wear_level wear_level; // 0xad
+ uint8_t raw[6];
+ };
+ uint8_t rsvd[2];
+};
+
+struct smart_log_add {
+ union {
+ union {
+ struct smart_log_add_v0 {
+ struct smart_log_add_item_12 program_fail_count;
+ struct smart_log_add_item_12 erase_fail_count;
+ struct smart_log_add_item_12 wear_leveling_count;
+ struct smart_log_add_item_12 end_to_end_error_count;
+ struct smart_log_add_item_12 crc_error_count;
+ struct smart_log_add_item_12 timed_workload_media_wear;
+ struct smart_log_add_item_12 timed_workload_host_reads;
+ struct smart_log_add_item_12 timed_workload_timer;
+ struct smart_log_add_item_12 thermal_throttle_status;
+ struct smart_log_add_item_12 retry_buffer_overflow_counter;
+ struct smart_log_add_item_12 pll_lock_loss_count;
+ struct smart_log_add_item_12 nand_bytes_written;
+ struct smart_log_add_item_12 host_bytes_written;
+ struct smart_log_add_item_12 system_area_life_remaining;
+ struct smart_log_add_item_12 nand_bytes_read;
+ struct smart_log_add_item_12 temperature;
+ struct smart_log_add_item_12 power_consumption;
+ struct smart_log_add_item_12 power_on_temperature;
+ struct smart_log_add_item_12 power_loss_protection;
+ struct smart_log_add_item_12 read_fail_count;
+ struct smart_log_add_item_12 thermal_throttle_time;
+ struct smart_log_add_item_12 flash_error_media_count;
+ } v0;
+
+ struct smart_log_add_item_12 v0_raw[22];
+ };
+
+ union {
+ struct smart_log_add_v2 {
+ struct smart_log_add_item_12 program_fail_count;
+ struct smart_log_add_item_12 erase_fail_count;
+ struct smart_log_add_item_12 wear_leveling_count;
+ struct smart_log_add_item_12 end_to_end_error_count;
+ struct smart_log_add_item_12 crc_error_count;
+ struct smart_log_add_item_12 timed_workload_media_wear;
+ struct smart_log_add_item_12 timed_workload_host_reads;
+ struct smart_log_add_item_12 timed_workload_timer;
+ struct smart_log_add_item_12 thermal_throttle_status;
+ struct smart_log_add_item_12 lifetime_write_amplification;
+ struct smart_log_add_item_12 pll_lock_loss_count;
+ struct smart_log_add_item_12 nand_bytes_written;
+ struct smart_log_add_item_12 host_bytes_written;
+ struct smart_log_add_item_12 system_area_life_remaining;
+ struct smart_log_add_item_12 firmware_update_count;
+ struct smart_log_add_item_12 dram_cecc_count;
+ struct smart_log_add_item_12 dram_uecc_count;
+ struct smart_log_add_item_12 xor_pass_count;
+ struct smart_log_add_item_12 xor_fail_count;
+ struct smart_log_add_item_12 xor_invoked_count;
+ struct smart_log_add_item_12 inflight_read_io_cmd;
+ struct smart_log_add_item_12 flash_error_media_count;
+ struct smart_log_add_item_12 nand_bytes_read;
+ struct smart_log_add_item_12 temp_since_born;
+ struct smart_log_add_item_12 power_consumption;
+ struct smart_log_add_item_12 temp_since_bootup;
+ struct smart_log_add_item_12 thermal_throttle_time;
+ } v2;
+
+ struct smart_log_add_item_12 v2_raw[27];
+ };
+
+ union {
+ struct smart_log_add_v3 {
+ struct smart_log_add_item_10 program_fail_count;
+ struct smart_log_add_item_10 erase_fail_count;
+ struct smart_log_add_item_10 wear_leveling_count;
+ struct smart_log_add_item_10 ext_e2e_err_count;
+ struct smart_log_add_item_10 crc_err_count;
+ struct smart_log_add_item_10 nand_bytes_written;
+ struct smart_log_add_item_10 host_bytes_written;
+ struct smart_log_add_item_10 reallocated_sector_count;
+ struct smart_log_add_item_10 uncorrectable_sector_count;
+ struct smart_log_add_item_10 nand_uecc_detection;
+ struct smart_log_add_item_10 nand_xor_correction;
+ struct smart_log_add_item_10 gc_count;
+ struct smart_log_add_item_10 dram_uecc_detection_count;
+ struct smart_log_add_item_10 sram_uecc_detection_count;
+ struct smart_log_add_item_10 internal_raid_recovery_fail_count;
+ struct smart_log_add_item_10 inflight_cmds;
+ struct smart_log_add_item_10 internal_e2e_err_count;
+ struct smart_log_add_item_10 die_fail_count;
+ struct smart_log_add_item_10 wear_leveling_execution_count;
+ struct smart_log_add_item_10 read_disturb_count;
+ struct smart_log_add_item_10 data_retention_count;
+ struct smart_log_add_item_10 capacitor_health;
+ } v3;
+
+ struct smart_log_add_item_10 v3_raw[24];
+ };
+
+ uint8_t raw[512];
+ };
+};
+
+static void smart_log_add_v0_print(struct smart_log_add_item_12 *item, int item_count)
+{
+ static const struct smart_log_add_item items[0xff] = {
+ [0xab] = {0, "program_fail_count" },
+ [0xac] = {1, "erase_fail_count" },
+ [0xad] = {2, "wear_leveling_count" },
+ [0xb8] = {3, "end_to_end_error_count" },
+ [0xc7] = {4, "crc_error_count" },
+ [0xe2] = {5, "timed_workload_media_wear" },
+ [0xe3] = {6, "timed_workload_host_reads" },
+ [0xe4] = {7, "timed_workload_timer" },
+ [0xea] = {8, "thermal_throttle_status" },
+ [0xf0] = {9, "retry_buffer_overflow_counter"},
+ [0xf3] = {10, "pll_lock_loss_count" },
+ [0xf4] = {11, "nand_bytes_written" },
+ [0xf5] = {12, "host_bytes_written" },
+ [0xf6] = {13, "system_area_life_remaining" },
+ [0xfa] = {14, "nand_bytes_read" },
+ [0xe7] = {15, "temperature" },
+ [0xe8] = {16, "power_consumption" },
+ [0xaf] = {17, "power_on_temperature" },
+ [0xec] = {18, "power_loss_protection" },
+ [0xf2] = {19, "read_fail_count" },
+ [0xeb] = {20, "thermal_throttle_time" },
+ [0xed] = {21, "flash_error_media_count" },
+ };
+
+ for (int i = 0; i < item_count; i++, item++) {
+ if (item->id == 0)
+ continue;
+
+ printf("%#-12" PRIx8 "%-36s%-12d", item->id, items[item->id].attr, item->norm);
+ switch (item->id) {
+ case 0xad:
+ printf("min: %d, max: %d, avg: %d\n",
+ le16_to_cpu(item->wear_level.min),
+ le16_to_cpu(item->wear_level.max),
+ le16_to_cpu(item->wear_level.avg));
+ break;
+ case 0xe7:
+ printf("max: %d °C (%d K), min: %d °C (%d K), curr: %d °C (%d K)\n",
+ K2C(le16_to_cpu(item->temp_since_born.max)),
+ le16_to_cpu(item->temp_since_born.max),
+ K2C(le16_to_cpu(item->temp_since_born.min)),
+ le16_to_cpu(item->temp_since_born.min),
+ K2C(le16_to_cpu(item->temp_since_born.curr)),
+ le16_to_cpu(item->temp_since_born.curr));
+ break;
+ case 0xe8:
+ printf("max: %d, min: %d, curr: %d\n",
+ le16_to_cpu(item->power_consumption.max),
+ le16_to_cpu(item->power_consumption.min),
+ le16_to_cpu(item->power_consumption.curr));
+ break;
+ case 0xaf:
+ printf("max: %d °C (%d K), min: %d °C (%d K), curr: %d °C (%d K)\n",
+ K2C(le16_to_cpu(item->temp_since_power_on.max)),
+ le16_to_cpu(item->temp_since_power_on.max),
+ K2C(le16_to_cpu(item->temp_since_power_on.min)),
+ le16_to_cpu(item->temp_since_power_on.min),
+ K2C(le16_to_cpu(item->temp_since_power_on.curr)),
+ le16_to_cpu(item->temp_since_power_on.curr));
+ break;
+ default:
+ printf("%" PRIu64 "\n", int48_to_long(item->raw));
+ break;
+ }
+ }
+}
+
+static void smart_log_add_v2_print(struct smart_log_add_item_12 *item, int item_count)
+{
+ static const struct smart_log_add_item items[0xff] = {
+ [0xab] = {0, "program_fail_count" },
+ [0xac] = {1, "erase_fail_count" },
+ [0xad] = {2, "wear_leveling_count" },
+ [0xb8] = {3, "end_to_end_error_count" },
+ [0xc7] = {4, "crc_error_count" },
+ [0xe2] = {5, "timed_workload_media_wear" },
+ [0xe3] = {6, "timed_workload_host_reads" },
+ [0xe4] = {7, "timed_workload_timer" },
+ [0xea] = {8, "thermal_throttle_status" },
+ [0xf0] = {9, "lifetime_write_amplification"},
+ [0xf3] = {10, "pll_lock_loss_count" },
+ [0xf4] = {11, "nand_bytes_written" },
+ [0xf5] = {12, "host_bytes_written" },
+ [0xf6] = {13, "system_area_life_remaining" },
+ [0xf9] = {14, "firmware_update_count" },
+ [0xfa] = {15, "dram_cecc_count" },
+ [0xfb] = {16, "dram_uecc_count" },
+ [0xfc] = {17, "xor_pass_count" },
+ [0xfd] = {18, "xor_fail_count" },
+ [0xfe] = {19, "xor_invoked_count" },
+ [0xe5] = {20, "inflight_read_io_cmd" },
+ [0xe6] = {21, "flash_error_media_count" },
+ [0xf8] = {22, "nand_bytes_read" },
+ [0xe7] = {23, "temp_since_born" },
+ [0xe8] = {24, "power_consumption" },
+ [0xaf] = {25, "temp_since_bootup" },
+ [0xeb] = {26, "thermal_throttle_time" },
+ };
+
+ for (int i = 0; i < item_count; i++, item++) {
+ if (item->id == 0)
+ continue;
+
+ printf("%#-12" PRIx8 "%-36s%-12d", item->id, items[item->id].attr, item->norm);
+ switch (item->id) {
+ case 0xad:
+ printf("min: %d, max: %d, avg: %d\n",
+ le16_to_cpu(item->wear_level.min),
+ le16_to_cpu(item->wear_level.max),
+ le16_to_cpu(item->wear_level.avg));
+ break;
+ case 0xe7:
+ printf("max: %d °C (%d K), min: %d °C (%d K), curr: %d °C (%d K)\n",
+ K2C(le16_to_cpu(item->temp_since_born.max)),
+ le16_to_cpu(item->temp_since_born.max),
+ K2C(le16_to_cpu(item->temp_since_born.min)),
+ le16_to_cpu(item->temp_since_born.min),
+ K2C(le16_to_cpu(item->temp_since_born.curr)),
+ le16_to_cpu(item->temp_since_born.curr));
+ break;
+ case 0xe8:
+ printf("max: %d, min: %d, curr: %d\n",
+ le16_to_cpu(item->power_consumption.max),
+ le16_to_cpu(item->power_consumption.min),
+ le16_to_cpu(item->power_consumption.curr));
+ break;
+ case 0xaf:
+ printf("max: %d °C (%d K), min: %d °C (%d K), curr: %d °C (%d K)\n",
+ K2C(le16_to_cpu(item->temp_since_power_on.max)),
+ le16_to_cpu(item->temp_since_power_on.max),
+ K2C(le16_to_cpu(item->temp_since_power_on.min)),
+ le16_to_cpu(item->temp_since_power_on.min),
+ K2C(le16_to_cpu(item->temp_since_power_on.curr)),
+ le16_to_cpu(item->temp_since_power_on.curr));
+ break;
+ default:
+ printf("%" PRIu64 "\n", int48_to_long(item->raw));
+ break;
+ }
+ }
+}
+
+static void smart_log_add_v3_print(struct smart_log_add_item_10 *item, int item_count)
+{
+ static const struct smart_log_add_item items[0xff] = {
+ [0xab] = {0, "program_fail_count" },
+ [0xac] = {1, "erase_fail_count" },
+ [0xad] = {2, "wear_leveling_count" },
+ [0xb8] = {3, "ext_e2e_err_count" },
+ [0xc7] = {4, "crc_err_count" },
+ [0xf4] = {5, "nand_bytes_written" },
+ [0xf5] = {6, "host_bytes_written" },
+ [0xd0] = {7, "reallocated_sector_count" },
+ [0xd1] = {8, "uncorrectable_sector_count" },
+ [0xd2] = {9, "nand_uecc_detection" },
+ [0xd3] = {10, "nand_xor_correction" },
+ [0xd4] = {12, "gc_count" }, // 11 is reserved
+ [0xd5] = {13, "dram_uecc_detection_count" },
+ [0xd6] = {14, "sram_uecc_detection_count" },
+ [0xd7] = {15, "internal_raid_recovery_fail_count"},
+ [0xd8] = {16, "inflight_cmds" },
+ [0xd9] = {17, "internal_e2e_err_count" },
+ [0xda] = {19, "die_fail_count" }, // 18 is reserved
+ [0xdb] = {20, "wear_leveling_execution_count" },
+ [0xdc] = {21, "read_disturb_count" },
+ [0xdd] = {22, "data_retention_count" },
+ [0xde] = {23, "capacitor_health" },
+ };
+
+ for (int i = 0; i < item_count; i++, item++) {
+ if (item->id == 0)
+ continue;
+
+ printf("%#-12" PRIx8 "%-36s%-12d", item->id, items[item->id].attr, item->norm);
+ switch (item->id) {
+ case 0xad:
+ printf("min: %d, max: %d, avg: %d\n",
+ le16_to_cpu(item->wear_level.min),
+ le16_to_cpu(item->wear_level.max),
+ le16_to_cpu(item->wear_level.avg));
+ break;
+ default:
+ printf("%" PRIu64 "\n", int48_to_long(item->raw));
+ break;
+ }
+ }
+}
+
+static void smart_log_add_print(struct smart_log_add *log, const char *devname)
+{
+ uint8_t version = log->raw[511];
+
+ printf("Version: %u\n", version);
+ printf("\n");
+ printf("Additional Smart Log for NVMe device: %s\n", devname);
+ printf("\n");
+
+ printf("%-12s%-36s%-12s%s\n", "Id", "Key", "Normalized", "Raw");
+
+ switch (version) {
+ case 0:
+ return smart_log_add_v0_print(&log->v0_raw[0],
+ sizeof(struct smart_log_add_v0) / sizeof(struct smart_log_add_item_12));
+ case 2:
+ return smart_log_add_v2_print(&log->v2_raw[0],
+ sizeof(struct smart_log_add_v2) / sizeof(struct smart_log_add_item_12));
+ case 3:
+ return smart_log_add_v3_print(&log->v3_raw[0],
+ sizeof(struct smart_log_add_v3) / sizeof(struct smart_log_add_item_10));
+
+ case 1:
+ fprintf(stderr, "Version %d: N/A\n", version);
+ break;
+ default:
+ fprintf(stderr, "Version %d: Not supported yet\n", version);
+ break;
+ }
+}
+
+static int mb_get_smart_log_add(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ int err = 0;
+
+ // Get the configuration
+
+ struct config {
+ bool raw_binary;
+ };
+
+ struct config cfg = {0};
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, "dump the whole log buffer in binary format"),
+ OPT_END()};
+
+ // Open device
+
+ struct nvme_dev *dev = NULL;
+
+ err = parse_and_open(&dev, argc, argv, cmd->help, opts);
+ if (err)
+ return err;
+
+ // Get log
+
+ struct smart_log_add log = {0};
+
+ err = nvme_get_log_simple(dev_fd(dev), LID_SMART_LOG_ADD, sizeof(struct smart_log_add), &log);
+ if (!err) {
+ if (!cfg.raw_binary)
+ smart_log_add_print(&log, dev->name);
+ else
+ d_raw((unsigned char *)&log, sizeof(struct smart_log_add));
+ } else if (err > 0) {
+ nvme_show_status(err);
+ } else {
+ nvme_show_error("%s: %s", cmd->name, nvme_strerror(errno));
+ }
+
+ // Close device
+
+ dev_close(dev);
+ return err;
+}
+
+// performance-monitor
+
+struct latency_stats_bucket {
+ char *start_threshold;
+ char *end_threshold;
+};
+
+struct __packed latency_stats {
+ union {
+ struct latency_stats_v2_0 {
+ uint32_t minor_version;
+ uint32_t major_version;
+ uint32_t bucket_read_data[32];
+ uint32_t rsvd[32];
+ uint32_t bucket_write_data[32];
+ uint32_t rsvd1[32];
+ uint32_t bucket_trim_data[32];
+ uint32_t rsvd2[32];
+ uint8_t rsvd3[248];
+ } v2_0;
+ uint8_t raw[1024];
+ };
+};
+
+struct __packed high_latency_log {
+ union {
+ struct high_latency_log_v1 {
+ uint32_t version;
+ struct high_latency_log_entry {
+ uint64_t timestamp; // ms
+ uint32_t latency;
+ uint32_t qid;
+ uint32_t opcode : 8;
+ uint32_t fuse : 2;
+ uint32_t psdt : 2;
+ uint32_t cid : 16;
+ uint32_t rsvd : 4;
+ uint32_t nsid;
+ uint64_t slba;
+ uint32_t nlb : 16;
+ uint32_t dtype : 8;
+ uint32_t pinfo : 4;
+ uint32_t fua : 1;
+ uint32_t lr : 1;
+ uint32_t rsvd1 : 2;
+ uint8_t rsvd2[28];
+ } entries[1024];
+ } v1;
+ uint8_t raw[4 + 1024 * 64];
+ };
+};
+
+struct __packed performance_stats {
+ union {
+ struct performance_stats_v1 {
+ uint8_t version;
+ uint8_t rsvd[3];
+ struct performance_stats_timestamp {
+ uint8_t timestamp[6];
+ struct performance_stats_entry {
+ uint16_t read_iops; // K IOPS
+ uint16_t read_bandwidth; // MiB
+ uint32_t read_latency; // us
+ uint32_t read_latency_max; // us
+ uint16_t write_iops; // K IOPS
+ uint16_t write_bandwidth; // MiB
+ uint32_t write_latency; // us
+ uint32_t write_latency_max; // us
+ } entries[3600];
+ } timestamps[24];
+ } v1;
+ uint8_t raw[4 + 24 * (6 + 3600 * 24)];
+ };
+};
+
+static int mb_set_latency_feature(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ int err = 0;
+
+ // Get the configuration
+
+ struct config {
+ uint32_t perf_monitor;
+ uint32_t cmd_mask;
+ uint32_t read_threshold;
+ uint32_t write_threshold;
+ uint32_t de_allocate_trim_threshold;
+ };
+
+ struct config cfg = {0};
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("sel-perf-log", 's', &cfg.perf_monitor,
+ "Select features to turn on, default: Disable\n"
+ " bit 0: latency statistics\n"
+ " bit 1: high latency log\n"
+ " bit 2: Performance stat"),
+ OPT_UINT("set-commands-mask", 'm', &cfg.cmd_mask,
+ "Set Enable, default: Disable\n"
+ " bit 0: Read commands\n"
+ " bit 1: high Write commands\n"
+ " bit 2: De-allocate/TRIM (this bit is not worked for Performance stat.)"),
+ OPT_UINT("set-read-threshold", 'r', &cfg.read_threshold,
+ "set read high latency log threshold, it's a 0-based value and unit is 10ms"),
+ OPT_UINT("set-write-threshold", 'w', &cfg.write_threshold,
+ "set write high latency log threshold, it's a 0-based value and unit is 10ms"),
+ OPT_UINT("set-trim-threshold", 't', &cfg.de_allocate_trim_threshold,
+ "set trim high latency log threshold, it's a 0-based value and unit is 10ms"),
+ OPT_END()};
+
+ // Open device
+
+ struct nvme_dev *dev = NULL;
+
+ err = parse_and_open(&dev, argc, argv, cmd->help, opts);
+ if (err)
+ return err;
+
+
+ // Set feature
+
+ uint32_t result = 0;
+
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = FID_LATENCY_FEATURE,
+ .nsid = 0,
+ .cdw11 = 0 | cfg.perf_monitor,
+ .cdw12 = 0 | cfg.cmd_mask,
+ .cdw13 = 0 |
+ (cfg.read_threshold & 0xff) |
+ ((cfg.write_threshold & 0xff) << 8) |
+ ((cfg.de_allocate_trim_threshold & 0xff) << 16),
+ .cdw15 = 0,
+ .save = 0,
+ .uuidx = 0,
+ .data = NULL,
+ .data_len = 0,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ err = nvme_set_features(&args);
+ if (!err)
+ printf("%s have done successfully. result = %#" PRIx32 ".\n", cmd->name, result);
+ else if (err > 0)
+ nvme_show_status(err);
+ else
+ nvme_show_error("%s: %s", cmd->name, nvme_strerror(errno));
+
+ // Close device
+
+ dev_close(dev);
+ return err;
+}
+
+static int mb_get_latency_feature(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ int err = 0;
+
+ // Get the configuration
+
+ OPT_ARGS(opts) = {
+ OPT_END()};
+
+ // Open device
+
+ struct nvme_dev *dev = NULL;
+
+ err = parse_and_open(&dev, argc, argv, cmd->help, opts);
+ if (err)
+ return err;
+
+ // Get feature
+
+ uint32_t result = 0;
+
+ err = nvme_get_features_simple(dev_fd(dev), FID_LATENCY_FEATURE, 0, &result);
+ if (!err) {
+ printf("%s have done successfully. result = %#" PRIx32 ".\n", cmd->name, result);
+
+ printf("latency statistics enable status = %d\n", (result & (0x01 << 0)) >> 0);
+ printf("high latency enable status = %d\n", (result & (0x01 << 1)) >> 1);
+ printf("performance stat enable status = %d\n", (result & (0x01 << 2)) >> 2);
+
+ printf("Monitor Read command = %d\n", (result & (0x01 << 4)) >> 4);
+ printf("Monitor Write command = %d\n", (result & (0x01 << 5)) >> 5);
+ printf("Monitor Trim command = %d\n", (result & (0x01 << 6)) >> 6);
+
+ printf("Threshold for Read = %dms\n", (((result & (0xff << 8)) >> 8) + 1) * 10);
+ printf("Threshold for Write = %dms\n", (((result & (0xff << 16)) >> 16) + 1) * 10);
+ printf("Threshold for Trim = %dms\n", (((result & (0xff << 24)) >> 24) + 1) * 10);
+ } else if (err > 0) {
+ nvme_show_status(err);
+ } else {
+ nvme_show_error("%s: %s", cmd->name, nvme_strerror(errno));
+ }
+
+ // Close device
+
+ dev_close(dev);
+ return err;
+}
diff --git a/plugins/memblaze/memblaze-nvme.h b/plugins/memblaze/memblaze-nvme.h
new file mode 100644
index 0000000..e25267b
--- /dev/null
+++ b/plugins/memblaze/memblaze-nvme.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/memblaze/memblaze-nvme
+
+#if !defined(MEMBLAZE_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define MEMBLAZE_NVME
+
+#include "cmd.h"
+
+PLUGIN(NAME("memblaze", "Memblaze vendor specific extensions", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("smart-log-add", "Retrieve Memblaze SMART Log, show it", mb_get_additional_smart_log)
+ ENTRY("get-pm-status", "Get Memblaze Power Manager Status", mb_get_powermanager_status)
+ ENTRY("set-pm-status", "Set Memblaze Power Manager Status", mb_set_powermanager_status)
+ ENTRY("select-download", "Selective Firmware Download", mb_selective_download)
+ ENTRY("lat-stats", "Enable and disable Latency Statistics logging", mb_set_lat_stats)
+ ENTRY("lat-stats-print", "Retrieve IO Latency Statistics log, show it", mb_lat_stats_log_print)
+ ENTRY("lat-log", "Set Memblaze High Latency Log", mb_set_high_latency_log)
+ ENTRY("lat-log-print", "Output Memblaze High Latency Log", mb_high_latency_log_print)
+ ENTRY("clear-error-log", "Clear error log", memblaze_clear_error_log)
+ ENTRY("smart-log-add-x", "Retrieve Memblaze SMART Log, show it", mb_get_smart_log_add)
+ ENTRY("lat-set-feature-x", "Set Enable/Disable for Latency Monitor feature", mb_set_latency_feature)
+ ENTRY("lat-get-feature-x", "Get Enabled/Disabled of Latency Monitor feature", mb_get_latency_feature)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/memblaze/memblaze-utils.h b/plugins/memblaze/memblaze-utils.h
new file mode 100644
index 0000000..8914f95
--- /dev/null
+++ b/plugins/memblaze/memblaze-utils.h
@@ -0,0 +1,217 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __MEMBLAZE_UTILS_H__
+#define __MEMBLAZE_UTILS_H__
+
+#define SMART_INFO_OLD_SIZE 512
+#define SMART_INFO_NEW_SIZE 4096
+
+#define ID_SIZE 3
+#define NM_SIZE 2
+#define RAW_SIZE 7
+
+// Intel Format & new format
+/* Raisin Additional smart external ID */
+#define RAISIN_SI_VD_PROGRAM_FAIL_ID 0xAB
+#define RAISIN_SI_VD_ERASE_FAIL_ID 0xAC
+#define RAISIN_SI_VD_WEARLEVELING_COUNT_ID 0xAD
+#define RAISIN_SI_VD_E2E_DECTECTION_COUNT_ID 0xB8
+#define RAISIN_SI_VD_PCIE_CRC_ERR_COUNT_ID 0xC7
+#define RAISIN_SI_VD_TIMED_WORKLOAD_MEDIA_WEAR_ID 0xE2
+#define RAISIN_SI_VD_TIMED_WORKLOAD_HOST_READ_ID 0xE3
+#define RAISIN_SI_VD_TIMED_WORKLOAD_TIMER_ID 0xE4
+#define RAISIN_SI_VD_THERMAL_THROTTLE_STATUS_ID 0xEA
+#define RAISIN_SI_VD_RETRY_BUFF_OVERFLOW_COUNT_ID 0xF0
+#define RAISIN_SI_VD_PLL_LOCK_LOSS_COUNT_ID 0xF3
+#define RAISIN_SI_VD_TOTAL_WRITE_ID 0xF4
+#define RAISIN_SI_VD_HOST_WRITE_ID 0xF5
+#define RAISIN_SI_VD_SYSTEM_AREA_LIFE_LEFT_ID 0xF6
+#define RAISIN_SI_VD_TOTAL_READ_ID 0xFA
+#define RAISIN_SI_VD_TEMPT_SINCE_BORN_ID 0xE7
+#define RAISIN_SI_VD_POWER_CONSUMPTION_ID 0xE8
+#define RAISIN_SI_VD_TEMPT_SINCE_BOOTUP_ID 0xAF
+#define RAISIN_SI_VD_POWER_LOSS_PROTECTION_ID 0xEC
+#define RAISIN_SI_VD_READ_FAIL_ID 0xF2
+#define RAISIN_SI_VD_THERMAL_THROTTLE_TIME_ID 0xEB
+#define RAISIN_SI_VD_FLASH_MEDIA_ERROR_ID 0xED
+
+/* Raisin Additional smart internal ID */
+typedef enum
+{
+ /* smart attr following intel */
+ RAISIN_SI_VD_PROGRAM_FAIL = 0, /* 0xAB */
+ RAISIN_SI_VD_ERASE_FAIL = 1, /* 0xAC */
+ RAISIN_SI_VD_WEARLEVELING_COUNT = 2,/* 0xAD */
+ RAISIN_SI_VD_E2E_DECTECTION_COUNT = 3, /* 0xB8 */
+ RAISIN_SI_VD_PCIE_CRC_ERR_COUNT = 4, /* 0xC7, 2 port data in one attribute */
+ RAISIN_SI_VD_TIMED_WORKLOAD_MEDIA_WEAR = 5, /* 0xE2 , unknown definition*/
+ RAISIN_SI_VD_TIMED_WORKLOAD_HOST_READ = 6, /* 0xE3 , unknown definition */
+ RAISIN_SI_VD_TIMED_WORKLOAD_TIMER = 7, /* 0xE4 , unknown definition */
+ RAISIN_SI_VD_THERMAL_THROTTLE_STATUS = 8, /* 0xEA */
+ RAISIN_SI_VD_RETRY_BUFF_OVERFLOW_COUNT = 9, /* 0xF0, unknown definition*/
+ RAISIN_SI_VD_PLL_LOCK_LOSS_COUNT = 10, /* 0xF3, unknown definition*/
+ RAISIN_SI_VD_TOTAL_WRITE = 11, /* 0xF4, unit is 32MiB */
+ RAISIN_SI_VD_HOST_WRITE = 12, /* 0xF5, unit is 32MiB */
+ RAISIN_SI_VD_SYSTEM_AREA_LIFE_LEFT = 13, /* 0xF6, unknown definition*/
+ RAISIN_SI_VD_TOTAL_READ = 14, /* 0xFA, unit is 32MiB */
+
+ /* smart attr self defined */
+ RAISIN_SI_VD_TEMPT_SINCE_BORN = 15, /* 0xE7 */
+ RAISIN_SI_VD_POWER_CONSUMPTION = 16, /* 0xE8 */
+ RAISIN_SI_VD_TEMPT_SINCE_BOOTUP = 17, /* 0xAF */
+ RAISIN_SI_VD_POWER_LOSS_PROTECTION = 18, /* 0xEC */
+ RAISIN_SI_VD_READ_FAIL = 19, /* 0xF2 */
+ RAISIN_SI_VD_THERMAL_THROTTLE_TIME = 20, /* 0xEB */
+ RAISIN_SI_VD_FLASH_MEDIA_ERROR = 21, /* 0xED */
+ RAISIN_SI_VD_SMART_INFO_ITEMS_MAX,
+} RAISIN_si_vendor_smart_item_e;
+
+// Memblaze Format & old format
+enum {
+ /*0*/TOTAL_WRITE = 0,
+ /*1*/TOTAL_READ,
+ /*2*/THERMAL_THROTTLE,
+ /*3*/TEMPT_SINCE_RESET,
+ /*4*/POWER_CONSUMPTION,
+ /*5*/TEMPT_SINCE_BOOTUP,
+ /*6*/POWER_LOSS_PROTECTION,
+ /*7*/WEARLEVELING_COUNT,
+ /*8*/HOST_WRITE,
+ /*9*/THERMAL_THROTTLE_CNT,
+ /*10*/CORRECT_PCIE_PORT0,
+ /*11*/CORRECT_PCIE_PORT1,
+ /*12*/REBUILD_FAIL,
+ /*13*/ERASE_FAIL,
+ /*14*/PROGRAM_FAIL,
+ /*15*/READ_FAIL,
+ /*16*/NR_SMART_ITEMS = RAISIN_SI_VD_SMART_INFO_ITEMS_MAX,
+};
+
+// Memblaze Format & old format
+#pragma pack(push, 1)
+struct nvme_memblaze_smart_log_item {
+ __u8 id[3];
+ union {
+ __u8 __nmval[2];
+ __le16 nmval;
+ };
+ union {
+ __u8 rawval[6];
+ struct temperature {
+ __le16 max;
+ __le16 min;
+ __le16 curr;
+ } temperature;
+ struct power {
+ __le16 max;
+ __le16 min;
+ __le16 curr;
+ } power;
+ struct thermal_throttle_mb {
+ __u8 on;
+ __u32 count;
+ } thermal_throttle;
+ struct temperature_p {
+ __le16 max;
+ __le16 min;
+ } temperature_p;
+ struct power_loss_protection {
+ __u8 curr;
+ } power_loss_protection;
+ struct wearleveling_count {
+ __le16 min;
+ __le16 max;
+ __le16 avg;
+ } wearleveling_count;
+ struct thermal_throttle_cnt {
+ __u8 active;
+ __le32 cnt;
+ } thermal_throttle_cnt;
+ };
+ __u8 resv;
+};
+#pragma pack(pop)
+
+struct nvme_memblaze_smart_log {
+ struct nvme_memblaze_smart_log_item items[NR_SMART_ITEMS];
+ __u8 resv[SMART_INFO_OLD_SIZE - sizeof(struct nvme_memblaze_smart_log_item) * NR_SMART_ITEMS];
+};
+
+// Intel Format & new format
+struct nvme_p4_smart_log_item
+{
+ /* Item identifier */
+ __u8 id[ID_SIZE];
+ /* Normalized value or percentage. In the range from 0 to 100. */
+ __u8 nmVal[NM_SIZE];
+ /* raw value */
+ __u8 rawVal[RAW_SIZE];
+};
+
+struct nvme_p4_smart_log
+{
+ struct nvme_p4_smart_log_item itemArr[NR_SMART_ITEMS];
+
+ /**
+ * change 512 to 4096.
+ * because micron's getlogpage request,the size of many commands have changed to 4k.
+ * request size > user malloc size,casuing parameters that are closed in memery are dirty.
+ */
+ __u8 resv[SMART_INFO_NEW_SIZE - sizeof(struct nvme_p4_smart_log_item) * NR_SMART_ITEMS];
+};
+
+// base
+#define DD do{ printf("=Memblaze= %s[%d]-%s():\n", __FILE__, __LINE__, __func__); }while(0)
+#define DE(str) do{ printf("===ERROR!=== %s[%d]-%s():str=%s\n", __FILE__, __LINE__, __func__, \
+ str); }while(0)
+// integer
+#define DI(i) do{ printf("=Memblaze= %s[%d]-%s():int=%d\n", __FILE__, __LINE__, __func__, \
+ (int)i); } while(0)
+#define DPI(prompt, i) do{ printf("=Memblaze= %s[%d]-%s():%s=%d\n", __FILE__, __LINE__, __func__, \
+ prompt, i); }while(0)
+#define DAI(prompt, i, arr, max) do{ printf("=Memblaze= %s[%d]-%s():%s", __FILE__, __LINE__, __func__, prompt); \
+ for(i=0;i<max;i++) printf(" %d:%d", i, arr[i]); \
+ printf("\n"); }while(0)
+// char
+#define DC(c) do{ printf("=Memblaze= %s[%d]-%s():char=%c\n", __FILE__, __LINE__, __func__, c); \
+ }while(0)
+#define DPC(prompt, c) do{ printf("=Memblaze= %s[%d]-%s():%s=%c\n", __FILE__, __LINE__, __func__, \
+ prompt, c); }while(0)
+// address
+#define DA(add) do{ printf("=Memblaze= %s[%d]-%s():address=0x%08X\n", __FILE__, __LINE__, \
+ __func__, add); }while(0)
+#define DPA(prompt, add) do{ printf("=Memblaze= %s[%d]-%s():%s=0x%08X\n", __FILE__, __LINE__, __func__, \
+ prompt, add); }while(0)
+// string
+#define DS(str) do{ printf("=Memblaze= %s[%d]-%s():str=%s\n", __FILE__, __LINE__, __func__, str); \
+ }while(0)
+#define DPS(prompt, str) do{ printf("=Memblaze= %s[%d]-%s():%s=%s\n", __FILE__, __LINE__, __func__, \
+ prompt, str); }while(0)
+#define DAS(prompt, i, arr, max) do{ printf("=Memblaze= %s[%d]-%s():%s", __FILE__, __LINE__, __func__, prompt); \
+ for(i=0;i<max;i++) printf(" %d:%s", i, arr[i]); \
+ printf("\n"); }while(0)
+// array
+#define DR(str, k) do{ int ip; for(ip=0;ip<k;ip++) if(NULL != argv[ip]) \
+ printf("=Memblaze= %s[%d]-%s():%d=%s\n", \
+ __FILE__, __LINE__, __func__, ip, str[ip]); }while(0)
+#define DARG do{ DPI("argc", argc); \
+ int ip; for(ip=0;ip<argc;ip++) if(NULL != argv[ip]) \
+ printf("=Memblaze= %s[%d]-%s():%d=%s\n", \
+ __FILE__, __LINE__, __func__, ip, argv[ip]); }while(0)
+
+#define fPRINT_PARAM1(format) \
+ do { \
+ if (fdi) \
+ fprintf(fdi, format); \
+ if (print) \
+ printf(format); \
+ } while (0)
+
+#define fPRINT_PARAM2(format, value) \
+ do { \
+ if (fdi) \
+ fprintf(fdi, format, value); \
+ if (print) \
+ printf(format, value); \
+ } while (0)
+
+#endif // __MEMBLAZE_UTILS_H__
diff --git a/plugins/meson.build b/plugins/meson.build
new file mode 100644
index 0000000..bb4c9ad
--- /dev/null
+++ b/plugins/meson.build
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+if json_c_dep.found()
+ sources += [
+ 'plugins/amzn/amzn-nvme.c',
+ 'plugins/dell/dell-nvme.c',
+ 'plugins/dera/dera-nvme.c',
+ 'plugins/fdp/fdp.c',
+ 'plugins/huawei/huawei-nvme.c',
+ 'plugins/innogrit/innogrit-nvme.c',
+ 'plugins/inspur/inspur-nvme.c',
+ 'plugins/intel/intel-nvme.c',
+ 'plugins/memblaze/memblaze-nvme.c',
+ 'plugins/micron/micron-nvme.c',
+ 'plugins/nbft/nbft-plugin.c',
+ 'plugins/netapp/netapp-nvme.c',
+ 'plugins/nvidia/nvidia-nvme.c',
+ 'plugins/scaleflux/sfx-nvme.c',
+ 'plugins/seagate/seagate-nvme.c',
+ 'plugins/shannon/shannon-nvme.c',
+ 'plugins/solidigm/solidigm-nvme.c',
+ 'plugins/toshiba/toshiba-nvme.c',
+ 'plugins/transcend/transcend-nvme.c',
+ 'plugins/virtium/virtium-nvme.c',
+ 'plugins/wdc/wdc-nvme.c',
+ 'plugins/wdc/wdc-utils.c',
+ 'plugins/ymtc/ymtc-nvme.c',
+ 'plugins/zns/zns.c',
+ ]
+ subdir('solidigm')
+ subdir('ocp')
+ if conf.has('HAVE_SED_OPAL')
+ subdir('sed')
+ endif
+endif
diff --git a/plugins/micron/micron-nvme.c b/plugins/micron/micron-nvme.c
new file mode 100644
index 0000000..63a7a79
--- /dev/null
+++ b/plugins/micron/micron-nvme.c
@@ -0,0 +1,3390 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <string.h>
+#include <libgen.h>
+#include <sys/stat.h>
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include <limits.h>
+#include "linux/types.h"
+#include "nvme-print.h"
+#include "util/cleanup.h"
+
+#define CREATE_CMD
+#include "micron-nvme.h"
+
+/* Supported Vendor specific feature ids */
+#define MICRON_FEATURE_CLEAR_PCI_CORRECTABLE_ERRORS 0xC3
+#define MICRON_FEATURE_CLEAR_FW_ACTIVATION_HISTORY 0xC1
+#define MICRON_FEATURE_TELEMETRY_CONTROL_OPTION 0xCF
+#define MICRON_FEATURE_SMBUS_OPTION 0xD5
+
+/* Supported Vendor specific log page sizes */
+#define C5_log_size (((452 + 16 * 1024) / 4) * 4096)
+#define C0_log_size 512
+#define C2_log_size 4096
+#define D0_log_size 512
+#define FB_log_size 512
+#define E1_log_size 256
+#define MaxLogChunk (16 * 1024)
+#define CommonChunkSize (16 * 4096)
+
+#define min(x, y) ((x) > (y) ? (y) : (x))
+#define SensorCount 8
+
+/* Plugin version major_number.minor_number.patch */
+static const char *__version_major = "1";
+static const char *__version_minor = "0";
+static const char *__version_patch = "14";
+
+/*
+ * supported models of micron plugin; new models should be added at the end
+ * before UNKNOWN_MODEL. Make sure M5410 is first in the list !
+ */
+enum eDriveModel {
+ M5410 = 0,
+ M51AX,
+ M51BX,
+ M51CX,
+ M5407,
+ M5411,
+ UNKNOWN_MODEL
+};
+
+#define MICRON_VENDOR_ID 0x1344
+
+static char *fvendorid1 = "/sys/class/nvme/nvme%d/device/vendor";
+static char *fvendorid2 = "/sys/class/misc/nvme%d/device/vendor";
+static char *fdeviceid1 = "/sys/class/nvme/nvme%d/device/device";
+static char *fdeviceid2 = "/sys/class/misc/nvme%d/device/device";
+static unsigned short vendor_id;
+static unsigned short device_id;
+
+struct LogPageHeader_t {
+ unsigned char numDwordsInLogPageHeaderLo;
+ unsigned char logPageHeaderFormatVersion;
+ unsigned char logPageId;
+ unsigned char numDwordsInLogPageHeaderHi;
+ unsigned int numValidDwordsInPayload;
+ unsigned int numDwordsInEntireLogPage;
+};
+
+static void WriteData(__u8 *data, __u32 len, const char *dir, const char *file, const char *msg)
+{
+ char tempFolder[8192] = { 0 };
+ FILE *fpOutFile = NULL;
+
+ sprintf(tempFolder, "%s/%s", dir, file);
+ fpOutFile = fopen(tempFolder, "ab+");
+ if (fpOutFile) {
+ if (fwrite(data, 1, len, fpOutFile) != len)
+ printf("Failed to write %s data to %s\n", msg, tempFolder);
+ fclose(fpOutFile);
+ } else {
+ printf("Failed to open %s file to write %s\n", tempFolder, msg);
+ }
+}
+
+static int ReadSysFile(const char *file, unsigned short *id)
+{
+ int ret = 0;
+ char idstr[32] = { '\0' };
+ int fd = open(file, O_RDONLY);
+
+ if (fd < 0) {
+ perror(file);
+ return fd;
+ }
+
+ ret = read(fd, idstr, sizeof(idstr));
+ close(fd);
+ if (ret < 0)
+ perror("read");
+ else
+ *id = strtol(idstr, NULL, 16);
+
+ return ret;
+}
+
+static enum eDriveModel GetDriveModel(int idx)
+{
+ enum eDriveModel eModel = UNKNOWN_MODEL;
+ char path[512];
+
+ sprintf(path, fvendorid1, idx);
+ if (ReadSysFile(path, &vendor_id) < 0) {
+ sprintf(path, fvendorid2, idx);
+ ReadSysFile(path, &vendor_id);
+ }
+ sprintf(path, fdeviceid1, idx);
+ if (ReadSysFile(path, &device_id) < 0) {
+ sprintf(path, fdeviceid2, idx);
+ ReadSysFile(path, &device_id);
+ }
+ if (vendor_id == MICRON_VENDOR_ID) {
+ switch (device_id) {
+ case 0x5196:
+ fallthrough;
+ case 0x51A0:
+ fallthrough;
+ case 0x51A1:
+ fallthrough;
+ case 0x51A2:
+ eModel = M51AX;
+ break;
+ case 0x51B0:
+ fallthrough;
+ case 0x51B1:
+ fallthrough;
+ case 0x51B2:
+ eModel = M51BX;
+ break;
+ case 0x51C0:
+ fallthrough;
+ case 0x51C1:
+ fallthrough;
+ case 0x51C2:
+ fallthrough;
+ case 0x51C3:
+ eModel = M51CX;
+ break;
+ case 0x5405:
+ fallthrough;
+ case 0x5406:
+ fallthrough;
+ case 0x5407:
+ eModel = M5407;
+ break;
+ case 0x5410:
+ eModel = M5410;
+ break;
+ case 0x5411:
+ eModel = M5411;
+ break;
+ default:
+ break;
+ }
+ }
+ return eModel;
+}
+
+static int ZipAndRemoveDir(char *strDirName, char *strFileName)
+{
+ int err = 0;
+ char strBuffer[PATH_MAX];
+ int nRet;
+ bool is_tgz = false;
+ struct stat sb;
+
+ if (strstr(strFileName, ".tar.gz") || strstr(strFileName, ".tgz")) {
+ sprintf(strBuffer, "tar -zcf \"%s\" \"%s\"", strFileName, strDirName);
+ is_tgz = true;
+ } else {
+ sprintf(strBuffer, "zip -r \"%s\" \"%s\" >temp.txt 2>&1", strFileName,
+ strDirName);
+ }
+
+ err = EINVAL;
+ nRet = system(strBuffer);
+
+ /* check if log file is created, if not print error message */
+ if (nRet < 0 || (stat(strFileName, &sb) == -1)) {
+ if (is_tgz)
+ sprintf(strBuffer, "check if tar and gzip commands are installed");
+ else
+ sprintf(strBuffer, "check if zip command is installed");
+
+ fprintf(stderr, "Failed to create log data package, %s!\n", strBuffer);
+ }
+
+ sprintf(strBuffer, "rm -f -R \"%s\" >temp.txt 2>&1", strDirName);
+ nRet = system(strBuffer);
+ if (nRet < 0)
+ printf("Failed to remove temporary files!\n");
+
+ err = system("rm -f temp.txt");
+ return err;
+}
+
+static int SetupDebugDataDirectories(char *strSN, char *strFilePath,
+ char *strMainDirName, char *strOSDirName,
+ char *strCtrlDirName)
+{
+ int err = 0;
+ char strAppend[250];
+ struct stat st;
+ char *fileLocation = NULL;
+ char *fileName;
+ int length = 0;
+ int nIndex = 0;
+ char *strTemp = NULL;
+ struct stat dirStat;
+ int j;
+ int k = 0;
+ int i = 0;
+
+ if (strchr(strFilePath, '/')) {
+ fileName = strrchr(strFilePath, '\\');
+ if (!fileName)
+ fileName = strrchr(strFilePath, '/');
+
+ if (fileName) {
+ if (!strcmp(fileName, "/"))
+ goto exit_status;
+
+ while (strFilePath[nIndex] != '\0') {
+ if ('\\' == strFilePath[nIndex] && '\\' == strFilePath[nIndex + 1])
+ goto exit_status;
+ nIndex++;
+ }
+
+ length = (int)strlen(strFilePath) - (int)strlen(fileName);
+
+ if (fileName == strFilePath)
+ length = 1;
+
+ fileLocation = (char *)malloc(length + 1);
+ if (!fileLocation)
+ goto exit_status;
+ strncpy(fileLocation, strFilePath, length);
+ fileLocation[length] = '\0';
+
+ while (fileLocation[k] != '\0') {
+ if (fileLocation[k] == '\\')
+ fileLocation[k] = '/';
+ k++;
+ }
+
+ length = (int)strlen(fileLocation);
+
+ if (':' == fileLocation[length - 1]) {
+ strTemp = (char *)malloc(length + 2);
+ if (!strTemp) {
+ free(fileLocation);
+ goto exit_status;
+ }
+ strcpy(strTemp, fileLocation);
+ strcat(strTemp, "/");
+ free(fileLocation);
+
+ length = (int)strlen(strTemp);
+ fileLocation = (char *)malloc(length + 1);
+ if (!fileLocation) {
+ free(strTemp);
+ goto exit_status;
+ }
+
+ memcpy(fileLocation, strTemp, length + 1);
+ free(strTemp);
+ }
+
+ if (stat(fileLocation, &st)) {
+ free(fileLocation);
+ goto exit_status;
+ }
+ free(fileLocation);
+ } else {
+ goto exit_status;
+ }
+ }
+
+ nIndex = 0;
+ for (i = 0; i < (int)strlen(strSN); i++) {
+ if (strSN[i] != ' ' && strSN[i] != '\n' && strSN[i] != '\t' && strSN[i] != '\r')
+ strMainDirName[nIndex++] = strSN[i];
+ }
+ strMainDirName[nIndex] = '\0';
+
+ j = 1;
+ while (!stat(strMainDirName, &dirStat)) {
+ strMainDirName[nIndex] = '\0';
+ sprintf(strAppend, "-%d", j);
+ strcat(strMainDirName, strAppend);
+ j++;
+ }
+
+ if (mkdir(strMainDirName, 0777) < 0) {
+ err = -1;
+ goto exit_status;
+ }
+
+ if (strOSDirName) {
+ sprintf(strOSDirName, "%s/%s", strMainDirName, "OS");
+ if (mkdir(strOSDirName, 0777) < 0) {
+ rmdir(strMainDirName);
+ err = -1;
+ goto exit_status;
+ }
+ }
+ if (strCtrlDirName) {
+ sprintf(strCtrlDirName, "%s/%s", strMainDirName, "Controller");
+ if (mkdir(strCtrlDirName, 0777) < 0) {
+ if (strOSDirName)
+ rmdir(strOSDirName);
+ rmdir(strMainDirName);
+ err = -1;
+ }
+ }
+
+exit_status:
+ return err;
+}
+
+static int GetLogPageSize(int nFD, unsigned char ucLogID, int *nLogSize)
+{
+ int err = 0;
+ unsigned char pTmpBuf[CommonChunkSize] = { 0 };
+ struct LogPageHeader_t *pLogHeader = NULL;
+
+ if (ucLogID == 0xC1 || ucLogID == 0xC2 || ucLogID == 0xC4) {
+ err = nvme_get_log_simple(nFD, ucLogID, CommonChunkSize, pTmpBuf);
+ if (!err) {
+ pLogHeader = (struct LogPageHeader_t *) pTmpBuf;
+ struct LogPageHeader_t *pLogHeader1 = (struct LogPageHeader_t *) pLogHeader;
+ *nLogSize = (int)(pLogHeader1->numDwordsInEntireLogPage) * 4;
+ if (!pLogHeader1->logPageHeaderFormatVersion) {
+ printf("Unsupported log page format version %d of log page : 0x%X\n",
+ ucLogID, err);
+ *nLogSize = 0;
+ err = -1;
+ }
+ } else {
+ printf("Getting size of log page : 0x%X failed with %d (ignored)!\n",
+ ucLogID, err);
+ *nLogSize = 0;
+ }
+ }
+ return err;
+}
+
+static int NVMEGetLogPage(int nFD, unsigned char ucLogID, unsigned char *pBuffer, int nBuffSize)
+{
+ int err = 0;
+ struct nvme_passthru_cmd cmd = { 0 };
+ unsigned int uiNumDwords = (unsigned int)nBuffSize / sizeof(unsigned int);
+ unsigned int uiMaxChunk = uiNumDwords;
+ unsigned int uiNumChunks = 1;
+ unsigned int uiXferDwords = 0;
+ unsigned long long ullBytesRead = 0;
+ unsigned char *pTempPtr = pBuffer;
+ unsigned char ucOpCode = 0x02;
+
+ if (!ullBytesRead && (ucLogID == 0xE6 || ucLogID == 0xE7))
+ uiMaxChunk = 4096;
+ else if (uiMaxChunk > 16 * 1024)
+ uiMaxChunk = 16 * 1024;
+
+ uiNumChunks = uiNumDwords / uiMaxChunk;
+ if (uiNumDwords % uiMaxChunk > 0)
+ uiNumChunks += 1;
+
+ for (unsigned int i = 0; i < uiNumChunks; i++) {
+ memset(&cmd, 0, sizeof(cmd));
+ uiXferDwords = uiMaxChunk;
+ if (i == uiNumChunks - 1 && uiNumDwords % uiMaxChunk > 0)
+ uiXferDwords = uiNumDwords % uiMaxChunk;
+
+ cmd.opcode = ucOpCode;
+ cmd.cdw10 |= ucLogID;
+ cmd.cdw10 |= ((uiXferDwords - 1) & 0x0000FFFF) << 16;
+
+ if (ucLogID == 0x7)
+ cmd.cdw10 |= 0x80;
+ if (!ullBytesRead && (ucLogID == 0xE6 || ucLogID == 0xE7))
+ cmd.cdw11 = 1;
+ if (ullBytesRead > 0 && !(ucLogID == 0xE6 || ucLogID == 0xE7)) {
+ unsigned long long ullOffset = ullBytesRead;
+
+ cmd.cdw12 = ullOffset & 0xFFFFFFFF;
+ cmd.cdw13 = (ullOffset >> 32) & 0xFFFFFFFF;
+ }
+
+ cmd.addr = (__u64) (uintptr_t) pTempPtr;
+ cmd.nsid = 0xFFFFFFFF;
+ cmd.data_len = uiXferDwords * 4;
+ err = nvme_submit_admin_passthru(nFD, &cmd, NULL);
+ ullBytesRead += uiXferDwords * 4;
+ pTempPtr = pBuffer + ullBytesRead;
+ }
+
+ return err;
+}
+
+static int NVMEResetLog(int nFD, unsigned char ucLogID, int nBufferSize,
+ long long llMaxSize)
+{
+ unsigned int *pBuffer = NULL;
+ int err = 0;
+
+ pBuffer = (unsigned int *)calloc(1, nBufferSize);
+ if (!pBuffer)
+ return err;
+
+ while (!err && llMaxSize > 0) {
+ err = NVMEGetLogPage(nFD, ucLogID, (unsigned char *)pBuffer, nBufferSize);
+ if (err) {
+ free(pBuffer);
+ return err;
+ }
+
+ if (pBuffer[0] == 0xdeadbeef)
+ break;
+
+ llMaxSize = llMaxSize - nBufferSize;
+ }
+
+ free(pBuffer);
+ return err;
+}
+
+static int GetCommonLogPage(int nFD, unsigned char ucLogID,
+ unsigned char **pBuffer, int nBuffSize)
+{
+ unsigned char *pTempPtr = NULL;
+ int err = 0;
+
+ pTempPtr = (unsigned char *)malloc(nBuffSize);
+ if (!pTempPtr)
+ goto exit_status;
+ memset(pTempPtr, 0, nBuffSize);
+ err = nvme_get_log_simple(nFD, ucLogID, nBuffSize, pTempPtr);
+ *pBuffer = pTempPtr;
+
+exit_status:
+ return err;
+}
+
+/*
+ * Plugin Commands
+ */
+static int micron_parse_options(struct nvme_dev **dev, int argc, char **argv,
+ const char *desc,
+ struct argconfig_commandline_options *opts,
+ enum eDriveModel *modelp)
+{
+ int idx;
+ int err = parse_and_open(dev, argc, argv, desc, opts);
+
+ if (err) {
+ perror("open");
+ return -1;
+ }
+
+ if (modelp) {
+ if (sscanf(argv[optind], "/dev/nvme%d", &idx) != 1)
+ idx = 0;
+ *modelp = GetDriveModel(idx);
+ }
+
+ return 0;
+}
+
+static int micron_fw_commit(int fd, int select)
+{
+ struct nvme_passthru_cmd cmd = {
+ .opcode = nvme_admin_fw_commit,
+ .cdw10 = 8,
+ .cdw12 = select,
+ };
+ return ioctl(fd, NVME_IOCTL_ADMIN_CMD, &cmd);
+}
+
+static int micron_selective_download(int argc, char **argv,
+ struct command *cmd, struct plugin *plugin)
+{
+ const char *desc =
+ "This performs a selective firmware download, which allows the user to\n"
+ "select which firmware binary to update for 9200 devices. This requires\n"
+ "a power cycle once the update completes. The options available are:\n\n"
+ "OOB - This updates the OOB and main firmware\n"
+ "EEP - This updates the eeprom and main firmware\n"
+ "ALL - This updates the eeprom, OOB, and main firmware";
+ const char *fw = "firmware file (required)";
+ const char *select = "FW Select (e.g., --select=ALL)";
+ int xfer = 4096;
+ void *fw_buf;
+ int selectNo, fw_fd, fw_size, err, offset = 0;
+ struct nvme_dev *dev;
+ struct stat sb;
+
+ struct config {
+ char *fw;
+ char *select;
+ };
+
+ struct config cfg = {
+ .fw = "",
+ .select = "\0",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_STRING("fw", 'f', "FILE", &cfg.fw, fw),
+ OPT_STRING("select", 's', "flag", &cfg.select, select),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ if (strlen(cfg.select) != 3) {
+ fprintf(stderr, "Invalid select flag\n");
+ dev_close(dev);
+ return -EINVAL;
+ }
+
+ for (int i = 0; i < 3; i++)
+ cfg.select[i] = toupper(cfg.select[i]);
+
+ if (!strncmp(cfg.select, "OOB", 3)) {
+ selectNo = 18;
+ } else if (!strncmp(cfg.select, "EEP", 3)) {
+ selectNo = 10;
+ } else if (!strncmp(cfg.select, "ALL", 3)) {
+ selectNo = 26;
+ } else {
+ fprintf(stderr, "Invalid select flag\n");
+ dev_close(dev);
+ return -EINVAL;
+ }
+
+ fw_fd = open(cfg.fw, O_RDONLY);
+ if (fw_fd < 0) {
+ fprintf(stderr, "no firmware file provided\n");
+ dev_close(dev);
+ return -EINVAL;
+ }
+
+ err = fstat(fw_fd, &sb);
+ if (err < 0) {
+ perror("fstat");
+ err = errno;
+ goto out;
+ }
+
+ fw_size = sb.st_size;
+ if (fw_size & 0x3) {
+ fprintf(stderr, "Invalid size:%d for f/w image\n", fw_size);
+ err = EINVAL;
+ goto out;
+ }
+
+ if (posix_memalign(&fw_buf, getpagesize(), fw_size)) {
+ fprintf(stderr, "No memory for f/w size:%d\n", fw_size);
+ err = ENOMEM;
+ goto out;
+ }
+
+ if (read(fw_fd, fw_buf, fw_size) != ((ssize_t) (fw_size))) {
+ err = errno;
+ goto out_free;
+ }
+
+ while (fw_size > 0) {
+ xfer = min(xfer, fw_size);
+
+ struct nvme_fw_download_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .offset = offset,
+ .data_len = xfer,
+ .data = fw_buf,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ };
+
+ err = nvme_fw_download(&args);
+ if (err < 0) {
+ perror("fw-download");
+ goto out_free;
+ } else if (err) {
+ nvme_show_status(err);
+ goto out_free;
+ }
+ fw_buf += xfer;
+ fw_size -= xfer;
+ offset += xfer;
+ }
+
+ err = micron_fw_commit(dev_fd(dev), selectNo);
+
+ if (err == 0x10B || err == 0x20B) {
+ err = 0;
+ fprintf(stderr,
+ "Update successful! Power cycle for changes to take effect\n");
+ }
+
+out_free:
+ free(fw_buf);
+out:
+ close(fw_fd);
+ dev_close(dev);
+ return err;
+}
+
+static int micron_smbus_option(int argc, char **argv,
+ struct command *cmd, struct plugin *plugin)
+{
+ __u32 result = 0;
+ __u32 cdw11 = 0;
+ const char *desc = "Enable/Disable/Get status of SMBUS option on controller";
+ const char *option = "enable or disable or status";
+ const char *value =
+ "1 - hottest component temperature, 0 - composite temperature (default) for enable option, 0 (current), 1 (default), 2 (saved) for status options";
+ const char *save = "1 - persistent, 0 - non-persistent (default)";
+ int fid = MICRON_FEATURE_SMBUS_OPTION;
+ enum eDriveModel model = UNKNOWN_MODEL;
+ struct nvme_dev *dev;
+ int err = 0;
+
+ struct {
+ char *option;
+ int value;
+ int save;
+ int status;
+ } opt = {
+ .option = "disable",
+ .value = 0,
+ .save = 0,
+ .status = 0,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_STRING("option", 'o', "option", &opt.option, option),
+ OPT_UINT("value", 'v', &opt.value, value),
+ OPT_UINT("save", 's', &opt.save, save),
+ OPT_END()
+ };
+
+ err = micron_parse_options(&dev, argc, argv, desc, opts, &model);
+ if (err < 0)
+ return err;
+
+ if (model != M5407 && model != M5411) {
+ printf("This option is not supported for specified drive\n");
+ dev_close(dev);
+ return err;
+ }
+
+ if (!strcmp(opt.option, "enable")) {
+ cdw11 = opt.value << 1 | 1;
+ err = nvme_set_features_simple(dev_fd(dev), fid, 1, cdw11, opt.save,
+ &result);
+ if (!err)
+ printf("successfully enabled SMBus on drive\n");
+ else
+ printf("Failed to enabled SMBus on drive\n");
+ } else if (!strcmp(opt.option, "status")) {
+ struct nvme_get_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = fid,
+ .nsid = 1,
+ .sel = opt.value,
+ .cdw11 = 0,
+ .uuidx = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ err = nvme_get_features(&args);
+ if (!err)
+ printf("SMBus status on the drive: %s (returns %s temperature)\n",
+ (result & 1) ? "enabled" : "disabled",
+ (result & 2) ? "hottest component" : "composite");
+ else
+ printf("Failed to retrieve SMBus status on the drive\n");
+ } else if (!strcmp(opt.option, "disable")) {
+ cdw11 = opt.value << 1 | 0;
+ err = nvme_set_features_simple(dev_fd(dev), fid, 1, cdw11, opt.save,
+ &result);
+ if (!err)
+ printf("Successfully disabled SMBus on drive\n");
+ else
+ printf("Failed to disable SMBus on drive\n");
+ } else {
+ printf("Invalid option %s, valid values are enable, disable or status\n",
+ opt.option);
+ dev_close(dev);
+ return -1;
+ }
+
+ close(dev_fd(dev));
+ return err;
+}
+
+static int micron_temp_stats(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+
+ struct nvme_smart_log smart_log;
+ unsigned int temperature = 0, i = 0, err = 0;
+ unsigned int tempSensors[SensorCount] = { 0 };
+ const char *desc = "Retrieve Micron temperature info for the given device ";
+ const char *fmt = "output format normal|json";
+ struct format {
+ char *fmt;
+ };
+ struct format cfg = {
+ .fmt = "normal",
+ };
+ bool is_json = false;
+ struct json_object *root;
+ struct json_object *logPages;
+ struct nvme_dev *dev;
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("format", 'f', &cfg.fmt, fmt),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err) {
+ printf("\nDevice not found\n");
+ return -1;
+ }
+
+ if (!strcmp(cfg.fmt, "json"))
+ is_json = true;
+
+ err = nvme_get_log_smart(dev_fd(dev), 0xffffffff, false, &smart_log);
+ if (!err) {
+ temperature = ((smart_log.temperature[1] << 8) | smart_log.temperature[0]);
+ temperature = temperature ? temperature - 273 : 0;
+ for (i = 0; i < SensorCount && tempSensors[i]; i++) {
+ tempSensors[i] = le16_to_cpu(smart_log.temp_sensor[i]);
+ tempSensors[i] = tempSensors[i] ? tempSensors[i] - 273 : 0;
+ }
+ if (is_json) {
+ struct json_object *stats = json_create_object();
+ char tempstr[64] = { 0 };
+
+ root = json_create_object();
+ logPages = json_create_array();
+ json_object_add_value_array(root, "Micron temperature information", logPages);
+ sprintf(tempstr, "%u C", temperature);
+ json_object_add_value_string(stats, "Current Composite Temperature", tempstr);
+ for (i = 0; i < SensorCount && tempSensors[i]; i++) {
+ char sensor_str[256] = { 0 };
+ char datastr[64] = { 0 };
+
+ sprintf(sensor_str, "Temperature Sensor #%d", (i + 1));
+ sprintf(datastr, "%u C", tempSensors[i]);
+ json_object_add_value_string(stats, sensor_str, datastr);
+ }
+ json_array_add_value_object(logPages, stats);
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+ } else {
+ printf("Micron temperature information:\n");
+ printf("%-10s : %u C\n", "Current Composite Temperature", temperature);
+ for (i = 0; i < SensorCount && tempSensors[i]; i++)
+ printf("%-10s%d : %u C\n", "Temperature Sensor #", i + 1, tempSensors[i]);
+ }
+ }
+ dev_close(dev);
+ return err;
+}
+
+static int micron_pcie_stats(int argc, char **argv,
+ struct command *cmd, struct plugin *plugin)
+{
+ int i, err = 0, bus, domain, device, function, ctrlIdx;
+ char strTempFile[1024], strTempFile2[1024], command[1024];
+ struct nvme_dev *dev;
+ char *businfo = NULL;
+ char *devicename = NULL;
+ char tdevice[NAME_MAX] = { 0 };
+ ssize_t sLinkSize = 0;
+ FILE *fp;
+ char correctable[8] = { 0 };
+ char uncorrectable[8] = { 0 };
+ struct nvme_passthru_cmd admin_cmd = { 0 };
+ enum eDriveModel eModel = UNKNOWN_MODEL;
+ char *res;
+ bool is_json = true;
+ bool counters = false;
+ struct format {
+ char *fmt;
+ };
+ const char *desc = "Retrieve PCIe event counters";
+ const char *fmt = "output format json|normal";
+ struct format cfg = {
+ .fmt = "json",
+ };
+ struct pcie_error_counters {
+ __u16 receiver_error;
+ __u16 bad_tlp;
+ __u16 bad_dllp;
+ __u16 replay_num_rollover;
+ __u16 replay_timer_timeout;
+ __u16 advisory_non_fatal_error;
+ __u16 DLPES;
+ __u16 poisoned_tlp;
+ __u16 FCPC;
+ __u16 completion_timeout;
+ __u16 completion_abort;
+ __u16 unexpected_completion;
+ __u16 receiver_overflow;
+ __u16 malformed_tlp;
+ __u16 ecrc_error;
+ __u16 unsupported_request_error;
+ } pcie_error_counters = { 0 };
+
+ struct {
+ char *err;
+ int bit;
+ int val;
+ } pcie_correctable_errors[] = {
+ { "Unsupported Request Error Status (URES)", 20,
+ offsetof(struct pcie_error_counters, unsupported_request_error)},
+ { "ECRC Error Status (ECRCES)", 19,
+ offsetof(struct pcie_error_counters, ecrc_error)},
+ { "Malformed TLP Status (MTS)", 18,
+ offsetof(struct pcie_error_counters, malformed_tlp)},
+ { "Receiver Overflow Status (ROS)", 17,
+ offsetof(struct pcie_error_counters, receiver_overflow)},
+ { "Unexpected Completion Status (UCS)", 16,
+ offsetof(struct pcie_error_counters, unexpected_completion)},
+ { "Completer Abort Status (CAS)", 15,
+ offsetof(struct pcie_error_counters, completion_abort)},
+ { "Completion Timeout Status (CTS)", 14,
+ offsetof(struct pcie_error_counters, completion_timeout)},
+ { "Flow Control Protocol Error Status (FCPES)", 13,
+ offsetof(struct pcie_error_counters, FCPC)},
+ { "Poisoned TLP Status (PTS)", 12,
+ offsetof(struct pcie_error_counters, poisoned_tlp)},
+ { "Data Link Protocol Error Status (DLPES)", 4,
+ offsetof(struct pcie_error_counters, DLPES)},
+ },
+ pcie_uncorrectable_errors[] = {
+ { "Advisory Non-Fatal Error Status (ANFES)", 13,
+ offsetof(struct pcie_error_counters, advisory_non_fatal_error)},
+ { "Replay Timer Timeout Status (RTS)", 12,
+ offsetof(struct pcie_error_counters, replay_timer_timeout)},
+ { "REPLAY_NUM Rollover Status (RRS)", 8,
+ offsetof(struct pcie_error_counters, replay_num_rollover)},
+ { "Bad DLLP Status (BDS)", 7,
+ offsetof(struct pcie_error_counters, bad_dllp)},
+ { "Bad TLP Status (BTS)", 6,
+ offsetof(struct pcie_error_counters, bad_tlp)},
+ { "Receiver Error Status (RES)", 0,
+ offsetof(struct pcie_error_counters, receiver_error)},
+ };
+
+ __u32 correctable_errors;
+ __u32 uncorrectable_errors;
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("format", 'f', &cfg.fmt, fmt),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err) {
+ printf("\nDevice not found\n");
+ return -1;
+ }
+
+ /* pull log details based on the model name */
+ if (sscanf(argv[optind], "/dev/nvme%d", &ctrlIdx) != 1)
+ ctrlIdx = 0;
+ eModel = GetDriveModel(ctrlIdx);
+ if (eModel == UNKNOWN_MODEL) {
+ printf("Unsupported drive model for vs-pcie-stats command\n");
+ goto out;
+ }
+
+ if (!strcmp(cfg.fmt, "normal"))
+ is_json = false;
+
+ if (eModel == M5407) {
+ admin_cmd.opcode = 0xD6;
+ admin_cmd.addr = (__u64)(uintptr_t)&pcie_error_counters;
+ admin_cmd.data_len = sizeof(pcie_error_counters);
+ admin_cmd.cdw10 = 1;
+ err = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd, NULL);
+ if (!err) {
+ counters = true;
+ correctable_errors = 10;
+ uncorrectable_errors = 6;
+ goto print_stats;
+ }
+ }
+
+ if (strstr(argv[optind], "/dev/nvme") && strstr(argv[optind], "n1")) {
+ devicename = strrchr(argv[optind], '/');
+ } else if (strstr(argv[optind], "/dev/nvme")) {
+ devicename = strrchr(argv[optind], '/');
+ sprintf(tdevice, "%s%s", devicename, "n1");
+ devicename = tdevice;
+ } else {
+ printf("Invalid device specified!\n");
+ goto out;
+ }
+ sprintf(strTempFile, "/sys/block/%s/device", devicename);
+ memset(strTempFile2, 0x0, 1024);
+ sLinkSize = readlink(strTempFile, strTempFile2, 1023);
+ if (sLinkSize < 0) {
+ err = -errno;
+ printf("Failed to read device\n");
+ goto out;
+ }
+ if (strstr(strTempFile2, "../../nvme")) {
+ sprintf(strTempFile, "/sys/block/%s/device/device", devicename);
+ memset(strTempFile2, 0x0, 1024);
+ sLinkSize = readlink(strTempFile, strTempFile2, 1023);
+ if (sLinkSize < 0) {
+ err = -errno;
+ printf("Failed to read device\n");
+ goto out;
+ }
+ }
+ businfo = strrchr(strTempFile2, '/');
+ if (sscanf(businfo, "/%x:%x:%x.%x", &domain, &bus, &device, &function) != 4)
+ domain = bus = device = function = 0;
+ sprintf(command, "setpci -s %x:%x.%x ECAP_AER+10.L", bus, device,
+ function);
+ fp = popen(command, "r");
+ if (!fp) {
+ printf("Failed to retrieve error count\n");
+ goto out;
+ }
+ res = fgets(correctable, sizeof(correctable), fp);
+ if (!res) {
+ printf("Failed to retrieve error count\n");
+ pclose(fp);
+ goto out;
+ }
+ pclose(fp);
+
+ sprintf(command, "setpci -s %x:%x.%x ECAP_AER+0x4.L", bus, device,
+ function);
+ fp = popen(command, "r");
+ if (!fp) {
+ printf("Failed to retrieve error count\n");
+ goto out;
+ }
+ res = fgets(uncorrectable, sizeof(uncorrectable), fp);
+ if (!res) {
+ printf("Failed to retrieve error count\n");
+ pclose(fp);
+ goto out;
+ }
+ pclose(fp);
+
+ correctable_errors = (__u32)strtol(correctable, NULL, 16);
+ uncorrectable_errors = (__u32)strtol(uncorrectable, NULL, 16);
+
+print_stats:
+ if (is_json) {
+ struct json_object *root = json_create_object();
+ struct json_object *pcieErrors = json_create_array();
+ struct json_object *stats = json_create_object();
+ __u8 *pcounter = (__u8 *)&pcie_error_counters;
+
+ json_object_add_value_array(root, "PCIE Stats", pcieErrors);
+ for (i = 0; i < ARRAY_SIZE(pcie_correctable_errors); i++) {
+ __u16 val = counters ? *(__u16 *)(pcounter + pcie_correctable_errors[i].val) :
+ (correctable_errors >> pcie_correctable_errors[i].bit) & 1;
+ json_object_add_value_int(stats, pcie_correctable_errors[i].err, val);
+ }
+ for (i = 0; i < ARRAY_SIZE(pcie_uncorrectable_errors); i++) {
+ __u16 val = counters ? *(__u16 *)(pcounter + pcie_uncorrectable_errors[i].val) :
+ (uncorrectable_errors >> pcie_uncorrectable_errors[i].bit) & 1;
+ json_object_add_value_int(stats, pcie_uncorrectable_errors[i].err, val);
+ }
+ json_array_add_value_object(pcieErrors, stats);
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+ } else if (counters == true) {
+ __u8 *pcounter = (__u8 *)&pcie_error_counters;
+
+ for (i = 0; i < ARRAY_SIZE(pcie_correctable_errors); i++)
+ printf("%-42s : %-1hu\n", pcie_correctable_errors[i].err,
+ *(__u16 *)(pcounter + pcie_correctable_errors[i].val));
+ for (i = 0; i < ARRAY_SIZE(pcie_uncorrectable_errors); i++)
+ printf("%-42s : %-1hu\n", pcie_uncorrectable_errors[i].err,
+ *(__u16 *)(pcounter + pcie_uncorrectable_errors[i].val));
+ } else if (eModel == M5407 || eModel == M5410) {
+ for (i = 0; i < ARRAY_SIZE(pcie_correctable_errors); i++)
+ printf("%-42s : %-1d\n", pcie_correctable_errors[i].err,
+ ((correctable_errors >> pcie_correctable_errors[i].bit) & 1));
+ for (i = 0; i < ARRAY_SIZE(pcie_uncorrectable_errors); i++)
+ printf("%-42s : %-1d\n", pcie_uncorrectable_errors[i].err,
+ ((uncorrectable_errors >> pcie_uncorrectable_errors[i].bit) & 1));
+ } else {
+ printf("PCIE Stats:\n");
+ printf("Device correctable errors detected: %s\n", correctable);
+ printf("Device uncorrectable errors detected: %s\n", uncorrectable);
+ }
+
+out:
+ dev_close(dev);
+ return err;
+}
+
+static int micron_clear_pcie_correctable_errors(int argc, char **argv,
+ struct command *cmd,
+ struct plugin *plugin)
+{
+ int err = -EINVAL, bus, domain, device, function;
+ char strTempFile[1024], strTempFile2[1024], command[1024];
+ struct nvme_dev *dev;
+ char *businfo = NULL;
+ char *devicename = NULL;
+ char tdevice[PATH_MAX] = { 0 };
+ ssize_t sLinkSize = 0;
+ enum eDriveModel model = UNKNOWN_MODEL;
+ struct nvme_passthru_cmd admin_cmd = { 0 };
+ char correctable[8] = { 0 };
+ FILE *fp;
+ char *res;
+ const char *desc = "Clear PCIe Device Correctable Errors";
+ __u32 result = 0;
+ __u8 fid = MICRON_FEATURE_CLEAR_PCI_CORRECTABLE_ERRORS;
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ err = micron_parse_options(&dev, argc, argv, desc, opts, &model);
+ if (err < 0)
+ return err;
+
+ /* For M51CX models, PCIe errors are cleared using 0xC3 feature */
+ if (model == M51CX) {
+ err = nvme_set_features_simple(dev_fd(dev), fid, 0, (1 << 31), false,
+ &result);
+ if (!err)
+ err = (int)result;
+ if (!err) {
+ printf("Device correctable errors are cleared!\n");
+ goto out;
+ }
+ } else if (model == M5407) {
+ admin_cmd.opcode = 0xD6;
+ admin_cmd.addr = 0;
+ admin_cmd.cdw10 = 0;
+ err = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd, NULL);
+ if (!err) {
+ printf("Device correctable error counters are cleared!\n");
+ goto out;
+ } else {
+ /* proceed to clear status bits using sysfs interface */
+ }
+ }
+
+ if (strstr(argv[optind], "/dev/nvme") && strstr(argv[optind], "n1")) {
+ devicename = strrchr(argv[optind], '/');
+ } else if (strstr(argv[optind], "/dev/nvme")) {
+ devicename = strrchr(argv[optind], '/');
+ sprintf(tdevice, "%s%s", devicename, "n1");
+ devicename = tdevice;
+ } else {
+ printf("Invalid device specified!\n");
+ goto out;
+ }
+ err = snprintf(strTempFile, sizeof(strTempFile),
+ "/sys/block/%s/device", devicename);
+ if (err < 0)
+ goto out;
+
+ memset(strTempFile2, 0x0, 1024);
+ sLinkSize = readlink(strTempFile, strTempFile2, 1023);
+ if (sLinkSize < 0) {
+ err = -errno;
+ printf("Failed to read device\n");
+ goto out;
+ }
+ if (strstr(strTempFile2, "../../nvme")) {
+ err = snprintf(strTempFile, sizeof(strTempFile),
+ "/sys/block/%s/device/device", devicename);
+ if (err < 0)
+ goto out;
+ memset(strTempFile2, 0x0, 1024);
+ sLinkSize = readlink(strTempFile, strTempFile2, 1023);
+ if (sLinkSize < 0) {
+ err = -errno;
+ printf("Failed to read device\n");
+ goto out;
+ }
+ }
+ businfo = strrchr(strTempFile2, '/');
+ if (sscanf(businfo, "/%x:%x:%x.%x", &domain, &bus, &device, &function) != 4)
+ domain = bus = device = function = 0;
+ sprintf(command, "setpci -s %x:%x.%x ECAP_AER+0x10.L=0xffffffff", bus,
+ device, function);
+ err = -1;
+ fp = popen(command, "r");
+ if (!fp) {
+ printf("Failed to clear error count\n");
+ goto out;
+ }
+ pclose(fp);
+
+ sprintf(command, "setpci -s %x:%x.%x ECAP_AER+0x10.L", bus, device,
+ function);
+ fp = popen(command, "r");
+ if (!fp) {
+ printf("Failed to retrieve error count\n");
+ goto out;
+ }
+ res = fgets(correctable, sizeof(correctable), fp);
+ if (!res) {
+ printf("Failed to retrieve error count\n");
+ pclose(fp);
+ goto out;
+ }
+ pclose(fp);
+ printf("Device correctable errors cleared!\n");
+ printf("Device correctable errors detected: %s\n", correctable);
+ err = 0;
+out:
+ dev_close(dev);
+ return err;
+}
+
+static struct logpage {
+ const char *field;
+ char datastr[128];
+} d0_log_page[] = {
+ { "NAND Writes (Bytes Written)", { 0 }},
+ { "Program Failure Count", { 0 }},
+ { "Erase Failures", { 0 }},
+ { "Bad Block Count", { 0 }},
+ { "NAND XOR/RAID Recovery Trigger Events", { 0 }},
+ { "NSZE Change Supported", { 0 }},
+ { "Number of NSZE Modifications", { 0 }}
+};
+
+static void init_d0_log_page(__u8 *buf, __u8 nsze)
+{
+ unsigned int logD0[D0_log_size/sizeof(int)] = { 0 };
+ __u64 count_lo, count_hi, count;
+
+ memcpy(logD0, buf, sizeof(logD0));
+
+
+ count = ((__u64)logD0[45] << 32) | logD0[44];
+ sprintf(d0_log_page[0].datastr, "0x%"PRIx64, le64_to_cpu(count));
+
+ count_hi = ((__u64)logD0[39] << 32) | logD0[38];
+ count_lo = ((__u64)logD0[37] << 32) | logD0[36];
+ if (count_hi)
+ sprintf(d0_log_page[1].datastr, "0x%"PRIx64"%016"PRIx64,
+ le64_to_cpu(count_hi), le64_to_cpu(count_lo));
+ else
+ sprintf(d0_log_page[1].datastr, "0x%"PRIx64, le64_to_cpu(count_lo));
+
+ count = ((__u64)logD0[25] << 32) | logD0[24];
+ sprintf(d0_log_page[2].datastr, "0x%"PRIx64, le64_to_cpu(count));
+
+ sprintf(d0_log_page[3].datastr, "0x%x", logD0[3]);
+
+ count_lo = ((__u64)logD0[37] << 32) | logD0[36];
+ count = ((__u64)logD0[25] << 32) | logD0[24];
+ count = (__u64)logD0[3] - (count_lo + count);
+ sprintf(d0_log_page[4].datastr, "0x%"PRIx64, le64_to_cpu(count));
+
+ sprintf(d0_log_page[5].datastr, "0x%x", nsze);
+ sprintf(d0_log_page[6].datastr, "0x%x", logD0[1]);
+}
+
+/* OCP and Vendor specific log data format */
+struct micron_vs_logpage {
+ char *field;
+ int size; /* FB client spec version 1.0 sizes - M5410 models */
+ int size2; /* FB client spec version 0.7 sizes - M5407 models */
+}
+/* Smart Health Log information as per OCP spec M51CX models */
+ocp_c0_log_page[] = {
+ { "Physical Media Units Written", 16},
+ { "Physical Media Units Read", 16 },
+ { "Raw Bad User NAND Block Count", 6},
+ { "Normalized Bad User NAND Block Count", 2},
+ { "Raw Bad System NAND Block Count", 6},
+ { "Normalized Bad System NAND Block Count", 2},
+ { "XOR Recovery Count", 8},
+ { "Uncorrectable Read Error Count", 8},
+ { "Soft ECC Error Count", 8},
+ { "SSD End to End Detected Counts", 4},
+ { "SSD End to End Corrected Errors", 4},
+ { "System data % life-used", 1},
+ { "Refresh Count", 7},
+ { "Maximum User Data Erase Count", 4},
+ { "Minimum User Data Erase Count", 4},
+ { "Thermal Throttling Count", 1},
+ { "Thermal Throttling Status", 1},
+ { "Reserved", 6},
+ { "PCIe Correctable Error count", 8},
+ { "Incomplete Shutdowns", 4},
+ { "Reserved", 4},
+ { "% Free Blocks", 1},
+ { "Reserved", 7},
+ { "Capacitor Health", 2},
+ { "Reserved", 6},
+ { "Unaligned I/O", 8},
+ { "Security Version Number", 8},
+ { "NUSE", 8},
+ { "PLP Start Count", 16},
+ { "Endurance Estimate", 16},
+ { "Reserved", 302},
+ { "Log Page Version", 2},
+ { "Log Page GUID", 16},
+},
+/* Extended SMART log information */
+e1_log_page[] = {
+ { "Reserved", 12},
+ { "Grown Bad Block Count", 4},
+ { "Per Block Max Erase Count", 4},
+ { "Power On Minutes", 4},
+ { "Reserved", 24},
+ { "Write Protect Reason", 4},
+ { "Reserved", 12},
+ { "Drive Capacity", 8},
+ { "Reserved", 8},
+ { "Total Erase Count", 8},
+ { "Lifetime Use Rate", 8},
+ { "Erase Fail Count", 8},
+ { "Reserved", 8},
+ { "Reported UC Errors", 8},
+ { "Reserved", 24},
+ { "Program Fail Count", 16},
+ { "Total Bytes Read", 16},
+ { "Total Bytes Written", 16},
+ { "Reserved", 16},
+ { "TU Size", 4},
+ { "Total Block Stripe Count", 4},
+ { "Free Block Stripe Count", 4},
+ { "Block Stripe Size", 8},
+ { "Reserved", 16},
+ { "User Block Min Erase Count", 4},
+ { "User Block Avg Erase Count", 4},
+ { "User Block Max Erase Count", 4},
+},
+/* Vendor Specific Health Log information */
+fb_log_page[] = {
+ { "Physical Media Units Written - TLC", 16, 16 },
+ { "Physical Media Units Written - SLC", 16, 16 },
+ { "Normalized Bad User NAND Block Count", 2, 2},
+ { "Raw Bad User NAND Block Count", 6, 6},
+ { "XOR Recovery Count", 8, 8},
+ { "Uncorrectable Read Error Count", 8, 8},
+ { "SSD End to End Corrected Errors", 8, 8},
+ { "SSD End to End Detected Counts", 4, 8},
+ { "SSD End to End Uncorrected Counts", 4, 8},
+ { "System data % life-used", 1, 1},
+ { "Reserved", 0, 3},
+ { "Minimum User Data Erase Count - TLC", 8, 8},
+ { "Maximum User Data Erase Count - TLC", 8, 8},
+ { "Average User Data Erase Count - TLC", 0, 8},
+ { "Minimum User Data Erase Count - SLC", 8, 8},
+ { "Maximum User Data Erase Count - SLC", 8, 8},
+ { "Average User Data Erase Count - SLC", 0, 8},
+ { "Normalized Program Fail Count", 2, 2},
+ { "Raw Program Fail Count", 6, 6},
+ { "Normalized Erase Fail Count", 2, 2},
+ { "Raw Erase Fail Count", 6, 6},
+ { "Pcie Correctable Error Count", 8, 8},
+ { "% Free Blocks (User)", 1, 1},
+ { "Reserved", 0, 3},
+ { "Security Version Number", 8, 8},
+ { "% Free Blocks (System)", 1, 1},
+ { "Reserved", 0, 3},
+ { "Dataset Management (Deallocate) Commands", 16, 16},
+ { "Incomplete TRIM Data", 8, 8},
+ { "% Age of Completed TRIM", 1, 2},
+ { "Background Back-Pressure Gauge", 1, 1},
+ { "Reserved", 0, 3},
+ { "Soft ECC Error Count", 8, 8},
+ { "Refresh Count", 8, 8},
+ { "Normalized Bad System NAND Block Count", 2, 2},
+ { "Raw Bad System NAND Block Count", 6, 6},
+ { "Endurance Estimate", 16, 16},
+ { "Thermal Throttling Status", 1, 1},
+ { "Thermal Throttling Count", 1, 1},
+ { "Unaligned I/O", 8, 8},
+ { "Physical Media Units Read", 16, 16},
+ { "Reserved", 279, 0},
+ { "Log Page Version", 2, 0},
+ { "READ CMDs exceeding threshold", 0, 4},
+ { "WRITE CMDs exceeding threshold", 0, 4},
+ { "TRIMs CMDs exceeding threshold", 0, 4},
+ { "Reserved", 0, 4},
+ { "Reserved", 0, 210},
+ { "Log Page Version", 0, 2},
+ { "Log Page GUID", 0, 16},
+};
+
+/*
+ * Common function to print Micron VS log pages
+ * - buf: raw log data
+ * - log_page: format of the data
+ * - field_count: log field count
+ * - stats: json object to add fields
+ * - spec: ocp spec index
+ */
+static void print_micron_vs_logs(__u8 *buf, struct micron_vs_logpage *log_page, int field_count,
+ struct json_object *stats, __u8 spec)
+{
+ __u64 lval_lo, lval_hi;
+ __u32 ival;
+ __u16 sval;
+ __u8 cval, lval[8] = { 0 };
+ int field;
+ int offset = 0;
+
+ for (field = 0; field < field_count; field++) {
+ char datastr[1024] = { 0 };
+ char *sfield = NULL;
+ int size = !spec ? log_page[field].size : log_page[field].size2;
+
+ if (!size)
+ continue;
+ sfield = log_page[field].field;
+ if (size == 16) {
+ if (strstr(sfield, "GUID")) {
+ sprintf(datastr, "0x%"PRIx64"%"PRIx64"",
+ (uint64_t)le64_to_cpu(*(uint64_t *)(&buf[offset + 8])),
+ (uint64_t)le64_to_cpu(*(uint64_t *)(&buf[offset])));
+ } else {
+ lval_lo = *((__u64 *)(&buf[offset]));
+ lval_hi = *((__u64 *)(&buf[offset + 8]));
+ if (lval_hi)
+ sprintf(datastr, "0x%"PRIx64"%016"PRIx64"",
+ le64_to_cpu(lval_hi), le64_to_cpu(lval_lo));
+ else
+ sprintf(datastr, "0x%"PRIx64"", le64_to_cpu(lval_lo));
+ }
+ } else if (size == 8) {
+ lval_lo = *((__u64 *)(&buf[offset]));
+ sprintf(datastr, "0x%"PRIx64"", le64_to_cpu(lval_lo));
+ } else if (size == 7) {
+ /* 7 bytes will be in little-endian format, with last byte as MSB */
+ memcpy(&lval[0], &buf[offset], 7);
+ memcpy((void *)&lval_lo, lval, 8);
+ sprintf(datastr, "0x%"PRIx64"", le64_to_cpu(lval_lo));
+ } else if (size == 6) {
+ ival = *((__u32 *)(&buf[offset]));
+ sval = *((__u16 *)(&buf[offset + 4]));
+ lval_lo = (((__u64)sval << 32) | ival);
+ sprintf(datastr, "0x%"PRIx64"", le64_to_cpu(lval_lo));
+ } else if (size == 4) {
+ ival = *((__u32 *)(&buf[offset]));
+ sprintf(datastr, "0x%x", le32_to_cpu(ival));
+ } else if (size == 2) {
+ sval = *((__u16 *)(&buf[offset]));
+ sprintf(datastr, "0x%04x", le16_to_cpu(sval));
+ } else if (size == 1) {
+ cval = buf[offset];
+ sprintf(datastr, "0x%02x", cval);
+ } else {
+ sprintf(datastr, "0");
+ }
+ offset += size;
+ /* do not print reserved values */
+ if (strstr(sfield, "Reserved"))
+ continue;
+ if (stats)
+ json_object_add_value_string(stats, sfield, datastr);
+ else
+ printf("%-40s : %-4s\n", sfield, datastr);
+ }
+}
+
+static void print_smart_cloud_health_log(__u8 *buf, bool is_json)
+{
+ struct json_object *root;
+ struct json_object *logPages;
+ struct json_object *stats = NULL;
+ int field_count = ARRAY_SIZE(ocp_c0_log_page);
+
+ if (is_json) {
+ root = json_create_object();
+ stats = json_create_object();
+ logPages = json_create_array();
+ json_object_add_value_array(root, "OCP SMART Cloud Health Log: 0xC0",
+ logPages);
+ }
+
+ print_micron_vs_logs(buf, ocp_c0_log_page, field_count, stats, 0);
+
+ if (is_json) {
+ json_array_add_value_object(logPages, stats);
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+ }
+}
+
+static void print_nand_stats_fb(__u8 *buf, __u8 *buf2, __u8 nsze, bool is_json, __u8 spec)
+{
+ struct json_object *root;
+ struct json_object *logPages;
+ struct json_object *stats = NULL;
+ int field_count = ARRAY_SIZE(fb_log_page);
+
+ if (is_json) {
+ root = json_create_object();
+ stats = json_create_object();
+ logPages = json_create_array();
+ json_object_add_value_array(root, "Extended Smart Log Page : 0xFB",
+ logPages);
+ }
+
+ print_micron_vs_logs(buf, fb_log_page, field_count, stats, spec);
+
+ /* print last three entries from D0 log page */
+ if (buf2) {
+ init_d0_log_page(buf2, nsze);
+
+ if (is_json) {
+ for (int i = 0; i < 7; i++)
+ json_object_add_value_string(stats,
+ d0_log_page[i].field,
+ d0_log_page[i].datastr);
+ } else {
+ for (int i = 0; i < 7; i++)
+ printf("%-40s : %s\n", d0_log_page[i].field, d0_log_page[i].datastr);
+ }
+ }
+
+ if (is_json) {
+ json_array_add_value_object(logPages, stats);
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+ }
+}
+
+static void print_nand_stats_d0(__u8 *buf, __u8 oacs, bool is_json)
+{
+ init_d0_log_page(buf, oacs);
+
+ if (is_json) {
+ struct json_object *root = json_create_object();
+ struct json_object *stats = json_create_object();
+ struct json_object *logPages = json_create_array();
+
+ json_object_add_value_array(root,
+ "Extended Smart Log Page : 0xD0",
+ logPages);
+
+ for (int i = 0; i < 7; i++)
+ json_object_add_value_string(stats,
+ d0_log_page[i].field,
+ d0_log_page[i].datastr);
+
+ json_array_add_value_object(logPages, stats);
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+ } else {
+ for (int i = 0; i < 7; i++)
+ printf("%-40s : %s\n", d0_log_page[i].field, d0_log_page[i].datastr);
+ }
+}
+
+static bool nsze_from_oacs; /* read nsze for now from idd[4059] */
+
+static int micron_nand_stats(int argc, char **argv,
+ struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Retrieve Micron NAND stats for the given device ";
+ unsigned int extSmartLog[D0_log_size/sizeof(int)] = { 0 };
+ unsigned int logFB[FB_log_size/sizeof(int)] = { 0 };
+ enum eDriveModel eModel = UNKNOWN_MODEL;
+ struct nvme_id_ctrl ctrl;
+ struct nvme_dev *dev;
+ int err, ctrlIdx;
+ __u8 nsze;
+ bool has_d0_log = true;
+ bool has_fb_log = false;
+ bool is_json = true;
+ struct format {
+ char *fmt;
+ };
+ const char *fmt = "output format json|normal";
+ struct format cfg = {
+ .fmt = "json",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("format", 'f', &cfg.fmt, fmt),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err) {
+ printf("\nDevice not found\n");
+ return -1;
+ }
+
+ if (!strcmp(cfg.fmt, "normal"))
+ is_json = false;
+
+ err = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (err) {
+ printf("Error %d retrieving controller identification data\n", err);
+ goto out;
+ }
+
+ /* pull log details based on the model name */
+ if (sscanf(argv[optind], "/dev/nvme%d", &ctrlIdx) != 1)
+ ctrlIdx = 0;
+ eModel = GetDriveModel(ctrlIdx);
+ if ((eModel == UNKNOWN_MODEL) || (eModel == M51CX)) {
+ printf("Unsupported drive model for vs-nand-stats command\n");
+ err = -1;
+ goto out;
+ }
+
+ err = nvme_get_log_simple(dev_fd(dev), 0xD0, D0_log_size, extSmartLog);
+ has_d0_log = !err;
+
+ /* should check for firmware version if this log is supported or not */
+ if (eModel == M5407 || eModel == M5410) {
+ err = nvme_get_log_simple(dev_fd(dev), 0xFB, FB_log_size, logFB);
+ has_fb_log = !err;
+ }
+
+ nsze = (ctrl.vs[987] == 0x12);
+ if (!nsze && nsze_from_oacs)
+ nsze = ((ctrl.oacs >> 3) & 0x1);
+ err = 0;
+ if (has_fb_log) {
+ __u8 spec = (eModel == M5410) ? 0 : 1; /* FB spec version */
+
+ print_nand_stats_fb((__u8 *)logFB, (__u8 *)extSmartLog, nsze, is_json, spec);
+ } else if (has_d0_log) {
+ print_nand_stats_d0((__u8 *)extSmartLog, nsze, is_json);
+ } else {
+ printf("Unable to retrieve extended smart log for the drive\n");
+ err = -ENOTTY;
+ }
+out:
+ dev_close(dev);
+ if (err > 0)
+ nvme_show_status(err);
+
+ return err;
+}
+
+static void print_ext_smart_logs_e1(__u8 *buf, bool is_json)
+{
+ struct json_object *root;
+ struct json_object *logPages;
+ struct json_object *stats = NULL;
+ int field_count = ARRAY_SIZE(e1_log_page);
+
+ if (is_json) {
+ root = json_create_object();
+ stats = json_create_object();
+ logPages = json_create_array();
+ json_object_add_value_array(root, "SMART Extended Log:0xE1", logPages);
+ } else {
+ printf("SMART Extended Log:0xE1\n");
+ }
+
+ print_micron_vs_logs(buf, e1_log_page, field_count, stats, 0);
+
+ if (is_json) {
+ json_array_add_value_object(logPages, stats);
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+ }
+}
+
+static int micron_smart_ext_log(int argc, char **argv,
+ struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Retrieve extended SMART logs for the given device ";
+ unsigned int extSmartLog[E1_log_size/sizeof(int)] = { 0 };
+ enum eDriveModel eModel = UNKNOWN_MODEL;
+ int err = 0, ctrlIdx;
+ struct nvme_dev *dev;
+ bool is_json = true;
+ struct format {
+ char *fmt;
+ };
+ const char *fmt = "output format json|normal";
+ struct format cfg = {
+ .fmt = "json",
+ };
+ OPT_ARGS(opts) = {
+ OPT_FMT("format", 'f', &cfg.fmt, fmt),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err) {
+ printf("\nDevice not found\n");
+ return -1;
+ }
+ if (!strcmp(cfg.fmt, "normal"))
+ is_json = false;
+
+ if (sscanf(argv[optind], "/dev/nvme%d", &ctrlIdx) != 1)
+ ctrlIdx = 0;
+ eModel = GetDriveModel(ctrlIdx);
+ if (eModel != M51CX) {
+ printf("Unsupported drive model for vs-smart-ext-log command\n");
+ err = -1;
+ goto out;
+ }
+ err = nvme_get_log_simple(dev_fd(dev), 0xE1, E1_log_size, extSmartLog);
+ if (!err)
+ print_ext_smart_logs_e1((__u8 *)extSmartLog, is_json);
+
+out:
+ dev_close(dev);
+ if (err > 0)
+ nvme_show_status(err);
+ return err;
+}
+
+static void GetDriveInfo(const char *strOSDirName, int nFD,
+ struct nvme_id_ctrl *ctrlp)
+{
+ FILE *fpOutFile = NULL;
+ char tempFile[256] = { 0 };
+ char strBuffer[1024] = { 0 };
+ char model[41] = { 0 };
+ char serial[21] = { 0 };
+ char fwrev[9] = { 0 };
+ char *strPDir = strdup(strOSDirName);
+ char *strDest = dirname(strPDir);
+
+ sprintf(tempFile, "%s/%s", strDest, "drive-info.txt");
+ fpOutFile = fopen(tempFile, "w+");
+ if (!fpOutFile) {
+ printf("Failed to create %s\n", tempFile);
+ free(strPDir);
+ return;
+ }
+
+ strncpy(model, ctrlp->mn, 40);
+ strncpy(serial, ctrlp->sn, 20);
+ strncpy(fwrev, ctrlp->fr, 8);
+
+ sprintf(strBuffer,
+ "********************\nDrive Info\n********************\n");
+
+ fprintf(fpOutFile, "%s", strBuffer);
+ sprintf(strBuffer,
+ "%-20s : /dev/nvme%d\n%-20s : %s\n%-20s : %-20s\n%-20s : %-20s\n",
+ "Device Name", nFD,
+ "Model No", (char *)model,
+ "Serial No", (char *)serial, "FW-Rev", (char *)fwrev);
+
+ fprintf(fpOutFile, "%s", strBuffer);
+
+ sprintf(strBuffer,
+ "\n********************\nPCI Info\n********************\n");
+
+ fprintf(fpOutFile, "%s", strBuffer);
+
+ sprintf(strBuffer,
+ "%-22s : %04X\n%-22s : %04X\n",
+ "VendorId", vendor_id, "DeviceId", device_id);
+ fprintf(fpOutFile, "%s", strBuffer);
+ fclose(fpOutFile);
+ free(strPDir);
+}
+
+static void GetTimestampInfo(const char *strOSDirName)
+{
+ __u8 outstr[1024];
+ time_t t;
+ struct tm *tmp;
+ size_t num;
+ char *strPDir;
+ char *strDest;
+
+ t = time(NULL);
+ tmp = localtime(&t);
+ if (!tmp)
+ return;
+
+ num = strftime((char *)outstr, sizeof(outstr),
+ "Timestamp (UTC): %a, %d %b %Y %T %z", tmp);
+ num += sprintf((char *)(outstr + num), "\nPackage Version: 1.4");
+ if (num) {
+ strPDir = strdup(strOSDirName);
+ strDest = dirname(strPDir);
+ WriteData(outstr, num, strDest, "timestamp_info.txt", "timestamp");
+ free(strPDir);
+ }
+}
+
+static void GetCtrlIDDInfo(const char *dir, struct nvme_id_ctrl *ctrlp)
+{
+ WriteData((__u8 *)ctrlp, sizeof(*ctrlp), dir,
+ "nvme_controller_identify_data.bin", "id-ctrl");
+}
+
+static void GetSmartlogData(int fd, const char *dir)
+{
+ struct nvme_smart_log smart_log;
+
+ if (!nvme_get_log_smart(fd, -1, false, &smart_log))
+ WriteData((__u8 *)&smart_log, sizeof(smart_log), dir,
+ "smart_data.bin", "smart log");
+}
+
+static void GetErrorlogData(int fd, int entries, const char *dir)
+{
+ int logSize = entries * sizeof(struct nvme_error_log_page);
+ struct nvme_error_log_page *error_log =
+ (struct nvme_error_log_page *)calloc(1, logSize);
+
+ if (!error_log)
+ return;
+
+ if (!nvme_get_log_error(fd, entries, false, error_log))
+ WriteData((__u8 *)error_log, logSize, dir,
+ "error_information_log.bin", "error log");
+
+ free(error_log);
+}
+
+static void GetGenericLogs(int fd, const char *dir)
+{
+ struct nvme_self_test_log self_test_log;
+ struct nvme_firmware_slot fw_log;
+ struct nvme_cmd_effects_log effects;
+ struct nvme_persistent_event_log pevent_log;
+ _cleanup_huge_ struct nvme_mem_huge mh = { 0, };
+ void *pevent_log_info = NULL;
+ __u32 log_len = 0;
+ int err = 0;
+
+ /* get self test log */
+ if (!nvme_get_log_device_self_test(fd, &self_test_log))
+ WriteData((__u8 *)&self_test_log, sizeof(self_test_log), dir,
+ "drive_self_test.bin", "self test log");
+
+ /* get fw slot info log */
+ if (!nvme_get_log_fw_slot(fd, false, &fw_log))
+ WriteData((__u8 *)&fw_log, sizeof(fw_log), dir,
+ "firmware_slot_info_log.bin", "firmware log");
+
+ /* get effects log */
+ if (!nvme_get_log_cmd_effects(fd, NVME_CSI_NVM, &effects))
+ WriteData((__u8 *)&effects, sizeof(effects), dir,
+ "command_effects_log.bin", "effects log");
+
+ /* get persistent event log */
+ (void)nvme_get_log_persistent_event(fd, NVME_PEVENT_LOG_RELEASE_CTX,
+ sizeof(pevent_log), &pevent_log);
+ memset(&pevent_log, 0, sizeof(pevent_log));
+ err = nvme_get_log_persistent_event(fd, NVME_PEVENT_LOG_EST_CTX_AND_READ,
+ sizeof(pevent_log), &pevent_log);
+ if (err) {
+ fprintf(stderr, "Setting persistent event log read ctx failed (ignored)!\n");
+ return;
+ }
+
+ log_len = le64_to_cpu(pevent_log.tll);
+ pevent_log_info = nvme_alloc_huge(log_len, &mh);
+ if (!pevent_log_info) {
+ perror("could not alloc buffer for persistent event log page (ignored)!\n");
+ return;
+ }
+
+ err = nvme_get_log_persistent_event(fd, NVME_PEVENT_LOG_READ,
+ log_len, pevent_log_info);
+ if (!err)
+ WriteData((__u8 *)pevent_log_info, log_len, dir,
+ "persistent_event_log.bin", "persistent event log");
+}
+
+static void GetNSIDDInfo(int fd, const char *dir, int nsid)
+{
+ char file[PATH_MAX] = { 0 };
+ struct nvme_id_ns ns;
+
+ if (!nvme_identify_ns(fd, nsid, &ns)) {
+ sprintf(file, "identify_namespace_%d_data.bin", nsid);
+ WriteData((__u8 *)&ns, sizeof(ns), dir, file, "id-ns");
+ }
+}
+
+static void GetOSConfig(const char *strOSDirName)
+{
+ FILE *fpOSConfig = NULL;
+ char strBuffer[1024];
+ char strFileName[PATH_MAX];
+ int i;
+
+ struct {
+ char *strcmdHeader;
+ char *strCommand;
+ } cmdArray[] = {
+ { (char *)"SYSTEM INFORMATION", (char *)"uname -a >> %s" },
+ { (char *)"LINUX KERNEL MODULE INFORMATION", (char *)"lsmod >> %s" },
+ { (char *)"LINUX SYSTEM MEMORY INFORMATION", (char *)"cat /proc/meminfo >> %s" },
+ { (char *)"SYSTEM INTERRUPT INFORMATION", (char *)"cat /proc/interrupts >> %s" },
+ { (char *)"CPU INFORMATION", (char *)"cat /proc/cpuinfo >> %s" },
+ { (char *)"IO MEMORY MAP INFORMATION", (char *)"cat /proc/iomem >> %s" },
+ { (char *)"MAJOR NUMBER AND DEVICE GROUP", (char *)"cat /proc/devices >> %s" },
+ { (char *)"KERNEL DMESG", (char *)"dmesg >> %s" },
+ { (char *)"/VAR/LOG/MESSAGES", (char *)"cat /var/log/messages >> %s" }
+ };
+
+ sprintf(strFileName, "%s/%s", strOSDirName, "os_config.txt");
+
+ for (i = 0; i < 7; i++) {
+ fpOSConfig = fopen(strFileName, "a+");
+ if (fpOSConfig) {
+ fprintf(fpOSConfig,
+ "\n\n\n\n%s\n-----------------------------------------------\n",
+ cmdArray[i].strcmdHeader);
+ fclose(fpOSConfig);
+ fpOSConfig = NULL;
+ }
+ snprintf(strBuffer, sizeof(strBuffer) - 1,
+ cmdArray[i].strCommand, strFileName);
+ if (system(strBuffer))
+ fprintf(stderr, "Failed to send \"%s\"\n", strBuffer);
+ }
+}
+
+static int micron_telemetry_log(int fd, __u8 type, __u8 **data,
+ int *logSize, int da)
+{
+ int err, bs = 512, offset = bs;
+ unsigned short data_area[4];
+ unsigned char ctrl_init = (type == 0x8);
+
+ __u8 *buffer = (unsigned char *)calloc(bs, 1);
+
+ if (!buffer)
+ return -1;
+ if (ctrl_init)
+ err = nvme_get_log_telemetry_ctrl(fd, true, 0, bs, buffer);
+ else
+ err = nvme_get_log_telemetry_host(fd, 0, bs, buffer);
+ if (err) {
+ fprintf(stderr, "Failed to get telemetry log header for 0x%X\n", type);
+ if (buffer)
+ free(buffer);
+ return err;
+ }
+
+ /* compute size of the log */
+ data_area[1] = buffer[9] << 8 | buffer[8];
+ data_area[2] = buffer[11] << 8 | buffer[10];
+ data_area[3] = buffer[13] << 8 | buffer[12];
+ data_area[0] = data_area[1] > data_area[2] ? data_area[1] : data_area[2];
+ data_area[0] = data_area[3] > data_area[0] ? data_area[3] : data_area[0];
+
+ if (!data_area[da]) {
+ fprintf(stderr, "Requested telemetry data for 0x%X is empty\n", type);
+ if (buffer) {
+ free(buffer);
+ buffer = NULL;
+ }
+ return -1;
+ }
+
+ *logSize = data_area[da] * bs;
+ offset = bs;
+ err = 0;
+ buffer = (unsigned char *)realloc(buffer, (size_t)(*logSize));
+ if (buffer) {
+ while (!err && offset != *logSize) {
+ if (ctrl_init)
+ err = nvme_get_log_telemetry_ctrl(fd, true, 0, *logSize, buffer + offset);
+ else
+ err = nvme_get_log_telemetry_host(fd, 0, *logSize, buffer + offset);
+ offset += bs;
+ }
+ }
+
+ if (!err && buffer) {
+ *data = buffer;
+ } else {
+ fprintf(stderr, "Failed to get telemetry data for 0x%x\n", type);
+ if (buffer)
+ free(buffer);
+ }
+
+ return err;
+}
+
+static int GetTelemetryData(int fd, const char *dir)
+{
+ unsigned char *buffer = NULL;
+ int i, err, logSize = 0;
+ char msg[256] = { 0 };
+ struct {
+ __u8 log;
+ char *file;
+ } tmap[] = {
+ {0x07, "nvmetelemetrylog.bin"},
+ {0x08, "nvmetelemetrylog.bin"},
+ };
+
+ for (i = 0; i < (int)(ARRAY_SIZE(tmap)); i++) {
+ err = micron_telemetry_log(fd, tmap[i].log, &buffer, &logSize, 0);
+ if (!err && logSize > 0 && buffer) {
+ sprintf(msg, "telemetry log: 0x%X", tmap[i].log);
+ WriteData(buffer, logSize, dir, tmap[i].file, msg);
+ }
+ if (buffer) {
+ free(buffer);
+ buffer = NULL;
+ }
+ logSize = 0;
+ }
+ return err;
+}
+
+static int GetFeatureSettings(int fd, const char *dir)
+{
+ unsigned char *bufp, buf[4096] = { 0 };
+ int i, err, len, errcnt = 0;
+ __u32 attrVal = 0;
+ char msg[256] = { 0 };
+
+ struct features {
+ int id;
+ char *file;
+ } fmap[] = {
+ {0x01, "nvme_feature_setting_arbitration.bin"},
+ {0x02, "nvme_feature_setting_pm.bin"},
+ {0x03, "nvme_feature_setting_lba_range_namespace_1.bin"},
+ {0x04, "nvme_feature_setting_temp_threshold.bin"},
+ {0x05, "nvme_feature_setting_error_recovery.bin"},
+ {0x06, "nvme_feature_setting_volatile_write_cache.bin"},
+ {0x07, "nvme_feature_setting_num_queues.bin"},
+ {0x08, "nvme_feature_setting_interrupt_coalescing.bin"},
+ {0x09, "nvme_feature_setting_interrupt_vec_config.bin"},
+ {0x0A, "nvme_feature_setting_write_atomicity.bin"},
+ {0x0B, "nvme_feature_setting_async_event_config.bin"},
+ {0x80, "nvme_feature_setting_sw_progress_marker.bin"},
+ };
+
+ for (i = 0; i < (int)(ARRAY_SIZE(fmap)); i++) {
+ if (fmap[i].id == 0x03) {
+ len = 4096;
+ bufp = (unsigned char *)(&buf[0]);
+ } else {
+ len = 0;
+ bufp = NULL;
+ }
+
+ struct nvme_get_features_args args = {
+ .args_size = sizeof(args),
+ .fd = fd,
+ .fid = fmap[i].id,
+ .nsid = 1,
+ .sel = 0,
+ .cdw11 = 0x0,
+ .uuidx = 0,
+ .data_len = len,
+ .data = bufp,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &attrVal,
+ };
+ err = nvme_get_features(&args);
+ if (!err) {
+ sprintf(msg, "feature: 0x%X", fmap[i].id);
+ WriteData((__u8 *)&attrVal, sizeof(attrVal), dir, fmap[i].file, msg);
+ if (bufp)
+ WriteData(bufp, len, dir, fmap[i].file, msg);
+ } else {
+ fprintf(stderr, "Feature 0x%x data not retrieved, error %d (ignored)!\n",
+ fmap[i].id, err);
+ errcnt++;
+ }
+ }
+ return (int)(errcnt == ARRAY_SIZE(fmap));
+}
+
+static int micron_drive_info(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ const char *desc = "Get drive HW information";
+ struct nvme_id_ctrl ctrl = { 0 };
+ struct nvme_passthru_cmd admin_cmd = { 0 };
+ struct fb_drive_info {
+ unsigned char hw_ver_major;
+ unsigned char hw_ver_minor;
+ unsigned char ftl_unit_size;
+ unsigned char bs_ver_major;
+ unsigned char bs_ver_minor;
+ } dinfo = { 0 };
+ enum eDriveModel model = UNKNOWN_MODEL;
+ bool is_json = false;
+ struct json_object *root, *driveInfo;
+ struct nvme_dev *dev;
+ struct format {
+ char *fmt;
+ };
+ int err = 0;
+
+ const char *fmt = "output format normal";
+ struct format cfg = {
+ .fmt = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("format", 'f', &cfg.fmt, fmt),
+ OPT_END()
+ };
+
+ err = micron_parse_options(&dev, argc, argv, desc, opts, &model);
+ if (err < 0)
+ return err;
+
+ if (model == UNKNOWN_MODEL) {
+ fprintf(stderr, "ERROR : Unsupported drive for vs-drive-info cmd");
+ dev_close(dev);
+ return -1;
+ }
+
+ if (!strcmp(cfg.fmt, "json"))
+ is_json = true;
+
+ if (model == M5407) {
+ admin_cmd.opcode = 0xD4,
+ admin_cmd.addr = (__u64) (uintptr_t) &dinfo;
+ admin_cmd.data_len = (__u32)sizeof(dinfo);
+ admin_cmd.cdw12 = 3;
+ err = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd, NULL);
+ if (err) {
+ fprintf(stderr, "ERROR : drive-info opcode failed with 0x%x\n", err);
+ dev_close(dev);
+ return -1;
+ }
+ } else {
+ err = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (err) {
+ fprintf(stderr, "ERROR : identify_ctrl() failed with 0x%x\n", err);
+ dev_close(dev);
+ return -1;
+ }
+ dinfo.hw_ver_major = ctrl.vs[820];
+ dinfo.hw_ver_minor = ctrl.vs[821];
+ dinfo.ftl_unit_size = ctrl.vs[822];
+ }
+
+ if (is_json) {
+ struct json_object *pinfo = json_create_object();
+ char tempstr[64] = { 0 };
+
+ root = json_create_object();
+ driveInfo = json_create_array();
+ json_object_add_value_array(root, "Micron Drive HW Information", driveInfo);
+ sprintf(tempstr, "%u.%u", dinfo.hw_ver_major, dinfo.hw_ver_minor);
+ json_object_add_value_string(pinfo, "Drive Hardware Version", tempstr);
+
+ if (dinfo.ftl_unit_size) {
+ sprintf(tempstr, "%u KB", dinfo.ftl_unit_size);
+ json_object_add_value_string(pinfo, "FTL_unit_size", tempstr);
+ }
+
+ if (dinfo.bs_ver_major || dinfo.bs_ver_minor) {
+ sprintf(tempstr, "%u.%u", dinfo.bs_ver_major, dinfo.bs_ver_minor);
+ json_object_add_value_string(pinfo, "Boot Spec.Version", tempstr);
+ }
+
+ json_array_add_value_object(driveInfo, pinfo);
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+ } else {
+ printf("Drive Hardware Version: %u.%u\n",
+ dinfo.hw_ver_major, dinfo.hw_ver_minor);
+
+ if (dinfo.ftl_unit_size)
+ printf("FTL_unit_size: %u KB\n", dinfo.ftl_unit_size);
+
+ if (dinfo.bs_ver_major || dinfo.bs_ver_minor)
+ printf("Boot Spec.Version: %u.%u\n",
+ dinfo.bs_ver_major, dinfo.bs_ver_minor);
+ }
+
+ dev_close(dev);
+ return 0;
+}
+
+static int micron_cloud_ssd_plugin_version(int argc, char **argv,
+ struct command *cmd, struct plugin *plugin)
+{
+ printf("nvme-cli Micron cloud SSD plugin version: %s.%s\n",
+ __version_major, __version_minor);
+ return 0;
+}
+
+static int micron_plugin_version(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ printf("nvme-cli Micron plugin version: %s.%s.%s\n",
+ __version_major, __version_minor, __version_patch);
+ return 0;
+}
+
+/* Binary format of firmware activation history entry */
+struct __packed fw_activation_history_entry {
+ __u8 version;
+ __u8 length;
+ __u16 rsvd1;
+ __le16 valid;
+ __le64 power_on_hour;
+ __le64 rsvd2;
+ __le64 power_cycle_count;
+ __u8 previous_fw[8];
+ __u8 activated_fw[8];
+ __u8 slot;
+ __u8 commit_action_type;
+ __le16 result;
+ __u8 rsvd3[14];
+};
+
+/* Binary format for firmware activation history table */
+struct __packed micron_fw_activation_history_table {
+ __u8 log_page;
+ __u8 rsvd1[3];
+ __le32 num_entries;
+ struct fw_activation_history_entry entries[20];
+ __u8 rsvd2[2790];
+ __u16 version;
+ __u8 GUID[16];
+};
+
+static int display_fw_activate_entry(int entry_count, struct fw_activation_history_entry *entry,
+ char *formatted_entry, struct json_object *stats)
+{
+ time_t timestamp, hours;
+ char buffer[32];
+ __u8 minutes, seconds;
+ static const char * const ca[] = {"000b", "001b", "010b", "011b"};
+ char *ptr = formatted_entry;
+ int index = 0, entry_size = 82;
+
+ if ((entry->version != 1 && entry->version != 2) || entry->length != 64)
+ return -EINVAL;
+
+ sprintf(ptr, "%d", entry_count);
+ ptr += 10;
+
+ timestamp = (le64_to_cpu(entry->power_on_hour) & 0x0000FFFFFFFFFFFFUL) / 1000;
+ hours = timestamp / 3600;
+ minutes = (timestamp % 3600) / 60;
+ seconds = (timestamp % 3600) % 60;
+ sprintf(ptr, "|%"PRIu64":%u:%u", (uint64_t)hours, minutes, seconds);
+ ptr += 12;
+
+ sprintf(ptr, "| %"PRIu64, le64_to_cpu(entry->power_cycle_count));
+ ptr += 10;
+
+ /* firmware details */
+ memset(buffer, 0, sizeof(buffer));
+ memcpy(buffer, entry->previous_fw, sizeof(entry->previous_fw));
+ sprintf(ptr, "| %s", buffer);
+ ptr += 11;
+
+ memset(buffer, 0, sizeof(buffer));
+ memcpy(buffer, entry->activated_fw, sizeof(entry->activated_fw));
+ sprintf(ptr, "| %s", buffer);
+ ptr += 12;
+
+ /* firmware slot and commit action*/
+ sprintf(ptr, "| %d", entry->slot);
+ ptr += 9;
+
+ if (entry->commit_action_type <= 3)
+ sprintf(ptr, "| %s", ca[entry->commit_action_type]);
+ else
+ sprintf(ptr, "| xxxb");
+ ptr += 9;
+
+ /* result */
+ if (entry->result)
+ sprintf(ptr, "| Fail #%d", entry->result);
+ else
+ sprintf(ptr, "| pass");
+
+ /* replace all null characters with spaces */
+ ptr = formatted_entry;
+ while (index < entry_size) {
+ if (ptr[index] == '\0')
+ ptr[index] = ' ';
+ index++;
+ }
+ return 0;
+}
+
+static void micron_fw_activation_history_header_print(void)
+{
+ /* header to be printed field widths = 10 | 12 | 10 | 11 | 12 | 9 | 9 | 9 */
+ printf("__________________________________________________________________________________\n");
+ printf(" | | | | | | |\n");
+ printf("Firmware | Power On | Power | Previous | New FW | Slot | Commit | Result\n");
+ printf("Activation| Hour | cycle | firmware | activated | number | Action |\n");
+ printf("Counter | | count | | | | Type |\n");
+ printf("__________|___________|_________|__________|___________|________|________|________\n");
+}
+
+static int micron_fw_activation_history(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ const char *desc = "Retrieve Firmware Activation history of the given drive";
+ char formatted_output[100];
+ int count = 0;
+ unsigned int logC2[C2_log_size/sizeof(int)] = { 0 };
+ enum eDriveModel eModel = UNKNOWN_MODEL;
+ struct nvme_dev *dev;
+ struct format {
+ char *fmt;
+ };
+ int err;
+
+ const char *fmt = "output format normal";
+ struct format cfg = {
+ .fmt = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("format", 'f', &cfg.fmt, fmt),
+ OPT_END()
+ };
+
+ err = micron_parse_options(&dev, argc, argv, desc, opts, &eModel);
+ if (err < 0)
+ return -1;
+
+ if (strcmp(cfg.fmt, "normal")) {
+ fprintf(stderr, "only normal format is supported currently\n");
+ dev_close(dev);
+ return -1;
+ }
+
+ /* check if product supports fw_history log */
+ err = -EINVAL;
+ if (eModel != M51CX) {
+ fprintf(stderr, "Unsupported drive model for vs-fw-activate-history command\n");
+ goto out;
+ }
+
+ err = nvme_get_log_simple(dev_fd(dev), 0xC2, C2_log_size, logC2);
+ if (err) {
+ fprintf(stderr, "Failed to retrieve fw activation history log, error: %x\n", err);
+ goto out;
+ }
+
+ /* check if we have at least one entry to print */
+ struct micron_fw_activation_history_table *table =
+ (struct micron_fw_activation_history_table *)logC2;
+
+ /* check version and log page */
+ if (table->log_page != 0xC2 || (table->version != 2 && table->version != 1)) {
+ fprintf(stderr, "Unsupported fw activation history page: %x, version: %x\n",
+ table->log_page, table->version);
+ goto out;
+ }
+
+ if (!table->num_entries) {
+ fprintf(stderr, "No entries were found in fw activation history log\n");
+ goto out;
+ }
+
+ micron_fw_activation_history_header_print();
+ for (count = 0; count < table->num_entries; count++) {
+ memset(formatted_output, '\0', 100);
+ if (!display_fw_activate_entry(count, &table->entries[count], formatted_output,
+ NULL))
+ printf("%s\n", formatted_output);
+ }
+out:
+ dev_close(dev);
+ return err;
+}
+
+#define MICRON_FID_LATENCY_MONITOR 0xD0
+#define MICRON_LOG_LATENCY_MONITOR 0xD1
+
+static int micron_latency_stats_track(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ int err = 0;
+ __u32 result = 0;
+ const char *desc = "Enable, Disable or Get cmd latency monitoring stats";
+ const char *option = "enable or disable or status, default is status";
+ const char *command =
+ "commands to monitor for - all|read|write|trim, default is all i.e, enabled for all commands";
+ const char *thrtime =
+ "The threshold value to use for latency monitoring in milliseconds, default is 800ms";
+
+ int fid = MICRON_FID_LATENCY_MONITOR;
+ enum eDriveModel model = UNKNOWN_MODEL;
+ uint32_t command_mask = 0x7; /* 1:read 2:write 4:trim 7:all */
+ uint32_t timing_mask = 0x08080800; /* R[31-24]:W[23:16]:T[15:8]:0 */
+ uint32_t enable = 2;
+ struct nvme_dev *dev;
+ struct {
+ char *option;
+ char *command;
+ uint32_t threshold;
+ } opt = {
+ .option = "status",
+ .command = "all",
+ .threshold = 0
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_STRING("option", 'o', "option", &opt.option, option),
+ OPT_STRING("command", 'c', "command", &opt.command, command),
+ OPT_UINT("threshold", 't', &opt.threshold, thrtime),
+ OPT_END()
+ };
+
+
+ err = micron_parse_options(&dev, argc, argv, desc, opts, &model);
+ if (err < 0)
+ return -1;
+
+ if (!strcmp(opt.option, "enable")) {
+ enable = 1;
+ } else if (!strcmp(opt.option, "disable")) {
+ enable = 0;
+ } else if (strcmp(opt.option, "status")) {
+ printf("Invalid control option %s specified\n", opt.option);
+ dev_close(dev);
+ return -1;
+ }
+
+ struct nvme_get_features_args g_args = {
+ .args_size = sizeof(g_args),
+ .fd = dev_fd(dev),
+ .fid = fid,
+ .nsid = 0,
+ .sel = 0,
+ .cdw11 = 0,
+ .uuidx = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ err = nvme_get_features(&g_args);
+ if (err) {
+ printf("Failed to retrieve latency monitoring feature status\n");
+ dev_close(dev);
+ return err;
+ }
+
+ /* If it is to retrieve the status only */
+ if (enable == 2) {
+ printf("Latency Tracking Statistics is currently %s",
+ (result & 0xFFFF0000) ? "enabled" : "disabled");
+ if ((result & 7) == 7) {
+ printf(" for All commands\n");
+ } else if ((result & 7) > 0) {
+ printf(" for");
+ if (result & 1)
+ printf(" Read");
+ if (result & 2)
+ printf(" Write");
+ if (result & 4)
+ printf(" Trim");
+ printf(" commands\n");
+ } else if (!result) {
+ printf("\n");
+ }
+ dev_close(dev);
+ return err;
+ }
+
+ /* read and validate threshold values if enable option is specified */
+ if (enable == 1) {
+ if (opt.threshold > 2550) {
+ printf("The maximum threshold value cannot be more than 2550 ms\n");
+ dev_close(dev);
+ return -1;
+ } else if (opt.threshold % 10) {
+ /* timing mask is in terms of 10ms units, so min allowed is 10ms */
+ printf("The threshold value should be multiple of 10 ms\n");
+ dev_close(dev);
+ return -1;
+ }
+ opt.threshold /= 10;
+ }
+
+ /* read-in command(s) to be monitored */
+ if (!strcmp(opt.command, "read")) {
+ command_mask = 0x1;
+ timing_mask = (opt.threshold << 24);
+ } else if (!strcmp(opt.command, "write")) {
+ command_mask = 0x2;
+ timing_mask = (opt.threshold << 16);
+ } else if (!strcmp(opt.command, "trim")) {
+ command_mask = 0x4;
+ timing_mask = (opt.threshold << 8);
+ } else if (strcmp(opt.command, "all")) {
+ printf("Invalid command %s specified for option %s\n",
+ opt.command, opt.option);
+ dev_close(dev);
+ return -1;
+ }
+
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = MICRON_FID_LATENCY_MONITOR,
+ .nsid = 0,
+ .cdw11 = enable,
+ .cdw12 = command_mask,
+ .save = 1,
+ .uuidx = 0,
+ .cdw13 = timing_mask,
+ .cdw15 = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+ err = nvme_set_features(&args);
+ if (!err) {
+ printf("Successfully %sd latency monitoring for %s commands with %dms threshold\n",
+ opt.option, opt.command, !opt.threshold ? 800 : opt.threshold * 10);
+ } else {
+ printf("Failed to %s latency monitoring for %s commands with %dms threshold\n",
+ opt.option, opt.command, !opt.threshold ? 800 : opt.threshold * 10);
+ }
+
+ dev_close(dev);
+ return err;
+}
+
+
+static int micron_latency_stats_logs(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+#define LATENCY_LOG_ENTRIES 16
+ struct latency_log_entry {
+ uint64_t timestamp;
+ uint32_t latency;
+ uint32_t cmdtag;
+ union {
+ struct {
+ uint32_t opcode:8;
+ uint32_t fuse:2;
+ uint32_t rsvd1:4;
+ uint32_t psdt:2;
+ uint32_t cid:16;
+ };
+ uint32_t dw0;
+ };
+ uint32_t nsid;
+ uint32_t slba_low;
+ uint32_t slba_high;
+ union {
+ struct {
+ uint32_t nlb:16;
+ uint32_t rsvd2:9;
+ uint32_t deac:1;
+ uint32_t prinfo:4;
+ uint32_t fua:1;
+ uint32_t lr:1;
+ };
+ uint32_t dw12;
+ };
+ uint32_t dsm;
+ uint32_t rfu[6];
+ } log[LATENCY_LOG_ENTRIES];
+ enum eDriveModel model = UNKNOWN_MODEL;
+ struct nvme_dev *dev;
+ int err = -1;
+ const char *desc = "Display Latency tracking log information";
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ err = micron_parse_options(&dev, argc, argv, desc, opts, &model);
+ if (err)
+ return err;
+ memset(&log, 0, sizeof(log));
+ err = nvme_get_log_simple(dev_fd(dev), 0xD1, sizeof(log), &log);
+ if (err) {
+ if (err < 0)
+ printf("Unable to retrieve latency stats log the drive\n");
+ dev_close(dev);
+ return err;
+ }
+ /* print header and each log entry */
+ printf("Timestamp, Latency, CmdTag, Opcode, Fuse, Psdt, Cid, Nsid, Slba_L, Slba_H, Nlb, ");
+ printf("DEAC, PRINFO, FUA, LR\n");
+ for (int i = 0; i < LATENCY_LOG_ENTRIES; i++)
+ printf("%"PRIu64",%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u\n",
+ log[i].timestamp, log[i].latency, log[i].cmdtag, log[i].opcode,
+ log[i].fuse, log[i].psdt, log[i].cid, log[i].nsid,
+ log[i].slba_low, log[i].slba_high, log[i].nlb,
+ log[i].deac, log[i].prinfo, log[i].fua, log[i].lr);
+ printf("\n");
+ dev_close(dev);
+ return err;
+}
+
+static int micron_latency_stats_info(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ const char *desc = "display command latency statistics";
+ const char *command = "command to display stats - all|read|write|trimdefault is all";
+ int err = 0;
+ struct nvme_dev *dev;
+ enum eDriveModel model = UNKNOWN_MODEL;
+ #define LATENCY_BUCKET_COUNT 32
+ #define LATENCY_BUCKET_RSVD 32
+ struct micron_latency_stats {
+ uint64_t version; /* major << 32 | minior */
+ uint64_t all_cmds[LATENCY_BUCKET_COUNT + LATENCY_BUCKET_RSVD];
+ uint64_t read_cmds[LATENCY_BUCKET_COUNT + LATENCY_BUCKET_RSVD];
+ uint64_t write_cmds[LATENCY_BUCKET_COUNT + LATENCY_BUCKET_RSVD];
+ uint64_t trim_cmds[LATENCY_BUCKET_COUNT + LATENCY_BUCKET_RSVD];
+ uint32_t reserved[255]; /* round up to 4K */
+ } log;
+
+ struct latency_thresholds {
+ uint32_t start;
+ uint32_t end;
+ char *unit;
+ } thresholds[LATENCY_BUCKET_COUNT] = {
+ {0, 50, "us"}, {50, 100, "us"}, {100, 150, "us"}, {150, 200, "us"},
+ {200, 300, "us"}, {300, 400, "us"}, {400, 500, "us"}, {500, 600, "us"},
+ {600, 700, "us"}, {700, 800, "us"}, {800, 900, "us"}, {900, 1000, "us"},
+ {1, 5, "ms"}, {5, 10, "ms"}, {10, 20, "ms"}, {20, 50, "ms"}, {50, 100, "ms"},
+ {100, 200, "ms"}, {200, 300, "ms"}, {300, 400, "ms"}, {400, 500, "ms"},
+ {500, 600, "ms"}, {600, 700, "ms"}, {700, 800, "ms"}, {800, 900, "ms"},
+ {900, 1000, "ms"}, {1, 2, "s"}, {2, 3, "s"}, {3, 4, "s"}, {4, 5, "s"},
+ {5, 8, "s"},
+ {8, INT_MAX, "s"},
+ };
+
+ struct {
+ char *command;
+ } opt = {
+ .command = "all"
+ };
+
+ uint64_t *cmd_stats = &log.all_cmds[0];
+ char *cmd_str = "All";
+
+ OPT_ARGS(opts) = {
+ OPT_STRING("command", 'c', "command", &opt.command, command),
+ OPT_END()
+ };
+
+ err = micron_parse_options(&dev, argc, argv, desc, opts, &model);
+ if (err < 0)
+ return err;
+ if (!strcmp(opt.command, "read")) {
+ cmd_stats = &log.read_cmds[0];
+ cmd_str = "Read";
+ } else if (!strcmp(opt.command, "write")) {
+ cmd_stats = &log.write_cmds[0];
+ cmd_str = "Write";
+ } else if (!strcmp(opt.command, "trim")) {
+ cmd_stats = &log.trim_cmds[0];
+ cmd_str = "Trim";
+ } else if (strcmp(opt.command, "all")) {
+ printf("Invalid command option %s to display latency stats\n", opt.command);
+ dev_close(dev);
+ return -1;
+ }
+
+ memset(&log, 0, sizeof(log));
+ err = nvme_get_log_simple(dev_fd(dev), 0xD0, sizeof(log), &log);
+ if (err) {
+ if (err < 0)
+ printf("Unable to retrieve latency stats log the drive\n");
+ dev_close(dev);
+ return err;
+ }
+ printf("Micron IO %s Command Latency Statistics\n"
+ "Major Revision : %d\nMinor Revision : %d\n",
+ cmd_str, (int)(log.version >> 32), (int)(log.version & 0xFFFFFFFF));
+ printf("=============================================\n");
+ printf("Bucket Start End Command Count\n");
+ printf("=============================================\n");
+
+ for (int b = 0; b < LATENCY_BUCKET_COUNT; b++) {
+ int bucket = b + 1;
+ char start[32] = { 0 };
+ char end[32] = { 0 };
+
+ sprintf(start, "%u%s", thresholds[b].start, thresholds[b].unit);
+ if (thresholds[b].end == INT_MAX)
+ sprintf(end, "INF");
+ else
+ sprintf(end, "%u%s", thresholds[b].end, thresholds[b].unit);
+ printf("%2d %8s %8s %8"PRIu64"\n", bucket, start, end, cmd_stats[b]);
+ }
+ dev_close(dev);
+ return err;
+}
+
+static int micron_ocp_smart_health_logs(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ const char *desc = "Retrieve Smart or Extended Smart Health log for the given device ";
+ unsigned int logC0[C0_log_size/sizeof(int)] = { 0 };
+ unsigned int logFB[FB_log_size/sizeof(int)] = { 0 };
+ struct nvme_id_ctrl ctrl;
+ enum eDriveModel eModel = UNKNOWN_MODEL;
+ struct nvme_dev *dev;
+ bool is_json = true;
+ struct format {
+ char *fmt;
+ };
+ const char *fmt = "output format normal|json";
+ struct format cfg = {
+ .fmt = "json",
+ };
+ int err = 0;
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("format", 'f', &cfg.fmt, fmt),
+ OPT_END()
+ };
+
+ err = micron_parse_options(&dev, argc, argv, desc, opts, &eModel);
+ if (err < 0)
+ return -1;
+
+ if (!strcmp(cfg.fmt, "normal"))
+ is_json = false;
+
+ /* For M5410 and M5407, this option prints 0xFB log page */
+ if (eModel == M5410 || eModel == M5407) {
+ __u8 spec = (eModel == M5410) ? 0 : 1;
+ __u8 nsze;
+
+ err = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (!err)
+ err = nvme_get_log_simple(dev_fd(dev), 0xFB, FB_log_size, logFB);
+ if (err) {
+ if (err < 0)
+ printf("Unable to retrieve smart log 0xFB for the drive\n");
+ goto out;
+ }
+
+ nsze = (ctrl.vs[987] == 0x12);
+ if (!nsze && nsze_from_oacs)
+ nsze = ((ctrl.oacs >> 3) & 0x1);
+ print_nand_stats_fb((__u8 *)logFB, NULL, nsze, is_json, spec);
+ goto out;
+ }
+
+ /* check for models that support 0xC0 log */
+ if (eModel != M51CX) {
+ printf("Unsupported drive model for vs-smart-add-log command\n");
+ err = -1;
+ goto out;
+ }
+
+ err = nvme_get_log_simple(dev_fd(dev), 0xC0, C0_log_size, logC0);
+ if (!err)
+ print_smart_cloud_health_log((__u8 *)logC0, is_json);
+ else if (err < 0)
+ printf("Unable to retrieve extended smart log 0xC0 for the drive\n");
+out:
+ dev_close(dev);
+ if (err > 0)
+ nvme_show_status(err);
+ return err;
+}
+
+static int micron_clr_fw_activation_history(int argc, char **argv,
+ struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Clear FW activation history";
+ __u32 result = 0;
+ __u8 fid = MICRON_FEATURE_CLEAR_FW_ACTIVATION_HISTORY;
+ enum eDriveModel model = UNKNOWN_MODEL;
+ struct nvme_dev *dev;
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+ int err = 0;
+
+ err = micron_parse_options(&dev, argc, argv, desc, opts, &model);
+ if (err < 0)
+ return err;
+
+ if (model != M51CX) {
+ printf("This option is not supported for specified drive\n");
+ dev_close(dev);
+ return err;
+ }
+
+ err = nvme_set_features_simple(dev_fd(dev), fid, 1 << 31, 0, 0, &result);
+ if (!err)
+ err = (int)result;
+ else
+ printf("Failed to clear fw activation history, error = 0x%x\n", err);
+
+ dev_close(dev);
+ return err;
+}
+
+static int micron_telemetry_cntrl_option(int argc, char **argv,
+ struct command *cmd, struct plugin *plugin)
+{
+ int err = 0;
+ __u32 result = 0;
+ const char *desc = "Enable or Disable Controller telemetry log generation";
+ const char *option = "enable or disable or status";
+ const char *select =
+ "select/save values: enable/disable options1 - save (persistent), 0 - non-persistent and for status options: 0 - current, 1 - default, 2-saved";
+ int fid = MICRON_FEATURE_TELEMETRY_CONTROL_OPTION;
+ enum eDriveModel model = UNKNOWN_MODEL;
+ struct nvme_id_ctrl ctrl = { 0 };
+ struct nvme_dev *dev;
+
+ struct {
+ char *option;
+ int select;
+ } opt = {
+ .option = "disable",
+ .select = 0,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_STRING("option", 'o', "option", &opt.option, option),
+ OPT_UINT("select", 's', &opt.select, select),
+ OPT_END()
+ };
+
+ err = micron_parse_options(&dev, argc, argv, desc, opts, &model);
+ if (err < 0)
+ return -1;
+
+ err = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if ((ctrl.lpa & 0x8) != 0x8) {
+ printf("drive doesn't support host/controller generated telemetry logs\n");
+ dev_close(dev);
+ return err;
+ }
+
+ if (!strcmp(opt.option, "enable")) {
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = fid,
+ .nsid = 1,
+ .cdw11 = 1,
+ .cdw12 = 0,
+ .save = (opt.select & 0x1),
+ .uuidx = 0,
+ .cdw15 = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+ err = nvme_set_features(&args);
+ if (!err)
+ printf("successfully set controller telemetry option\n");
+ else
+ printf("Failed to set controller telemetry option\n");
+ } else if (!strcmp(opt.option, "disable")) {
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = fid,
+ .nsid = 1,
+ .cdw11 = 0,
+ .cdw12 = 0,
+ .save = (opt.select & 0x1),
+ .uuidx = 0,
+ .cdw15 = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+ err = nvme_set_features(&args);
+ if (!err)
+ printf("successfully disabled controller telemetry option\n");
+ else
+ printf("Failed to disable controller telemetry option\n");
+ } else if (!strcmp(opt.option, "status")) {
+ struct nvme_get_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = fid,
+ .nsid = 1,
+ .sel = opt.select & 0x3,
+ .cdw11 = 0,
+ .uuidx = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ err = nvme_get_features(&args);
+ if (!err)
+ printf("Controller telemetry option : %s\n",
+ (result) ? "enabled" : "disabled");
+ else
+ printf("Failed to retrieve controller telemetry option\n");
+ } else {
+ printf("invalid option %s, valid values are enable,disable or status\n",
+ opt.option);
+ dev_close(dev);
+ return -1;
+ }
+
+ dev_close(dev);
+ return err;
+}
+
+/* M51XX models log page header */
+struct micron_common_log_header {
+ uint8_t id;
+ uint8_t version;
+ uint16_t pn;
+ uint32_t log_size;
+ uint32_t max_size;
+ uint32_t write_pointer;
+ uint32_t next_pointer;
+ uint32_t overwritten_bytes;
+ uint8_t flags;
+ uint8_t reserved[7];
+};
+
+/* helper function to retrieve logs with specific offset and max chunk size */
+int nvme_get_log_lpo(int fd, __u8 log_id, __u32 lpo, __u32 chunk,
+ __u32 data_len, void *data)
+{
+ __u32 offset = lpo, xfer_len = data_len;
+ void *ptr = data;
+ struct nvme_get_log_args args = {
+ .lpo = offset,
+ .result = NULL,
+ .log = ptr,
+ .args_size = sizeof(args),
+ .fd = fd,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .lid = log_id,
+ .len = xfer_len,
+ .nsid = NVME_NSID_ALL,
+ .csi = NVME_CSI_NVM,
+ .lsi = NVME_LOG_LSI_NONE,
+ .lsp = NVME_LOG_LSP_NONE,
+ .uuidx = NVME_UUID_NONE,
+ .rae = false,
+ .ot = false,
+ };
+ int ret = 0;
+
+ /* divide data into multiple chunks */
+ do {
+ xfer_len = data_len - offset;
+ if (xfer_len > chunk)
+ xfer_len = chunk;
+
+ args.lpo = offset;
+ args.log = ptr;
+ args.len = xfer_len;
+ ret = nvme_get_log(&args);
+ if (ret)
+ return ret;
+ offset += xfer_len;
+ ptr += xfer_len;
+ } while (offset < data_len);
+ return ret;
+}
+
+/* retrieves logs with common log format */
+static int get_common_log(int fd, uint8_t id, uint8_t **buf, int *size)
+{
+ struct micron_common_log_header hdr = { 0 };
+ int log_size = sizeof(hdr), first = 0, second = 0;
+ uint8_t *buffer = NULL;
+ int ret = -1;
+ int chunk = 0x4000; /* max chunk size to be used for these logs */
+
+ ret = nvme_get_log_simple(fd, id, sizeof(hdr), &hdr);
+ if (ret) {
+ fprintf(stderr, "pull hdr failed for %u with error: 0x%x\n", id, ret);
+ return ret;
+ }
+
+ if (hdr.id != id || !hdr.log_size || !hdr.max_size ||
+ hdr.write_pointer < sizeof(hdr)) {
+ fprintf(stderr,
+ "invalid log data for LOG: 0x%X, id: 0x%X, size: %u, max: %u, wp: %u, flags: %u, np: %u\n",
+ id, hdr.id, hdr.log_size, hdr.max_size, hdr.write_pointer, hdr.flags,
+ hdr.next_pointer);
+ return 1;
+ }
+
+ /*
+ * we may have just 32-bytes for some models; write to wfile if log hasn't
+ * yet reached its max size
+ */
+ if (hdr.log_size == sizeof(hdr)) {
+ buffer = (uint8_t *)malloc(sizeof(hdr));
+ if (!buffer) {
+ fprintf(stderr, "malloc of %zu bytes failed for log: 0x%X\n",
+ sizeof(hdr), id);
+ return -ENOMEM;
+ }
+ memcpy(buffer, (uint8_t *)&hdr, sizeof(hdr));
+ } else if (hdr.log_size < hdr.max_size) {
+ buffer = (uint8_t *)malloc(sizeof(hdr) + hdr.log_size);
+ if (!buffer) {
+ fprintf(stderr, "malloc of %zu bytes failed for log: 0x%X\n",
+ hdr.log_size + sizeof(hdr), id);
+ return -ENOMEM;
+ }
+ memcpy(buffer, &hdr, sizeof(hdr));
+ ret = nvme_get_log_lpo(fd, id, sizeof(hdr), chunk, hdr.log_size,
+ buffer + sizeof(hdr));
+ if (!ret)
+ log_size += hdr.log_size;
+ } else if (hdr.log_size >= hdr.max_size) {
+ /*
+ * reached maximum, to maintain, sequence we need to depend on write
+ * pointer to detect wrap-overs. FW doesn't yet implement the condition
+ * hdr.log_size > hdr.max_size; also ignore over-written log data; we
+ * also ignore collisions for now
+ */
+ buffer = (uint8_t *)malloc(hdr.max_size + sizeof(hdr));
+ if (!buffer) {
+ fprintf(stderr, "malloc of %zu bytes failed for log: 0x%X\n",
+ hdr.max_size + sizeof(hdr), id);
+ return -ENOMEM;
+ }
+ memcpy(buffer, &hdr, sizeof(hdr));
+
+ first = hdr.max_size - hdr.write_pointer;
+ second = hdr.write_pointer - sizeof(hdr);
+
+ if (first) {
+ ret = nvme_get_log_lpo(fd, id, hdr.write_pointer, chunk, first,
+ buffer + sizeof(hdr));
+ if (ret) {
+ free(buffer);
+ fprintf(stderr, "failed to get log: 0x%X\n", id);
+ return ret;
+ }
+ log_size += first;
+ }
+ if (second) {
+ ret = nvme_get_log_lpo(fd, id, sizeof(hdr), chunk, second,
+ buffer + sizeof(hdr) + first);
+ if (ret) {
+ fprintf(stderr, "failed to get log: 0x%X\n", id);
+ free(buffer);
+ return ret;
+ }
+ log_size += second;
+ }
+ }
+ *buf = buffer;
+ *size = log_size;
+ return ret;
+}
+
+static int micron_internal_logs(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ int err = -EINVAL;
+ int ctrlIdx, telemetry_option = 0;
+ char strOSDirName[1024];
+ char strCtrlDirName[1024];
+ char strMainDirName[256];
+ unsigned int *puiIDDBuf;
+ unsigned int uiMask;
+ struct nvme_id_ctrl ctrl;
+ char sn[20] = { 0 };
+ char msg[256] = { 0 };
+ int c_logs_index = 8; /* should be current size of aVendorLogs */
+ struct nvme_dev *dev;
+ struct {
+ unsigned char ucLogPage;
+ const char *strFileName;
+ int nLogSize;
+ int nMaxSize;
+ } aVendorLogs[32] = {
+ { 0x03, "firmware_slot_info_log.bin", 512, 0 },
+ { 0xC1, "nvmelog_C1.bin", 0, 0 },
+ { 0xC2, "nvmelog_C2.bin", 0, 0 },
+ { 0xC4, "nvmelog_C4.bin", 0, 0 },
+ { 0xC5, "nvmelog_C5.bin", C5_log_size, 0 },
+ { 0xD0, "nvmelog_D0.bin", D0_log_size, 0 },
+ { 0xE6, "nvmelog_E6.bin", 0, 0 },
+ { 0xE7, "nvmelog_E7.bin", 0, 0 }
+ },
+ aM51XXLogs[] = {
+ { 0xFB, "nvmelog_FB.bin", 4096, 0 }, /* this should be collected first for M51AX */
+ { 0xD0, "nvmelog_D0.bin", 512, 0 },
+ { 0x03, "firmware_slot_info_log.bin", 512, 0},
+ { 0xF7, "nvmelog_F7.bin", 4096, 512 * 1024 },
+ { 0xF8, "nvmelog_F8.bin", 4096, 512 * 1024 },
+ { 0xF9, "nvmelog_F9.bin", 4096, 200 * 1024 * 1024 },
+ { 0xFC, "nvmelog_FC.bin", 4096, 200 * 1024 * 1024 },
+ { 0xFD, "nvmelog_FD.bin", 4096, 80 * 1024 * 1024 }
+ },
+ aM51AXLogs[] = {
+ { 0xCA, "nvmelog_CA.bin", 512, 0 },
+ { 0xFA, "nvmelog_FA.bin", 4096, 15232 },
+ { 0xF6, "nvmelog_F6.bin", 4096, 512 * 1024 },
+ { 0xFE, "nvmelog_FE.bin", 4096, 512 * 1024 },
+ { 0xFF, "nvmelog_FF.bin", 4096, 162 * 1024 },
+ { 0x04, "changed_namespace_log.bin", 4096, 0 },
+ { 0x05, "command_effects_log.bin", 4096, 0 },
+ { 0x06, "drive_self_test.bin", 4096, 0 }
+ },
+ aM51BXLogs[] = {
+ { 0xFA, "nvmelog_FA.bin", 4096, 16376 },
+ { 0xFE, "nvmelog_FE.bin", 4096, 256 * 1024 },
+ { 0xFF, "nvmelog_FF.bin", 4096, 64 * 1024 },
+ { 0xCA, "nvmelog_CA.bin", 512, 1024 }
+ },
+ aM51CXLogs[] = {
+ { 0xE1, "nvmelog_E1.bin", 0, 0 },
+ { 0xE2, "nvmelog_E2.bin", 0, 0 },
+ { 0xE3, "nvmelog_E3.bin", 0, 0 },
+ { 0xE4, "nvmelog_E4.bin", 0, 0 },
+ { 0xE5, "nvmelog_E5.bin", 0, 0 },
+ { 0xE8, "nvmelog_E8.bin", 0, 0 },
+ { 0xE9, "nvmelog_E9.bin", 0, 0 },
+ { 0xEA, "nvmelog_EA.bin", 0, 0 },
+ };
+
+ enum eDriveModel eModel;
+
+ const char *desc = "This retrieves the micron debug log package";
+ const char *package = "Log output data file name (required)";
+ const char *type = "telemetry log type - host or controller";
+ const char *data_area = "telemetry log data area 1, 2 or 3";
+ unsigned char *dataBuffer = NULL;
+ int bSize = 0;
+ int maxSize = 0;
+
+ struct config {
+ char *type;
+ char *package;
+ int data_area;
+ int log;
+ };
+
+ struct config cfg = {
+ .type = "",
+ .package = "",
+ .data_area = -1,
+ .log = 0x07,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_STRING("type", 't', "log type", &cfg.type, type),
+ OPT_STRING("package", 'p', "FILE", &cfg.package, package),
+ OPT_UINT("data_area", 'd', &cfg.data_area, data_area),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ /* if telemetry type is specified, check for data area */
+ if (strlen(cfg.type)) {
+ if (!strcmp(cfg.type, "controller")) {
+ cfg.log = 0x08;
+ } else if (strcmp(cfg.type, "host")) {
+ printf("telemetry type (host or controller) should be specified i.e. -t=host\n");
+ goto out;
+ }
+
+ if (cfg.data_area <= 0 || cfg.data_area > 3) {
+ printf("data area must be selected using -d option ie --d=1,2,3\n");
+ goto out;
+ }
+ telemetry_option = 1;
+ } else if (cfg.data_area > 0) {
+ printf("data area option is valid only for telemetry option (i.e --type=host|controller)\n");
+ goto out;
+ }
+
+ if (!strlen(cfg.package)) {
+ if (telemetry_option)
+ printf("Log data file must be specified. ie -p=logfile.bin\n");
+ else
+ printf("Log data file must be specified. ie -p=logfile.zip or -p=logfile.tgz|logfile.tar.gz\n");
+ goto out;
+ }
+
+ /* pull log details based on the model name */
+ if (sscanf(argv[optind], "/dev/nvme%d", &ctrlIdx) != 1)
+ ctrlIdx = 0;
+ eModel = GetDriveModel(ctrlIdx);
+ if (eModel == UNKNOWN_MODEL) {
+ printf("Unsupported drive model for vs-internal-log collection\n");
+ goto out;
+ }
+
+ err = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (err)
+ goto out;
+
+ err = -EINVAL;
+ if (telemetry_option) {
+ if ((ctrl.lpa & 0x8) != 0x8) {
+ printf("telemetry option is not supported for specified drive\n");
+ goto out;
+ }
+ int logSize = 0; __u8 *buffer = NULL; const char *dir = ".";
+
+ err = micron_telemetry_log(dev_fd(dev), cfg.log, &buffer, &logSize,
+ cfg.data_area);
+ if (!err && logSize > 0 && buffer) {
+ sprintf(msg, "telemetry log: 0x%X", cfg.log);
+ WriteData(buffer, logSize, dir, cfg.package, msg);
+ free(buffer);
+ }
+ goto out;
+ }
+
+ printf("Preparing log package. This will take a few seconds...\n");
+
+ /* trim spaces out of serial number string */
+ int i, j = 0;
+
+ for (i = 0; i < sizeof(ctrl.sn); i++) {
+ if (isblank((int)ctrl.sn[i]))
+ continue;
+ sn[j++] = ctrl.sn[i];
+ }
+ sn[j] = '\0';
+ strcpy(ctrl.sn, sn);
+
+ SetupDebugDataDirectories(ctrl.sn, cfg.package, strMainDirName, strOSDirName, strCtrlDirName);
+
+ GetTimestampInfo(strOSDirName);
+ GetCtrlIDDInfo(strCtrlDirName, &ctrl);
+ GetOSConfig(strOSDirName);
+ GetDriveInfo(strOSDirName, ctrlIdx, &ctrl);
+
+ for (int i = 1; i <= ctrl.nn; i++)
+ GetNSIDDInfo(dev_fd(dev), strCtrlDirName, i);
+
+ GetSmartlogData(dev_fd(dev), strCtrlDirName);
+ GetErrorlogData(dev_fd(dev), ctrl.elpe, strCtrlDirName);
+ GetGenericLogs(dev_fd(dev), strCtrlDirName);
+ /* pull if telemetry log data is supported */
+ if ((ctrl.lpa & 0x8) == 0x8)
+ GetTelemetryData(dev_fd(dev), strCtrlDirName);
+
+ GetFeatureSettings(dev_fd(dev), strCtrlDirName);
+
+ if (eModel != M5410 && eModel != M5407) {
+ memcpy(&aVendorLogs[c_logs_index], aM51XXLogs, sizeof(aM51XXLogs));
+ c_logs_index += ARRAY_SIZE(aM51XXLogs);
+ if (eModel == M51AX)
+ memcpy((char *)&aVendorLogs[c_logs_index], aM51AXLogs, sizeof(aM51AXLogs));
+ else if (eModel == M51BX)
+ memcpy((char *)&aVendorLogs[c_logs_index], aM51BXLogs, sizeof(aM51BXLogs));
+ else if (eModel == M51CX)
+ memcpy((char *)&aVendorLogs[c_logs_index], aM51CXLogs, sizeof(aM51CXLogs));
+ }
+
+ for (int i = 0; i < (int)(ARRAY_SIZE(aVendorLogs)) && aVendorLogs[i].ucLogPage; i++) {
+ err = -1;
+ switch (aVendorLogs[i].ucLogPage) {
+ case 0xE1:
+ fallthrough;
+ case 0xE5:
+ fallthrough;
+ case 0xE9:
+ err = 1;
+ break;
+ case 0xE2:
+ fallthrough;
+ case 0xE3:
+ fallthrough;
+ case 0xE4:
+ fallthrough;
+ case 0xE8:
+ fallthrough;
+ case 0xEA:
+ err = get_common_log(dev_fd(dev), aVendorLogs[i].ucLogPage,
+ &dataBuffer, &bSize);
+ break;
+ case 0xC1:
+ fallthrough;
+ case 0xC2:
+ fallthrough;
+ case 0xC4:
+ err = GetLogPageSize(dev_fd(dev), aVendorLogs[i].ucLogPage,
+ &bSize);
+ if (!err && bSize > 0)
+ err = GetCommonLogPage(dev_fd(dev), aVendorLogs[i].ucLogPage,
+ &dataBuffer, bSize);
+ break;
+ case 0xE6:
+ fallthrough;
+ case 0xE7:
+ puiIDDBuf = (unsigned int *)&ctrl;
+ uiMask = puiIDDBuf[1015];
+ if (!uiMask || (aVendorLogs[i].ucLogPage == 0xE6 && uiMask == 2) ||
+ (aVendorLogs[i].ucLogPage == 0xE7 && uiMask == 1)) {
+ bSize = 0;
+ } else {
+ bSize = (int)puiIDDBuf[1023];
+ if (bSize % (16 * 1024))
+ bSize += (16 * 1024) - (bSize % (16 * 1024));
+ }
+ dataBuffer = (unsigned char *)malloc(bSize);
+ if (bSize && dataBuffer) {
+ memset(dataBuffer, 0, bSize);
+ if (eModel == M5410 || eModel == M5407)
+ err = NVMEGetLogPage(dev_fd(dev),
+ aVendorLogs[i].ucLogPage, dataBuffer,
+ bSize);
+ else
+ err = nvme_get_log_simple(dev_fd(dev),
+ aVendorLogs[i].ucLogPage,
+ bSize, dataBuffer);
+ }
+ break;
+ case 0xF7:
+ fallthrough;
+ case 0xF9:
+ fallthrough;
+ case 0xFC:
+ fallthrough;
+ case 0xFD:
+ if (eModel == M51BX)
+ (void)NVMEResetLog(dev_fd(dev), aVendorLogs[i].ucLogPage,
+ aVendorLogs[i].nLogSize, aVendorLogs[i].nMaxSize);
+ fallthrough;
+ default:
+ bSize = aVendorLogs[i].nLogSize;
+ dataBuffer = (unsigned char *)malloc(bSize);
+ if (!dataBuffer)
+ break;
+ memset(dataBuffer, 0, bSize);
+ err = nvme_get_log_simple(dev_fd(dev), aVendorLogs[i].ucLogPage,
+ bSize, dataBuffer);
+ maxSize = aVendorLogs[i].nMaxSize - bSize;
+ while (!err && maxSize > 0 && ((unsigned int *)dataBuffer)[0] != 0xdeadbeef) {
+ sprintf(msg, "log 0x%x", aVendorLogs[i].ucLogPage);
+ WriteData(dataBuffer, bSize, strCtrlDirName, aVendorLogs[i].strFileName, msg);
+ err = nvme_get_log_simple(dev_fd(dev),
+ aVendorLogs[i].ucLogPage,
+ bSize, dataBuffer);
+ if (err || (((unsigned int *)dataBuffer)[0] == 0xdeadbeef))
+ break;
+ maxSize -= bSize;
+ }
+ break;
+ }
+
+ if (!err && dataBuffer && ((unsigned int *)dataBuffer)[0] != 0xdeadbeef) {
+ sprintf(msg, "log 0x%x", aVendorLogs[i].ucLogPage);
+ WriteData(dataBuffer, bSize, strCtrlDirName, aVendorLogs[i].strFileName, msg);
+ }
+
+ if (dataBuffer) {
+ free(dataBuffer);
+ dataBuffer = NULL;
+ }
+ }
+
+ err = ZipAndRemoveDir(strMainDirName, cfg.package);
+out:
+ dev_close(dev);
+ return err;
+}
+
+#define MIN_LOG_SIZE 512
+static int micron_logpage_dir(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ int err = -1;
+ const char *desc = "List the supported log pages";
+ enum eDriveModel model = UNKNOWN_MODEL;
+ char logbuf[MIN_LOG_SIZE];
+ struct nvme_dev *dev;
+ int i;
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ err = micron_parse_options(&dev, argc, argv, desc, opts, &model);
+ if (err < 0)
+ return err;
+
+ struct nvme_supported_logs {
+ uint8_t log_id;
+ uint8_t supported;
+ char *desc;
+ } log_list[] = {
+ {0x00, 0, "Support Log Pages"},
+ {0x01, 0, "Error Information"},
+ {0x02, 0, "SMART / Health Information"},
+ {0x03, 0, "Firmware Slot Information"},
+ {0x04, 0, "Changed Namespace List"},
+ {0x05, 0, "Commands Supported and Effects"},
+ {0x06, 0, "Device Self Test"},
+ {0x07, 0, "Telemetry Host-Initiated"},
+ {0x08, 0, "Telemetry Controller-Initiated"},
+ {0x09, 0, "Endurance Group Information"},
+ {0x0A, 0, "Predictable Latency Per NVM Set"},
+ {0x0B, 0, "Predictable Latency Event Aggregate"},
+ {0x0C, 0, "Asymmetric Namespace Access"},
+ {0x0D, 0, "Persistent Event Log"},
+ {0x0E, 0, "Predictable Latency Event Aggregate"},
+ {0x0F, 0, "Endurance Group Event Aggregate"},
+ {0x10, 0, "Media Unit Status"},
+ {0x11, 0, "Supported Capacity Configuration List"},
+ {0x12, 0, "Feature Identifiers Supported and Effects"},
+ {0x13, 0, "NVMe-MI Commands Supported and Effects"},
+ {0x14, 0, "Command and Feature lockdown"},
+ {0x15, 0, "Boot Partition"},
+ {0x16, 0, "Rotational Media Information"},
+ {0x70, 0, "Discovery"},
+ {0x80, 0, "Reservation Notification"},
+ {0x81, 0, "Sanitize Status"},
+ {0xC0, 0, "SMART Cloud Health Log"},
+ {0xC2, 0, "Firmware Activation History"},
+ {0xC3, 0, "Latency Monitor Log"},
+ };
+
+ printf("Supported log page list\nLog ID : Description\n");
+ for (i = 0; i < ARRAY_SIZE(log_list); i++) {
+ err = nvme_get_log_simple(dev_fd(dev), log_list[i].log_id,
+ MIN_LOG_SIZE, &logbuf[0]);
+ if (err)
+ continue;
+ printf("%02Xh : %s\n", log_list[i].log_id, log_list[i].desc);
+ }
+
+ return err;
+}
diff --git a/plugins/micron/micron-nvme.h b/plugins/micron/micron-nvme.h
new file mode 100644
index 0000000..4f7b892
--- /dev/null
+++ b/plugins/micron/micron-nvme.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/micron/micron-nvme
+
+#if !defined(MICRON_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define MICRON_NVME
+
+#include "cmd.h"
+
+PLUGIN(NAME("micron", "Micron vendor specific extensions", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("select-download", "Selective Firmware Download", micron_selective_download)
+ ENTRY("vs-temperature-stats", "Retrieve Micron temperature statistics ", micron_temp_stats)
+ ENTRY("vs-pcie-stats", "Retrieve Micron PCIe error stats", micron_pcie_stats)
+ ENTRY("clear-pcie-correctable-errors", "Clear correctable PCIe errors", micron_clear_pcie_correctable_errors)
+ ENTRY("vs-internal-log", "Retrieve Micron logs", micron_internal_logs)
+ ENTRY("vs-telemetry-controller-option", "Enable/Disable controller telemetry log generation", micron_telemetry_cntrl_option)
+ ENTRY("vs-nand-stats", "Retrieve NAND Stats", micron_nand_stats)
+ ENTRY("vs-smart-ext-log", "Retrieve extended SMART logs", micron_smart_ext_log)
+ ENTRY("vs-drive-info", "Retrieve Drive information", micron_drive_info)
+ ENTRY("plugin-version", "Display plugin version info", micron_plugin_version)
+ ENTRY("cloud-SSD-plugin-version", "Display plugin version info", micron_cloud_ssd_plugin_version)
+ ENTRY("log-page-directory", "Retrieve log page directory", micron_logpage_dir)
+ ENTRY("vs-fw-activate-history", "Display FW activation history", micron_fw_activation_history)
+ ENTRY("latency-tracking", "Latency monitoring feature control", micron_latency_stats_track)
+ ENTRY("latency-stats", "Latency information for tracked commands", micron_latency_stats_info)
+ ENTRY("latency-logs", "Latency log details tracked by drive", micron_latency_stats_logs)
+ ENTRY("vs-smart-add-log", "Retrieve extended SMART data", micron_ocp_smart_health_logs)
+ ENTRY("clear-fw-activate-history", "Clear FW activation history", micron_clr_fw_activation_history)
+ ENTRY("vs-smbus-option", "Enable/Disable SMBUS on the drive", micron_smbus_option)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/nbft/nbft-plugin.c b/plugins/nbft/nbft-plugin.c
new file mode 100644
index 0000000..2193ffb
--- /dev/null
+++ b/plugins/nbft/nbft-plugin.c
@@ -0,0 +1,563 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <errno.h>
+#include <stdio.h>
+#include <fnmatch.h>
+
+#include "nvme-print.h"
+#include "nvme.h"
+#include "nbft.h"
+#include "libnvme.h"
+#include "fabrics.h"
+
+#define CREATE_CMD
+#include "nbft-plugin.h"
+
+static const char dash[100] = {[0 ... 98] = '-', [99] = '\0'};
+
+#define PCI_SEGMENT(sbdf) ((sbdf & 0xffff0000) >> 16)
+#define PCI_BUS(sbdf) ((sbdf & 0x0000ff00) >> 8)
+#define PCI_DEV(sbdf) ((sbdf & 0x000000f8) >> 3)
+#define PCI_FUNC(sbdf) ((sbdf & 0x00000007) >> 0)
+
+static const char *pci_sbdf_to_string(__u16 pci_sbdf)
+{
+ static char pcidev[13];
+
+ snprintf(pcidev, sizeof(pcidev), "%x:%x:%x.%x",
+ PCI_SEGMENT(pci_sbdf),
+ PCI_BUS(pci_sbdf),
+ PCI_DEV(pci_sbdf),
+ PCI_FUNC(pci_sbdf));
+ return pcidev;
+}
+
+static char *mac_addr_to_string(unsigned char mac_addr[6])
+{
+ static char mac_string[18];
+
+ snprintf(mac_string, sizeof(mac_string), "%02x:%02x:%02x:%02x:%02x:%02x",
+ mac_addr[0],
+ mac_addr[1],
+ mac_addr[2],
+ mac_addr[3],
+ mac_addr[4],
+ mac_addr[5]);
+ return mac_string;
+}
+
+static json_object *hfi_to_json(struct nbft_info_hfi *hfi)
+{
+ struct json_object *hfi_json;
+
+ hfi_json = json_create_object();
+ if (!hfi_json)
+ return NULL;
+
+ if (json_object_add_value_int(hfi_json, "index", hfi->index)
+ || json_object_add_value_string(hfi_json, "transport", hfi->transport))
+ goto fail;
+
+ if (strcmp(hfi->transport, "tcp") == 0) {
+ if (json_object_add_value_string(hfi_json, "pcidev",
+ pci_sbdf_to_string(hfi->tcp_info.pci_sbdf))
+ || json_object_add_value_string(hfi_json, "mac_addr",
+ mac_addr_to_string(hfi->tcp_info.mac_addr))
+ || json_object_add_value_int(hfi_json, "vlan",
+ hfi->tcp_info.vlan)
+ || json_object_add_value_int(hfi_json, "ip_origin",
+ hfi->tcp_info.ip_origin)
+ || json_object_add_value_string(hfi_json, "ipaddr",
+ hfi->tcp_info.ipaddr)
+ || json_object_add_value_int(hfi_json, "subnet_mask_prefix",
+ hfi->tcp_info.subnet_mask_prefix)
+ || json_object_add_value_string(hfi_json, "gateway_ipaddr",
+ hfi->tcp_info.gateway_ipaddr)
+ || json_object_add_value_int(hfi_json, "route_metric",
+ hfi->tcp_info.route_metric)
+ || json_object_add_value_string(hfi_json, "primary_dns_ipaddr",
+ hfi->tcp_info.primary_dns_ipaddr)
+ || json_object_add_value_string(hfi_json, "secondary_dns_ipaddr",
+ hfi->tcp_info.secondary_dns_ipaddr)
+ || json_object_add_value_string(hfi_json, "dhcp_server_ipaddr",
+ hfi->tcp_info.dhcp_server_ipaddr)
+ || (hfi->tcp_info.host_name
+ && json_object_add_value_string(hfi_json, "host_name",
+ hfi->tcp_info.host_name))
+ || json_object_add_value_int(hfi_json, "this_hfi_is_default_route",
+ hfi->tcp_info.this_hfi_is_default_route)
+ || json_object_add_value_int(hfi_json, "dhcp_override",
+ hfi->tcp_info.dhcp_override))
+ goto fail;
+ else
+ return hfi_json;
+ }
+fail:
+ json_free_object(hfi_json);
+ return NULL;
+}
+
+static json_object *ssns_to_json(struct nbft_info_subsystem_ns *ss)
+{
+ struct json_object *ss_json;
+ struct json_object *hfi_array_json;
+ char json_str[40];
+ char *json_str_p;
+ int i;
+
+ ss_json = json_create_object();
+ if (!ss_json)
+ return NULL;
+
+ hfi_array_json = json_create_array();
+ if (!hfi_array_json)
+ goto fail;
+
+ for (i = 0; i < ss->num_hfis; i++)
+ if (json_array_add_value_object(hfi_array_json,
+ json_object_new_int(ss->hfis[i]->index)))
+ goto fail;
+
+ if (json_object_add_value_int(ss_json, "index", ss->index)
+ || json_object_add_value_int(ss_json, "num_hfis", ss->num_hfis)
+ || json_object_object_add(ss_json, "hfis", hfi_array_json)
+ || json_object_add_value_string(ss_json, "transport", ss->transport)
+ || json_object_add_value_string(ss_json, "traddr", ss->traddr)
+ || json_object_add_value_string(ss_json, "trsvcid", ss->trsvcid)
+ || json_object_add_value_int(ss_json, "subsys_port_id", ss->subsys_port_id)
+ || json_object_add_value_int(ss_json, "nsid", ss->nsid))
+ goto fail;
+
+ memset(json_str, 0, sizeof(json_str));
+ json_str_p = json_str;
+
+ switch (ss->nid_type) {
+ case NBFT_INFO_NID_TYPE_EUI64:
+ if (json_object_add_value_string(ss_json, "nid_type", "eui64"))
+ goto fail;
+ for (i = 0; i < 8; i++)
+ json_str_p += sprintf(json_str_p, "%02x", ss->nid[i]);
+ break;
+
+ case NBFT_INFO_NID_TYPE_NGUID:
+ if (json_object_add_value_string(ss_json, "nid_type", "nguid"))
+ goto fail;
+ for (i = 0; i < 16; i++)
+ json_str_p += sprintf(json_str_p, "%02x", ss->nid[i]);
+ break;
+
+ case NBFT_INFO_NID_TYPE_NS_UUID:
+ if (json_object_add_value_string(ss_json, "nid_type", "uuid"))
+ goto fail;
+ nvme_uuid_to_string(ss->nid, json_str);
+ break;
+
+ default:
+ break;
+ }
+ if (json_object_add_value_string(ss_json, "nid", json_str))
+ goto fail;
+
+ if ((ss->subsys_nqn
+ && json_object_add_value_string(ss_json, "subsys_nqn", ss->subsys_nqn))
+ || json_object_add_value_int(ss_json, "controller_id", ss->controller_id)
+ || json_object_add_value_int(ss_json, "asqsz", ss->asqsz)
+ || (ss->dhcp_root_path_string
+ && json_object_add_value_string(ss_json, "dhcp_root_path_string",
+ ss->dhcp_root_path_string))
+ || json_object_add_value_int(ss_json, "pdu_header_digest_required",
+ ss->pdu_header_digest_required)
+ || json_object_add_value_int(ss_json, "data_digest_required",
+ ss->data_digest_required))
+ goto fail;
+
+ return ss_json;
+fail:
+ json_free_object(ss_json);
+ return NULL;
+}
+
+static json_object *discovery_to_json(struct nbft_info_discovery *disc)
+{
+ struct json_object *disc_json;
+
+ disc_json = json_create_object();
+ if (!disc_json)
+ return NULL;
+
+ if (json_object_add_value_int(disc_json, "index", disc->index)
+ || (disc->security
+ && json_object_add_value_int(disc_json, "security", disc->security->index))
+ || (disc->hfi
+ && json_object_add_value_int(disc_json, "hfi", disc->hfi->index))
+ || (disc->uri
+ && json_object_add_value_string(disc_json, "uri", disc->uri))
+ || (disc->nqn
+ && json_object_add_value_string(disc_json, "nqn", disc->nqn))) {
+ json_free_object(disc_json);
+ return NULL;
+ } else
+ return disc_json;
+}
+
+static const char *primary_admin_host_flag_to_str(unsigned int primary)
+{
+ static const char * const str[] = {
+ [NBFT_INFO_PRIMARY_ADMIN_HOST_FLAG_NOT_INDICATED] = "not indicated",
+ [NBFT_INFO_PRIMARY_ADMIN_HOST_FLAG_UNSELECTED] = "unselected",
+ [NBFT_INFO_PRIMARY_ADMIN_HOST_FLAG_SELECTED] = "selected",
+ [NBFT_INFO_PRIMARY_ADMIN_HOST_FLAG_RESERVED] = "reserved",
+ };
+
+ if (primary > NBFT_INFO_PRIMARY_ADMIN_HOST_FLAG_RESERVED)
+ return "INVALID";
+ return str[primary];
+}
+
+static struct json_object *nbft_to_json(struct nbft_info *nbft, bool show_subsys,
+ bool show_hfi, bool show_discovery)
+{
+ struct json_object *nbft_json, *host_json;
+
+ nbft_json = json_create_object();
+ if (!nbft_json)
+ return NULL;
+
+ if (json_object_add_value_string(nbft_json, "filename", nbft->filename))
+ goto fail;
+
+ host_json = json_create_object();
+ if (!host_json)
+ goto fail;
+ if ((nbft->host.nqn
+ && json_object_add_value_string(host_json, "nqn", nbft->host.nqn))
+ || (nbft->host.id
+ && json_object_add_value_string(host_json, "id",
+ util_uuid_to_string(nbft->host.id))))
+ goto fail;
+ json_object_add_value_int(host_json, "host_id_configured",
+ nbft->host.host_id_configured);
+ json_object_add_value_int(host_json, "host_nqn_configured",
+ nbft->host.host_nqn_configured);
+ json_object_add_value_string(host_json, "primary_admin_host_flag",
+ primary_admin_host_flag_to_str(nbft->host.primary));
+ if (json_object_object_add(nbft_json, "host", host_json)) {
+ json_free_object(host_json);
+ goto fail;
+ }
+
+ if (show_subsys) {
+ struct json_object *subsys_array_json, *subsys_json;
+ struct nbft_info_subsystem_ns **ss;
+
+ subsys_array_json = json_create_array();
+ if (!subsys_array_json)
+ goto fail;
+ for (ss = nbft->subsystem_ns_list; ss && *ss; ss++) {
+ subsys_json = ssns_to_json(*ss);
+ if (!subsys_json)
+ goto fail;
+ if (json_object_array_add(subsys_array_json, subsys_json)) {
+ json_free_object(subsys_json);
+ goto fail;
+ }
+ }
+ if (json_object_object_add(nbft_json, "subsystem", subsys_array_json)) {
+ json_free_object(subsys_array_json);
+ goto fail;
+ }
+ }
+ if (show_hfi) {
+ struct json_object *hfi_array_json, *hfi_json;
+ struct nbft_info_hfi **hfi;
+
+ hfi_array_json = json_create_array();
+ if (!hfi_array_json)
+ goto fail;
+ for (hfi = nbft->hfi_list; hfi && *hfi; hfi++) {
+ hfi_json = hfi_to_json(*hfi);
+ if (!hfi_json)
+ goto fail;
+ if (json_object_array_add(hfi_array_json, hfi_json)) {
+ json_free_object(hfi_json);
+ goto fail;
+ }
+ }
+ if (json_object_object_add(nbft_json, "hfi", hfi_array_json)) {
+ json_free_object(hfi_array_json);
+ goto fail;
+ }
+ }
+ if (show_discovery) {
+ struct json_object *discovery_array_json, *discovery_json;
+ struct nbft_info_discovery **disc;
+
+ discovery_array_json = json_create_array();
+ if (!discovery_array_json)
+ goto fail;
+ for (disc = nbft->discovery_list; disc && *disc; disc++) {
+ discovery_json = discovery_to_json(*disc);
+ if (!discovery_json)
+ goto fail;
+ if (json_object_array_add(discovery_array_json, discovery_json)) {
+ json_free_object(discovery_json);
+ goto fail;
+ }
+ }
+ if (json_object_object_add(nbft_json, "discovery", discovery_array_json)) {
+ json_free_object(discovery_array_json);
+ goto fail;
+ }
+ }
+ return nbft_json;
+fail:
+ json_free_object(nbft_json);
+ return NULL;
+}
+
+static int json_show_nbfts(struct list_head *nbft_list, bool show_subsys,
+ bool show_hfi, bool show_discovery)
+{
+ struct json_object *nbft_json_array, *nbft_json;
+ struct nbft_file_entry *entry;
+
+ nbft_json_array = json_create_array();
+ if (!nbft_json_array)
+ return -ENOMEM;
+
+ list_for_each(nbft_list, entry, node) {
+ nbft_json = nbft_to_json(entry->nbft, show_subsys, show_hfi, show_discovery);
+ if (!nbft_json)
+ goto fail;
+ if (json_object_array_add(nbft_json_array, nbft_json)) {
+ json_free_object(nbft_json);
+ goto fail;
+ }
+ }
+
+ json_print_object(nbft_json_array, NULL);
+ printf("\n");
+ json_free_object(nbft_json_array);
+ return 0;
+fail:
+ json_free_object(nbft_json_array);
+ return -ENOMEM;
+}
+
+static void print_nbft_hfi_info(struct nbft_info *nbft)
+{
+ struct nbft_info_hfi **hfi;
+ unsigned int ip_width = 8, gw_width = 8, dns_width = 8;
+
+ hfi = nbft->hfi_list;
+ if (!hfi || !*hfi)
+ return;
+
+ for (; *hfi; hfi++) {
+ unsigned int len;
+
+ len = strlen((*hfi)->tcp_info.ipaddr);
+ if (len > ip_width)
+ ip_width = len;
+ len = strlen((*hfi)->tcp_info.gateway_ipaddr);
+ if (len > gw_width)
+ gw_width = len;
+ len = strlen((*hfi)->tcp_info.primary_dns_ipaddr);
+ if (len > dns_width)
+ dns_width = len;
+ }
+
+ printf("\nNBFT HFIs:\n\n");
+ printf("%-3.3s|%-4.4s|%-10.10s|%-17.17s|%-4.4s|%-*.*s|%-4.4s|%-*.*s|%-*.*s\n",
+ "Idx", "Trsp", "PCI Addr", "MAC Addr", "DHCP",
+ ip_width, ip_width, "IP Addr", "Mask",
+ gw_width, gw_width, "Gateway", dns_width, dns_width, "DNS");
+ printf("%-.3s+%-.4s+%-.10s+%-.17s+%-.4s+%-.*s+%-.4s+%-.*s+%-.*s\n",
+ dash, dash, dash, dash, dash, ip_width, dash, dash,
+ gw_width, dash, dns_width, dash);
+ for (hfi = nbft->hfi_list; *hfi; hfi++)
+ printf("%-3d|%-4.4s|%-10.10s|%-17.17s|%-4.4s|%-*.*s|%-4d|%-*.*s|%-*.*s\n",
+ (*hfi)->index,
+ (*hfi)->transport,
+ pci_sbdf_to_string((*hfi)->tcp_info.pci_sbdf),
+ mac_addr_to_string((*hfi)->tcp_info.mac_addr),
+ (*hfi)->tcp_info.dhcp_override ? "yes" : "no",
+ ip_width, ip_width, (*hfi)->tcp_info.ipaddr,
+ (*hfi)->tcp_info.subnet_mask_prefix,
+ gw_width, gw_width, (*hfi)->tcp_info.gateway_ipaddr,
+ dns_width, dns_width, (*hfi)->tcp_info.primary_dns_ipaddr);
+}
+
+static void print_nbft_discovery_info(struct nbft_info *nbft)
+{
+ struct nbft_info_discovery **disc;
+ unsigned int nqn_width = 20, uri_width = 12;
+
+ disc = nbft->discovery_list;
+ if (!disc || !*disc)
+ return;
+
+ for (; *disc; disc++) {
+ size_t len;
+
+ len = strlen((*disc)->uri);
+ if (len > uri_width)
+ uri_width = len;
+ len = strlen((*disc)->nqn);
+ if (len > nqn_width)
+ nqn_width = len;
+ }
+
+ printf("\nNBFT Discovery Controllers:\n\n");
+ printf("%-3.3s|%-*.*s|%-*.*s\n", "Idx", uri_width, uri_width, "URI",
+ nqn_width, nqn_width, "NQN");
+ printf("%-.3s+%-.*s+%-.*s\n", dash, uri_width, dash, nqn_width, dash);
+ for (disc = nbft->discovery_list; *disc; disc++)
+ printf("%-3d|%-*.*s|%-*.*s\n", (*disc)->index,
+ uri_width, uri_width, (*disc)->uri,
+ nqn_width, nqn_width, (*disc)->nqn);
+}
+
+#define HFIS_LEN 20
+static size_t print_hfis(const struct nbft_info_subsystem_ns *ss, char buf[HFIS_LEN])
+{
+ char hfi_buf[HFIS_LEN];
+ size_t len, ofs;
+ int i;
+
+ len = snprintf(hfi_buf, sizeof(hfi_buf), "%d", ss->hfis[0]->index);
+ for (i = 1; i < ss->num_hfis; i++) {
+ ofs = len;
+ len += snprintf(hfi_buf + ofs, sizeof(hfi_buf) - ofs, ",%d",
+ ss->hfis[i]->index);
+ /*
+ * If the list doesn't fit in HFIS_LEN characters,
+ * truncate and end with "..."
+ */
+ if (len >= sizeof(hfi_buf)) {
+ while (ofs < sizeof(hfi_buf) - 1)
+ hfi_buf[ofs++] = '.';
+ hfi_buf[ofs] = '\0';
+ len = sizeof(hfi_buf) - 1;
+ break;
+ }
+ }
+ if (buf)
+ memcpy(buf, hfi_buf, len + 1);
+ return len;
+}
+
+
+static void print_nbft_subsys_info(struct nbft_info *nbft)
+{
+ struct nbft_info_subsystem_ns **ss;
+ unsigned int nqn_width = 20, adr_width = 8, hfi_width = 4;
+
+ ss = nbft->subsystem_ns_list;
+ if (!ss || !*ss)
+ return;
+ for (; *ss; ss++) {
+ size_t len;
+
+ len = strlen((*ss)->subsys_nqn);
+ if (len > nqn_width)
+ nqn_width = len;
+ len = strlen((*ss)->traddr);
+ if (len > adr_width)
+ adr_width = len;
+ len = print_hfis(*ss, NULL);
+ if (len > hfi_width)
+ hfi_width = len;
+ }
+
+ printf("\nNBFT Subsystems:\n\n");
+ printf("%-3.3s|%-*.*s|%-4.4s|%-*.*s|%-5.5s|%-*.*s\n",
+ "Idx", nqn_width, nqn_width, "NQN",
+ "Trsp", adr_width, adr_width, "Address", "SvcId", hfi_width, hfi_width, "HFIs");
+ printf("%-.3s+%-.*s+%-.4s+%-.*s+%-.5s+%-.*s\n",
+ dash, nqn_width, dash, dash, adr_width, dash, dash, hfi_width, dash);
+ for (ss = nbft->subsystem_ns_list; *ss; ss++) {
+ char hfi_buf[HFIS_LEN];
+
+ print_hfis(*ss, hfi_buf);
+ printf("%-3d|%-*.*s|%-4.4s|%-*.*s|%-5.5s|%-*.*s\n",
+ (*ss)->index, nqn_width, nqn_width, (*ss)->subsys_nqn,
+ (*ss)->transport, adr_width, adr_width, (*ss)->traddr,
+ (*ss)->trsvcid, hfi_width, hfi_width, hfi_buf);
+ }
+}
+
+static void normal_show_nbft(struct nbft_info *nbft, bool show_subsys,
+ bool show_hfi, bool show_discovery)
+{
+ printf("%s:\n", nbft->filename);
+ if ((!nbft->hfi_list || !*nbft->hfi_list) &&
+ (!nbft->security_list || !*nbft->security_list) &&
+ (!nbft->discovery_list || !*nbft->discovery_list) &&
+ (!nbft->subsystem_ns_list || !*nbft->subsystem_ns_list))
+ printf("(empty)\n");
+ else {
+ if (show_subsys)
+ print_nbft_subsys_info(nbft);
+ if (show_hfi)
+ print_nbft_hfi_info(nbft);
+ if (show_discovery)
+ print_nbft_discovery_info(nbft);
+ }
+}
+
+static void normal_show_nbfts(struct list_head *nbft_list, bool show_subsys,
+ bool show_hfi, bool show_discovery)
+{
+ bool not_first = false;
+ struct nbft_file_entry *entry;
+
+ list_for_each(nbft_list, entry, node) {
+ if (not_first)
+ printf("\n");
+ normal_show_nbft(entry->nbft, show_subsys, show_hfi, show_discovery);
+ not_first = true;
+ }
+}
+
+int show_nbft(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Display contents of the ACPI NBFT files.";
+ struct list_head nbft_list;
+ char *format = "normal";
+ char *nbft_path = NBFT_SYSFS_PATH;
+ enum nvme_print_flags flags;
+ int ret;
+ bool show_subsys = false, show_hfi = false, show_discovery = false;
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &format, "Output format: normal|json"),
+ OPT_FLAG("subsystem", 's', &show_subsys, "show NBFT subsystems"),
+ OPT_FLAG("hfi", 'H', &show_hfi, "show NBFT HFIs"),
+ OPT_FLAG("discovery", 'd', &show_discovery, "show NBFT discovery controllers"),
+ OPT_STRING("nbft-path", 0, "STR", &nbft_path, "user-defined path for NBFT tables"),
+ OPT_END()
+ };
+
+ ret = argconfig_parse(argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ ret = validate_output_format(format, &flags);
+ if (ret < 0)
+ return ret;
+
+ if (!(show_subsys || show_hfi || show_discovery))
+ show_subsys = show_hfi = show_discovery = true;
+
+ list_head_init(&nbft_list);
+ ret = read_nbft_files(&nbft_list, nbft_path);
+ if (!ret) {
+ if (flags == NORMAL)
+ normal_show_nbfts(&nbft_list, show_subsys, show_hfi, show_discovery);
+ else if (flags == JSON)
+ ret = json_show_nbfts(&nbft_list, show_subsys, show_hfi, show_discovery);
+ free_nbfts(&nbft_list);
+ }
+ return ret;
+}
diff --git a/plugins/nbft/nbft-plugin.h b/plugins/nbft/nbft-plugin.h
new file mode 100644
index 0000000..018349d
--- /dev/null
+++ b/plugins/nbft/nbft-plugin.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/nbft/nbft-plugin
+
+#if !defined(NBFT) || defined(CMD_HEADER_MULTI_READ)
+#define NBFT
+
+#include "cmd.h"
+
+PLUGIN(NAME("nbft", "ACPI NBFT table extensions", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("show", "Show contents of ACPI NBFT tables", show_nbft)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/netapp/netapp-nvme.c b/plugins/netapp/netapp-nvme.c
new file mode 100644
index 0000000..2ecdcc5
--- /dev/null
+++ b/plugins/netapp/netapp-nvme.c
@@ -0,0 +1,654 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2018 NetApp, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <stdio.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+
+#include "util/suffix.h"
+
+#define CREATE_CMD
+#include "netapp-nvme.h"
+
+#define ONTAP_C2_LOG_ID 0xC2
+#define ONTAP_C2_LOG_SIZE 4096
+#define ONTAP_LABEL_LEN 260
+#define ONTAP_NS_PATHLEN 525
+
+enum {
+ NNORMAL,
+ NJSON,
+ NCOLUMN,
+};
+
+enum {
+ ONTAP_C2_LOG_SUPPORTED_LSP = 0x0,
+ ONTAP_C2_LOG_NSINFO_LSP = 0x1,
+};
+
+enum {
+ ONTAP_VSERVER_TLV = 0x11,
+ ONTAP_VOLUME_TLV = 0x12,
+ ONTAP_NS_TLV = 0x13,
+};
+
+static const char *dev_path = "/dev/";
+
+struct smdevice_info {
+ unsigned int nsid;
+ struct nvme_id_ctrl ctrl;
+ struct nvme_id_ns ns;
+ char dev[265];
+};
+
+struct ontapdevice_info {
+ unsigned int nsid;
+ struct nvme_id_ctrl ctrl;
+ struct nvme_id_ns ns;
+ unsigned char uuid[NVME_UUID_LEN];
+ unsigned char log_data[ONTAP_C2_LOG_SIZE];
+ char dev[265];
+};
+
+#define ARRAY_LABEL_LEN 60
+#define VOLUME_LABEL_LEN 60
+
+/*
+ * Format of the string isn't tightly controlled yet. For now, squash UCS-2 into
+ * ASCII. dst buffer must be at least count + 1 bytes long
+ */
+static void netapp_convert_string(char *dst, char *src, unsigned int count)
+{
+ int i;
+
+ if (!dst || !src || !count)
+ return;
+
+ memset(dst, 0, count + 1);
+ for (i = 0; i < count; i++)
+ dst[i] = src[i * 2 + 1];
+ /* the json routines won't accept empty strings */
+ if (strlen(dst) == 0 && count)
+ dst[0] = ' ';
+}
+
+static void netapp_nguid_to_str(char *str, __u8 *nguid)
+{
+ int i;
+
+ memset(str, 0, 33);
+ for (i = 0; i < 16; i++)
+ str += sprintf(str, "%02x", nguid[i]);
+}
+
+static void netapp_get_ns_size(char *size, unsigned long long *lba,
+ struct nvme_id_ns *ns)
+{
+ __u8 lba_index;
+
+ nvme_id_ns_flbas_to_lbaf_inuse(ns->flbas, &lba_index);
+ *lba = 1ULL << ns->lbaf[lba_index].ds;
+ double nsze = le64_to_cpu(ns->nsze) * (*lba);
+ const char *s_suffix = suffix_si_get(&nsze);
+
+ sprintf(size, "%.2f%sB", nsze, s_suffix);
+}
+
+static void ontap_labels_to_str(char *dst, char *src, int count)
+{
+ int i;
+
+ memset(dst, 0, ONTAP_LABEL_LEN);
+ for (i = 0; i < count; i++) {
+ if (src[i] >= '!' && src[i] <= '~')
+ dst[i] = src[i];
+ else
+ break;
+ }
+ dst[i] = '\0';
+}
+
+static void netapp_get_ontap_labels(char *vsname, char *nspath,
+ unsigned char *log_data)
+{
+ int lsp, tlv, label_len;
+ char *vserver_name, *volume_name, *namespace_name;
+ char vol_name[ONTAP_LABEL_LEN], ns_name[ONTAP_LABEL_LEN];
+ const char *ontap_vol = "/vol/";
+ int i, j;
+
+ /* get the lsp */
+ lsp = (*(__u8 *)&log_data[16]) & 0x0F;
+ if (lsp != ONTAP_C2_LOG_NSINFO_LSP)
+ /* lsp not related to nsinfo */
+ return;
+
+ /* get the vserver tlv and name */
+ tlv = *(__u8 *)&log_data[32];
+ if (tlv == ONTAP_VSERVER_TLV) {
+ label_len = (*(__u16 *)&log_data[34]) * 4;
+ vserver_name = (char *)&log_data[36];
+ ontap_labels_to_str(vsname, vserver_name, label_len);
+ } else {
+ /* not the expected vserver tlv */
+ fprintf(stderr, "Unable to fetch ONTAP vserver name\n");
+ return;
+ }
+
+ i = 36 + label_len;
+ j = i + 2;
+ /* get the volume tlv and name */
+ tlv = *(__u8 *)&log_data[i];
+ if (tlv == ONTAP_VOLUME_TLV) {
+ label_len = (*(__u16 *)&log_data[j]) * 4;
+ volume_name = (char *)&log_data[j + 2];
+ ontap_labels_to_str(vol_name, volume_name, label_len);
+ } else {
+ /* not the expected volume tlv */
+ fprintf(stderr, "Unable to fetch ONTAP volume name\n");
+ return;
+ }
+
+ i += 4 + label_len;
+ j += 4 + label_len;
+ /* get the namespace tlv and name */
+ tlv = *(__u8 *)&log_data[i];
+ if (tlv == ONTAP_NS_TLV) {
+ label_len = (*(__u16 *)&log_data[j]) * 4;
+ namespace_name = (char *)&log_data[j + 2];
+ ontap_labels_to_str(ns_name, namespace_name, label_len);
+ } else {
+ /* not the expected namespace tlv */
+ fprintf(stderr, "Unable to fetch ONTAP namespace name\n");
+ return;
+ }
+
+ snprintf(nspath, ONTAP_NS_PATHLEN, "%s%s%s%s", ontap_vol,
+ vol_name, "/", ns_name);
+}
+
+static void netapp_smdevice_json(struct json_object *devices, char *devname,
+ char *arrayname, char *volname, int nsid, char *nguid,
+ char *ctrl, char *astate, char *size, long long lba,
+ long long nsze)
+{
+ struct json_object *device_attrs;
+
+ device_attrs = json_create_object();
+ json_object_add_value_string(device_attrs, "Device", devname);
+ json_object_add_value_string(device_attrs, "Array_Name", arrayname);
+ json_object_add_value_string(device_attrs, "Volume_Name", volname);
+ json_object_add_value_int(device_attrs, "NSID", nsid);
+ json_object_add_value_string(device_attrs, "Volume_ID", nguid);
+ json_object_add_value_string(device_attrs, "Controller", ctrl);
+ json_object_add_value_string(device_attrs, "Access_State", astate);
+ json_object_add_value_string(device_attrs, "Size", size);
+ json_object_add_value_int(device_attrs, "LBA_Data_Size", lba);
+ json_object_add_value_int(device_attrs, "Namespace_Size", nsze);
+
+ json_array_add_value_object(devices, device_attrs);
+}
+
+static void netapp_ontapdevice_json(struct json_object *devices, char *devname,
+ char *vsname, char *nspath, int nsid, char *uuid,
+ char *size, long long lba, long long nsze)
+{
+ struct json_object *device_attrs;
+
+ device_attrs = json_create_object();
+ json_object_add_value_string(device_attrs, "Device", devname);
+ json_object_add_value_string(device_attrs, "Vserver", vsname);
+ json_object_add_value_string(device_attrs, "Namespace_Path", nspath);
+ json_object_add_value_int(device_attrs, "NSID", nsid);
+ json_object_add_value_string(device_attrs, "UUID", uuid);
+ json_object_add_value_string(device_attrs, "Size", size);
+ json_object_add_value_int(device_attrs, "LBA_Data_Size", lba);
+ json_object_add_value_int(device_attrs, "Namespace_Size", nsze);
+
+ json_array_add_value_object(devices, device_attrs);
+}
+
+static void netapp_smdevices_print(struct smdevice_info *devices, int count, int format)
+{
+ struct json_object *root = NULL;
+ struct json_object *json_devices = NULL;
+ int i, slta;
+ char array_label[ARRAY_LABEL_LEN / 2 + 1];
+ char volume_label[VOLUME_LABEL_LEN / 2 + 1];
+ char nguid_str[33];
+ char basestr[] =
+ "%s, Array Name %s, Volume Name %s, NSID %d, Volume ID %s, Controller %c, Access State %s, %s\n";
+ char columnstr[] = "%-16s %-30s %-30s %4d %32s %c %-12s %9s\n";
+ char *formatstr = basestr; /* default to "normal" output format */
+ __u8 lba_index;
+
+ if (format == NCOLUMN) {
+ /* for column output, change output string and print column headers */
+ formatstr = columnstr;
+ printf("%-16s %-30s %-30s %-4s %-32s %-4s %-12s %-9s\n",
+ "Device", "Array Name", "Volume Name", "NSID",
+ "Volume ID", "Ctrl", "Access State", " Size");
+ printf("%-16s %-30s %-30s %-4s %-32s %-4s %-12s %-9s\n",
+ "----------------", "------------------------------",
+ "------------------------------", "----",
+ "--------------------------------", "----",
+ "------------", "---------");
+ } else if (format == NJSON) {
+ /* prepare for json output */
+ root = json_create_object();
+ json_devices = json_create_array();
+ }
+
+ for (i = 0; i < count; i++) {
+ nvme_id_ns_flbas_to_lbaf_inuse(devices[i].ns.flbas, &lba_index);
+ unsigned long long lba = 1ULL << devices[i].ns.lbaf[lba_index].ds;
+ double nsze = le64_to_cpu(devices[i].ns.nsze) * lba;
+ const char *s_suffix = suffix_si_get(&nsze);
+ char size[128];
+
+ sprintf(size, "%.2f%sB", nsze, s_suffix);
+ netapp_convert_string(array_label, (char *)&devices[i].ctrl.vs[20],
+ ARRAY_LABEL_LEN / 2);
+ slta = devices[i].ctrl.vs[0] & 0x1;
+ netapp_convert_string(volume_label, (char *)devices[i].ns.vs,
+ VOLUME_LABEL_LEN / 2);
+ netapp_nguid_to_str(nguid_str, devices[i].ns.nguid);
+ if (format == NJSON)
+ netapp_smdevice_json(json_devices, devices[i].dev,
+ array_label, volume_label, devices[i].nsid,
+ nguid_str, slta ? "A" : "B", "unknown", size,
+ lba, le64_to_cpu(devices[i].ns.nsze));
+ else
+ printf(formatstr, devices[i].dev, array_label,
+ volume_label, devices[i].nsid, nguid_str,
+ slta ? 'A' : 'B', "unknown", size);
+ }
+
+ if (format == NJSON) {
+ /* complete the json output */
+ json_object_add_value_array(root, "SMdevices", json_devices);
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+ }
+}
+
+static void netapp_ontapdevices_print(struct ontapdevice_info *devices,
+ int count, int format)
+{
+ struct json_object *root = NULL;
+ struct json_object *json_devices = NULL;
+ char vsname[ONTAP_LABEL_LEN] = " ";
+ char nspath[ONTAP_NS_PATHLEN] = " ";
+ unsigned long long lba;
+ char size[128];
+ char uuid_str[37] = " ";
+ int i;
+
+ char basestr[] = "%s, Vserver %s, Namespace Path %s, NSID %d, UUID %s, %s\n";
+ char columnstr[] = "%-16s %-25s %-50s %-4d %-38s %-9s\n";
+
+ /* default to 'normal' output format */
+ char *formatstr = basestr;
+
+ if (format == NCOLUMN) {
+ /* change output string and print column headers */
+ formatstr = columnstr;
+ printf("%-16s %-25s %-50s %-4s %-38s %-9s\n",
+ "Device", "Vserver", "Namespace Path",
+ "NSID", "UUID", "Size");
+ printf("%-16s %-25s %-50s %-4s %-38s %-9s\n",
+ "----------------", "-------------------------",
+ "--------------------------------------------------",
+ "----", "--------------------------------------",
+ "---------");
+ } else if (format == NJSON) {
+ /* prepare for json output */
+ root = json_create_object();
+ json_devices = json_create_array();
+ }
+
+ for (i = 0; i < count; i++) {
+
+ netapp_get_ns_size(size, &lba, &devices[i].ns);
+ nvme_uuid_to_string(devices[i].uuid, uuid_str);
+ netapp_get_ontap_labels(vsname, nspath, devices[i].log_data);
+
+ if (format == NJSON) {
+ netapp_ontapdevice_json(json_devices, devices[i].dev,
+ vsname, nspath, devices[i].nsid,
+ uuid_str, size, lba,
+ le64_to_cpu(devices[i].ns.nsze));
+ } else
+ printf(formatstr, devices[i].dev, vsname, nspath,
+ devices[i].nsid, uuid_str, size);
+ }
+
+ if (format == NJSON) {
+ /* complete the json output */
+ json_object_add_value_array(root, "ONTAPdevices", json_devices);
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+ }
+}
+
+static int nvme_get_ontap_c2_log(int fd, __u32 nsid, void *buf, __u32 buflen)
+{
+ struct nvme_passthru_cmd get_log;
+ int err;
+
+ memset(buf, 0, buflen);
+ memset(&get_log, 0, sizeof(struct nvme_passthru_cmd));
+
+ get_log.opcode = nvme_admin_get_log_page;
+ get_log.nsid = nsid;
+ get_log.addr = (__u64)(uintptr_t)buf;
+ get_log.data_len = buflen;
+
+ __u32 numd = (get_log.data_len >> 2) - 1;
+ __u32 numdu = numd >> 16;
+ __u32 numdl = numd & 0xFFFF;
+
+ get_log.cdw10 = ONTAP_C2_LOG_ID | (numdl << 16);
+ get_log.cdw10 |= ONTAP_C2_LOG_NSINFO_LSP << 8;
+ get_log.cdw11 = numdu;
+
+ err = nvme_submit_admin_passthru(fd, &get_log, NULL);
+ if (err) {
+ fprintf(stderr, "ioctl error %0x\n", err);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int netapp_smdevices_get_info(int fd, struct smdevice_info *item,
+ const char *dev)
+{
+ int err;
+
+ err = nvme_identify_ctrl(fd, &item->ctrl);
+ if (err) {
+ fprintf(stderr,
+ "Identify Controller failed to %s (%s)\n", dev,
+ err < 0 ? strerror(-err) :
+ nvme_status_to_string(err, false));
+ return 0;
+ }
+
+ if (strncmp("NetApp E-Series", item->ctrl.mn, 15) != 0)
+ return 0; /* not the right model of controller */
+
+ err = nvme_get_nsid(fd, &item->nsid);
+ err = nvme_identify_ns(fd, item->nsid, &item->ns);
+ if (err) {
+ fprintf(stderr,
+ "Unable to identify namespace for %s (%s)\n",
+ dev, err < 0 ? strerror(-err) :
+ nvme_status_to_string(err, false));
+ return 0;
+ }
+ strncpy(item->dev, dev, sizeof(item->dev) - 1);
+
+ return 1;
+}
+
+static int netapp_ontapdevices_get_info(int fd, struct ontapdevice_info *item,
+ const char *dev)
+{
+ int err;
+ void *nsdescs;
+
+ err = nvme_identify_ctrl(fd, &item->ctrl);
+ if (err) {
+ fprintf(stderr, "Identify Controller failed to %s (%s)\n",
+ dev, err < 0 ? strerror(-err) :
+ nvme_status_to_string(err, false));
+ return 0;
+ }
+
+ if (strncmp("NetApp ONTAP Controller", item->ctrl.mn, 23) != 0)
+ /* not the right controller model */
+ return 0;
+
+ err = nvme_get_nsid(fd, &item->nsid);
+
+ err = nvme_identify_ns(fd, item->nsid, &item->ns);
+ if (err) {
+ fprintf(stderr, "Unable to identify namespace for %s (%s)\n",
+ dev, err < 0 ? strerror(-err) :
+ nvme_status_to_string(err, false));
+ return 0;
+ }
+
+ if (posix_memalign(&nsdescs, getpagesize(), 0x1000)) {
+ fprintf(stderr, "Cannot allocate controller list payload\n");
+ return 0;
+ }
+
+ err = nvme_identify_ns_descs(fd, item->nsid, nsdescs);
+ if (err) {
+ fprintf(stderr, "Unable to identify namespace descriptor for %s (%s)\n",
+ dev, err < 0 ? strerror(-err) :
+ nvme_status_to_string(err, false));
+ free(nsdescs);
+ return 0;
+ }
+
+ memcpy(item->uuid, nsdescs + sizeof(struct nvme_ns_id_desc), sizeof(item->uuid));
+ free(nsdescs);
+
+ err = nvme_get_ontap_c2_log(fd, item->nsid, item->log_data, ONTAP_C2_LOG_SIZE);
+ if (err) {
+ fprintf(stderr, "Unable to get log page data for %s (%s)\n",
+ dev, err < 0 ? strerror(-err) :
+ nvme_status_to_string(err, false));
+ return 0;
+ }
+
+ strncpy(item->dev, dev, sizeof(item->dev) - 1);
+
+ return 1;
+}
+
+static int netapp_nvme_filter(const struct dirent *d)
+{
+ char path[264];
+ struct stat bd;
+ int ctrl, ns, partition;
+
+ if (d->d_name[0] == '.')
+ return 0;
+
+ if (strstr(d->d_name, "nvme")) {
+ snprintf(path, sizeof(path), "%s%s", dev_path, d->d_name);
+ if (stat(path, &bd))
+ return 0;
+ if (sscanf(d->d_name, "nvme%dn%d", &ctrl, &ns) != 2)
+ return 0;
+ if (sscanf(d->d_name, "nvme%dn%dp%d", &ctrl, &ns, &partition) == 3)
+ return 0;
+ return 1;
+ }
+ return 0;
+}
+
+static int netapp_output_format(char *format)
+{
+ if (!format)
+ return -EINVAL;
+ if (!strcmp(format, "normal"))
+ return NNORMAL;
+ if (!strcmp(format, "json"))
+ return NJSON;
+ if (!strcmp(format, "column"))
+ return NCOLUMN;
+ return -EINVAL;
+}
+
+/* handler for 'nvme netapp smdevices' */
+static int netapp_smdevices(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Display information about E-Series volumes.";
+
+ struct dirent **devices;
+ int num, i, fd, ret, fmt;
+ struct smdevice_info *smdevices;
+ char path[264];
+ int num_smdevices = 0;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json|column"),
+ OPT_END()
+ };
+
+ ret = argconfig_parse(argc, argv, desc, opts);
+ if (ret < 0)
+ return ret;
+
+ fmt = netapp_output_format(cfg.output_format);
+ if (fmt != NNORMAL && fmt != NCOLUMN && fmt != NJSON) {
+ fprintf(stderr, "Unrecognized output format: %s\n", cfg.output_format);
+ return -EINVAL;
+ }
+
+ num = scandir(dev_path, &devices, netapp_nvme_filter, alphasort);
+ if (num <= 0) {
+ fprintf(stderr, "No NVMe devices detected.\n");
+ return num;
+ }
+
+ smdevices = calloc(num, sizeof(*smdevices));
+ if (!smdevices) {
+ fprintf(stderr, "Unable to allocate memory for devices.\n");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < num; i++) {
+ snprintf(path, sizeof(path), "%s%s", dev_path,
+ devices[i]->d_name);
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "Unable to open %s: %s\n", path,
+ strerror(errno));
+ continue;
+ }
+
+ num_smdevices += netapp_smdevices_get_info(fd,
+ &smdevices[num_smdevices], path);
+ close(fd);
+ }
+
+ if (num_smdevices)
+ netapp_smdevices_print(smdevices, num_smdevices, fmt);
+
+ for (i = 0; i < num; i++)
+ free(devices[i]);
+ free(devices);
+ free(smdevices);
+ return 0;
+}
+
+/* handler for 'nvme netapp ontapdevices' */
+static int netapp_ontapdevices(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Display information about ONTAP devices.";
+ struct dirent **devices;
+ int num, i, fd, ret, fmt;
+ struct ontapdevice_info *ontapdevices;
+ char path[264];
+ int num_ontapdevices = 0;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json|column"),
+ OPT_END()
+ };
+
+ ret = argconfig_parse(argc, argv, desc, opts);
+ if (ret < 0)
+ return ret;
+
+ fmt = netapp_output_format(cfg.output_format);
+ if (fmt != NNORMAL && fmt != NCOLUMN && fmt != NJSON) {
+ fprintf(stderr, "Unrecognized output format: %s\n", cfg.output_format);
+ return -EINVAL;
+ }
+
+ num = scandir(dev_path, &devices, netapp_nvme_filter, alphasort);
+ if (num <= 0) {
+ fprintf(stderr, "No NVMe devices detected.\n");
+ return num;
+ }
+
+ ontapdevices = calloc(num, sizeof(*ontapdevices));
+ if (!ontapdevices) {
+ fprintf(stderr, "Unable to allocate memory for devices.\n");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < num; i++) {
+ snprintf(path, sizeof(path), "%s%s", dev_path,
+ devices[i]->d_name);
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "Unable to open %s: %s\n", path,
+ strerror(errno));
+ continue;
+ }
+
+ num_ontapdevices += netapp_ontapdevices_get_info(fd,
+ &ontapdevices[num_ontapdevices], path);
+
+ close(fd);
+ }
+
+ if (num_ontapdevices)
+ netapp_ontapdevices_print(ontapdevices, num_ontapdevices, fmt);
+
+ for (i = 0; i < num; i++)
+ free(devices[i]);
+ free(devices);
+ free(ontapdevices);
+ return 0;
+}
diff --git a/plugins/netapp/netapp-nvme.h b/plugins/netapp/netapp-nvme.h
new file mode 100644
index 0000000..73de4b4
--- /dev/null
+++ b/plugins/netapp/netapp-nvme.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/netapp/netapp-nvme
+
+#if !defined(NETAPP_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define NETAPP_NVME
+
+#include "cmd.h"
+
+PLUGIN(NAME("netapp", "NetApp vendor specific extensions", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("smdevices", "NetApp SMdevices", netapp_smdevices)
+ ENTRY("ontapdevices", "NetApp ONTAPdevices", netapp_ontapdevices)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/nvidia/nvidia-nvme.c b/plugins/nvidia/nvidia-nvme.c
new file mode 100644
index 0000000..71e0bc3
--- /dev/null
+++ b/plugins/nvidia/nvidia-nvme.c
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+
+#define CREATE_CMD
+#include "nvidia-nvme.h"
+
+struct nvme_vu_id_ctrl_field {
+ __u16 json_rpc_2_0_mjr;
+ __u16 json_rpc_2_0_mnr;
+ __u16 json_rpc_2_0_ter;
+ __u8 reserved0[1018];
+};
+
+static void json_nvidia_id_ctrl(struct nvme_vu_id_ctrl_field *id,
+ char *json_rpc_2_0_ver, struct json_object *root)
+{
+ json_object_add_value_string(root, "json_rpc_2_0_ver",
+ json_rpc_2_0_ver);
+}
+
+static void nvidia_id_ctrl(__u8 *vs, struct json_object *root)
+{
+ struct nvme_vu_id_ctrl_field *id = (struct nvme_vu_id_ctrl_field *)vs;
+ char json_rpc_2_0_ver[16] = { 0 };
+
+ snprintf(json_rpc_2_0_ver, sizeof(json_rpc_2_0_ver), "0x%04x%04x%04x",
+ le16_to_cpu(id->json_rpc_2_0_mjr),
+ le16_to_cpu(id->json_rpc_2_0_mnr),
+ le16_to_cpu(id->json_rpc_2_0_ter));
+
+ if (root) {
+ json_nvidia_id_ctrl(id, json_rpc_2_0_ver, root);
+ return;
+ }
+
+ printf("json_rpc_2_0_ver : %s\n", json_rpc_2_0_ver);
+}
+
+static int id_ctrl(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ return __id_ctrl(argc, argv, cmd, plugin, nvidia_id_ctrl);
+}
diff --git a/plugins/nvidia/nvidia-nvme.h b/plugins/nvidia/nvidia-nvme.h
new file mode 100644
index 0000000..3d870e1
--- /dev/null
+++ b/plugins/nvidia/nvidia-nvme.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/nvidia/nvidia-nvme
+
+#if !defined(NVIDIA_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define NVIDIA_NVME
+
+#include "cmd.h"
+
+PLUGIN(NAME("nvidia", "NVIDIA vendor specific extensions", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("id-ctrl", "Send NVMe Identify Controller", id_ctrl)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/ocp/meson.build b/plugins/ocp/meson.build
new file mode 100644
index 0000000..64447ff
--- /dev/null
+++ b/plugins/ocp/meson.build
@@ -0,0 +1,8 @@
+sources += [
+ 'plugins/ocp/ocp-utils.c',
+ 'plugins/ocp/ocp-nvme.c',
+ 'plugins/ocp/ocp-clear-features.c',
+ 'plugins/ocp/ocp-smart-extended-log.c',
+ 'plugins/ocp/ocp-fw-activation-history.c',
+]
+
diff --git a/plugins/ocp/ocp-clear-features.c b/plugins/ocp/ocp-clear-features.c
new file mode 100644
index 0000000..0f49584
--- /dev/null
+++ b/plugins/ocp/ocp-clear-features.c
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Authors: haro.panosyan@solidigm.com
+ * leonardo.da.cunha@solidigm.com
+ */
+
+#include <unistd.h>
+#include "ocp-utils.h"
+#include "nvme-print.h"
+
+static const __u8 OCP_FID_CLEAR_FW_ACTIVATION_HISTORY = 0xC1;
+static const __u8 OCP_FID_CLEAR_PCIE_CORRECTABLE_ERROR_COUNTERS = 0xC3;
+
+static int ocp_clear_feature(int argc, char **argv, const char *desc, const __u8 fid)
+{
+ __u32 result = 0;
+ __u32 clear = 1 << 31;
+ struct nvme_dev *dev;
+ int uuid_index = 0;
+ bool uuid = true;
+ int err;
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("no-uuid", 'n', NULL,
+ "Skip UUID index search (UUID index not required for OCP 1.0)"),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ if (opts[0].seen)
+ uuid = false;
+
+ if (uuid) {
+ /* OCP 2.0 requires UUID index support */
+ err = ocp_get_uuid_index(dev, &uuid_index);
+ if (err || !uuid_index) {
+ fprintf(stderr, "ERROR: No OCP UUID index found\n");
+ goto close_dev;
+ }
+ }
+
+ struct nvme_set_features_args args = {
+ .result = &result,
+ .data = NULL,
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .nsid = 0,
+ .cdw11 = clear,
+ .cdw12 = 0,
+ .cdw13 = 0,
+ .cdw15 = 0,
+ .data_len = 0,
+ .save = 0,
+ .uuidx = uuid_index,
+ .fid = fid,
+ };
+
+ err = nvme_set_features(&args);
+
+ if (err == 0)
+ printf("Success : %s\n", desc);
+ else if (err > 0)
+ nvme_show_status(err);
+ else
+ printf("Fail : %s\n", desc);
+close_dev:
+ /* Redundant close() to make static code analysis happy */
+ close(dev->direct.fd);
+ dev_close(dev);
+ return err;
+}
+
+int ocp_clear_fw_update_history(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "OCP Clear Firmware Update History";
+
+ return ocp_clear_feature(argc, argv, desc, OCP_FID_CLEAR_FW_ACTIVATION_HISTORY);
+}
+
+int ocp_clear_pcie_correctable_errors(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ const char *desc = "OCP Clear PCIe Correctable Error Counters";
+
+ return ocp_clear_feature(argc, argv, desc,
+ OCP_FID_CLEAR_PCIE_CORRECTABLE_ERROR_COUNTERS);
+}
diff --git a/plugins/ocp/ocp-clear-features.h b/plugins/ocp/ocp-clear-features.h
new file mode 100644
index 0000000..99766dd
--- /dev/null
+++ b/plugins/ocp/ocp-clear-features.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Authors: haro.panosyan@solidigm.com
+ * leonardo.da.cunha@solidigm.com
+ */
+
+int ocp_clear_fw_update_history(int argc, char **argv, struct command *cmd, struct plugin *plugin);
+
+int ocp_clear_pcie_correctable_errors(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin);
diff --git a/plugins/ocp/ocp-fw-activation-history.c b/plugins/ocp/ocp-fw-activation-history.c
new file mode 100644
index 0000000..ad96c6b
--- /dev/null
+++ b/plugins/ocp/ocp-fw-activation-history.c
@@ -0,0 +1,225 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: karl.dedow@solidigm.com
+ */
+
+#include "ocp-fw-activation-history.h"
+
+#include <errno.h>
+#include <stdio.h>
+
+#include "common.h"
+#include "nvme-print.h"
+
+#include "ocp-utils.h"
+
+static const unsigned char ocp_fw_activation_history_guid[16] = {
+ 0x6D, 0x79, 0x9a, 0x76,
+ 0xb4, 0xda, 0xf6, 0xa3,
+ 0xe2, 0x4d, 0xb2, 0x8a,
+ 0xac, 0xf3, 0x1c, 0xd1
+};
+
+struct __packed fw_activation_history_entry {
+ __u8 ver_num;
+ __u8 entry_length;
+ __u16 reserved1;
+ __u16 activation_count;
+ __u64 timestamp;
+ __u64 reserved2;
+ __u64 power_cycle_count;
+ char previous_fw[8];
+ char new_fw[8];
+ __u8 slot_number;
+ __u8 commit_action;
+ __u16 result;
+ __u8 reserved3[14];
+};
+
+struct __packed fw_activation_history {
+ __u8 log_id;
+ __u8 reserved1[3];
+ __u32 valid_entries;
+ struct fw_activation_history_entry entries[20];
+ __u8 reserved2[2790];
+ __u16 log_page_version;
+ __u64 log_page_guid[2];
+};
+
+static void ocp_fw_activation_history_normal(const struct fw_activation_history *fw_history)
+{
+ printf("Firmware History Log:\n");
+
+ printf(" %-26s%d\n", "log identifier:", fw_history->log_id);
+ printf(" %-26s%d\n", "valid entries:", le32_to_cpu(fw_history->valid_entries));
+
+ printf(" entries:\n");
+
+ for (int index = 0; index < fw_history->valid_entries; index++) {
+ const struct fw_activation_history_entry *entry = &fw_history->entries[index];
+
+ printf(" entry[%d]:\n", le32_to_cpu(index));
+ printf(" %-22s%d\n", "version number:", entry->ver_num);
+ printf(" %-22s%d\n", "entry length:", entry->entry_length);
+ printf(" %-22s%d\n", "activation count:",
+ le16_to_cpu(entry->activation_count));
+ printf(" %-22s%"PRIu64"\n", "timestamp:",
+ le64_to_cpu(entry->timestamp));
+ printf(" %-22s%"PRIu64"\n", "power cycle count:",
+ le64_to_cpu(entry->power_cycle_count));
+ printf(" %-22s%.*s\n", "previous firmware:", (int)sizeof(entry->previous_fw),
+ entry->previous_fw);
+ printf(" %-22s%.*s\n", "new firmware:", (int)sizeof(entry->new_fw),
+ entry->new_fw);
+ printf(" %-22s%d\n", "slot number:", entry->slot_number);
+ printf(" %-22s%d\n", "commit action type:", entry->commit_action);
+ printf(" %-22s%d\n", "result:", le16_to_cpu(entry->result));
+ }
+
+ printf(" %-26s%d\n", "log page version:",
+ le16_to_cpu(fw_history->log_page_version));
+
+ printf(" %-26s0x%"PRIx64"%"PRIx64"\n", "log page guid:",
+ le64_to_cpu(fw_history->log_page_guid[1]),
+ le64_to_cpu(fw_history->log_page_guid[0]));
+
+ printf("\n");
+}
+
+static void ocp_fw_activation_history_json(const struct fw_activation_history *fw_history)
+{
+ struct json_object *root = json_create_object();
+
+ json_object_add_value_uint(root, "log identifier", fw_history->log_id);
+ json_object_add_value_uint(root, "valid entries", le32_to_cpu(fw_history->valid_entries));
+
+ struct json_object *entries = json_create_array();
+
+ for (int index = 0; index < fw_history->valid_entries; index++) {
+ const struct fw_activation_history_entry *entry = &fw_history->entries[index];
+ struct json_object *entry_obj = json_create_object();
+
+ json_object_add_value_uint(entry_obj, "version number", entry->ver_num);
+ json_object_add_value_uint(entry_obj, "entry length", entry->entry_length);
+ json_object_add_value_uint(entry_obj, "activation count",
+ le16_to_cpu(entry->activation_count));
+ json_object_add_value_uint64(entry_obj, "timestamp",
+ le64_to_cpu(entry->timestamp));
+ json_object_add_value_uint(entry_obj, "power cycle count",
+ le64_to_cpu(entry->power_cycle_count));
+
+ struct json_object *fw = json_object_new_string_len(entry->previous_fw,
+ sizeof(entry->previous_fw));
+
+ json_object_add_value_object(entry_obj, "previous firmware", fw);
+
+ fw = json_object_new_string_len(entry->new_fw, sizeof(entry->new_fw));
+
+ json_object_add_value_object(entry_obj, "new firmware", fw);
+ json_object_add_value_uint(entry_obj, "slot number", entry->slot_number);
+ json_object_add_value_uint(entry_obj, "commit action type", entry->commit_action);
+ json_object_add_value_uint(entry_obj, "result", le16_to_cpu(entry->result));
+
+ json_array_add_value_object(entries, entry_obj);
+ }
+
+ json_object_add_value_array(root, "entries", entries);
+
+ json_object_add_value_uint(root, "log page version",
+ le16_to_cpu(fw_history->log_page_version));
+
+ char guid[2 * sizeof(fw_history->log_page_guid) + 3] = { 0 };
+
+ sprintf(guid, "0x%"PRIx64"%"PRIx64"",
+ le64_to_cpu(fw_history->log_page_guid[1]),
+ le64_to_cpu(fw_history->log_page_guid[0]));
+ json_object_add_value_string(root, "log page guid", guid);
+
+ json_print_object(root, NULL);
+ json_free_object(root);
+
+ printf("\n");
+}
+
+int ocp_fw_activation_history_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ const __u8 log_id = 0xC2;
+ const char *description = "Retrieves the OCP firmware activation history log.";
+
+ char *format = "normal";
+
+ OPT_ARGS(options) = {
+ OPT_FMT("output-format", 'o', &format, "output format : normal | json"),
+ OPT_END()
+ };
+
+ struct nvme_dev *dev = NULL;
+ int err = parse_and_open(&dev, argc, argv, description, options);
+
+ if (err)
+ return err;
+
+ int uuid_index = 0;
+
+ /*
+ * Best effort attempt at uuid. Otherwise, assume no index (i.e. 0)
+ * Log GUID check will ensure correctness of returned data
+ */
+ ocp_get_uuid_index(dev, &uuid_index);
+
+ struct fw_activation_history fw_history = { 0 };
+
+ struct nvme_get_log_args args = {
+ .lpo = 0,
+ .result = NULL,
+ .log = &fw_history,
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .lid = log_id,
+ .len = sizeof(fw_history),
+ .nsid = NVME_NSID_ALL,
+ .csi = NVME_CSI_NVM,
+ .lsi = NVME_LOG_LSI_NONE,
+ .lsp = 0,
+ .uuidx = uuid_index,
+ .rae = false,
+ .ot = false,
+ };
+
+ err = nvme_get_log(&args);
+
+ if (err)
+ nvme_show_status(err);
+
+ dev_close(dev);
+
+ int guid_cmp_res = memcmp(fw_history.log_page_guid, ocp_fw_activation_history_guid,
+ sizeof(ocp_fw_activation_history_guid));
+
+ if (!err && guid_cmp_res) {
+ fprintf(stderr,
+ "Error: Unexpected data. Log page guid does not match with expected.\n");
+ err = -EINVAL;
+ }
+
+ if (!err) {
+ enum nvme_print_flags print_flag;
+
+ err = validate_output_format(format, &print_flag);
+ if (err < 0) {
+ fprintf(stderr, "Error: Invalid output format.\n");
+ return err;
+ }
+
+ if (print_flag == JSON)
+ ocp_fw_activation_history_json(&fw_history);
+ else if (print_flag == NORMAL)
+ ocp_fw_activation_history_normal(&fw_history);
+ }
+
+ return err;
+}
diff --git a/plugins/ocp/ocp-fw-activation-history.h b/plugins/ocp/ocp-fw-activation-history.h
new file mode 100644
index 0000000..a7f9058
--- /dev/null
+++ b/plugins/ocp/ocp-fw-activation-history.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Authors: karl.dedow@solidigm.com
+ */
+
+#ifndef OCP_FIRMWARE_ACTIVATION_HISTORY_H
+#define OCP_FIRMWARE_ACTIVATION_HISTORY_H
+
+struct command;
+struct plugin;
+
+int ocp_fw_activation_history_log(int argc, char **argv,
+ struct command *cmd, struct plugin *plugin);
+
+#endif
diff --git a/plugins/ocp/ocp-nvme.c b/plugins/ocp/ocp-nvme.c
new file mode 100644
index 0000000..53ae0f4
--- /dev/null
+++ b/plugins/ocp/ocp-nvme.c
@@ -0,0 +1,2971 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Meta Platforms, Inc.
+ *
+ * Authors: Arthur Shau <arthurshau@meta.com>,
+ * Wei Zhang <wzhang@meta.com>,
+ * Venkat Ramesh <venkatraghavan@meta.com>
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "util/types.h"
+#include "nvme-print.h"
+
+#include "ocp-smart-extended-log.h"
+#include "ocp-clear-features.h"
+#include "ocp-fw-activation-history.h"
+
+#define CREATE_CMD
+#include "ocp-nvme.h"
+#include "ocp-utils.h"
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+/// Latency Monitor Log
+
+#define C3_LATENCY_MON_LOG_BUF_LEN 0x200
+#define C3_LATENCY_MON_OPCODE 0xC3
+#define C3_LATENCY_MON_VERSION 0x0001
+#define C3_GUID_LENGTH 16
+#define NVME_FEAT_OCP_LATENCY_MONITOR 0xC5
+
+#define C3_ACTIVE_BUCKET_TIMER_INCREMENT 5
+#define C3_ACTIVE_THRESHOLD_INCREMENT 5
+#define C3_MINIMUM_WINDOW_INCREMENT 100
+#define C3_BUCKET_NUM 4
+
+static __u8 lat_mon_guid[C3_GUID_LENGTH] = {
+ 0x92, 0x7a, 0xc0, 0x8c,
+ 0xd0, 0x84, 0x6c, 0x9c,
+ 0x70, 0x43, 0xe6, 0xd4,
+ 0x58, 0x5e, 0xd4, 0x85
+};
+
+#define READ 3
+#define WRITE 2
+#define TRIM 1
+#define RESERVED 0
+
+struct __packed ssd_latency_monitor_log {
+ __u8 feature_status; /* 0x00 */
+ __u8 rsvd1; /* 0x01 */
+ __le16 active_bucket_timer; /* 0x02 */
+ __le16 active_bucket_timer_threshold; /* 0x04 */
+ __u8 active_threshold_a; /* 0x06 */
+ __u8 active_threshold_b; /* 0x07 */
+ __u8 active_threshold_c; /* 0x08 */
+ __u8 active_threshold_d; /* 0x09 */
+ __le16 active_latency_config; /* 0x0A */
+ __u8 active_latency_min_window; /* 0x0C */
+ __u8 rsvd2[0x13]; /* 0x0D */
+
+ __le32 active_bucket_counter[4][4]; /* 0x20 - 0x5F */
+ __le64 active_latency_timestamp[4][3]; /* 0x60 - 0xBF */
+ __le16 active_measured_latency[4][3]; /* 0xC0 - 0xD7 */
+ __le16 active_latency_stamp_units; /* 0xD8 */
+ __u8 rsvd3[0x16]; /* 0xDA */
+
+ __le32 static_bucket_counter[4][4]; /* 0x0F0 - 0x12F */
+ __le64 static_latency_timestamp[4][3]; /* 0x130 - 0x18F */
+ __le16 static_measured_latency[4][3]; /* 0x190 - 0x1A7 */
+ __le16 static_latency_stamp_units; /* 0x1A8 */
+ __u8 rsvd4[0x16]; /* 0x1AA */
+
+ __le16 debug_log_trigger_enable; /* 0x1C0 */
+ __le16 debug_log_measured_latency; /* 0x1C2 */
+ __le64 debug_log_latency_stamp; /* 0x1C4 */
+ __le16 debug_log_ptr; /* 0x1CC */
+ __le16 debug_log_counter_trigger; /* 0x1CE */
+ __u8 debug_log_stamp_units; /* 0x1D0 */
+ __u8 rsvd5[0x1D]; /* 0x1D1 */
+
+ __le16 log_page_version; /* 0x1EE */
+ __u8 log_page_guid[0x10]; /* 0x1F0 */
+};
+
+struct __packed feature_latency_monitor {
+ __u16 active_bucket_timer_threshold;
+ __u8 active_threshold_a;
+ __u8 active_threshold_b;
+ __u8 active_threshold_c;
+ __u8 active_threshold_d;
+ __u16 active_latency_config;
+ __u8 active_latency_minimum_window;
+ __u16 debug_log_trigger_enable;
+ __u8 discard_debug_log;
+ __u8 latency_monitor_feature_enable;
+ __u8 reserved[4083];
+};
+
+static int ocp_print_C3_log_normal(struct nvme_dev *dev,
+ struct ssd_latency_monitor_log *log_data)
+{
+ char ts_buf[128];
+ int i, j;
+
+ printf("-Latency Monitor/C3 Log Page Data-\n");
+ printf(" Controller : %s\n", dev->name);
+ printf(" Feature Status 0x%x\n",
+ log_data->feature_status);
+ printf(" Active Bucket Timer %d min\n",
+ C3_ACTIVE_BUCKET_TIMER_INCREMENT *
+ le16_to_cpu(log_data->active_bucket_timer));
+ printf(" Active Bucket Timer Threshold %d min\n",
+ C3_ACTIVE_BUCKET_TIMER_INCREMENT *
+ le16_to_cpu(log_data->active_bucket_timer_threshold));
+ printf(" Active Threshold A %d ms\n",
+ C3_ACTIVE_THRESHOLD_INCREMENT *
+ le16_to_cpu(log_data->active_threshold_a+1));
+ printf(" Active Threshold B %d ms\n",
+ C3_ACTIVE_THRESHOLD_INCREMENT *
+ le16_to_cpu(log_data->active_threshold_b+1));
+ printf(" Active Threshold C %d ms\n",
+ C3_ACTIVE_THRESHOLD_INCREMENT *
+ le16_to_cpu(log_data->active_threshold_c+1));
+ printf(" Active Threshold D %d ms\n",
+ C3_ACTIVE_THRESHOLD_INCREMENT *
+ le16_to_cpu(log_data->active_threshold_d+1));
+ printf(" Active Latency Configuration 0x%x \n",
+ le16_to_cpu(log_data->active_latency_config));
+ printf(" Active Latency Minimum Window %d ms\n",
+ C3_MINIMUM_WINDOW_INCREMENT *
+ le16_to_cpu(log_data->active_latency_min_window));
+ printf(" Active Latency Stamp Units %d\n",
+ le16_to_cpu(log_data->active_latency_stamp_units));
+ printf(" Static Latency Stamp Units %d\n",
+ le16_to_cpu(log_data->static_latency_stamp_units));
+ printf(" Debug Log Trigger Enable %d\n",
+ le16_to_cpu(log_data->debug_log_trigger_enable));
+ printf(" Debug Log Measured Latency %d\n",
+ le16_to_cpu(log_data->debug_log_measured_latency));
+ if (le64_to_cpu(log_data->debug_log_latency_stamp) == -1) {
+ printf(" Debug Log Latency Time Stamp N/A\n");
+ } else {
+ convert_ts(le64_to_cpu(log_data->debug_log_latency_stamp), ts_buf);
+ printf(" Debug Log Latency Time Stamp %s\n", ts_buf);
+ }
+ printf(" Debug Log Pointer %d\n",
+ le16_to_cpu(log_data->debug_log_ptr));
+ printf(" Debug Counter Trigger Source %d\n",
+ le16_to_cpu(log_data->debug_log_counter_trigger));
+ printf(" Debug Log Stamp Units %d\n",
+ le16_to_cpu(log_data->debug_log_stamp_units));
+ printf(" Log Page Version %d\n",
+ le16_to_cpu(log_data->log_page_version));
+
+ char guid[(C3_GUID_LENGTH * 2) + 1];
+ char *ptr = &guid[0];
+
+ for (i = C3_GUID_LENGTH - 1; i >= 0; i--)
+ ptr += sprintf(ptr, "%02X", log_data->log_page_guid[i]);
+
+ printf(" Log Page GUID %s\n", guid);
+ printf("\n");
+
+ printf(" Read Write Deallocate/Trim\n");
+ for (i = 0; i < C3_BUCKET_NUM; i++) {
+ printf(" Active Bucket Counter: Bucket %d %27d %27d %27d\n",
+ i,
+ le32_to_cpu(log_data->active_bucket_counter[i][READ]),
+ le32_to_cpu(log_data->active_bucket_counter[i][WRITE]),
+ le32_to_cpu(log_data->active_bucket_counter[i][TRIM]));
+ }
+
+ for (i = 0; i < C3_BUCKET_NUM; i++) {
+ printf(" Active Latency Time Stamp: Bucket %d ", i);
+ for (j = 2; j >= 0; j--) {
+ if (le64_to_cpu(log_data->active_latency_timestamp[3-i][j]) == -1) {
+ printf(" N/A ");
+ } else {
+ convert_ts(le64_to_cpu(log_data->active_latency_timestamp[3-i][j]), ts_buf);
+ printf("%s ", ts_buf);
+ }
+ }
+ printf("\n");
+ }
+
+ for (i = 0; i < C3_BUCKET_NUM; i++) {
+ printf(" Active Measured Latency: Bucket %d %27d ms %27d ms %27d ms\n",
+ i,
+ le16_to_cpu(log_data->active_measured_latency[3-i][READ-1]),
+ le16_to_cpu(log_data->active_measured_latency[3-i][WRITE-1]),
+ le16_to_cpu(log_data->active_measured_latency[3-i][TRIM-1]));
+ }
+
+ printf("\n");
+ for (i = 0; i < C3_BUCKET_NUM; i++) {
+ printf(" Static Bucket Counter: Bucket %d %27d %27d %27d\n",
+ i,
+ le32_to_cpu(log_data->static_bucket_counter[i][READ]),
+ le32_to_cpu(log_data->static_bucket_counter[i][WRITE]),
+ le32_to_cpu(log_data->static_bucket_counter[i][TRIM]));
+ }
+
+ for (i = 0; i < C3_BUCKET_NUM; i++) {
+ printf(" Static Latency Time Stamp: Bucket %d ", i);
+ for (j = 2; j >= 0; j--) {
+ if (le64_to_cpu(log_data->static_latency_timestamp[3-i][j]) == -1) {
+ printf(" N/A ");
+ } else {
+ convert_ts(le64_to_cpu(log_data->static_latency_timestamp[3-i][j]), ts_buf);
+ printf("%s ", ts_buf);
+ }
+ }
+ printf("\n");
+ }
+
+ for (i = 0; i < C3_BUCKET_NUM; i++) {
+ printf(" Static Measured Latency: Bucket %d %27d ms %27d ms %27d ms\n",
+ i,
+ le16_to_cpu(log_data->static_measured_latency[3-i][READ-1]),
+ le16_to_cpu(log_data->static_measured_latency[3-i][WRITE-1]),
+ le16_to_cpu(log_data->static_measured_latency[3-i][TRIM-1]));
+ }
+
+ return 0;
+}
+
+static void ocp_print_C3_log_json(struct ssd_latency_monitor_log *log_data)
+{
+ struct json_object *root;
+ char ts_buf[128];
+ char buf[128];
+ int i, j;
+ char *operation[3] = {"Trim", "Write", "Read"};
+
+ root = json_create_object();
+
+ json_object_add_value_uint(root, "Feature Status",
+ log_data->feature_status);
+ json_object_add_value_uint(root, "Active Bucket Timer",
+ C3_ACTIVE_BUCKET_TIMER_INCREMENT *
+ le16_to_cpu(log_data->active_bucket_timer));
+ json_object_add_value_uint(root, "Active Bucket Timer Threshold",
+ C3_ACTIVE_BUCKET_TIMER_INCREMENT *
+ le16_to_cpu(log_data->active_bucket_timer_threshold));
+ json_object_add_value_uint(root, "Active Threshold A",
+ C3_ACTIVE_THRESHOLD_INCREMENT *
+ le16_to_cpu(log_data->active_threshold_a + 1));
+ json_object_add_value_uint(root, "Active Threshold B",
+ C3_ACTIVE_THRESHOLD_INCREMENT *
+ le16_to_cpu(log_data->active_threshold_b + 1));
+ json_object_add_value_uint(root, "Active Threshold C",
+ C3_ACTIVE_THRESHOLD_INCREMENT *
+ le16_to_cpu(log_data->active_threshold_c + 1));
+ json_object_add_value_uint(root, "Active Threshold D",
+ C3_ACTIVE_THRESHOLD_INCREMENT *
+ le16_to_cpu(log_data->active_threshold_d + 1));
+ json_object_add_value_uint(root, "Active Latency Configuration",
+ le16_to_cpu(log_data->active_latency_config));
+ json_object_add_value_uint(root, "Active Latency Minimum Window",
+ C3_MINIMUM_WINDOW_INCREMENT *
+ le16_to_cpu(log_data->active_latency_min_window));
+
+ for (i = 0; i < C3_BUCKET_NUM; i++) {
+ struct json_object *bucket;
+
+ bucket = json_create_object();
+ sprintf(buf, "Active Bucket Counter: Bucket %d", i);
+ for (j = 2; j >= 0; j--) {
+ json_object_add_value_uint(bucket, operation[j],
+ le32_to_cpu(log_data->active_bucket_counter[i][j+1]));
+ }
+ json_object_add_value_object(root, buf, bucket);
+ }
+
+ for (i = 0; i < C3_BUCKET_NUM; i++) {
+ struct json_object *bucket;
+
+ bucket = json_create_object();
+ sprintf(buf, "Active Latency Time Stamp: Bucket %d", i);
+ for (j = 2; j >= 0; j--) {
+ if (le64_to_cpu(log_data->active_latency_timestamp[3-i][j]) == -1) {
+ json_object_add_value_string(bucket, operation[j], "NA");
+ } else {
+ convert_ts(le64_to_cpu(log_data->active_latency_timestamp[3-i][j]), ts_buf);
+ json_object_add_value_string(bucket, operation[j], ts_buf);
+ }
+ }
+ json_object_add_value_object(root, buf, bucket);
+ }
+
+ for (i = 0; i < C3_BUCKET_NUM; i++) {
+ struct json_object *bucket;
+
+ bucket = json_create_object();
+ sprintf(buf, "Active Measured Latency: Bucket %d", i);
+ for (j = 2; j >= 0; j--) {
+ json_object_add_value_uint(bucket, operation[j],
+ le16_to_cpu(log_data->active_measured_latency[3-i][j]));
+ }
+ json_object_add_value_object(root, buf, bucket);
+ }
+
+ json_object_add_value_uint(root, "Active Latency Stamp Units",
+ le16_to_cpu(log_data->active_latency_stamp_units));
+
+ for (i = 0; i < C3_BUCKET_NUM; i++) {
+ struct json_object *bucket;
+
+ bucket = json_create_object();
+ sprintf(buf, "Static Bucket Counter: Bucket %d", i);
+ for (j = 2; j >= 0; j--) {
+ json_object_add_value_uint(bucket, operation[j],
+ le32_to_cpu(log_data->static_bucket_counter[i][j+1]));
+ }
+ json_object_add_value_object(root, buf, bucket);
+ }
+
+ for (i = 0; i < C3_BUCKET_NUM; i++) {
+ struct json_object *bucket;
+
+ bucket = json_create_object();
+ sprintf(buf, "Static Latency Time Stamp: Bucket %d", i);
+ for (j = 2; j >= 0; j--) {
+ if (le64_to_cpu(log_data->static_latency_timestamp[3-i][j]) == -1) {
+ json_object_add_value_string(bucket, operation[j], "NA");
+ } else {
+ convert_ts(le64_to_cpu(log_data->static_latency_timestamp[3-i][j]), ts_buf);
+ json_object_add_value_string(bucket, operation[j], ts_buf);
+ }
+ }
+ json_object_add_value_object(root, buf, bucket);
+ }
+
+ for (i = 0; i < C3_BUCKET_NUM; i++) {
+ struct json_object *bucket;
+
+ bucket = json_create_object();
+ sprintf(buf, "Static Measured Latency: Bucket %d", i);
+ for (j = 2; j >= 0; j--) {
+ json_object_add_value_uint(bucket, operation[j],
+ le16_to_cpu(log_data->static_measured_latency[3-i][j]));
+ }
+ json_object_add_value_object(root, buf, bucket);
+ }
+
+ json_object_add_value_uint(root, "Static Latency Stamp Units",
+ le16_to_cpu(log_data->static_latency_stamp_units));
+ json_object_add_value_uint(root, "Debug Log Trigger Enable",
+ le16_to_cpu(log_data->debug_log_trigger_enable));
+ json_object_add_value_uint(root, "Debug Log Measured Latency",
+ le16_to_cpu(log_data->debug_log_measured_latency));
+ if (le64_to_cpu(log_data->debug_log_latency_stamp) == -1) {
+ json_object_add_value_string(root, "Debug Log Latency Time Stamp", "NA");
+ } else {
+ convert_ts(le64_to_cpu(log_data->debug_log_latency_stamp), ts_buf);
+ json_object_add_value_string(root, "Debug Log Latency Time Stamp", ts_buf);
+ }
+ json_object_add_value_uint(root, "Debug Log Pointer",
+ le16_to_cpu(log_data->debug_log_ptr));
+ json_object_add_value_uint(root, "Debug Counter Trigger Source",
+ le16_to_cpu(log_data->debug_log_counter_trigger));
+ json_object_add_value_uint(root, "Debug Log Stamp Units",
+ le16_to_cpu(log_data->debug_log_stamp_units));
+ json_object_add_value_uint(root, "Log Page Version",
+ le16_to_cpu(log_data->log_page_version));
+
+ char guid[(C3_GUID_LENGTH * 2) + 1];
+ char *ptr = &guid[0];
+
+ for (i = C3_GUID_LENGTH - 1; i >= 0; i--)
+ ptr += sprintf(ptr, "%02X", log_data->log_page_guid[i]);
+
+ json_object_add_value_string(root, "Log Page GUID", guid);
+
+ json_print_object(root, NULL);
+ printf("\n");
+
+ json_free_object(root);
+}
+
+static int get_c3_log_page(struct nvme_dev *dev, char *format)
+{
+ struct ssd_latency_monitor_log *log_data;
+ enum nvme_print_flags fmt;
+ int ret;
+ __u8 *data;
+ int i;
+
+ ret = validate_output_format(format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR : OCP : invalid output format\n");
+ return ret;
+ }
+
+ data = malloc(sizeof(__u8) * C3_LATENCY_MON_LOG_BUF_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR : OCP : malloc : %s\n", strerror(errno));
+ return -1;
+ }
+ memset(data, 0, sizeof(__u8) * C3_LATENCY_MON_LOG_BUF_LEN);
+
+ ret = nvme_get_log_simple(dev_fd(dev), C3_LATENCY_MON_OPCODE,
+ C3_LATENCY_MON_LOG_BUF_LEN, data);
+
+ if (strcmp(format, "json"))
+ fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret, false), ret);
+
+ if (!ret) {
+ log_data = (struct ssd_latency_monitor_log *)data;
+
+ /* check log page version */
+ if (log_data->log_page_version != C3_LATENCY_MON_VERSION) {
+ fprintf(stderr,
+ "ERROR : OCP : invalid latency monitor version\n");
+ ret = -1;
+ goto out;
+ }
+
+ /*
+ * check log page guid
+ * Verify GUID matches
+ */
+ for (i = 0; i < 16; i++) {
+ if (lat_mon_guid[i] != log_data->log_page_guid[i]) {
+ int j;
+
+ fprintf(stderr, "ERROR : OCP : Unknown GUID in C3 Log Page data\n");
+ fprintf(stderr, "ERROR : OCP : Expected GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", lat_mon_guid[j]);
+
+ fprintf(stderr, "\nERROR : OCP : Actual GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", log_data->log_page_guid[j]);
+ fprintf(stderr, "\n");
+
+ ret = -1;
+ goto out;
+ }
+ }
+
+ switch (fmt) {
+ case NORMAL:
+ ocp_print_C3_log_normal(dev, log_data);
+ break;
+ case JSON:
+ ocp_print_C3_log_json(log_data);
+ break;
+ default:
+ fprintf(stderr, "unhandled output format\n");
+
+ }
+ } else {
+ fprintf(stderr,
+ "ERROR : OCP : Unable to read C3 data from buffer\n");
+ }
+
+out:
+ free(data);
+ return ret;
+}
+
+static int ocp_latency_monitor_log(int argc, char **argv,
+ struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Retrieve latency monitor log data.";
+ struct nvme_dev *dev;
+ int ret = 0;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format,
+ "output Format: normal|json"),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ ret = get_c3_log_page(dev, cfg.output_format);
+ if (ret)
+ fprintf(stderr,
+ "ERROR : OCP : Failure reading the C3 Log Page, ret = %d\n",
+ ret);
+
+ dev_close(dev);
+ return ret;
+}
+
+int ocp_set_latency_monitor_feature(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ int err = -1;
+ struct nvme_dev *dev;
+ __u32 result;
+ struct feature_latency_monitor buf = {0,};
+ __u32 nsid = NVME_NSID_ALL;
+ struct stat nvme_stat;
+ struct nvme_id_ctrl ctrl;
+
+ const char *desc = "Set Latency Monitor feature.";
+ const char *active_bucket_timer_threshold = "This is the value that loads the Active Bucket Timer Threshold.";
+ const char *active_threshold_a = "This is the value that loads into the Active Threshold A.";
+ const char *active_threshold_b = "This is the value that loads into the Active Threshold B.";
+ const char *active_threshold_c = "This is the value that loads into the Active Threshold C.";
+ const char *active_threshold_d = "This is the value that loads into the Active Threshold D.";
+ const char *active_latency_config = "This is the value that loads into the Active Latency Configuration.";
+ const char *active_latency_minimum_window = "This is the value that loads into the Active Latency Minimum Window.";
+ const char *debug_log_trigger_enable = "This is the value that loads into the Debug Log Trigger Enable.";
+ const char *discard_debug_log = "Discard Debug Log.";
+ const char *latency_monitor_feature_enable = "Latency Monitor Feature Enable.";
+
+ struct config {
+ __u16 active_bucket_timer_threshold;
+ __u8 active_threshold_a;
+ __u8 active_threshold_b;
+ __u8 active_threshold_c;
+ __u8 active_threshold_d;
+ __u16 active_latency_config;
+ __u8 active_latency_minimum_window;
+ __u16 debug_log_trigger_enable;
+ __u8 discard_debug_log;
+ __u8 latency_monitor_feature_enable;
+ };
+
+ struct config cfg = {
+ .active_bucket_timer_threshold = 0x7E0,
+ .active_threshold_a = 0x5,
+ .active_threshold_b = 0x13,
+ .active_threshold_c = 0x1E,
+ .active_threshold_d = 0x2E,
+ .active_latency_config = 0xFFF,
+ .active_latency_minimum_window = 0xA,
+ .debug_log_trigger_enable = 0,
+ .discard_debug_log = 0,
+ .latency_monitor_feature_enable = 0x7,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("active_bucket_timer_threshold", 't', &cfg.active_bucket_timer_threshold, active_bucket_timer_threshold),
+ OPT_UINT("active_threshold_a", 'a', &cfg.active_threshold_a, active_threshold_a),
+ OPT_UINT("active_threshold_b", 'b', &cfg.active_threshold_b, active_threshold_b),
+ OPT_UINT("active_threshold_c", 'c', &cfg.active_threshold_c, active_threshold_c),
+ OPT_UINT("active_threshold_d", 'd', &cfg.active_threshold_d, active_threshold_d),
+ OPT_UINT("active_latency_config", 'f', &cfg.active_latency_config, active_latency_config),
+ OPT_UINT("active_latency_minimum_window", 'w', &cfg.active_latency_minimum_window, active_latency_minimum_window),
+ OPT_UINT("debug_log_trigger_enable", 'r', &cfg.debug_log_trigger_enable, debug_log_trigger_enable),
+ OPT_UINT("discard_debug_log", 'l', &cfg.discard_debug_log, discard_debug_log),
+ OPT_UINT("latency_monitor_feature_enable", 'e', &cfg.latency_monitor_feature_enable, latency_monitor_feature_enable),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = fstat(dev_fd(dev), &nvme_stat);
+ if (err < 0)
+ return err;
+
+ if (S_ISBLK(nvme_stat.st_mode)) {
+ err = nvme_get_nsid(dev_fd(dev), &nsid);
+ if (err < 0) {
+ perror("invalid-namespace-id");
+ return err;
+ }
+ }
+
+ err = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (err)
+ return err;
+
+ memset(&buf, 0, sizeof(struct feature_latency_monitor));
+
+ buf.active_bucket_timer_threshold = cfg.active_bucket_timer_threshold;
+ buf.active_threshold_a = cfg.active_threshold_a;
+ buf.active_threshold_b = cfg.active_threshold_b;
+ buf.active_threshold_c = cfg.active_threshold_c;
+ buf.active_threshold_d = cfg.active_threshold_d;
+ buf.active_latency_config = cfg.active_latency_config;
+ buf.active_latency_minimum_window = cfg.active_latency_minimum_window;
+ buf.debug_log_trigger_enable = cfg.debug_log_trigger_enable;
+ buf.discard_debug_log = cfg.discard_debug_log;
+ buf.latency_monitor_feature_enable = cfg.latency_monitor_feature_enable;
+
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = NVME_FEAT_OCP_LATENCY_MONITOR,
+ .nsid = 0,
+ .cdw12 = 0,
+ .save = 1,
+ .data_len = sizeof(struct feature_latency_monitor),
+ .data = (void *)&buf,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ err = nvme_set_features(&args);
+ if (err < 0) {
+ perror("set-feature");
+ } else if (!err) {
+ printf("NVME_FEAT_OCP_LATENCY_MONITOR: 0x%02x\n", NVME_FEAT_OCP_LATENCY_MONITOR);
+ printf("active bucket timer threshold: 0x%x\n", buf.active_bucket_timer_threshold);
+ printf("active threshold a: 0x%x\n", buf.active_threshold_a);
+ printf("active threshold b: 0x%x\n", buf.active_threshold_b);
+ printf("active threshold c: 0x%x\n", buf.active_threshold_c);
+ printf("active threshold d: 0x%x\n", buf.active_threshold_d);
+ printf("active latency config: 0x%x\n", buf.active_latency_config);
+ printf("active latency minimum window: 0x%x\n", buf.active_latency_minimum_window);
+ printf("debug log trigger enable: 0x%x\n", buf.debug_log_trigger_enable);
+ printf("discard debug log: 0x%x\n", buf.discard_debug_log);
+ printf("latency monitor feature enable: 0x%x\n", buf.latency_monitor_feature_enable);
+ } else if (err > 0) {
+ fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(err, false), err);
+ }
+
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+/// EOL/PLP Failure Mode
+
+static const char *eol_plp_failure_mode_to_string(__u8 mode)
+{
+ switch (mode) {
+ case 1:
+ return "Read only mode (ROM)";
+ case 2:
+ return "Write through mode (WTM)";
+ case 3:
+ return "Normal mode";
+ default:
+ break;
+ }
+
+ return "Reserved";
+}
+
+static int eol_plp_failure_mode_get(struct nvme_dev *dev, const __u32 nsid,
+ const __u8 fid, __u8 sel)
+{
+ __u32 result;
+ int err;
+
+ struct nvme_get_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = fid,
+ .nsid = nsid,
+ .sel = sel,
+ .cdw11 = 0,
+ .uuidx = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ err = nvme_get_features(&args);
+ if (!err) {
+ nvme_show_result("End of Life Behavior (feature: %#0*x): %#0*x (%s: %s)",
+ fid ? 4 : 2, fid, result ? 10 : 8, result,
+ nvme_select_to_string(sel),
+ eol_plp_failure_mode_to_string(result));
+ if (sel == NVME_GET_FEATURES_SEL_SUPPORTED)
+ nvme_show_select_result(fid, result);
+ } else {
+ nvme_show_error("Could not get feature: %#0*x.", fid ? 4 : 2, fid);
+ }
+
+ return err;
+}
+
+static int eol_plp_failure_mode_set(struct nvme_dev *dev, const __u32 nsid,
+ const __u8 fid, __u8 mode, bool save,
+ bool uuid)
+{
+ __u32 result;
+ int err;
+ int uuid_index = 0;
+
+ if (uuid) {
+ /* OCP 2.0 requires UUID index support */
+ err = ocp_get_uuid_index(dev, &uuid_index);
+ if (err || !uuid_index) {
+ nvme_show_error("ERROR: No OCP UUID index found");
+ return err;
+ }
+ }
+
+
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = fid,
+ .nsid = nsid,
+ .cdw11 = mode << 30,
+ .cdw12 = 0,
+ .save = save,
+ .uuidx = uuid_index,
+ .cdw15 = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ err = nvme_set_features(&args);
+ if (err > 0) {
+ nvme_show_status(err);
+ } else if (err < 0) {
+ nvme_show_perror("Define EOL/PLP failure mode");
+ fprintf(stderr, "Command failed while parsing.\n");
+ } else {
+ nvme_show_result("Successfully set mode (feature: %#0*x): %#0*x (%s: %s).",
+ fid ? 4 : 2, fid, mode ? 10 : 8, mode,
+ save ? "Save" : "Not save",
+ eol_plp_failure_mode_to_string(mode));
+ }
+
+ return err;
+}
+
+static int eol_plp_failure_mode(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ const char *desc = "Define EOL or PLP circuitry failure mode.\n"
+ "No argument prints current mode.";
+ const char *mode = "[0-3]: default/rom/wtm/normal";
+ const char *save = "Specifies that the controller shall save the attribute";
+ const char *sel = "[0-3,8]: current/default/saved/supported/changed";
+ const __u32 nsid = 0;
+ const __u8 fid = 0xc2;
+ struct nvme_dev *dev;
+ int err;
+
+ struct config {
+ __u8 mode;
+ bool save;
+ __u8 sel;
+ };
+
+ struct config cfg = {
+ .mode = 0,
+ .save = false,
+ .sel = 0,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_BYTE("mode", 'm', &cfg.mode, mode),
+ OPT_FLAG("save", 's', &cfg.save, save),
+ OPT_BYTE("sel", 'S', &cfg.sel, sel),
+ OPT_FLAG("no-uuid", 'n', NULL,
+ "Skip UUID index search (UUID index not required for OCP 1.0)"),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ if (argconfig_parse_seen(opts, "mode"))
+ err = eol_plp_failure_mode_set(dev, nsid, fid, cfg.mode,
+ cfg.save,
+ !argconfig_parse_seen(opts, "no-uuid"));
+ else
+ err = eol_plp_failure_mode_get(dev, nsid, fid, cfg.sel);
+
+ dev_close(dev);
+
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+/// Telemetry Log
+
+#define TELEMETRY_HEADER_SIZE 512
+#define TELEMETRY_BYTE_PER_BLOCK 512
+#define TELEMETRY_TRANSFER_SIZE 1024
+#define FILE_NAME_SIZE 2048
+
+enum TELEMETRY_TYPE {
+ TELEMETRY_TYPE_NONE = 0,
+ TELEMETRY_TYPE_HOST = 7,
+ TELEMETRY_TYPE_CONTROLLER = 8,
+ TELEMETRY_TYPE_HOST_0 = 9,
+ TELEMETRY_TYPE_HOST_1 = 10,
+};
+
+struct telemetry_initiated_log {
+ __u8 LogIdentifier;
+ __u8 Reserved1[4];
+ __u8 IEEE[3];
+ __le16 DataArea1LastBlock;
+ __le16 DataArea2LastBlock;
+ __le16 DataArea3LastBlock;
+ __u8 Reserved2[368];
+ __u8 DataAvailable;
+ __u8 DataGenerationNumber;
+ __u8 ReasonIdentifier[128];
+};
+
+struct telemetry_data_area_1 {
+ __le16 major_version;
+ __le16 minor_version;
+ __u8 reserved1[4];
+ __le64 timestamp;
+ __u8 log_page_guid[16];
+ __u8 no_of_tps_supp;
+ __u8 tps;
+ __u8 reserved2[6];
+ __le16 sls;
+ __u8 reserved3[8];
+ __le16 fw_revision;
+ __u8 reserved4[32];
+ __le16 da1_stat_start;
+ __le16 da1_stat_size;
+ __le16 da2_stat_start;
+ __le16 da2_stat_size;
+ __u8 reserved5[32];
+ __u8 event_fifo_da[16];
+ __le64 event_fifo_start[16];
+ __le64 event_fifo_size[16];
+ __u8 reserved6[80];
+ __u8 smart_health_info[512];
+ __u8 smart_health_info_extended[512];
+};
+static void get_serial_number(struct nvme_id_ctrl *ctrl, char *sn)
+{
+ int i;
+ /* Remove trailing spaces from the name */
+ for (i = 0; i < sizeof(ctrl->sn); i++) {
+ if (ctrl->sn[i] == ' ')
+ break;
+ sn[i] = ctrl->sn[i];
+ }
+}
+
+static int get_telemetry_header(struct nvme_dev *dev, __u32 ns, __u8 tele_type,
+ __u32 data_len, void *data, __u8 nLSP, __u8 nRAE)
+{
+ struct nvme_passthru_cmd cmd = {
+ .opcode = nvme_admin_get_log_page,
+ .nsid = ns,
+ .addr = (__u64)(uintptr_t) data,
+ .data_len = data_len,
+ };
+
+ __u32 numd = (data_len >> 2) - 1;
+ __u16 numdu = numd >> 16;
+ __u16 numdl = numd & 0xffff;
+
+ cmd.cdw10 = tele_type | (nLSP & 0x0F) << 8 | (nRAE & 0x01) << 15 | (numdl & 0xFFFF) << 16;
+ cmd.cdw11 = numdu;
+ cmd.cdw12 = 0;
+ cmd.cdw13 = 0;
+ cmd.cdw14 = 0;
+
+ return nvme_submit_admin_passthru(dev_fd(dev), &cmd, NULL);
+}
+
+static void print_telemetry_header(struct telemetry_initiated_log *logheader,
+ int tele_type)
+{
+ if (logheader) {
+ unsigned int i = 0, j = 0;
+
+ if (tele_type == TELEMETRY_TYPE_HOST)
+ printf("============ Telemetry Host Header ============\n");
+ else
+ printf("========= Telemetry Controller Header =========\n");
+
+ printf("Log Identifier : 0x%02X\n", logheader->LogIdentifier);
+ printf("IEEE : 0x%02X%02X%02X\n",
+ logheader->IEEE[0], logheader->IEEE[1], logheader->IEEE[2]);
+ printf("Data Area 1 Last Block : 0x%04X\n",
+ le16_to_cpu(logheader->DataArea1LastBlock));
+ printf("Data Area 2 Last Block : 0x%04X\n",
+ le16_to_cpu(logheader->DataArea2LastBlock));
+ printf("Data Area 3 Last Block : 0x%04X\n",
+ le16_to_cpu(logheader->DataArea3LastBlock));
+ printf("Data Available : 0x%02X\n", logheader->DataAvailable);
+ printf("Data Generation Number : 0x%02X\n", logheader->DataGenerationNumber);
+ printf("Reason Identifier :\n");
+
+ for (i = 0; i < 8; i++) {
+ for (j = 0; j < 16; j++)
+ printf("%02X ", logheader->ReasonIdentifier[127 - ((i * 16) + j)]);
+ printf("\n");
+ }
+ printf("===============================================\n\n");
+ }
+}
+static int get_telemetry_data(struct nvme_dev *dev, __u32 ns, __u8 tele_type,
+ __u32 data_len, void *data, __u8 nLSP, __u8 nRAE,
+ __u64 offset)
+{
+ struct nvme_passthru_cmd cmd = {
+ .opcode = nvme_admin_get_log_page,
+ .nsid = ns,
+ .addr = (__u64)(uintptr_t) data,
+ .data_len = data_len,
+ };
+ __u32 numd = (data_len >> 2) - 1;
+ __u16 numdu = numd >> 16;
+ __u16 numdl = numd & 0xffff;
+ cmd.cdw10 = tele_type | (nLSP & 0x0F) << 8 | (nRAE & 0x01) << 15 | (numdl & 0xFFFF) << 16;
+ cmd.cdw11 = numdu;
+ cmd.cdw12 = offset;
+ cmd.cdw13 = 0;
+ cmd.cdw14 = 0;
+ return nvme_submit_admin_passthru(dev_fd(dev), &cmd, NULL);
+}
+static void print_telemetry_data_area_1(struct telemetry_data_area_1 *da1,
+ int tele_type)
+{
+ if (da1) {
+ unsigned int i = 0;
+ if (tele_type == TELEMETRY_TYPE_HOST)
+ printf("============ Telemetry Host Data area 1 ============\n");
+ else
+ printf("========= Telemetry Controller Data area 1 =========\n");
+ printf("Major Version : 0x%x\n", le16_to_cpu(da1->major_version));
+ printf("Minor Version : 0x%x\n", le16_to_cpu(da1->minor_version));
+ for (i = 0; i < 4; i++)
+ printf("reserved1 : 0x%x\n", da1->reserved1[i]);
+ printf("Timestamp : %"PRIu64"\n", le64_to_cpu(da1->timestamp));
+ for (i = 15; i >= 0; i--)
+ printf("%x", da1->log_page_guid[i]);
+ printf("Number Telemetry Profiles Supported : 0x%x\n", da1->no_of_tps_supp);
+ printf("Telemetry Profile Selected (TPS) : 0x%x\n", da1->tps);
+ for (i = 0; i < 6; i++)
+ printf("reserved2 : 0x%x\n", da1->reserved2[i]);
+ printf("Telemetry String Log Size (SLS) : 0x%x\n", le16_to_cpu(da1->sls));
+ for (i = 0; i < 8; i++)
+ printf("reserved3 : 0x%x\n", da1->reserved3[i]);
+ printf("Firmware Revision : 0x%x\n", le16_to_cpu(da1->fw_revision));
+ for (i = 0; i < 32; i++)
+ printf("reserved4 : 0x%x\n", da1->reserved4[i]);
+ printf("Data Area 1 Statistic Start : 0x%x\n", le16_to_cpu(da1->da1_stat_start));
+ printf("Data Area 1 Statistic Size : 0x%x\n", le16_to_cpu(da1->da1_stat_size));
+ printf("Data Area 2 Statistic Start : 0x%x\n", le16_to_cpu(da1->da2_stat_start));
+ printf("Data Area 2 Statistic Size : 0x%x\n", le16_to_cpu(da1->da2_stat_size));
+ for (i = 0; i < 32; i++)
+ printf("reserved5 : 0x%x\n", da1->reserved5[i]);
+ for (i = 0; i < 17; i++){
+ printf("Event FIFO %d Data Area : 0x%x\n", i, da1->event_fifo_da[i]);
+ printf("Event FIFO %d Start : %"PRIu64"\n", i, le64_to_cpu(da1->event_fifo_start[i]));
+ printf("Event FIFO %d Size : %"PRIu64"\n", i, le64_to_cpu(da1->event_fifo_size[i]));
+ }
+ for (i = 0; i < 80; i++)
+ printf("reserved6 : 0x%x\n", da1->reserved6[i]);
+ for (i = 0; i < 512; i++){
+ printf("SMART / Health Information : 0x%x\n", da1->smart_health_info[i]);
+ printf("SMART / Health Information Extended : 0x%x\n", da1->smart_health_info_extended[i]);
+ }
+ printf("===============================================\n\n");
+ }
+}
+static void print_telemetry_da1_stat(__u8 *da1_stat, int tele_type, __u16 buf_size)
+{
+ if (da1_stat) {
+ unsigned int i = 0;
+ if (tele_type == TELEMETRY_TYPE_HOST)
+ printf("============ Telemetry Host Data area 1 Statistics ============\n");
+ else
+ printf("========= Telemetry Controller Data area 1 Statistics =========\n");
+ while((i + 8) < buf_size) {
+ printf("Statistics Identifier : 0x%x\n", (da1_stat[i] | da1_stat[i+1] << 8));
+ printf("Statistics info : 0x%x\n", da1_stat[i+2]);
+ printf("NS info : 0x%x\n", da1_stat[i+3]);
+ printf("Statistic Data Size : 0x%x\n", (da1_stat[i+4] | da1_stat[i+5] << 8));
+ printf("Reserved : 0x%x\n", (da1_stat[i+6] | da1_stat[i+7] << 8));
+ i = 8 + ((da1_stat[i+4] | da1_stat[i+5] << 8) * 4);
+ }
+ printf("===============================================\n\n");
+ }
+}
+static void print_telemetry_da1_fifo(__u8 *da1_fifo, int tele_type, __u16 buf_size)
+{
+ if (da1_fifo) {
+ unsigned int i = 0;
+ if (tele_type == TELEMETRY_TYPE_HOST)
+ printf("============ Telemetry Host Data area 1 FIFO ============\n");
+ else
+ printf("========= Telemetry Controller Data area 1 FIFO =========\n");
+ while((i + 4) < buf_size) {
+ printf("Debug Event Class Type : 0x%x\n", da1_fifo[i]);
+ printf("Event ID : 0x%x\n", (da1_fifo[i+1] | da1_fifo[i+2] << 8));
+ printf("Event Data Size : 0x%x\n", da1_fifo[3]);
+ i = 4 + ((da1_fifo[3]) * 4);
+ }
+ printf("===============================================\n\n");
+ }
+}
+static void print_telemetry_da2_stat(__u8 *da1_stat, int tele_type, __u16 buf_size)
+{
+ if (da1_stat) {
+ unsigned int i = 0;
+ if (tele_type == TELEMETRY_TYPE_HOST)
+ printf("============ Telemetry Host Data area 1 Statistics ============\n");
+ else
+ printf("========= Telemetry Controller Data area 1 Statistics =========\n");
+ while((i + 8) < buf_size) {
+ printf("Statistics Identifier : 0x%x\n", (da1_stat[i] | da1_stat[i+1] << 8));
+ printf("Statistics info : 0x%x\n", da1_stat[i+2]);
+ printf("NS info : 0x%x\n", da1_stat[i+3]);
+ printf("Statistic Data Size : 0x%x\n", (da1_stat[i+4] | da1_stat[i+5] << 8));
+ printf("Reserved : 0x%x\n", (da1_stat[i+6] | da1_stat[i+7] << 8));
+ i = 8 + ((da1_stat[i+4] | da1_stat[i+5] << 8) * 4);
+ }
+ printf("===============================================\n\n");
+ }
+}
+static void print_telemetry_da2_fifo(__u8 *da1_fifo, int tele_type, __u16 buf_size)
+{
+ if (da1_fifo) {
+ unsigned int i = 0;
+ if (tele_type == TELEMETRY_TYPE_HOST)
+ printf("============ Telemetry Host Data area 1 Statistics ============\n");
+ else
+ printf("========= Telemetry Controller Data area 1 Statistics =========\n");
+ while((i + 4) < buf_size) {
+ printf("Debug Event Class Type : 0x%x\n", da1_fifo[i]);
+ printf("Event ID : 0x%x\n", (da1_fifo[i+1] | da1_fifo[i+2] << 8));
+ printf("Event Data Size : 0x%x\n", da1_fifo[3]);
+ i = 4 + ((da1_fifo[3]) * 4);
+ }
+ printf("===============================================\n\n");
+ }
+}
+
+static int extract_dump_get_log(struct nvme_dev *dev, char *featurename, char *filename, char *sn,
+ int dumpsize, int transfersize, __u32 nsid, __u8 log_id,
+ __u8 lsp, __u64 offset, bool rae)
+{
+ int i = 0, err = 0;
+
+ char *data = calloc(transfersize, sizeof(char));
+ char filepath[FILE_NAME_SIZE] = {0,};
+ int output = 0;
+ int total_loop_cnt = dumpsize / transfersize;
+ int last_xfer_size = dumpsize % transfersize;
+
+ if (last_xfer_size)
+ total_loop_cnt++;
+ else
+ last_xfer_size = transfersize;
+
+ if (filename == 0)
+ snprintf(filepath, FILE_NAME_SIZE, "%s_%s.bin", featurename, sn);
+ else
+ snprintf(filepath, FILE_NAME_SIZE, "%s%s_%s.bin", filename, featurename, sn);
+
+ for (i = 0; i < total_loop_cnt; i++) {
+ memset(data, 0, transfersize);
+
+ struct nvme_get_log_args args = {
+ .lpo = offset,
+ .result = NULL,
+ .log = (void *)data,
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .lid = log_id,
+ .len = transfersize,
+ .nsid = nsid,
+ .lsp = lsp,
+ .uuidx = 0,
+ .rae = rae,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .csi = NVME_CSI_NVM,
+ .ot = false,
+ };
+
+ err = nvme_get_log(&args);
+ if (err) {
+ if (i > 0)
+ goto close_output;
+ else
+ goto end;
+ }
+
+ if (i != total_loop_cnt - 1) {
+ if (!i) {
+ output = open(filepath, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (output < 0) {
+ err = -13;
+ goto end;
+ }
+ }
+ if (write(output, data, transfersize) < 0) {
+ err = -10;
+ goto close_output;
+ }
+ } else {
+ if (write(output, data, last_xfer_size) < 0) {
+ err = -10;
+ goto close_output;
+ }
+ }
+ offset += transfersize;
+ printf("%d%%\r", (i + 1) * 100 / total_loop_cnt);
+ }
+ printf("100%%\nThe log file was saved at \"%s\"\n", filepath);
+
+close_output:
+ close(output);
+
+end:
+ free(data);
+ return err;
+}
+
+static int get_telemetry_dump(struct nvme_dev *dev, char *filename, char *sn,
+ enum TELEMETRY_TYPE tele_type, int data_area, bool header_print)
+{
+ __u32 err = 0, nsid = 0;
+ __u8 lsp = 0, rae = 0;
+ unsigned int i = 0;
+ char data[TELEMETRY_TRANSFER_SIZE] = { 0 };
+ char data1[1536] = { 0 };
+ char *featurename = 0;
+ struct telemetry_initiated_log *logheader = (struct telemetry_initiated_log *)data;
+ struct telemetry_data_area_1 *da1 = (struct telemetry_data_area_1 *)data1;
+ __u64 offset = 0, size = 0;
+ char dumpname[FILE_NAME_SIZE] = { 0 };
+
+ if (tele_type == TELEMETRY_TYPE_HOST_0) {
+ featurename = "Host(0)";
+ lsp = 0;
+ rae = 0;
+ tele_type = TELEMETRY_TYPE_HOST;
+ } else if (tele_type == TELEMETRY_TYPE_HOST_1) {
+ featurename = "Host(1)";
+ lsp = 1;
+ rae = 0;
+ tele_type = TELEMETRY_TYPE_HOST;
+ } else {
+ featurename = "Controller";
+ lsp = 0;
+ rae = 1;
+ }
+
+ err = get_telemetry_header(dev, nsid, tele_type, TELEMETRY_HEADER_SIZE,
+ (void *)data, lsp, rae);
+ if (err)
+ return err;
+
+ if (header_print)
+ print_telemetry_header(logheader, tele_type);
+ err = get_telemetry_data(dev, nsid, tele_type, 1536,
+ (void *)data1, lsp, rae, 512);
+ if (err)
+ return err;
+ print_telemetry_data_area_1(da1, tele_type);
+ char *da1_stat = calloc((da1->da1_stat_size * 4), sizeof(char));
+ err = get_telemetry_data(dev, nsid, tele_type, (da1->da1_stat_size) * 4,
+ (void *)da1_stat, lsp, rae, (da1->da1_stat_start) * 4);
+ if (err)
+ return err;
+ print_telemetry_da1_stat((void *)da1_stat, tele_type, (da1->da1_stat_size) * 4);
+ for (i = 0; i < 17 ; i++){
+ if (da1->event_fifo_da[i] == 1){
+ char *da1_fifo = calloc((da1->event_fifo_size[i]) * 4, sizeof(char));
+ err = get_telemetry_data(dev, nsid, tele_type, (da1->event_fifo_size[i]) * 4,
+ (void *)da1_stat, lsp, rae, (da1->event_fifo_start[i]) * 4);
+ if (err)
+ return err;
+ print_telemetry_da1_fifo((void *)da1_fifo, tele_type, (da1->event_fifo_size[i]) * 4);
+ }
+ }
+ char *da2_stat = calloc((da1->da2_stat_size * 4), sizeof(char));
+ err = get_telemetry_data(dev, nsid, tele_type, (da1->da2_stat_size) * 4,
+ (void *)da2_stat, lsp, rae, (da1->da2_stat_start) * 4);
+ if (err)
+ return err;
+ print_telemetry_da2_stat((void *)da2_stat, tele_type, (da1->da2_stat_size) * 4);
+ for (i = 0; i < 17 ; i++){
+ if (da1->event_fifo_da[i] == 2){
+ char *da1_fifo = calloc((da1->event_fifo_size[i]) * 4, sizeof(char));
+ err = get_telemetry_data(dev, nsid, tele_type, (da1->event_fifo_size[i]) * 4,
+ (void *)da1_stat, lsp, rae, (da1->event_fifo_start[i]) * 4);
+ if (err)
+ return err;
+ print_telemetry_da2_fifo((void *)da1_fifo, tele_type, (da1->event_fifo_size[i]) * 4);
+ }
+ }
+
+ switch (data_area) {
+ case 1:
+ offset = TELEMETRY_HEADER_SIZE;
+ size = le16_to_cpu(logheader->DataArea1LastBlock);
+ break;
+ case 2:
+ offset = TELEMETRY_HEADER_SIZE
+ + (le16_to_cpu(logheader->DataArea1LastBlock) * TELEMETRY_BYTE_PER_BLOCK);
+ size = le16_to_cpu(logheader->DataArea2LastBlock)
+ - le16_to_cpu(logheader->DataArea1LastBlock);
+ break;
+ case 3:
+ offset = TELEMETRY_HEADER_SIZE
+ + (le16_to_cpu(logheader->DataArea2LastBlock) * TELEMETRY_BYTE_PER_BLOCK);
+ size = le16_to_cpu(logheader->DataArea3LastBlock)
+ - le16_to_cpu(logheader->DataArea2LastBlock);
+ break;
+ default:
+ break;
+ }
+
+ if (!size) {
+ printf("Telemetry %s Area %d is empty.\n", featurename, data_area);
+ return err;
+ }
+
+ snprintf(dumpname, FILE_NAME_SIZE,
+ "Telemetry_%s_Area_%d", featurename, data_area);
+ err = extract_dump_get_log(dev, dumpname, filename, sn, size * TELEMETRY_BYTE_PER_BLOCK,
+ TELEMETRY_TRANSFER_SIZE, nsid, tele_type,
+ 0, offset, rae);
+
+ return err;
+}
+
+static int ocp_telemetry_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ struct nvme_dev *dev;
+ int err = 0;
+ const char *desc = "Retrieve and save telemetry log.";
+ const char *type = "Telemetry Type; 'host[Create bit]' or 'controller'";
+ const char *area = "Telemetry Data Area; 1 or 3";
+ const char *file = "Output file name with path;\n"
+ "e.g. '-o ./path/name'\n'-o ./path1/path2/';\n"
+ "If requested path does not exist, the directory will be newly created.";
+
+ __u32 nsid = NVME_NSID_ALL;
+ struct stat nvme_stat;
+ char sn[21] = {0,};
+ struct nvme_id_ctrl ctrl;
+ bool is_support_telemetry_controller;
+
+ int tele_type = 0;
+ int tele_area = 0;
+
+ struct config {
+ char *type;
+ int area;
+ char *file;
+ };
+
+ struct config cfg = {
+ .type = NULL,
+ .area = 0,
+ .file = NULL,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_STR("telemetry_type", 't', &cfg.type, type),
+ OPT_INT("telemetry_data_area", 'a', &cfg.area, area),
+ OPT_FILE("output-file", 'o', &cfg.file, file),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = fstat(dev_fd(dev), &nvme_stat);
+ if (err < 0)
+ return err;
+
+ if (S_ISBLK(nvme_stat.st_mode)) {
+ err = nvme_get_nsid(dev_fd(dev), &nsid);
+ if (err < 0)
+ return err;
+ }
+
+ err = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (err)
+ return err;
+
+ get_serial_number(&ctrl, sn);
+
+ is_support_telemetry_controller = ((ctrl.lpa & 0x8) >> 3);
+
+ if (!cfg.type && !cfg.area) {
+ tele_type = TELEMETRY_TYPE_NONE;
+ tele_area = 0;
+ } else if (cfg.type && cfg.area) {
+ if (!strcmp(cfg.type, "host0"))
+ tele_type = TELEMETRY_TYPE_HOST_0;
+ else if (!strcmp(cfg.type, "host1"))
+ tele_type = TELEMETRY_TYPE_HOST_1;
+ else if (!strcmp(cfg.type, "controller"))
+ tele_type = TELEMETRY_TYPE_CONTROLLER;
+
+ tele_area = cfg.area;
+
+ if ((tele_area != 1 && tele_area != 3) ||
+ (tele_type == TELEMETRY_TYPE_CONTROLLER && tele_area != 3)) {
+ printf("\nUnsupported parameters entered.\n");
+ printf("Possible combinations; {'host0',1}, {'host0',3}, {'host1',1}, {'host1',3}, {'controller',3}\n");
+ return err;
+ }
+ } else {
+ printf("\nShould provide these all; 'telemetry_type' and 'telemetry_data_area'\n");
+ return err;
+ }
+
+ if (tele_type == TELEMETRY_TYPE_NONE) {
+ printf("\n-------------------------------------------------------------\n");
+ /* Host 0 (lsp == 0) must be executed before Host 1 (lsp == 1). */
+ printf("\nExtracting Telemetry Host 0 Dump (Data Area 1)...\n");
+
+ err = get_telemetry_dump(dev, cfg.file, sn,
+ TELEMETRY_TYPE_HOST_0, 1, true);
+ if (err)
+ fprintf(stderr, "NVMe Status: %s(%x)\n", nvme_status_to_string(err, false), err);
+
+ printf("\n-------------------------------------------------------------\n");
+
+ printf("\nExtracting Telemetry Host 0 Dump (Data Area 3)...\n");
+
+ err = get_telemetry_dump(dev, cfg.file, sn,
+ TELEMETRY_TYPE_HOST_0, 3, false);
+ if (err)
+ fprintf(stderr, "NVMe Status: %s(%x)\n", nvme_status_to_string(err, false), err);
+
+ printf("\n-------------------------------------------------------------\n");
+
+ printf("\nExtracting Telemetry Host 1 Dump (Data Area 1)...\n");
+
+ err = get_telemetry_dump(dev, cfg.file, sn,
+ TELEMETRY_TYPE_HOST_1, 1, true);
+ if (err)
+ fprintf(stderr, "NVMe Status: %s(%x)\n", nvme_status_to_string(err, false), err);
+
+ printf("\n-------------------------------------------------------------\n");
+
+ printf("\nExtracting Telemetry Host 1 Dump (Data Area 3)...\n");
+
+ err = get_telemetry_dump(dev, cfg.file, sn,
+ TELEMETRY_TYPE_HOST_1, 3, false);
+ if (err)
+ fprintf(stderr, "NVMe Status: %s(%x)\n", nvme_status_to_string(err, false), err);
+
+ printf("\n-------------------------------------------------------------\n");
+
+ printf("\nExtracting Telemetry Controller Dump (Data Area 3)...\n");
+
+ if (is_support_telemetry_controller == true) {
+ err = get_telemetry_dump(dev, cfg.file, sn,
+ TELEMETRY_TYPE_CONTROLLER, 3, true);
+ if (err)
+ fprintf(stderr, "NVMe Status: %s(%x)\n", nvme_status_to_string(err, false), err);
+ }
+
+ printf("\n-------------------------------------------------------------\n");
+ } else if (tele_type == TELEMETRY_TYPE_CONTROLLER) {
+ printf("Extracting Telemetry Controller Dump (Data Area %d)...\n", tele_area);
+
+ if (is_support_telemetry_controller == true) {
+ err = get_telemetry_dump(dev, cfg.file, sn, tele_type, tele_area, true);
+ if (err)
+ fprintf(stderr, "NVMe Status: %s(%x)\n", nvme_status_to_string(err, false), err);
+ }
+ } else {
+ printf("Extracting Telemetry Host(%d) Dump (Data Area %d)...\n",
+ (tele_type == TELEMETRY_TYPE_HOST_0) ? 0 : 1, tele_area);
+
+ err = get_telemetry_dump(dev, cfg.file, sn, tele_type, tele_area, true);
+ if (err)
+ fprintf(stderr, "NVMe Status: %s(%x)\n", nvme_status_to_string(err, false), err);
+ }
+
+ printf("telemetry-log done.\n");
+
+return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+/// Unsupported Requirement Log Page (LID : C5h)
+
+/* C5 Unsupported Requirement Log Page */
+#define C5_GUID_LENGTH 16
+#define C5_UNSUPPORTED_REQS_LEN 4096
+#define C5_UNSUPPORTED_REQS_OPCODE 0xC5
+#define C5_UNSUPPORTED_REQS_LOG_VERSION 0x1
+#define C5_NUM_UNSUPPORTED_REQ_ENTRIES 253
+
+static __u8 unsupported_req_guid[C5_GUID_LENGTH] = {
+ 0x2F, 0x72, 0x9C, 0x0E,
+ 0x99, 0x23, 0x2C, 0xBB,
+ 0x63, 0x48, 0x32, 0xD0,
+ 0xB7, 0x98, 0xBB, 0xC7
+};
+
+/*
+ * struct unsupported_requirement_log - unsupported requirement list
+ * @unsupported_count: Number of Unsupported Requirement IDs
+ * @rsvd1: Reserved
+ * @unsupported_req_list: Unsupported Requirements lists upto 253.
+ * @rsvd2: Reserved
+ * @log_page_version: indicates the version of the mapping this log page uses.
+ * Shall be set to 0001h
+ * @log_page_guid: Shall be set to C7BB98B7D0324863BB2C23990E9C722Fh.
+ */
+struct __packed unsupported_requirement_log {
+ __le16 unsupported_count;
+ __u8 rsvd1[14];
+ __u8 unsupported_req_list[C5_NUM_UNSUPPORTED_REQ_ENTRIES][16];
+ __u8 rsvd2[14];
+ __le16 log_page_version;
+ __u8 log_page_guid[C5_GUID_LENGTH];
+};
+
+/* Function declaration for unsupported requirement log page (LID:C5h) */
+static int ocp_unsupported_requirements_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin);
+
+static int ocp_print_C5_log_normal(struct nvme_dev *dev,
+ struct unsupported_requirement_log *log_data)
+{
+ int j;
+
+ printf("Unsupported Requirement-C5 Log Page Data-\n");
+
+ printf(" Number Unsupported Req IDs : 0x%x\n", le16_to_cpu(log_data->unsupported_count));
+
+ for (j = 0; j < le16_to_cpu(log_data->unsupported_count); j++)
+ printf(" Unsupported Requirement List %d : %s\n", j, log_data->unsupported_req_list[j]);
+
+ printf(" Log Page Version : 0x%x\n", le16_to_cpu(log_data->log_page_version));
+ printf(" Log page GUID : 0x");
+ for (j = C5_GUID_LENGTH - 1; j >= 0; j--)
+ printf("%x", log_data->log_page_guid[j]);
+ printf("\n");
+
+ return 0;
+}
+
+static void ocp_print_C5_log_json(struct unsupported_requirement_log *log_data)
+{
+ int j;
+ struct json_object *root;
+ char unsup_req_list_str[40];
+ char guid_buf[C5_GUID_LENGTH];
+ char *guid = guid_buf;
+
+ root = json_create_object();
+
+ json_object_add_value_int(root, "Number Unsupported Req IDs", le16_to_cpu(log_data->unsupported_count));
+
+ memset((void *)unsup_req_list_str, 0, 40);
+ for (j = 0; j < le16_to_cpu(log_data->unsupported_count); j++) {
+ sprintf((char *)unsup_req_list_str, "Unsupported Requirement List %d", j);
+ json_object_add_value_string(root, unsup_req_list_str, (char *)log_data->unsupported_req_list[j]);
+ }
+
+ json_object_add_value_int(root, "Log Page Version", le16_to_cpu(log_data->log_page_version));
+
+ memset((void *)guid, 0, C5_GUID_LENGTH);
+ for (j = C5_GUID_LENGTH - 1; j >= 0; j--)
+ guid += sprintf(guid, "%02x", log_data->log_page_guid[j]);
+ json_object_add_value_string(root, "Log page GUID", guid_buf);
+
+ json_print_object(root, NULL);
+ printf("\n");
+
+ json_free_object(root);
+}
+
+static void ocp_print_c5_log_binary(struct unsupported_requirement_log *log_data)
+{
+ return d_raw((unsigned char *)log_data, sizeof(*log_data));
+}
+
+static int get_c5_log_page(struct nvme_dev *dev, char *format)
+{
+ enum nvme_print_flags fmt;
+ int ret;
+ __u8 *data;
+ int i;
+ struct unsupported_requirement_log *log_data;
+ int j;
+
+ ret = validate_output_format(format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR : OCP : invalid output format\n");
+ return ret;
+ }
+
+ data = (__u8 *)malloc(sizeof(__u8) * C5_UNSUPPORTED_REQS_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR : OCP : malloc : %s\n", strerror(errno));
+ return -1;
+ }
+ memset(data, 0, sizeof(__u8) * C5_UNSUPPORTED_REQS_LEN);
+
+ ret = nvme_get_log_simple(dev_fd(dev), C5_UNSUPPORTED_REQS_OPCODE,
+ C5_UNSUPPORTED_REQS_LEN, data);
+ if (!ret) {
+ log_data = (struct unsupported_requirement_log *)data;
+
+ /* check log page version */
+ if (log_data->log_page_version != C5_UNSUPPORTED_REQS_LOG_VERSION) {
+ fprintf(stderr, "ERROR : OCP : invalid unsupported requirement version\n");
+ ret = -1;
+ goto out;
+ }
+
+ /*
+ * check log page guid
+ * Verify GUID matches
+ */
+ for (i = 0; i < 16; i++) {
+ if (unsupported_req_guid[i] != log_data->log_page_guid[i]) {
+ fprintf(stderr, "ERROR : OCP : Unknown GUID in C5 Log Page data\n");
+ fprintf(stderr, "ERROR : OCP : Expected GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", unsupported_req_guid[j]);
+ fprintf(stderr, "\nERROR : OCP : Actual GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", log_data->log_page_guid[j]);
+ fprintf(stderr, "\n");
+
+ ret = -1;
+ goto out;
+ }
+ }
+
+ switch (fmt) {
+ case NORMAL:
+ ocp_print_C5_log_normal(dev, log_data);
+ break;
+ case JSON:
+ ocp_print_C5_log_json(log_data);
+ break;
+ case BINARY:
+ ocp_print_c5_log_binary(log_data);
+ break;
+ default:
+ break;
+ }
+ } else {
+ fprintf(stderr, "ERROR : OCP : Unable to read C3 data from buffer\n");
+ }
+
+out:
+ free(data);
+ return ret;
+}
+
+
+static int ocp_unsupported_requirements_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ const char *desc = "Retrieve unsupported requirements log data.";
+ struct nvme_dev *dev;
+ int ret = 0;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "output Format: normal|json"),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ ret = get_c5_log_page(dev, cfg.output_format);
+ if (ret)
+ fprintf(stderr, "ERROR : OCP : Failure reading the C5 Log Page, ret = %d\n", ret);
+
+ dev_close(dev);
+ return ret;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+/// Error Recovery Log Page(0xC1)
+
+#define C1_ERROR_RECOVERY_LOG_BUF_LEN 0x200
+#define C1_ERROR_RECOVERY_OPCODE 0xC1
+#define C1_ERROR_RECOVERY_VERSION 0x0002
+#define C1_GUID_LENGTH 16
+static __u8 error_recovery_guid[C1_GUID_LENGTH] = {
+ 0x44, 0xd9, 0x31, 0x21,
+ 0xfe, 0x30, 0x34, 0xae,
+ 0xab, 0x4d, 0xfd, 0x3d,
+ 0xba, 0x83, 0x19, 0x5a
+};
+
+/**
+ * struct ocp_error_recovery_log_page - Error Recovery Log Page
+ * @panic_reset_wait_time: Panic Reset Wait Time
+ * @panic_reset_action: Panic Reset Action
+ * @device_recover_action_1: Device Recovery Action 1
+ * @panic_id: Panic ID
+ * @device_capabilities: Device Capabilities
+ * @vendor_specific_recovery_opcode: Vendor Specific Recovery Opcode
+ * @reserved: Reserved
+ * @vendor_specific_command_cdw12: Vendor Specific Command CDW12
+ * @vendor_specific_command_cdw13: Vendor Specific Command CDW13
+ * @vendor_specific_command_timeout: Vendor Specific Command Timeout
+ * @device_recover_action_2: Device Recovery Action 2
+ * @device_recover_action_2_timeout: Device Recovery Action 2 Timeout
+ * @reserved2: Reserved
+ * @log_page_version: Log Page Version
+ * @log_page_guid: Log Page GUID
+ */
+struct __packed ocp_error_recovery_log_page {
+ __le16 panic_reset_wait_time; /* 2 bytes - 0x00 - 0x01 */
+ __u8 panic_reset_action; /* 1 byte - 0x02 */
+ __u8 device_recover_action_1; /* 1 byte - 0x03 */
+ __le64 panic_id; /* 8 bytes - 0x04 - 0x0B */
+ __le32 device_capabilities; /* 4 bytes - 0x0C - 0x0F */
+ __u8 vendor_specific_recovery_opcode; /* 1 byte - 0x10 */
+ __u8 reserved[0x3]; /* 3 bytes - 0x11 - 0x13 */
+ __le32 vendor_specific_command_cdw12; /* 4 bytes - 0x14 - 0x17 */
+ __le32 vendor_specific_command_cdw13; /* 4 bytes - 0x18 - 0x1B */
+ __u8 vendor_specific_command_timeout; /* 1 byte - 0x1C */
+ __u8 device_recover_action_2; /* 1 byte - 0x1D */
+ __u8 device_recover_action_2_timeout; /* 1 byte - 0x1E */
+ __u8 reserved2[0x1cf]; /* 463 bytes - 0x1F - 0x1ED */
+ __le16 log_page_version; /* 2 bytes - 0x1EE - 0x1EF */
+ __u8 log_page_guid[0x10]; /* 16 bytes - 0x1F0 - 0x1FF */
+};
+
+static void ocp_print_c1_log_normal(struct ocp_error_recovery_log_page *log_data);
+static void ocp_print_c1_log_json(struct ocp_error_recovery_log_page *log_data);
+static void ocp_print_c1_log_binary(struct ocp_error_recovery_log_page *log_data);
+static int get_c1_log_page(struct nvme_dev *dev, char *format);
+static int ocp_error_recovery_log(int argc, char **argv, struct command *cmd, struct plugin *plugin);
+
+static void ocp_print_c1_log_normal(struct ocp_error_recovery_log_page *log_data)
+{
+ int i;
+
+ printf(" Error Recovery/C1 Log Page Data\n");
+ printf(" Panic Reset Wait Time : 0x%x\n", le16_to_cpu(log_data->panic_reset_wait_time));
+ printf(" Panic Reset Action : 0x%x\n", log_data->panic_reset_action);
+ printf(" Device Recovery Action 1 : 0x%x\n", log_data->device_recover_action_1);
+ printf(" Panic ID : 0x%x\n", le32_to_cpu(log_data->panic_id));
+ printf(" Device Capabilities : 0x%x\n", le32_to_cpu(log_data->device_capabilities));
+ printf(" Vendor Specific Recovery Opcode : 0x%x\n", log_data->vendor_specific_recovery_opcode);
+ printf(" Vendor Specific Command CDW12 : 0x%x\n", le32_to_cpu(log_data->vendor_specific_command_cdw12));
+ printf(" Vendor Specific Command CDW13 : 0x%x\n", le32_to_cpu(log_data->vendor_specific_command_cdw13));
+ printf(" Vendor Specific Command Timeout : 0x%x\n", log_data->vendor_specific_command_timeout);
+ printf(" Device Recovery Action 2 : 0x%x\n", log_data->device_recover_action_2);
+ printf(" Device Recovery Action 2 Timeout : 0x%x\n", log_data->device_recover_action_2_timeout);
+ printf(" Log Page Version : 0x%x\n", le16_to_cpu(log_data->log_page_version));
+ printf(" Log page GUID : 0x");
+ for (i = C1_GUID_LENGTH - 1; i >= 0; i--)
+ printf("%x", log_data->log_page_guid[i]);
+ printf("\n");
+}
+
+static void ocp_print_c1_log_json(struct ocp_error_recovery_log_page *log_data)
+{
+ struct json_object *root;
+
+ root = json_create_object();
+ char guid[64];
+
+ json_object_add_value_int(root, "Panic Reset Wait Time", le16_to_cpu(log_data->panic_reset_wait_time));
+ json_object_add_value_int(root, "Panic Reset Action", log_data->panic_reset_action);
+ json_object_add_value_int(root, "Device Recovery Action 1", log_data->device_recover_action_1);
+ json_object_add_value_int(root, "Panic ID", le32_to_cpu(log_data->panic_id));
+ json_object_add_value_int(root, "Device Capabilities", le32_to_cpu(log_data->device_capabilities));
+ json_object_add_value_int(root, "Vendor Specific Recovery Opcode", log_data->vendor_specific_recovery_opcode);
+ json_object_add_value_int(root, "Vendor Specific Command CDW12", le32_to_cpu(log_data->vendor_specific_command_cdw12));
+ json_object_add_value_int(root, "Vendor Specific Command CDW13", le32_to_cpu(log_data->vendor_specific_command_cdw13));
+ json_object_add_value_int(root, "Vendor Specific Command Timeout", log_data->vendor_specific_command_timeout);
+ json_object_add_value_int(root, "Device Recovery Action 2", log_data->device_recover_action_2);
+ json_object_add_value_int(root, "Device Recovery Action 2 Timeout", log_data->device_recover_action_2_timeout);
+ json_object_add_value_int(root, "Log Page Version", le16_to_cpu(log_data->log_page_version));
+
+ memset((void *)guid, 0, 64);
+ sprintf((char *)guid, "0x%"PRIx64"%"PRIx64"", (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[8]),
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[0]));
+ json_object_add_value_string(root, "Log page GUID", guid);
+
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+}
+
+static void ocp_print_c1_log_binary(struct ocp_error_recovery_log_page *log_data)
+{
+ return d_raw((unsigned char *)log_data, sizeof(*log_data));
+}
+
+static int get_c1_log_page(struct nvme_dev *dev, char *format)
+{
+ struct ocp_error_recovery_log_page *log_data;
+ enum nvme_print_flags fmt;
+ int ret;
+ __u8 *data;
+ int i, j;
+
+ ret = validate_output_format(format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR : OCP : invalid output format\n");
+ return ret;
+ }
+
+ data = (__u8 *)malloc(sizeof(__u8) * C1_ERROR_RECOVERY_LOG_BUF_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR : OCP : malloc : %s\n", strerror(errno));
+ return -1;
+ }
+ memset(data, 0, sizeof(__u8) * C1_ERROR_RECOVERY_LOG_BUF_LEN);
+
+ ret = nvme_get_log_simple(dev_fd(dev), C1_ERROR_RECOVERY_OPCODE, C1_ERROR_RECOVERY_LOG_BUF_LEN, data);
+
+ if (!ret) {
+ log_data = (struct ocp_error_recovery_log_page *)data;
+
+ /* check log page version */
+ if (log_data->log_page_version != C1_ERROR_RECOVERY_VERSION) {
+ fprintf(stderr, "ERROR : OCP : invalid error recovery log page version\n");
+ ret = -1;
+ goto out;
+ }
+
+ /*
+ * check log page guid
+ * Verify GUID matches
+ */
+ for (i = 0; i < 16; i++) {
+ if (error_recovery_guid[i] != log_data->log_page_guid[i]) {
+ fprintf(stderr, "ERROR : OCP : Unknown GUID in C1 Log Page data\n");
+ fprintf(stderr, "ERROR : OCP : Expected GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", error_recovery_guid[j]);
+ fprintf(stderr, "\nERROR : OCP : Actual GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", log_data->log_page_guid[j]);
+ fprintf(stderr, "\n");
+
+ ret = -1;
+ goto out;
+ }
+ }
+
+ switch (fmt) {
+ case NORMAL:
+ ocp_print_c1_log_normal(log_data);
+ break;
+ case JSON:
+ ocp_print_c1_log_json(log_data);
+ break;
+ case BINARY:
+ ocp_print_c1_log_binary(log_data);
+ break;
+ default:
+ break;
+ }
+ } else {
+ fprintf(stderr, "ERROR : OCP : Unable to read C1 data from buffer\n");
+ }
+
+out:
+ free(data);
+ return ret;
+}
+
+static int ocp_error_recovery_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Retrieve C1h Error Recovery Log data.";
+ struct nvme_dev *dev;
+ int ret = 0;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "output Format: normal|json|binary"),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ ret = get_c1_log_page(dev, cfg.output_format);
+ if (ret)
+ fprintf(stderr, "ERROR : OCP : Failure reading the C1h Log Page, ret = %d\n", ret);
+ dev_close(dev);
+ return ret;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+/// Device Capabilities (Log Identifier C4h) Requirements
+
+#define C4_DEV_CAP_REQ_LEN 0x1000
+#define C4_DEV_CAP_REQ_OPCODE 0xC4
+#define C4_DEV_CAP_REQ_VERSION 0x0001
+#define C4_GUID_LENGTH 16
+static __u8 dev_cap_req_guid[C4_GUID_LENGTH] = {
+ 0x97, 0x42, 0x05, 0x0d,
+ 0xd1, 0xe1, 0xc9, 0x98,
+ 0x5d, 0x49, 0x58, 0x4b,
+ 0x91, 0x3c, 0x05, 0xb7
+};
+
+/**
+ * struct ocp_device_capabilities_log_page - Device Capability Log page
+ * @pcie_exp_port: PCI Express Ports
+ * @oob_management_support: OOB Management Support
+ * @wz_cmd_support: Write Zeroes Command Support
+ * @sanitize_cmd_support: Sanitize Command Support
+ * @dsm_cmd_support: Dataset Management Command Support
+ * @wu_cmd_support: Write Uncorrectable Command Support
+ * @fused_operation_support: Fused Operation Support
+ * @min_valid_dssd_pwr_state: Minimum Valid DSSD Power State
+ * @dssd_pwr_state_desc: DSSD Power State Descriptors
+ * @vendor_specific_command_timeout: Vendor Specific Command Timeout
+ * @reserved: Reserved
+ * @log_page_version: Log Page Version
+ * @log_page_guid: Log Page GUID
+ */
+struct __packed ocp_device_capabilities_log_page {
+ __le16 pcie_exp_port;
+ __le16 oob_management_support;
+ __le16 wz_cmd_support;
+ __le16 sanitize_cmd_support;
+ __le16 dsm_cmd_support;
+ __le16 wu_cmd_support;
+ __le16 fused_operation_support;
+ __le16 min_valid_dssd_pwr_state;
+ __u8 dssd_pwr_state_desc[128];
+ __u8 reserved[3934];
+ __le16 log_page_version;
+ __u8 log_page_guid[16];
+};
+
+static void ocp_print_c4_log_normal(struct ocp_device_capabilities_log_page *log_data);
+static void ocp_print_c4_log_json(struct ocp_device_capabilities_log_page *log_data);
+static void ocp_print_c4_log_binary(struct ocp_device_capabilities_log_page *log_data);
+static int get_c4_log_page(struct nvme_dev *dev, char *format);
+static int ocp_device_capabilities_log(int argc, char **argv, struct command *cmd, struct plugin *plugin);
+
+static void ocp_print_c4_log_normal(struct ocp_device_capabilities_log_page *log_data)
+{
+ int i;
+
+ printf(" Device Capability/C4 Log Page Data\n");
+ printf(" PCI Express Ports : 0x%x\n", le16_to_cpu(log_data->pcie_exp_port));
+ printf(" OOB Management Support : 0x%x\n", le16_to_cpu(log_data->oob_management_support));
+ printf(" Write Zeroes Command Support : 0x%x\n", le16_to_cpu(log_data->wz_cmd_support));
+ printf(" Sanitize Command Support : 0x%x\n", le16_to_cpu(log_data->sanitize_cmd_support));
+ printf(" Dataset Management Command Support : 0x%x\n", le16_to_cpu(log_data->dsm_cmd_support));
+ printf(" Write Uncorrectable Command Support : 0x%x\n", le16_to_cpu(log_data->wu_cmd_support));
+ printf(" Fused Operation Support : 0x%x\n", le16_to_cpu(log_data->fused_operation_support));
+ printf(" Minimum Valid DSSD Power State : 0x%x\n", le16_to_cpu(log_data->min_valid_dssd_pwr_state));
+ printf(" DSSD Power State Descriptors : 0x");
+ for (i = 0; i <= 127; i++)
+ printf("%x", log_data->dssd_pwr_state_desc[i]);
+ printf("\n");
+ printf(" Log Page Version : 0x%x\n", le16_to_cpu(log_data->log_page_version));
+ printf(" Log page GUID : 0x");
+ for (i = C4_GUID_LENGTH - 1; i >= 0; i--)
+ printf("%x", log_data->log_page_guid[i]);
+ printf("\n");
+}
+
+static void ocp_print_c4_log_json(struct ocp_device_capabilities_log_page *log_data)
+{
+ struct json_object *root = json_create_object();
+ char guid[64];
+ int i;
+
+ json_object_add_value_int(root, "PCI Express Ports", le16_to_cpu(log_data->pcie_exp_port));
+ json_object_add_value_int(root, "OOB Management Support", le16_to_cpu(log_data->oob_management_support));
+ json_object_add_value_int(root, "Write Zeroes Command Support", le16_to_cpu(log_data->wz_cmd_support));
+ json_object_add_value_int(root, "Sanitize Command Support", le16_to_cpu(log_data->sanitize_cmd_support));
+ json_object_add_value_int(root, "Dataset Management Command Support", le16_to_cpu(log_data->dsm_cmd_support));
+ json_object_add_value_int(root, "Write Uncorrectable Command Support", le16_to_cpu(log_data->wu_cmd_support));
+ json_object_add_value_int(root, "Fused Operation Support", le16_to_cpu(log_data->fused_operation_support));
+ json_object_add_value_int(root, "Minimum Valid DSSD Power State", le16_to_cpu(log_data->min_valid_dssd_pwr_state));
+ for (i = 0; i <= 127; i++)
+ json_object_add_value_int(root, "DSSD Power State Descriptors", log_data->dssd_pwr_state_desc[i]);
+ json_object_add_value_int(root, "Log Page Version", le16_to_cpu(log_data->log_page_version));
+
+ memset((void *)guid, 0, 64);
+ sprintf((char *)guid, "0x%"PRIx64"%"PRIx64"", (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[8]),
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[0]));
+ json_object_add_value_string(root, "Log page GUID", guid);
+
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+}
+
+static void ocp_print_c4_log_binary(struct ocp_device_capabilities_log_page *log_data)
+{
+ return d_raw((unsigned char *)log_data, sizeof(*log_data));
+}
+
+static int get_c4_log_page(struct nvme_dev *dev, char *format)
+{
+ struct ocp_device_capabilities_log_page *log_data;
+ enum nvme_print_flags fmt;
+ int ret;
+ __u8 *data;
+ int i, j;
+
+ ret = validate_output_format(format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR : OCP : invalid output format\n");
+ return ret;
+ }
+
+ data = (__u8 *)malloc(sizeof(__u8) * C4_DEV_CAP_REQ_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR : OCP : malloc : %s\n", strerror(errno));
+ return -1;
+ }
+ memset(data, 0, sizeof(__u8) * C4_DEV_CAP_REQ_LEN);
+
+ ret = nvme_get_log_simple(dev_fd(dev), C4_DEV_CAP_REQ_OPCODE, C4_DEV_CAP_REQ_LEN, data);
+
+ if (!ret) {
+ log_data = (struct ocp_device_capabilities_log_page *)data;
+
+ /* check log page version */
+ if (log_data->log_page_version != C4_DEV_CAP_REQ_VERSION) {
+ fprintf(stderr, "ERROR : OCP : invalid device capabilities log page version\n");
+ ret = -1;
+ goto out;
+ }
+
+ /*
+ * check log page guid
+ * Verify GUID matches
+ */
+ for (i = 0; i < 16; i++) {
+ if (dev_cap_req_guid[i] != log_data->log_page_guid[i]) {
+ fprintf(stderr, "ERROR : OCP : Unknown GUID in C4 Log Page data\n");
+ fprintf(stderr, "ERROR : OCP : Expected GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", dev_cap_req_guid[j]);
+ fprintf(stderr, "\nERROR : OCP : Actual GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", log_data->log_page_guid[j]);
+ fprintf(stderr, "\n");
+
+ ret = -1;
+ goto out;
+ }
+ }
+
+ switch (fmt) {
+ case NORMAL:
+ ocp_print_c4_log_normal(log_data);
+ break;
+ case JSON:
+ ocp_print_c4_log_json(log_data);
+ break;
+ case BINARY:
+ ocp_print_c4_log_binary(log_data);
+ break;
+ default:
+ break;
+ }
+ } else {
+ fprintf(stderr, "ERROR : OCP : Unable to read C4 data from buffer\n");
+ }
+
+out:
+ free(data);
+ return ret;
+}
+
+static int ocp_device_capabilities_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Retrieve C4h Device Capabilities Log data.";
+ struct nvme_dev *dev;
+ int ret = 0;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "output Format: normal|json|binary"),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ ret = get_c4_log_page(dev, cfg.output_format);
+ if (ret)
+ fprintf(stderr, "ERROR : OCP : Failure reading the C4h Log Page, ret = %d\n", ret);
+ dev_close(dev);
+ return ret;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+/// DSSD Power State (Feature Identifier C7h) Set Feature
+
+static int set_dssd_power_state(struct nvme_dev *dev, const __u32 nsid,
+ const __u8 fid, __u8 power_state, bool save,
+ bool uuid)
+{
+ __u32 result;
+ int err;
+ int uuid_index = 0;
+
+ if (uuid) {
+ /* OCP 2.0 requires UUID index support */
+ err = ocp_get_uuid_index(dev, &uuid_index);
+ if (err || !uuid_index) {
+ nvme_show_error("ERROR: No OCP UUID index found");
+ return err;
+ }
+ }
+
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = fid,
+ .nsid = nsid,
+ .cdw11 = power_state,
+ .cdw12 = 0,
+ .save = save,
+ .uuidx = uuid_index,
+ .cdw15 = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ err = nvme_set_features(&args);
+ if (err > 0) {
+ nvme_show_status(err);
+ } else if (err < 0) {
+ nvme_show_perror("Define DSSD Power State");
+ fprintf(stderr, "Command failed while parsing.\n");
+ } else {
+ printf("Successfully set DSSD Power State (feature: 0xC7) to below values\n");
+ printf("DSSD Power State: 0x%x\n", power_state);
+ printf("Save bit Value: 0x%x\n", save);
+ }
+
+ return err;
+}
+
+static int set_dssd_power_state_feature(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ const char *desc = "Define DSSD Power State (Feature Identifier C7h) Set Feature.";
+ const char *power_state = "DSSD Power State to set in watts";
+ const char *save = "Specifies that the controller shall save the attribute";
+ const __u32 nsid = 0;
+ const __u8 fid = 0xC7;
+ struct nvme_dev *dev;
+ int err;
+
+ struct config {
+ __u8 power_state;
+ bool save;
+ };
+
+ struct config cfg = {
+ .power_state = 0,
+ .save = false,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_BYTE("power-state", 'p', &cfg.power_state, power_state),
+ OPT_FLAG("save", 's', &cfg.save, save),
+ OPT_FLAG("no-uuid", 'n', NULL,
+ "Skip UUID index search (UUID index not required for OCP 1.0)"),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ if (argconfig_parse_seen(opts, "power state"))
+ err = set_dssd_power_state(dev, nsid, fid, cfg.power_state,
+ cfg.save,
+ !argconfig_parse_seen(opts, "no-uuid"));
+
+ dev_close(dev);
+
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+/// plp_health_check_interval
+
+static int set_plp_health_check_interval(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+
+ const char *desc = "Define Issue Set Feature command (FID : 0xC6) PLP Health Check Interval";
+ const char *plp_health_interval = "[31:16]:PLP Health Check Interval";
+ const char *save = "Specifies that the controller shall save the attribute";
+ const __u32 nsid = 0;
+ const __u8 fid = 0xc6;
+ struct nvme_dev *dev;
+ int err;
+ __u32 result;
+ int uuid_index = 0;
+
+ struct config {
+ __le16 plp_health_interval;
+ bool save;
+ };
+
+ struct config cfg = {
+ .plp_health_interval = 0,
+ .save = false,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_BYTE("plp_health_interval", 'p', &cfg.plp_health_interval, plp_health_interval),
+ OPT_FLAG("save", 's', &cfg.save, save),
+ OPT_FLAG("no-uuid", 'n', NULL,
+ "Skip UUID index search (UUID index not required for OCP 1.0)"),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+
+ if (!argconfig_parse_seen(opts, "no-uuid")) {
+ /* OCP 2.0 requires UUID index support */
+ err = ocp_get_uuid_index(dev, &uuid_index);
+ if (err || !uuid_index) {
+ printf("ERROR: No OCP UUID index found");
+ return err;
+ }
+ }
+
+
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = fid,
+ .nsid = nsid,
+ .cdw11 = cfg.plp_health_interval << 16,
+ .cdw12 = 0,
+ .save = cfg.save,
+ .uuidx = uuid_index,
+ .cdw15 = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ err = nvme_set_features(&args);
+ if (err > 0) {
+ nvme_show_status(err);
+ } else if (err < 0) {
+ nvme_show_perror("Define PLP Health Check Interval");
+ fprintf(stderr, "Command failed while parsing.\n");
+ } else {
+ printf("Successfully set the PLP Health Check Interval");
+ printf("PLP Health Check Interval: 0x%x\n", cfg.plp_health_interval);
+ printf("Save bit Value: 0x%x\n", cfg.save);
+ }
+ return err;
+}
+
+static int get_plp_health_check_interval(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+
+ const char *desc = "Define Issue Get Feature command (FID : 0xC6) PLP Health Check Interval";
+ const char *sel = "[0-3,8]: current/default/saved/supported/changed";
+ const __u32 nsid = 0;
+ const __u8 fid = 0xc6;
+ struct nvme_dev *dev;
+ __u32 result;
+ int err;
+
+ struct config {
+ __u8 sel;
+ };
+
+ struct config cfg = {
+ .sel = 0,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_BYTE("sel", 'S', &cfg.sel, sel),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+
+ struct nvme_get_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = fid,
+ .nsid = nsid,
+ .sel = cfg.sel,
+ .cdw11 = 0,
+ .uuidx = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ err = nvme_get_features(&args);
+ if (!err) {
+ printf("get-feature:0xC6 %s value: %#08x\n", nvme_select_to_string(cfg.sel), result);
+
+ if (cfg.sel == NVME_GET_FEATURES_SEL_SUPPORTED)
+ nvme_show_select_result(fid, result);
+ } else {
+ nvme_show_error("Could not get feature: 0xC6");
+ }
+
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+/// Telemetry String Log Format Log Page (LID : C9h)
+
+/* C9 Telemetry String Log Format Log Page */
+#define C9_GUID_LENGTH 16
+#define C9_TELEMETRY_STRING_LOG_ENABLE_OPCODE 0xC9
+#define C9_TELEMETRY_STR_LOG_LEN 432
+#define C9_TELEMETRY_STR_LOG_SIST_OFST 431
+
+/**
+ * struct telemetry_str_log_format - Telemetry String Log Format
+ * @log_page_version: indicates the version of the mapping this log page uses
+ * Shall be set to 01h.
+ * @reserved1: Reserved.
+ * @log_page_guid: Shall be set to B13A83691A8F408B9EA495940057AA44h.
+ * @sls: Shall be set to the number of DWORDS in the String Log.
+ * @reserved2: reserved.
+ * @sits: shall be set to the number of DWORDS in the Statistics
+ * Identifier String Table
+ * @ests: Shall be set to the number of DWORDS from byte 0 of this
+ * log page to the start of the Event String Table
+ * @estsz: shall be set to the number of DWORDS in the Event String Table
+ * @vu_eve_sts: Shall be set to the number of DWORDS from byte 0 of this
+ * log page to the start of the VU Event String Table
+ * @vu_eve_st_sz: shall be set to the number of DWORDS in the VU Event String Table
+ * @ascts: the number of DWORDS from byte 0 of this log page until the ASCII Table Starts.
+ * @asctsz: the number of DWORDS in the ASCII Table
+ * @fifo1: FIFO 0 ASCII String
+ * @fifo2: FIFO 1 ASCII String
+ * @fifo3: FIFO 2 ASCII String
+ * @fifo4: FIFO 3 ASCII String
+ * @fif05: FIFO 4 ASCII String
+ * @fifo6: FIFO 5 ASCII String
+ * @fifo7: FIFO 6 ASCII String
+ * @fifo8: FIFO 7 ASCII String
+ * @fifo9: FIFO 8 ASCII String
+ * @fifo10: FIFO 9 ASCII String
+ * @fif011: FIFO 10 ASCII String
+ * @fif012: FIFO 11 ASCII String
+ * @fifo13: FIFO 12 ASCII String
+ * @fif014: FIFO 13 ASCII String
+ * @fif015: FIFO 14 ASCII String
+ * @fif016: FIFO 15 ASCII String
+ * @reserved3: reserved
+ */
+struct __attribute__((__packed__)) telemetry_str_log_format {
+ __u8 log_page_version;
+ __u8 reserved1[15];
+ __u8 log_page_guid[C9_GUID_LENGTH];
+ __le64 sls;
+ __u8 reserved2[24];
+ __le64 sits;
+ __le64 sitsz;
+ __le64 ests;
+ __le64 estsz;
+ __le64 vu_eve_sts;
+ __le64 vu_eve_st_sz;
+ __le64 ascts;
+ __le64 asctsz;
+ __u8 fifo1[16];
+ __u8 fifo2[16];
+ __u8 fifo3[16];
+ __u8 fifo4[16];
+ __u8 fifo5[16];
+ __u8 fifo6[16];
+ __u8 fifo7[16];
+ __u8 fifo8[16];
+ __u8 fifo9[16];
+ __u8 fifo10[16];
+ __u8 fifo11[16];
+ __u8 fifo12[16];
+ __u8 fifo13[16];
+ __u8 fifo14[16];
+ __u8 fifo15[16];
+ __u8 fifo16[16];
+ __u8 reserved3[48];
+};
+
+/*
+ * struct statistics_id_str_table_entry - Statistics Identifier String Table Entry
+ * @vs_si: Shall be set the Vendor Unique Statistic Identifier number.
+ * @reserved1: Reserved
+ * @ascii_id_len: Shall be set the number of ASCII Characters that are valid.
+ * @ascii_id_ofst: Shall be set to the offset from DWORD 0/Byte 0 of the Start
+ * of the ASCII Table to the first character of the string for
+ * this Statistic Identifier string..
+ * @reserved2 reserved
+ */
+struct __attribute__((__packed__)) statistics_id_str_table_entry {
+ __le16 vs_si;
+ __u8 reserved1;
+ __u8 ascii_id_len;
+ __le64 ascii_id_ofst;
+ __le32 reserved2;
+};
+
+/*
+ * struct event_id_str_table_entry - Event Identifier String Table Entry
+ * @deb_eve_class: Shall be set the Debug Class.
+ * @ei: Shall be set to the Event Identifier
+ * @ascii_id_len: Shall be set the number of ASCII Characters that are valid.
+ * @ascii_id_ofst: This is the offset from DWORD 0/ Byte 0 of the start of the
+ * ASCII table to the ASCII data for this identifier
+ * @reserved2 reserved
+ */
+struct __attribute__((__packed__)) event_id_str_table_entry {
+ __u8 deb_eve_class;
+ __le16 ei;
+ __u8 ascii_id_len;
+ __le64 ascii_id_ofst;
+ __le32 reserved2;
+};
+
+/*
+ * struct vu_event_id_str_table_entry - VU Event Identifier String Table Entry
+ * @deb_eve_class: Shall be set the Debug Class.
+ * @vu_ei: Shall be set to the VU Event Identifier
+ * @ascii_id_len: Shall be set the number of ASCII Characters that are valid.
+ * @ascii_id_ofst: This is the offset from DWORD 0/ Byte 0 of the start of the
+ * ASCII table to the ASCII data for this identifier
+ * @reserved reserved
+ */
+struct __attribute__((__packed__)) vu_event_id_str_table_entry {
+ __u8 deb_eve_class;
+ __le16 vu_ei;
+ __u8 ascii_id_len;
+ __le64 ascii_id_ofst;
+ __le32 reserved;
+};
+
+/* Function declaration for Telemetry String Log Format (LID:C9h) */
+static int ocp_telemetry_str_log_format(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin);
+
+
+static int ocp_print_C9_log_normal(struct telemetry_str_log_format *log_data,__u8 *log_data_buf)
+{
+ //calculating the index value for array
+ __le64 stat_id_index = (log_data->sitsz * 4) / 16;
+ __le64 eve_id_index = (log_data->estsz * 4) / 16;
+ __le64 vu_eve_index = (log_data->vu_eve_st_sz * 4) / 16;
+ __le64 ascii_table_index = (log_data->asctsz * 4);
+ //Calculating the offset for dynamic fields.
+ __le64 stat_id_str_table_ofst = C9_TELEMETRY_STR_LOG_SIST_OFST + (log_data->sitsz * 4);
+ __le64 event_str_table_ofst = stat_id_str_table_ofst + (log_data->estsz * 4);
+ __le64 vu_event_str_table_ofst = event_str_table_ofst + (log_data->vu_eve_st_sz * 4);
+ __le64 ascii_table_ofst = vu_event_str_table_ofst + (log_data->asctsz * 4);
+ struct statistics_id_str_table_entry stat_id_str_table_arr[stat_id_index];
+ struct event_id_str_table_entry event_id_str_table_arr[eve_id_index];
+ struct vu_event_id_str_table_entry vu_event_id_str_table_arr[vu_eve_index];
+ __u8 ascii_table_info_arr[ascii_table_index];
+ int j;
+
+ printf(" Log Page Version : 0x%x\n", log_data->log_page_version);
+
+ printf(" Reserved : ");
+ for (j = 0; j < 15; j++)
+ printf("%d", log_data->reserved1[j]);
+ printf("\n");
+
+ printf(" Log page GUID : 0x");
+ for (j = C9_GUID_LENGTH - 1; j >= 0; j--)
+ printf("%x", log_data->log_page_guid[j]);
+ printf("\n");
+
+ printf(" Telemetry String Log Size : 0x%lx\n", le64_to_cpu(log_data->sls));
+
+ printf(" Reserved : ");
+ for (j = 0; j < 24; j++)
+ printf("%d", log_data->reserved2[j]);
+ printf("\n");
+
+ printf(" Statistics Identifier String Table Start : 0x%lx\n", le64_to_cpu(log_data->sits));
+ printf(" Statistics Identifier String Table Size : 0x%lx\n", le64_to_cpu(log_data->sitsz));
+ printf(" Event String Table Start : 0x%lx\n", le64_to_cpu(log_data->ests));
+ printf(" Event String Table Size : 0x%lx\n", le64_to_cpu(log_data->estsz));
+ printf(" VU Event String Table Start : 0x%lx\n", le64_to_cpu(log_data->vu_eve_sts));
+ printf(" VU Event String Table Size : 0x%lx\n", le64_to_cpu(log_data->vu_eve_st_sz));
+ printf(" ASCII Table Start : 0x%lx\n", le64_to_cpu(log_data->ascts));
+ printf(" ASCII Table Size : 0x%lx\n", le64_to_cpu(log_data->asctsz));
+
+ printf(" FIFO 1 ASCII String\n");
+ printf(" index value ascii_val\n");
+ for (j = 0; j < 16; j++){
+ printf(" %d %d %c \n", j, log_data->fifo1[j], log_data->fifo1[j]);
+ }
+
+ printf(" FIFO 2 ASCII String\n");
+ printf(" index value ascii_val\n");
+ for (j = 0; j < 16; j++){
+ printf(" %d %d %c \n", j, log_data->fifo2[j], log_data->fifo2[j]);
+ }
+
+ printf(" FIFO 3 ASCII String\n");
+ printf(" index value ascii_val\n");
+ for (j = 0; j < 16; j++){
+ printf(" %d %d %c \n", j, log_data->fifo3[j], log_data->fifo3[j]);
+ }
+
+ printf(" FIFO 4 ASCII String\n");
+ printf(" index value ascii_val\n");
+ for (j = 0; j < 16; j++){
+
+ printf(" %d %d %c \n", j, log_data->fifo4[j], log_data->fifo4[j]);
+ }
+
+ printf(" FIFO 5 ASCII String\n");
+ printf(" index value ascii_val\n");
+ for (j = 0; j < 16; j++){
+ printf(" %d %d %c \n", j, log_data->fifo5[j], log_data->fifo5[j]);
+ }
+
+ printf(" FIFO 6 ASCII String\n");
+ printf(" index value ascii_val\n");
+ for (j = 0; j < 16; j++){
+ printf(" %d %d %c \n", j, log_data->fifo6[j], log_data->fifo6[j]);
+ }
+
+ printf(" FIFO 7 ASCII String\n");
+ printf(" index value ascii_val\n");
+ for (j = 0; j < 16; j++){
+ printf(" %d %d %c \n", j, log_data->fifo7[j], log_data->fifo7[j]);
+ }
+
+ printf(" FIFO 8 ASCII String\n");
+ printf(" index value ascii_val\n");
+ for (j = 0; j < 16; j++){
+ printf("index value ascii_val");
+ printf(" %d %d %c \n", j, log_data->fifo8[j], log_data->fifo8[j]);
+ }
+
+ printf(" FIFO 9 ASCII String\n");
+ printf(" index value ascii_val\n");
+ for (j = 0; j < 16; j++){
+ printf(" %d %d %c \n", j, log_data->fifo9[j], log_data->fifo9[j]);
+ }
+
+ printf(" FIFO 10 ASCII String\n");
+ printf(" index value ascii_val\n");
+ for (j = 0; j < 16; j++){
+ printf(" %d %d %c \n", j, log_data->fifo10[j], log_data->fifo10[j]);
+ }
+
+ printf(" FIFO 11 ASCII String\n");
+ printf(" index value ascii_val\n");
+ for (j = 0; j < 16; j++){
+ printf(" %d %d %c \n", j, log_data->fifo11[j], log_data->fifo11[j]);
+ }
+
+ printf(" FIFO 12 ASCII String\n");
+ printf(" index value ascii_val\n");
+ for (j = 0; j < 16; j++){
+ printf(" %d %d %c \n", j, log_data->fifo12[j], log_data->fifo12[j]);
+ }
+
+ printf(" FIFO 13 ASCII String\n");
+ printf(" index value ascii_val\n");
+ for (j = 0; j < 16; j++){
+ printf(" %d %d %c \n", j, log_data->fifo13[j], log_data->fifo13[j]);
+ }
+
+ printf(" FIFO 14 ASCII String\n");
+ printf(" index value ascii_val\n");
+ for (j = 0; j < 16; j++){
+ printf(" %d %d %c \n", j, log_data->fifo14[j], log_data->fifo14[j]);
+ }
+
+ printf(" FIFO 15 ASCII String\n");
+ printf(" index value ascii_val\n");
+ for (j = 0; j < 16; j++){
+ printf(" %d %d %c \n", j, log_data->fifo15[j], log_data->fifo16[j]);
+ }
+
+ printf(" FIFO 16 ASCII String\n");
+ printf(" index value ascii_val\n");
+ for (j = 0; j < 16; j++){
+ printf(" %d %d %c \n", j, log_data->fifo16[j], log_data->fifo16[j]);
+ }
+
+ printf(" Reserved : ");
+ for (j = 0; j < 48; j++)
+ printf("%d", log_data->reserved3[j]);
+ printf("\n");
+
+ memcpy(stat_id_str_table_arr, (__u8*)log_data_buf + stat_id_str_table_ofst, (log_data->sitsz * 4));
+ memcpy(event_id_str_table_arr, (__u8*)log_data_buf + event_str_table_ofst, (log_data->estsz * 4));
+ memcpy(vu_event_id_str_table_arr, (__u8*)log_data_buf + vu_event_str_table_ofst, (log_data->vu_eve_st_sz * 4));
+ memcpy(ascii_table_info_arr, (__u8*)log_data_buf + ascii_table_ofst, (log_data->asctsz * 4));
+
+ printf(" Statistics Identifier String Table\n");
+ for (j = 0; j < stat_id_index; j++){
+ printf(" Vendor Specific Statistic Identifier : 0x%x\n",le16_to_cpu(stat_id_str_table_arr[j].vs_si));
+ printf(" Reserved : 0x%d",stat_id_str_table_arr[j].reserved1);
+ printf(" ASCII ID Length : 0x%x\n",stat_id_str_table_arr[j].ascii_id_len);
+ printf(" ASCII ID offset : 0x%lx\n",le64_to_cpu(stat_id_str_table_arr[j].ascii_id_ofst));
+ printf(" Reserved : 0x%d\n",stat_id_str_table_arr[j].reserved2);
+ }
+
+ printf(" Event Identifier String Table Entry\n");
+ for (j = 0; j < eve_id_index; j++){
+ printf(" Debug Event Class : 0x%x\n",event_id_str_table_arr[j].deb_eve_class);
+ printf(" Event Identifier : 0x%x\n",le16_to_cpu(event_id_str_table_arr[j].ei));
+ printf(" ASCII ID Length : 0x%x\n",event_id_str_table_arr[j].ascii_id_len);
+ printf(" ASCII ID offset : 0x%lx\n",le64_to_cpu(event_id_str_table_arr[j].ascii_id_ofst));
+ printf(" Reserved : 0x%d\n",event_id_str_table_arr[j].reserved2);
+
+ }
+
+ printf(" VU Event Identifier String Table Entry\n");
+ for (j = 0; j < vu_eve_index; j++){
+ printf(" Debug Event Class : 0x%x\n",vu_event_id_str_table_arr[j].deb_eve_class);
+ printf(" VU Event Identifier : 0x%x\n",le16_to_cpu(vu_event_id_str_table_arr[j].vu_ei));
+ printf(" ASCII ID Length : 0x%x\n",vu_event_id_str_table_arr[j].ascii_id_len);
+ printf(" ASCII ID offset : 0x%lx\n",le64_to_cpu(vu_event_id_str_table_arr[j].ascii_id_ofst));
+ printf(" Reserved : 0x%d\n",vu_event_id_str_table_arr[j].reserved);
+
+ }
+
+ printf(" ASCII Table\n");
+ printf(" Byte Data_Byte ASCII_Character\n");
+ for (j = 0; j < ascii_table_index; j++){
+ printf(" %lld 0x%x %c \n",ascii_table_ofst+j,ascii_table_info_arr[j],ascii_table_info_arr[j]);
+ }
+ return 0;
+}
+
+static int ocp_print_C9_log_json(struct telemetry_str_log_format *log_data,__u8 *log_data_buf)
+{
+ struct json_object *root = json_create_object();
+ struct json_object *stat_table = json_create_object();
+ struct json_object *eve_table = json_create_object();
+ struct json_object *vu_eve_table = json_create_object();
+ struct json_object *entry = json_create_object();
+ char res_arr[48];
+ char *res = res_arr;
+ char guid_buf[C9_GUID_LENGTH];
+ char *guid = guid_buf;
+ char fifo_arr[16];
+ char *fifo = fifo_arr;
+ //calculating the index value for array
+ __le64 stat_id_index = (log_data->sitsz * 4) / 16;
+ __le64 eve_id_index = (log_data->estsz * 4) / 16;
+ __le64 vu_eve_index = (log_data->vu_eve_st_sz * 4) / 16;
+ __le64 ascii_table_index = (log_data->asctsz * 4);
+ //Calculating the offset for dynamic fields.
+ __le64 stat_id_str_table_ofst = C9_TELEMETRY_STR_LOG_SIST_OFST + (log_data->sitsz * 4);
+ __le64 event_str_table_ofst = stat_id_str_table_ofst + (log_data->estsz * 4);
+ __le64 vu_event_str_table_ofst = event_str_table_ofst + (log_data->vu_eve_st_sz * 4);
+ __le64 ascii_table_ofst = vu_event_str_table_ofst + (log_data->asctsz * 4);
+ struct statistics_id_str_table_entry stat_id_str_table_arr[stat_id_index];
+ struct event_id_str_table_entry event_id_str_table_arr[eve_id_index];
+ struct vu_event_id_str_table_entry vu_event_id_str_table_arr[vu_eve_index];
+ __u8 ascii_table_info_arr[ascii_table_index];
+ char ascii_buf[ascii_table_index];
+ char *ascii = ascii_buf;
+ int j;
+
+ json_object_add_value_int(root, "Log Page Version", le16_to_cpu(log_data->log_page_version));
+
+ memset((__u8 *)res, 0, 15);
+ for (j = 0; j < 15; j++)
+ res += sprintf(res, "%d", log_data->reserved1[j]);
+ json_object_add_value_string(root, "Reserved", res_arr);
+
+ memset((void *)guid, 0, C9_GUID_LENGTH);
+ for (j = C9_GUID_LENGTH - 1; j >= 0; j--)
+ guid += sprintf(guid, "%02x", log_data->log_page_guid[j]);
+ json_object_add_value_string(root, "Log page GUID", guid_buf);
+
+ json_object_add_value_int(root, "Telemetry String Log Size", le64_to_cpu(log_data->sls));
+
+ memset((__u8 *)res, 0, 24);
+ for (j = 0; j < 24; j++)
+ res += sprintf(res, "%d", log_data->reserved2[j]);
+ json_object_add_value_string(root, "Reserved", res_arr);
+
+ json_object_add_value_int(root, "Statistics Identifier String Table Start", le64_to_cpu(log_data->sits));
+ json_object_add_value_int(root, "Event String Table Start", le64_to_cpu(log_data->ests));
+ json_object_add_value_int(root, "Event String Table Size", le64_to_cpu(log_data->estsz));
+ json_object_add_value_int(root, "VU Event String Table Start", le64_to_cpu(log_data->vu_eve_sts));
+ json_object_add_value_int(root, "VU Event String Table Size", le64_to_cpu(log_data->vu_eve_st_sz));
+ json_object_add_value_int(root, "ASCII Table Start", le64_to_cpu(log_data->ascts));
+ json_object_add_value_int(root, "ASCII Table Size", le64_to_cpu(log_data->asctsz));
+
+ memset((void *)fifo, 0, 16);
+ for (j = 0; j < 16; j++)
+ fifo += sprintf(fifo, "%c", log_data->fifo1[j]);
+ json_object_add_value_string(root, "FIFO 1 ASCII String", fifo_arr);
+
+ memset((void *)fifo, 0, 16);
+ for (j = 0; j < 16; j++)
+ fifo += sprintf(fifo, "%c", log_data->fifo2[j]);
+ json_object_add_value_string(root, "FIFO 2 ASCII String", fifo_arr);
+
+ memset((void *)fifo, 0, 16);
+ for (j = 0; j < 16; j++)
+ fifo += sprintf(fifo, "%c", log_data->fifo3[j]);
+ json_object_add_value_string(root, "FIFO 3 ASCII String", fifo_arr);
+
+ memset((void *)fifo, 0, 16);
+ for (j = 0; j < 16; j++)
+ fifo += sprintf(fifo, "%c", log_data->fifo4[j]);
+ json_object_add_value_string(root, "FIFO 4 ASCII String", fifo_arr);
+
+ memset((void *)fifo, 0, 16);
+ for (j = 0; j < 16; j++)
+ fifo += sprintf(fifo, "%c", log_data->fifo5[j]);
+ json_object_add_value_string(root, "FIFO 5 ASCII String", fifo_arr);
+
+ memset((void *)fifo, 0, 16);
+ for (j = 0; j < 16; j++)
+ fifo += sprintf(fifo, "%c", log_data->fifo6[j]);
+ json_object_add_value_string(root, "FIFO 6 ASCII String", fifo_arr);
+
+ memset((void *)fifo, 0, 16);
+ for (j = 0; j < 16; j++)
+ fifo += sprintf(fifo, "%c", log_data->fifo7[j]);
+ json_object_add_value_string(root, "FIFO 7 ASCII String", fifo_arr);
+
+ memset((void *)fifo, 0, 16);
+ for (j = 0; j < 16; j++)
+ fifo += sprintf(fifo, "%c", log_data->fifo8[j]);
+ json_object_add_value_string(root, "FIFO 8 ASCII String", fifo_arr);
+
+ memset((void *)fifo, 0, 16);
+ for (j = 0; j < 16; j++)
+ fifo += sprintf(fifo, "%c", log_data->fifo9[j]);
+ json_object_add_value_string(root, "FIFO 9 ASCII String", fifo_arr);
+
+ memset((void *)fifo, 0, 16);
+ for (j = 0; j < 16; j++)
+ fifo += sprintf(fifo, "%c", log_data->fifo10[j]);
+ json_object_add_value_string(root, "FIFO 10 ASCII String", fifo_arr);
+
+ memset((void *)fifo, 0, 16);
+ for (j = 0; j < 16; j++)
+ fifo += sprintf(fifo, "%c", log_data->fifo11[j]);
+ json_object_add_value_string(root, "FIFO 11 ASCII String", fifo_arr);
+
+ memset((void *)fifo, 0, 16);
+ for (j = 0; j < 16; j++)
+ fifo += sprintf(fifo, "%c", log_data->fifo12[j]);
+ json_object_add_value_string(root, "FIFO 12 ASCII String", fifo_arr);
+
+ memset((void *)fifo, 0, 16);
+ for (j = 0; j < 16; j++)
+ fifo += sprintf(fifo, "%c", log_data->fifo13[j]);
+ json_object_add_value_string(root, "FIFO 13 ASCII String", fifo_arr);
+
+ memset((void *)fifo, 0, 16);
+ for (j = 0; j < 16; j++)
+ fifo += sprintf(fifo, "%c", log_data->fifo14[j]);
+ json_object_add_value_string(root, "FIFO 14 ASCII String", fifo_arr);
+
+ memset((void *)fifo, 0, 16);
+ for (j = 0; j < 16; j++)
+ fifo += sprintf(fifo, "%c", log_data->fifo15[j]);
+ json_object_add_value_string(root, "FIFO 15 ASCII String", fifo_arr);
+
+ memset((void *)fifo, 0, 16);
+ for (j = 0; j < 16; j++)
+ fifo += sprintf(fifo, "%c", log_data->fifo16[j]);
+ json_object_add_value_string(root, "FIFO 16 ASCII String", fifo_arr);
+
+ memset((__u8 *)res, 0, 48);
+ for (j = 0; j < 48; j++)
+ res += sprintf(res, "%d", log_data->reserved3[j]);
+ json_object_add_value_string(root, "Reserved", res_arr);
+
+ memcpy(stat_id_str_table_arr, (__u8*)log_data_buf + stat_id_str_table_ofst, (log_data->sitsz * 4));
+ memcpy(event_id_str_table_arr, (__u8*)log_data_buf + event_str_table_ofst, (log_data->estsz * 4));
+ memcpy(vu_event_id_str_table_arr, (__u8*)log_data_buf + vu_event_str_table_ofst, (log_data->vu_eve_st_sz * 4));
+ memcpy(ascii_table_info_arr, (__u8*)log_data_buf + ascii_table_ofst, (log_data->asctsz * 4));
+
+ for (j = 0; j < stat_id_index; j++){
+ json_object_add_value_int(entry, "Vendor Specific Statistic Identifier", le16_to_cpu(stat_id_str_table_arr[j].vs_si));
+ json_object_add_value_int(entry, "Reserved", le64_to_cpu(stat_id_str_table_arr[j].reserved1));
+ json_object_add_value_int(entry, "ASCII ID Length", le64_to_cpu(stat_id_str_table_arr[j].ascii_id_len));
+ json_object_add_value_int(entry, "ASCII ID offset", le64_to_cpu(stat_id_str_table_arr[j].ascii_id_ofst));
+ json_object_add_value_int(entry, "Reserved", le64_to_cpu(stat_id_str_table_arr[j].reserved2));
+ json_array_add_value_object(stat_table, entry);
+ }
+ json_object_add_value_array(root, "Statistics Identifier String Table", stat_table);
+
+ for (j = 0; j < eve_id_index; j++){
+ json_object_add_value_int(entry, "Debug Event Class", le16_to_cpu(event_id_str_table_arr[j].deb_eve_class));
+ json_object_add_value_int(entry, "Event Identifier", le16_to_cpu(event_id_str_table_arr[j].ei));
+ json_object_add_value_int(entry, "ASCII ID Length", le64_to_cpu(event_id_str_table_arr[j].ascii_id_len));
+ json_object_add_value_int(entry, "ASCII ID offset", le64_to_cpu(event_id_str_table_arr[j].ascii_id_ofst));
+ json_object_add_value_int(entry, "Reserved", le64_to_cpu(event_id_str_table_arr[j].reserved2));
+ json_array_add_value_object(eve_table, entry);
+ }
+ json_object_add_value_array(root, "Event Identifier String Table Entry", eve_table);
+
+ for (j = 0; j < vu_eve_index; j++){
+ json_object_add_value_int(entry, "Debug Event Class", le16_to_cpu(vu_event_id_str_table_arr[j].deb_eve_class));
+ json_object_add_value_int(entry, "VU Event Identifier", le16_to_cpu(vu_event_id_str_table_arr[j].vu_ei));
+ json_object_add_value_int(entry, "ASCII ID Length", le64_to_cpu(vu_event_id_str_table_arr[j].ascii_id_len));
+ json_object_add_value_int(entry, "ASCII ID offset", le64_to_cpu(vu_event_id_str_table_arr[j].ascii_id_ofst));
+ json_object_add_value_int(entry, "Reserved", le64_to_cpu(vu_event_id_str_table_arr[j].reserved));
+ json_array_add_value_object(vu_eve_table, entry);
+ }
+ json_object_add_value_array(root, "VU Event Identifier String Table Entry", vu_eve_table);
+
+ memset((void *)ascii, 0, ascii_table_index);
+ for (j = 0; j < ascii_table_index; j++)
+ ascii += sprintf(ascii, "%c", ascii_table_info_arr[j]);
+ json_object_add_value_string(root, "ASCII Table", ascii_buf);
+
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+ json_free_object(stat_table);
+ json_free_object(eve_table);
+ json_free_object(vu_eve_table);
+
+ return 0;
+}
+
+static void ocp_print_c9_log_binary(__u8 *log_data_buf,int total_log_page_size)
+{
+ return d_raw((unsigned char *)log_data_buf, total_log_page_size);
+}
+
+static int get_c9_log_page(struct nvme_dev *dev, char *format)
+{
+ int ret = 0;
+ __u8 *header_data;
+ struct telemetry_str_log_format *log_data;
+ enum nvme_print_flags fmt;
+ __u8 *full_log_buf_data = NULL;
+ __le64 stat_id_str_table_ofst = 0;
+ __le64 event_str_table_ofst = 0;
+ __le64 vu_event_str_table_ofst = 0;
+ __le64 ascii_table_ofst = 0;
+ __le64 total_log_page_sz = 0;
+
+ ret = validate_output_format(format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR : OCP : invalid output format\n");
+ return ret;
+ }
+
+ header_data = (__u8 *)malloc(sizeof(__u8) * C9_TELEMETRY_STR_LOG_LEN);
+ if (!header_data) {
+ fprintf(stderr, "ERROR : OCP : malloc : %s\n", strerror(errno));
+ return -1;
+ }
+ memset(header_data, 0, sizeof(__u8) * C9_TELEMETRY_STR_LOG_LEN);
+
+ ret = nvme_get_log_simple(dev_fd(dev), C9_TELEMETRY_STRING_LOG_ENABLE_OPCODE,
+ C9_TELEMETRY_STR_LOG_LEN, header_data);
+
+ if (!ret) {
+ log_data = (struct telemetry_str_log_format *)header_data;
+ printf("Statistics Identifier String Table Size = %lld\n",log_data->sitsz);
+ printf("Event String Table Size = %lld\n",log_data->estsz);
+ printf("VU Event String Table Size = %lld\n",log_data->vu_eve_st_sz);
+ printf("ASCII Table Size = %lld\n",log_data->asctsz);
+
+ //Calculating the offset for dynamic fields.
+ stat_id_str_table_ofst = C9_TELEMETRY_STR_LOG_SIST_OFST + (log_data->sitsz * 4);
+ event_str_table_ofst = stat_id_str_table_ofst + (log_data->estsz * 4);
+ vu_event_str_table_ofst = event_str_table_ofst + (log_data->vu_eve_st_sz * 4);
+ ascii_table_ofst = vu_event_str_table_ofst + (log_data->asctsz * 4);
+ total_log_page_sz = stat_id_str_table_ofst + event_str_table_ofst + vu_event_str_table_ofst + ascii_table_ofst;
+
+ printf("stat_id_str_table_ofst = %lld\n",stat_id_str_table_ofst);
+ printf("event_str_table_ofst = %lld\n",event_str_table_ofst);
+ printf("vu_event_str_table_ofst = %lld\n",vu_event_str_table_ofst);
+ printf("ascii_table_ofst = %lld\n",ascii_table_ofst);
+ printf("total_log_page_sz = %lld\n",total_log_page_sz);
+
+ full_log_buf_data = (__u8 *)malloc(sizeof(__u8) * total_log_page_sz);
+ if (!full_log_buf_data) {
+ fprintf(stderr, "ERROR : OCP : malloc : %s\n", strerror(errno));
+ return -1;
+ }
+ memset(full_log_buf_data, 0, sizeof(__u8) * total_log_page_sz);
+
+ ret = nvme_get_log_simple(dev_fd(dev), C9_TELEMETRY_STRING_LOG_ENABLE_OPCODE,
+ total_log_page_sz, full_log_buf_data);
+
+ if (!ret) {
+ switch (fmt) {
+ case NORMAL:
+ ocp_print_C9_log_normal(log_data,full_log_buf_data);
+ break;
+ case JSON:
+ ocp_print_C9_log_json(log_data,full_log_buf_data);
+ break;
+ case BINARY:
+ ocp_print_c9_log_binary(full_log_buf_data,total_log_page_sz);
+ break;
+ default:
+ fprintf(stderr, "unhandled output format\n");
+ break;
+ }
+ } else{
+ fprintf(stderr, "ERROR : OCP : Unable to read C9 data from buffer\n");
+ }
+ } else {
+ fprintf(stderr, "ERROR : OCP : Unable to read C9 data from buffer\n");
+ }
+
+ free(header_data);
+ free(full_log_buf_data);
+
+ return ret;
+}
+
+static int ocp_telemetry_str_log_format(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ struct nvme_dev *dev;
+ int ret = 0;
+ const char *desc = "Retrieve telemetry string log format";
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "output Format: normal|json"),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ ret = get_c9_log_page(dev, cfg.output_format);
+ if (ret)
+ fprintf(stderr, "ERROR : OCP : Failure reading the C9 Log Page, ret = %d\n", ret);
+
+ dev_close(dev);
+
+ return ret;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+/// Misc
+
+static int clear_fw_update_history(int argc, char **argv,
+ struct command *cmd, struct plugin *plugin)
+{
+ return ocp_clear_fw_update_history(argc, argv, cmd, plugin);
+}
+
+static int smart_add_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ return ocp_smart_add_log(argc, argv, cmd, plugin);
+}
+
+static int clear_pcie_correctable_error_counters(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ return ocp_clear_pcie_correctable_errors(argc, argv, cmd, plugin);
+}
+
+static int fw_activation_history_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ return ocp_fw_activation_history_log(argc, argv, cmd, plugin);
+}
diff --git a/plugins/ocp/ocp-nvme.h b/plugins/ocp/ocp-nvme.h
new file mode 100644
index 0000000..95539b0
--- /dev/null
+++ b/plugins/ocp/ocp-nvme.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (c) 2022 Meta Platforms, Inc.
+ *
+ * Authors: Arthur Shau <arthurshau@fb.com>,
+ * Wei Zhang <wzhang@fb.com>,
+ * Venkat Ramesh <venkatraghavan@fb.com>
+ */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/ocp/ocp-nvme
+
+#if !defined(OCP_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define OCP_NVME
+
+#include "cmd.h"
+
+PLUGIN(NAME("ocp", "OCP cloud SSD extensions", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("smart-add-log", "Retrieve extended SMART Information", smart_add_log)
+ ENTRY("latency-monitor-log", "Get Latency Monitor Log Page", ocp_latency_monitor_log)
+ ENTRY("set-latency-monitor-feature", "Set Latency Monitor feature", ocp_set_latency_monitor_feature)
+ ENTRY("internal-log", "Retrieve and save internal device telemetry log", ocp_telemetry_log)
+ ENTRY("clear-fw-activate-history", "Clear firmware update history log", clear_fw_update_history)
+ ENTRY("eol-plp-failure-mode", "Define EOL or PLP circuitry failure mode.", eol_plp_failure_mode)
+ ENTRY("clear-pcie-correctable-errors", "Clear PCIe correctable error counters", clear_pcie_correctable_error_counters)
+ ENTRY("fw-activate-history", "Get firmware activation history log", fw_activation_history_log)
+ ENTRY("unsupported-reqs-log", "Get Unsupported Requirements Log Page", ocp_unsupported_requirements_log)
+ ENTRY("error-recovery-log", "Retrieve Error Recovery Log Page", ocp_error_recovery_log)
+ ENTRY("device-capability-log", "Get Device capabilities Requirements Log Page", ocp_device_capabilities_log)
+ ENTRY("set-dssd-power-state-feature", "Get Device capabilities Requirements Log Page", set_dssd_power_state_feature)
+ ENTRY("set-plp-health-check-interval", "Set PLP Health Check Interval", set_plp_health_check_interval)
+ ENTRY("get-plp-health-check-interval", "Get PLP Health Check Interval", get_plp_health_check_interval)
+ ENTRY("telemetry-string-log", "Retrieve Telemetry string Log Page", ocp_telemetry_str_log_format)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/ocp/ocp-smart-extended-log.c b/plugins/ocp/ocp-smart-extended-log.c
new file mode 100644
index 0000000..0d8ba81
--- /dev/null
+++ b/plugins/ocp/ocp-smart-extended-log.c
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright (c) 2022 Meta Platforms, Inc.
+ *
+ * Authors: Arthur Shau <arthurshau@fb.com>,
+ * Wei Zhang <wzhang@fb.com>,
+ * Venkat Ramesh <venkatraghavan@fb.com>
+ */
+
+#include "ocp-smart-extended-log.h"
+
+#include <errno.h>
+#include <stdio.h>
+
+#include "common.h"
+#include "nvme-print.h"
+
+/* C0 SCAO Log Page */
+#define C0_SMART_CLOUD_ATTR_LEN 0x200
+#define C0_SMART_CLOUD_ATTR_OPCODE 0xC0
+#define C0_GUID_LENGTH 16
+
+static __u8 scao_guid[C0_GUID_LENGTH] = {
+ 0xC5, 0xAF, 0x10, 0x28,
+ 0xEA, 0xBF, 0xF2, 0xA4,
+ 0x9C, 0x4F, 0x6F, 0x7C,
+ 0xC9, 0x14, 0xD5, 0xAF
+};
+
+enum {
+ SCAO_PMUW = 0, /* Physical media units written */
+ SCAO_PMUR = 16, /* Physical media units read */
+ SCAO_BUNBR = 32, /* Bad user nand blocks raw */
+ SCAO_BUNBN = 38, /* Bad user nand blocks normalized */
+ SCAO_BSNBR = 40, /* Bad system nand blocks raw */
+ SCAO_BSNBN = 46, /* Bad system nand blocks normalized */
+ SCAO_XRC = 48, /* XOR recovery count */
+ SCAO_UREC = 56, /* Uncorrectable read error count */
+ SCAO_SEEC = 64, /* Soft ecc error count */
+ SCAO_EEDC = 72, /* End to end detected errors */
+ SCAO_EECE = 76, /* End to end corrected errors */
+ SCAO_SDPU = 80, /* System data percent used */
+ SCAO_RFSC = 81, /* Refresh counts */
+ SCAO_MXUDEC = 88, /* Max User data erase counts */
+ SCAO_MNUDEC = 92, /* Min User data erase counts */
+ SCAO_NTTE = 96, /* Number of Thermal throttling events */
+ SCAO_CTS = 97, /* Current throttling status */
+ SCAO_EVF = 98, /* Errata Version Field */
+ SCAO_PVF = 99, /* Point Version Field */
+ SCAO_MIVF = 101, /* Minor Version Field */
+ SCAO_MAVF = 103, /* Major Version Field */
+ SCAO_PCEC = 104, /* PCIe correctable error count */
+ SCAO_ICS = 112, /* Incomplete shutdowns */
+ SCAO_PFB = 120, /* Percent free blocks */
+ SCAO_CPH = 128, /* Capacitor health */
+ SCAO_NEV = 130, /* NVMe Errata Version */
+ SCAO_UIO = 136, /* Unaligned I/O */
+ SCAO_SVN = 144, /* Security Version Number */
+ SCAO_NUSE = 152, /* NUSE - Namespace utilization */
+ SCAO_PSC = 160, /* PLP start count */
+ SCAO_EEST = 176, /* Endurance estimate */
+ SCAO_PLRC = 192, /* PCIe Link Retraining Count */
+ SCAO_PSCC = 200, /* Power State Change Count */
+ SCAO_LPV = 494, /* Log page version */
+ SCAO_LPG = 496, /* Log page GUID */
+};
+
+static void ocp_print_C0_log_normal(void *data)
+{
+ uint16_t smart_log_ver = 0;
+ __u8 *log_data = data;
+
+ printf("SMART Cloud Attributes :-\n");
+
+ printf(" Physical media units written - %"PRIu64" %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PMUW + 8] & 0xFFFFFFFFFFFFFFFF),
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PMUW] & 0xFFFFFFFFFFFFFFFF));
+ printf(" Physical media units read - %"PRIu64" %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PMUR + 8] & 0xFFFFFFFFFFFFFFFF),
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PMUR] & 0xFFFFFFFFFFFFFFFF));
+ printf(" Bad user nand blocks - Raw %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_BUNBR] & 0x0000FFFFFFFFFFFF));
+ printf(" Bad user nand blocks - Normalized %d\n",
+ (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_BUNBN]));
+ printf(" Bad system nand blocks - Raw %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_BSNBR] & 0x0000FFFFFFFFFFFF));
+ printf(" Bad system nand blocks - Normalized %d\n",
+ (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_BSNBN]));
+ printf(" XOR recovery count %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_XRC]));
+ printf(" Uncorrectable read error count %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_UREC]));
+ printf(" Soft ecc error count %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_SEEC]));
+ printf(" End to end detected errors %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_EEDC]));
+ printf(" End to end corrected errors %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_EECE]));
+ printf(" System data percent used %d\n",
+ (__u8)log_data[SCAO_SDPU]);
+ printf(" Refresh counts %"PRIu64"\n",
+ (uint64_t)(le64_to_cpu(*(uint64_t *)&log_data[SCAO_RFSC]) & 0x00FFFFFFFFFFFFFF));
+ printf(" Max User data erase counts %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_MXUDEC]));
+ printf(" Min User data erase counts %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_MNUDEC]));
+ printf(" Number of Thermal throttling events %d\n",
+ (__u8)log_data[SCAO_NTTE]);
+ printf(" Current throttling status 0x%x\n",
+ (__u8)log_data[SCAO_CTS]);
+ printf(" PCIe correctable error count %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PCEC]));
+ printf(" Incomplete shutdowns %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_ICS]));
+ printf(" Percent free blocks %d\n",
+ (__u8)log_data[SCAO_PFB]);
+ printf(" Capacitor health %"PRIu16"\n",
+ (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_CPH]));
+ printf(" Unaligned I/O %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_UIO]));
+ printf(" Security Version Number %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_SVN]));
+ printf(" NUSE - Namespace utilization %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_NUSE]));
+ printf(" PLP start count %s\n",
+ uint128_t_to_string(le128_to_cpu(&log_data[SCAO_PSC])));
+ printf(" Endurance estimate %s\n",
+ uint128_t_to_string(le128_to_cpu(&log_data[SCAO_EEST])));
+ smart_log_ver = (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_LPV]);
+ printf(" Log page version %"PRIu16"\n", smart_log_ver);
+ printf(" Log page GUID 0x");
+ printf("%"PRIx64"%"PRIx64"\n", (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_LPG + 8]),
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_LPG]));
+ if (smart_log_ver > 2) {
+ printf(" Errata Version Field %d\n",
+ (__u8)log_data[SCAO_EVF]);
+ printf(" Point Version Field %"PRIu16"\n",
+ le16_to_cpu(*(uint16_t *)&log_data[SCAO_PVF]));
+ printf(" Minor Version Field %"PRIu16"\n",
+ le16_to_cpu(*(uint16_t *)&log_data[SCAO_MIVF]));
+ printf(" Major Version Field %d\n",
+ (__u8)log_data[SCAO_MAVF]);
+ printf(" NVMe Errata Version %d\n",
+ (__u8)log_data[SCAO_NEV]);
+ printf(" PCIe Link Retraining Count %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PLRC]));
+ printf(" Power State Change Count %"PRIu64"\n",
+ le64_to_cpu(*(uint64_t *)&log_data[SCAO_PSCC]));
+ }
+ printf("\n");
+}
+
+static void ocp_print_C0_log_json(void *data)
+{
+ struct json_object *root;
+ struct json_object *pmuw;
+ struct json_object *pmur;
+ uint16_t smart_log_ver = 0;
+ __u8 *log_data = data;
+ char guid[40];
+
+ root = json_create_object();
+ pmuw = json_create_object();
+ pmur = json_create_object();
+
+ json_object_add_value_uint64(pmuw, "hi",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PMUW + 8] & 0xFFFFFFFFFFFFFFFF));
+ json_object_add_value_uint64(pmuw, "lo",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PMUW] & 0xFFFFFFFFFFFFFFFF));
+ json_object_add_value_object(root, "Physical media units written", pmuw);
+ json_object_add_value_uint64(pmur, "hi",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PMUR + 8] & 0xFFFFFFFFFFFFFFFF));
+ json_object_add_value_uint64(pmur, "lo",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PMUR] & 0xFFFFFFFFFFFFFFFF));
+ json_object_add_value_object(root, "Physical media units read", pmur);
+ json_object_add_value_uint64(root, "Bad user nand blocks - Raw",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_BUNBR] & 0x0000FFFFFFFFFFFF));
+ json_object_add_value_uint(root, "Bad user nand blocks - Normalized",
+ (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_BUNBN]));
+ json_object_add_value_uint64(root, "Bad system nand blocks - Raw",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_BSNBR] & 0x0000FFFFFFFFFFFF));
+ json_object_add_value_uint(root, "Bad system nand blocks - Normalized",
+ (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_BSNBN]));
+ json_object_add_value_uint64(root, "XOR recovery count",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_XRC]));
+ json_object_add_value_uint64(root, "Uncorrectable read error count",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_UREC]));
+ json_object_add_value_uint64(root, "Soft ecc error count",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_SEEC]));
+ json_object_add_value_uint(root, "End to end detected errors",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_EEDC]));
+ json_object_add_value_uint(root, "End to end corrected errors",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_EECE]));
+ json_object_add_value_uint(root, "System data percent used",
+ (__u8)log_data[SCAO_SDPU]);
+ json_object_add_value_uint64(root, "Refresh counts",
+ (uint64_t)(le64_to_cpu(*(uint64_t *)&log_data[SCAO_RFSC]) & 0x00FFFFFFFFFFFFFF));
+ json_object_add_value_uint(root, "Max User data erase counts",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_MXUDEC]));
+ json_object_add_value_uint(root, "Min User data erase counts",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_MNUDEC]));
+ json_object_add_value_uint(root, "Number of Thermal throttling events",
+ (__u8)log_data[SCAO_NTTE]);
+ json_object_add_value_uint(root, "Current throttling status",
+ (__u8)log_data[SCAO_CTS]);
+ json_object_add_value_uint64(root, "PCIe correctable error count",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PCEC]));
+ json_object_add_value_uint(root, "Incomplete shutdowns",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_ICS]));
+ json_object_add_value_uint(root, "Percent free blocks",
+ (__u8)log_data[SCAO_PFB]);
+ json_object_add_value_uint(root, "Capacitor health",
+ (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_CPH]));
+ json_object_add_value_uint64(root, "Unaligned I/O",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_UIO]));
+ json_object_add_value_uint64(root, "Security Version Number",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_SVN]));
+ json_object_add_value_uint64(root, "NUSE - Namespace utilization",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_NUSE]));
+ json_object_add_value_uint128(root, "PLP start count",
+ le128_to_cpu(&log_data[SCAO_PSC]));
+ json_object_add_value_uint128(root, "Endurance estimate",
+ le128_to_cpu(&log_data[SCAO_EEST]));
+ smart_log_ver = (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_LPV]);
+
+ json_object_add_value_uint(root, "Log page version", smart_log_ver);
+
+ memset((void *)guid, 0, 40);
+ sprintf((char *)guid, "0x%"PRIx64"%"PRIx64"", (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_LPG + 8]),
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_LPG]));
+ json_object_add_value_string(root, "Log page GUID", guid);
+
+ if (smart_log_ver > 2) {
+ json_object_add_value_uint(root, "Errata Version Field",
+ (__u8)log_data[SCAO_EVF]);
+ json_object_add_value_uint(root, "Point Version Field",
+ le16_to_cpu(*(uint16_t *)&log_data[SCAO_PVF]));
+ json_object_add_value_uint(root, "Minor Version Field",
+ le16_to_cpu(*(uint16_t *)&log_data[SCAO_MIVF]));
+ json_object_add_value_uint(root, "Major Version Field",
+ (__u8)log_data[SCAO_MAVF]);
+ json_object_add_value_uint(root, "NVMe Errata Version",
+ (__u8)log_data[SCAO_NEV]);
+ json_object_add_value_uint(root, "PCIe Link Retraining Count",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PLRC]));
+ json_object_add_value_uint(root, "Power State Change Count",
+ le64_to_cpu(*(uint64_t *)&log_data[SCAO_PSCC]));
+ }
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+}
+
+static int get_c0_log_page(int fd, char *format)
+{
+ enum nvme_print_flags fmt;
+ __u8 *data;
+ int i;
+ int ret;
+
+ ret = validate_output_format(format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR : OCP : invalid output format\n");
+ return ret;
+ }
+
+ data = malloc(sizeof(__u8) * C0_SMART_CLOUD_ATTR_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR : OCP : malloc : %s\n", strerror(errno));
+ return -1;
+ }
+ memset(data, 0, sizeof(__u8) * C0_SMART_CLOUD_ATTR_LEN);
+
+ ret = nvme_get_log_simple(fd, C0_SMART_CLOUD_ATTR_OPCODE,
+ C0_SMART_CLOUD_ATTR_LEN, data);
+
+ if (strcmp(format, "json"))
+ fprintf(stderr, "NVMe Status:%s(%x)\n",
+ nvme_status_to_string(ret, false), ret);
+
+ if (ret == 0) {
+ /* check log page guid */
+ /* Verify GUID matches */
+ for (i = 0; i < 16; i++) {
+ if (scao_guid[i] != data[SCAO_LPG + i]) {
+ int j;
+
+ fprintf(stderr, "ERROR : OCP : Unknown GUID in C0 Log Page data\n");
+ fprintf(stderr, "ERROR : OCP : Expected GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", scao_guid[j]);
+
+ fprintf(stderr, "\nERROR : OCP : Actual GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", data[SCAO_LPG + j]);
+ fprintf(stderr, "\n");
+
+ ret = -1;
+ goto out;
+ }
+ }
+
+ /* print the data */
+ switch (fmt) {
+ case NORMAL:
+ ocp_print_C0_log_normal(data);
+ break;
+ case JSON:
+ ocp_print_C0_log_json(data);
+ break;
+ default:
+ break;
+ }
+ } else {
+ fprintf(stderr, "ERROR : OCP : Unable to read C0 data from buffer\n");
+ }
+
+out:
+ free(data);
+ return ret;
+}
+
+int ocp_smart_add_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ const char *desc = "Retrieve the extended SMART health data.";
+ struct nvme_dev *dev;
+ int ret = 0;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "output Format: normal|json"),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ ret = get_c0_log_page(dev_fd(dev), cfg.output_format);
+ if (ret)
+ fprintf(stderr, "ERROR : OCP : Failure reading the C0 Log Page, ret = %d\n",
+ ret);
+ dev_close(dev);
+ return ret;
+}
diff --git a/plugins/ocp/ocp-smart-extended-log.h b/plugins/ocp/ocp-smart-extended-log.h
new file mode 100644
index 0000000..42c1f98
--- /dev/null
+++ b/plugins/ocp/ocp-smart-extended-log.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (c) 2022 Meta Platforms, Inc.
+ *
+ * Authors: Arthur Shau <arthurshau@fb.com>,
+ * Wei Zhang <wzhang@fb.com>,
+ * Venkat Ramesh <venkatraghavan@fb.com>
+ */
+
+#ifndef OCP_SMART_EXTENDED_LOG_H
+#define OCP_SMART_EXTENDED_LOG_H
+
+struct command;
+struct plugin;
+
+int ocp_smart_add_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin);
+
+#endif
diff --git a/plugins/ocp/ocp-utils.c b/plugins/ocp/ocp-utils.c
new file mode 100644
index 0000000..1257b30
--- /dev/null
+++ b/plugins/ocp/ocp-utils.c
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <unistd.h>
+#include "ocp-utils.h"
+#include "nvme-print.h"
+
+const unsigned char ocp_uuid[NVME_UUID_LEN] = {
+ 0xc1, 0x94, 0xd5, 0x5b, 0xe0, 0x94, 0x47, 0x94, 0xa2, 0x1d,
+ 0x29, 0x99, 0x8f, 0x56, 0xbe, 0x6f };
+
+int ocp_get_uuid_index(struct nvme_dev *dev, int *index)
+{
+ struct nvme_id_uuid_list uuid_list;
+ int err = nvme_identify_uuid(dev_fd(dev), &uuid_list);
+
+ *index = 0;
+ if (err)
+ return err;
+
+ for (int i = 0; i < NVME_ID_UUID_LIST_MAX; i++) {
+ if (memcmp(ocp_uuid, &uuid_list.entry[i].uuid, NVME_UUID_LEN) == 0) {
+ *index = i + 1;
+ break;
+ }
+ }
+ return err;
+}
diff --git a/plugins/ocp/ocp-utils.h b/plugins/ocp/ocp-utils.h
new file mode 100644
index 0000000..d02bea9
--- /dev/null
+++ b/plugins/ocp/ocp-utils.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include "nvme.h"
+
+/**
+ * ocp_get_uuid_index() - Get OCP UUID index
+ * @dev: nvme device
+ * @index: integer pointer to here to save the index
+ * @result: The command completion result from CQE dword0
+ *
+ * Return: Zero if nvme device has UUID list log page, or result of get uuid list otherwise.
+ */
+int ocp_get_uuid_index(struct nvme_dev *dev, int *index);
diff --git a/plugins/scaleflux/sfx-nvme.c b/plugins/scaleflux/sfx-nvme.c
new file mode 100644
index 0000000..f752d5d
--- /dev/null
+++ b/plugins/scaleflux/sfx-nvme.c
@@ -0,0 +1,1687 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <linux/fs.h>
+#include <inttypes.h>
+#include <asm/byteorder.h>
+#include <sys/sysinfo.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <time.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "nvme-wrap.h"
+#include "nvme-print.h"
+#include "util/cleanup.h"
+
+#define CREATE_CMD
+#include "sfx-nvme.h"
+
+#define SFX_PAGE_SHIFT 12
+#define SECTOR_SHIFT 9
+
+#define SFX_GET_FREESPACE _IOWR('N', 0x240, struct sfx_freespace_ctx)
+#define NVME_IOCTL_CLR_CARD _IO('N', 0x47)
+
+#define IDEMA_CAP(exp_GB) (((__u64)exp_GB - 50ULL) * 1953504ULL + 97696368ULL)
+#define IDEMA_CAP2GB(exp_sector) (((__u64)exp_sector - 97696368ULL) / 1953504ULL + 50ULL)
+
+#define VANDA_MAJOR_IDX 0
+#define VANDA_MINOR_IDX 0
+
+#define MYRTLE_MAJOR_IDX 4
+#define MYRTLE_MINOR_IDX 1
+
+enum {
+ SFX_LOG_LATENCY_READ_STATS = 0xc1,
+ SFX_LOG_SMART = 0xc2,
+ SFX_LOG_LATENCY_WRITE_STATS = 0xc3,
+ SFX_LOG_QUAL = 0xc4,
+ SFX_LOG_MISMATCHLBA = 0xc5,
+ SFX_LOG_MEDIA = 0xc6,
+ SFX_LOG_BBT = 0xc7,
+ SFX_LOG_IDENTIFY = 0xcc,
+ SFX_FEAT_ATOMIC = 0x01,
+ SFX_FEAT_UP_P_CAP = 0xac,
+ SFX_FEAT_CLR_CARD = 0xdc,
+};
+
+enum sfx_nvme_admin_opcode {
+ nvme_admin_query_cap_info = 0xd3,
+ nvme_admin_change_cap = 0xd4,
+ nvme_admin_sfx_set_features = 0xd5,
+ nvme_admin_sfx_get_features = 0xd6,
+};
+
+struct sfx_freespace_ctx {
+ __u64 free_space;
+ __u64 phy_cap; /* physical capacity, in unit of sector */
+ __u64 phy_space; /* physical space considering OP, in unit of sector */
+ __u64 user_space; /* user required space, in unit of sector*/
+ __u64 hw_used; /* hw space used in 4K */
+ __u64 app_written; /* app data written in 4K */
+ __u64 out_of_space;
+ __u64 map_unit;
+ __u64 max_user_space;
+ __u64 extendible_user_cap_lba_count;
+ __u64 friendly_change_cap_support;
+};
+
+struct nvme_capacity_info {
+ __u64 lba_sec_sz;
+ __u64 phy_sec_sz;
+ __u64 used_space;
+ __u64 free_space;
+};
+
+struct __packed nvme_additional_smart_log_item {
+ __u8 key;
+ __u8 _kp[2];
+ __u8 norm;
+ __u8 _np;
+ union __packed {
+ __u8 raw[6];
+ struct __packed wear_level {
+ __le16 min;
+ __le16 max;
+ __le16 avg;
+ } wear_level;
+ struct __packed thermal_throttle {
+ __u8 pct;
+ __u32 count;
+ } thermal_throttle;
+ };
+ __u8 _rp;
+};
+
+struct nvme_additional_smart_log {
+ struct nvme_additional_smart_log_item program_fail_cnt;
+ struct nvme_additional_smart_log_item erase_fail_cnt;
+ struct nvme_additional_smart_log_item wear_leveling_cnt;
+ struct nvme_additional_smart_log_item e2e_err_cnt;
+ struct nvme_additional_smart_log_item crc_err_cnt;
+ struct nvme_additional_smart_log_item timed_workload_media_wear;
+ struct nvme_additional_smart_log_item timed_workload_host_reads;
+ struct nvme_additional_smart_log_item timed_workload_timer;
+ struct nvme_additional_smart_log_item thermal_throttle_status;
+ struct nvme_additional_smart_log_item retry_buffer_overflow_cnt;
+ struct nvme_additional_smart_log_item pll_lock_loss_cnt;
+ struct nvme_additional_smart_log_item nand_bytes_written;
+ struct nvme_additional_smart_log_item host_bytes_written;
+ struct nvme_additional_smart_log_item raid_recover_cnt; /* errors which can be recovered by RAID */
+ struct nvme_additional_smart_log_item prog_timeout_cnt;
+ struct nvme_additional_smart_log_item erase_timeout_cnt;
+ struct nvme_additional_smart_log_item read_timeout_cnt;
+ struct nvme_additional_smart_log_item read_ecc_cnt; /* retry cnt */
+ struct nvme_additional_smart_log_item non_media_crc_err_cnt;
+ struct nvme_additional_smart_log_item compression_path_err_cnt;
+ struct nvme_additional_smart_log_item out_of_space_flag;
+ struct nvme_additional_smart_log_item physical_usage_ratio;
+ struct nvme_additional_smart_log_item grown_bb; /* grown bad block */
+};
+
+int nvme_query_cap(int fd, __u32 nsid, __u32 data_len, void *data)
+{
+ int rc = 0;
+ struct nvme_passthru_cmd cmd = {
+ .opcode = nvme_admin_query_cap_info,
+ .nsid = nsid,
+ .addr = (__u64)(uintptr_t) data,
+ .data_len = data_len,
+ };
+
+ rc = ioctl(fd, SFX_GET_FREESPACE, data);
+ return rc ? nvme_submit_admin_passthru(fd, &cmd, NULL) : 0;
+}
+
+int nvme_change_cap(int fd, __u32 nsid, __u64 capacity)
+{
+ struct nvme_passthru_cmd cmd = {
+ .opcode = nvme_admin_change_cap,
+ .nsid = nsid,
+ .cdw10 = (capacity & 0xffffffff),
+ .cdw11 = (capacity >> 32),
+ };
+
+ return nvme_submit_admin_passthru(fd, &cmd, NULL);
+}
+
+int nvme_sfx_set_features(int fd, __u32 nsid, __u32 fid, __u32 value)
+{
+ struct nvme_passthru_cmd cmd = {
+ .opcode = nvme_admin_sfx_set_features,
+ .nsid = nsid,
+ .cdw10 = fid,
+ .cdw11 = value,
+ };
+
+ return nvme_submit_admin_passthru(fd, &cmd, NULL);
+}
+
+int nvme_sfx_get_features(int fd, __u32 nsid, __u32 fid, __u32 *result)
+{
+ int err = 0;
+ struct nvme_passthru_cmd cmd = {
+ .opcode = nvme_admin_sfx_get_features,
+ .nsid = nsid,
+ .cdw10 = fid,
+ };
+
+ err = nvme_submit_admin_passthru(fd, &cmd, NULL);
+ if (!err && result)
+ *result = cmd.result;
+
+ return err;
+}
+
+static void show_sfx_smart_log_jsn(struct nvme_additional_smart_log *smart,
+ unsigned int nsid, const char *devname)
+{
+ struct json_object *root, *entry_stats, *dev_stats, *multi;
+
+ root = json_create_object();
+ json_object_add_value_string(root, "Intel Smart log", devname);
+
+ dev_stats = json_create_object();
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->program_fail_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->program_fail_cnt.raw));
+ json_object_add_value_object(dev_stats, "program_fail_count", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->erase_fail_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->erase_fail_cnt.raw));
+ json_object_add_value_object(dev_stats, "erase_fail_count", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->wear_leveling_cnt.norm);
+ multi = json_create_object();
+ json_object_add_value_int(multi, "min", le16_to_cpu(smart->wear_leveling_cnt.wear_level.min));
+ json_object_add_value_int(multi, "max", le16_to_cpu(smart->wear_leveling_cnt.wear_level.max));
+ json_object_add_value_int(multi, "avg", le16_to_cpu(smart->wear_leveling_cnt.wear_level.avg));
+ json_object_add_value_object(entry_stats, "raw", multi);
+ json_object_add_value_object(dev_stats, "wear_leveling", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->e2e_err_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->e2e_err_cnt.raw));
+ json_object_add_value_object(dev_stats, "end_to_end_error_detection_count", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->crc_err_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->crc_err_cnt.raw));
+ json_object_add_value_object(dev_stats, "crc_error_count", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->timed_workload_media_wear.norm);
+ json_object_add_value_float(entry_stats, "raw", ((float)int48_to_long(smart->timed_workload_media_wear.raw)) / 1024);
+ json_object_add_value_object(dev_stats, "timed_workload_media_wear", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->timed_workload_host_reads.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->timed_workload_host_reads.raw));
+ json_object_add_value_object(dev_stats, "timed_workload_host_reads", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->timed_workload_timer.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->timed_workload_timer.raw));
+ json_object_add_value_object(dev_stats, "timed_workload_timer", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->thermal_throttle_status.norm);
+ multi = json_create_object();
+ json_object_add_value_int(multi, "pct", smart->thermal_throttle_status.thermal_throttle.pct);
+ json_object_add_value_int(multi, "cnt", smart->thermal_throttle_status.thermal_throttle.count);
+ json_object_add_value_object(entry_stats, "raw", multi);
+ json_object_add_value_object(dev_stats, "thermal_throttle_status", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->retry_buffer_overflow_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->retry_buffer_overflow_cnt.raw));
+ json_object_add_value_object(dev_stats, "retry_buffer_overflow_count", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->pll_lock_loss_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->pll_lock_loss_cnt.raw));
+ json_object_add_value_object(dev_stats, "pll_lock_loss_count", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->nand_bytes_written.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->nand_bytes_written.raw));
+ json_object_add_value_object(dev_stats, "nand_bytes_written", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->host_bytes_written.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->host_bytes_written.raw));
+ json_object_add_value_object(dev_stats, "host_bytes_written", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->raid_recover_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->raid_recover_cnt.raw));
+ json_object_add_value_object(dev_stats, "raid_recover_cnt", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->prog_timeout_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->prog_timeout_cnt.raw));
+ json_object_add_value_object(dev_stats, "prog_timeout_cnt", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->erase_timeout_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->erase_timeout_cnt.raw));
+ json_object_add_value_object(dev_stats, "erase_timeout_cnt", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->read_timeout_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->read_timeout_cnt.raw));
+ json_object_add_value_object(dev_stats, "read_timeout_cnt", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->read_ecc_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->read_ecc_cnt.raw));
+ json_object_add_value_object(dev_stats, "read_ecc_cnt", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->non_media_crc_err_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->non_media_crc_err_cnt.raw));
+ json_object_add_value_object(dev_stats, "non_media_crc_err_cnt", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->compression_path_err_cnt.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->compression_path_err_cnt.raw));
+ json_object_add_value_object(dev_stats, "compression_path_err_cnt", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->out_of_space_flag.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->out_of_space_flag.raw));
+ json_object_add_value_object(dev_stats, "out_of_space_flag", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->physical_usage_ratio.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->physical_usage_ratio.raw));
+ json_object_add_value_object(dev_stats, "physical_usage_ratio", entry_stats);
+
+ entry_stats = json_create_object();
+ json_object_add_value_int(entry_stats, "normalized", smart->grown_bb.norm);
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(smart->grown_bb.raw));
+ json_object_add_value_object(dev_stats, "grown_bb", entry_stats);
+
+ json_object_add_value_object(root, "Device stats", dev_stats);
+
+ json_print_object(root, NULL);
+ printf("/n");
+ json_free_object(root);
+}
+
+static void show_sfx_smart_log(struct nvme_additional_smart_log *smart,
+ unsigned int nsid, const char *devname)
+{
+ printf("Additional Smart Log for ScaleFlux device:%s namespace-id:%x\n",
+ devname, nsid);
+ printf("key normalized raw\n");
+ printf("program_fail_count : %3d%% %"PRIu64"\n",
+ smart->program_fail_cnt.norm,
+ int48_to_long(smart->program_fail_cnt.raw));
+ printf("erase_fail_count : %3d%% %"PRIu64"\n",
+ smart->erase_fail_cnt.norm,
+ int48_to_long(smart->erase_fail_cnt.raw));
+ printf("wear_leveling : %3d%% min: %u, max: %u, avg: %u\n",
+ smart->wear_leveling_cnt.norm,
+ le16_to_cpu(smart->wear_leveling_cnt.wear_level.min),
+ le16_to_cpu(smart->wear_leveling_cnt.wear_level.max),
+ le16_to_cpu(smart->wear_leveling_cnt.wear_level.avg));
+ printf("end_to_end_error_detection_count: %3d%% %"PRIu64"\n",
+ smart->e2e_err_cnt.norm,
+ int48_to_long(smart->e2e_err_cnt.raw));
+ printf("crc_error_count : %3d%% %"PRIu64"\n",
+ smart->crc_err_cnt.norm,
+ int48_to_long(smart->crc_err_cnt.raw));
+ printf("timed_workload_media_wear : %3d%% %.3f%%\n",
+ smart->timed_workload_media_wear.norm,
+ ((float)int48_to_long(smart->timed_workload_media_wear.raw)) / 1024);
+ printf("timed_workload_host_reads : %3d%% %"PRIu64"%%\n",
+ smart->timed_workload_host_reads.norm,
+ int48_to_long(smart->timed_workload_host_reads.raw));
+ printf("timed_workload_timer : %3d%% %"PRIu64" min\n",
+ smart->timed_workload_timer.norm,
+ int48_to_long(smart->timed_workload_timer.raw));
+ printf("thermal_throttle_status : %3d%% %u%%, cnt: %u\n",
+ smart->thermal_throttle_status.norm,
+ smart->thermal_throttle_status.thermal_throttle.pct,
+ smart->thermal_throttle_status.thermal_throttle.count);
+ printf("retry_buffer_overflow_count : %3d%% %"PRIu64"\n",
+ smart->retry_buffer_overflow_cnt.norm,
+ int48_to_long(smart->retry_buffer_overflow_cnt.raw));
+ printf("pll_lock_loss_count : %3d%% %"PRIu64"\n",
+ smart->pll_lock_loss_cnt.norm,
+ int48_to_long(smart->pll_lock_loss_cnt.raw));
+ printf("nand_bytes_written : %3d%% sectors: %"PRIu64"\n",
+ smart->nand_bytes_written.norm,
+ int48_to_long(smart->nand_bytes_written.raw));
+ printf("host_bytes_written : %3d%% sectors: %"PRIu64"\n",
+ smart->host_bytes_written.norm,
+ int48_to_long(smart->host_bytes_written.raw));
+ printf("raid_recover_cnt : %3d%% %"PRIu64"\n",
+ smart->raid_recover_cnt.norm,
+ int48_to_long(smart->raid_recover_cnt.raw));
+ printf("read_ecc_cnt : %3d%% %"PRIu64"\n",
+ smart->read_ecc_cnt.norm,
+ int48_to_long(smart->read_ecc_cnt.raw));
+ printf("prog_timeout_cnt : %3d%% %"PRIu64"\n",
+ smart->prog_timeout_cnt.norm,
+ int48_to_long(smart->prog_timeout_cnt.raw));
+ printf("erase_timeout_cnt : %3d%% %"PRIu64"\n",
+ smart->erase_timeout_cnt.norm,
+ int48_to_long(smart->erase_timeout_cnt.raw));
+ printf("read_timeout_cnt : %3d%% %"PRIu64"\n",
+ smart->read_timeout_cnt.norm,
+ int48_to_long(smart->read_timeout_cnt.raw));
+ printf("non_media_crc_err_cnt : %3d%% %" PRIu64 "\n",
+ smart->non_media_crc_err_cnt.norm,
+ int48_to_long(smart->non_media_crc_err_cnt.raw));
+ printf("compression_path_err_cnt : %3d%% %" PRIu64 "\n",
+ smart->compression_path_err_cnt.norm,
+ int48_to_long(smart->compression_path_err_cnt.raw));
+ printf("out_of_space_flag : %3d%% %" PRIu64 "\n",
+ smart->out_of_space_flag.norm,
+ int48_to_long(smart->out_of_space_flag.raw));
+ printf("phy_capacity_used_ratio : %3d%% %" PRIu64 "\n",
+ smart->physical_usage_ratio.norm,
+ int48_to_long(smart->physical_usage_ratio.raw));
+ printf("grown_bb_count : %3d%% %" PRIu64 "\n",
+ smart->grown_bb.norm, int48_to_long(smart->grown_bb.raw));
+
+
+}
+
+static int get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ struct nvme_additional_smart_log smart_log;
+ char *desc =
+ "Get ScaleFlux vendor specific additional smart log (optionally, for the specified namespace), and show it.";
+ const char *namespace = "(optional) desired namespace";
+ const char *raw = "dump output in binary format";
+ const char *json = "Dump output in json format";
+ struct nvme_dev *dev;
+ struct config {
+ __u32 namespace_id;
+ bool raw_binary;
+ bool json;
+ };
+ int err;
+
+ struct config cfg = {
+ .namespace_id = 0xffffffff,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace),
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_FLAG("json", 'j', &cfg.json, json),
+ OPT_END()
+ };
+
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = nvme_get_nsid_log(dev_fd(dev), false, 0xca, cfg.namespace_id,
+ sizeof(smart_log), (void *)&smart_log);
+ if (!err) {
+ if (cfg.json)
+ show_sfx_smart_log_jsn(&smart_log, cfg.namespace_id,
+ dev->name);
+ else if (!cfg.raw_binary)
+ show_sfx_smart_log(&smart_log, cfg.namespace_id,
+ dev->name);
+ else
+ d_raw((unsigned char *)&smart_log, sizeof(smart_log));
+ } else if (err > 0) {
+ nvme_show_status(err);
+ }
+ dev_close(dev);
+ return err;
+}
+
+struct __packed sfx_lat_stats_vanda {
+ __u16 maj;
+ __u16 min;
+ __u32 bucket_1[32]; /* 0~1ms, step 32us */
+ __u32 bucket_2[31]; /* 1~32ms, step 1ms */
+ __u32 bucket_3[31]; /* 32ms~1s, step 32ms */
+ __u32 bucket_4[1]; /* 1s~2s, specifically 1024ms~2047ms */
+ __u32 bucket_5[1]; /* 2s~4s, specifically 2048ms~4095ms */
+ __u32 bucket_6[1]; /* 4s+, specifically 4096ms+ */
+};
+
+struct __packed sfx_lat_stats_myrtle {
+ __u16 maj;
+ __u16 min;
+ __u32 bucket_1[64]; /* 0us~63us, step 1us */
+ __u32 bucket_2[64]; /* 63us~127us, step 1us */
+ __u32 bucket_3[64]; /* 127us~255us, step 2us */
+ __u32 bucket_4[64]; /* 255us~510us, step 4us */
+ __u32 bucket_5[64]; /* 510us~1.02ms step 8us */
+ __u32 bucket_6[64]; /* 1.02ms~2.04ms step 16us */
+ __u32 bucket_7[64]; /* 2.04ms~4.08ms step 32us */
+ __u32 bucket_8[64]; /* 4.08ms~8.16ms step 64us */
+ __u32 bucket_9[64]; /* 8.16ms~16.32ms step 128us */
+ __u32 bucket_10[64]; /* 16.32ms~32.64ms step 256us */
+ __u32 bucket_11[64]; /* 32.64ms~65.28ms step 512us */
+ __u32 bucket_12[64]; /* 65.28ms~130.56ms step 1.024ms */
+ __u32 bucket_13[64]; /* 130.56ms~261.12ms step 2.048ms */
+ __u32 bucket_14[64]; /* 261.12ms~522.24ms step 4.096ms */
+ __u32 bucket_15[64]; /* 522.24ms~1.04s step 8.192ms */
+ __u32 bucket_16[64]; /* 1.04s~2.09s step 16.384ms */
+ __u32 bucket_17[64]; /* 2.09s~4.18s step 32.768ms */
+ __u32 bucket_18[64]; /* 4.18s~8.36s step 65.536ms */
+ __u32 bucket_19[64]; /* 8.36s~ step 131.072ms */
+ __u64 average; /* average latency statistics */
+};
+
+
+struct __packed sfx_lat_status_ver {
+ __u16 maj;
+ __u16 min;
+};
+
+struct sfx_lat_stats {
+ union {
+ struct sfx_lat_status_ver ver;
+ struct sfx_lat_stats_vanda vanda;
+ struct sfx_lat_stats_myrtle myrtle;
+ };
+};
+
+static void show_lat_stats_vanda(struct sfx_lat_stats_vanda *stats, int write)
+{
+ int i;
+
+ printf("ScaleFlux IO %s Command Latency Statistics\n", write ? "Write" : "Read");
+ printf("-------------------------------------\n");
+ printf("Major Revision : %u\n", stats->maj);
+ printf("Minor Revision : %u\n", stats->min);
+
+ printf("\nGroup 1: Range is 0-1ms, step is 32us\n");
+ for (i = 0; i < 32; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_1[i]);
+
+ printf("\nGroup 2: Range is 1-32ms, step is 1ms\n");
+ for (i = 0; i < 31; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_2[i]);
+
+ printf("\nGroup 3: Range is 32ms-1s, step is 32ms:\n");
+ for (i = 0; i < 31; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_3[i]);
+
+ printf("\nGroup 4: Range is 1s-2s:\n");
+ printf("Bucket %2d: %u\n", 0, stats->bucket_4[0]);
+
+ printf("\nGroup 5: Range is 2s-4s:\n");
+ printf("Bucket %2d: %u\n", 0, stats->bucket_5[0]);
+
+ printf("\nGroup 6: Range is 4s+:\n");
+ printf("Bucket %2d: %u\n", 0, stats->bucket_6[0]);
+}
+
+static void show_lat_stats_myrtle(struct sfx_lat_stats_myrtle *stats, int write)
+{
+ int i;
+
+ printf("ScaleFlux IO %s Command Latency Statistics\n", write ? "Write" : "Read");
+ printf("-------------------------------------\n");
+ printf("Major Revision : %u\n", stats->maj);
+ printf("Minor Revision : %u\n", stats->min);
+
+ printf("\nGroup 1: Range is 0us~63us, step 1us\n");
+ for (i = 0; i < 64; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_1[i]);
+
+ printf("\nGroup 2: Range is 63us~127us, step 1us\n");
+ for (i = 0; i < 64; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_2[i]);
+
+ printf("\nGroup 3: Range is 127us~255us, step 2us\n");
+ for (i = 0; i < 64; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_3[i]);
+
+ printf("\nGroup 4: Range is 255us~510us, step 4us\n");
+ for (i = 0; i < 64; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_4[i]);
+
+ printf("\nGroup 5: Range is 510us~1.02ms step\n");
+ for (i = 0; i < 64; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_5[i]);
+
+ printf("\nGroup 6: Range is 1.02ms~2.04ms step 16us\n");
+ for (i = 0; i < 64; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_6[i]);
+
+ printf("\nGroup 7: Range is 2.04ms~4.08ms step 32us\n");
+ for (i = 0; i < 64; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_7[i]);
+
+ printf("\nGroup 8: Range is 4.08ms~8.16ms step 64us\n");
+ for (i = 0; i < 64; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_8[i]);
+
+ printf("\nGroup 9: Range is 8.16ms~16.32ms step 128us\n");
+ for (i = 0; i < 64; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_9[i]);
+
+ printf("\nGroup 10: Range is 16.32ms~32.64ms step 256us\n");
+ for (i = 0; i < 64; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_10[i]);
+
+ printf("\nGroup 11: Range is 32.64ms~65.28ms step 512us\n");
+ for (i = 0; i < 64; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_11[i]);
+
+ printf("\nGroup 12: Range is 65.28ms~130.56ms step 1.024ms\n");
+ for (i = 0; i < 64; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_12[i]);
+
+ printf("\nGroup 13: Range is 130.56ms~261.12ms step 2.048ms\n");
+ for (i = 0; i < 64; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_13[i]);
+
+ printf("\nGroup 14: Range is 261.12ms~522.24ms step 4.096ms\n");
+ for (i = 0; i < 64; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_14[i]);
+
+ printf("\nGroup 15: Range is 522.24ms~1.04s step 8.192ms\n");
+ for (i = 0; i < 64; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_15[i]);
+
+ printf("\nGroup 16: Range is 1.04s~2.09s step 16.384ms\n");
+ for (i = 0; i < 64; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_16[i]);
+
+ printf("\nGroup 17: Range is 2.09s~4.18s step 32.768ms\n");
+ for (i = 0; i < 64; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_17[i]);
+
+ printf("\nGroup 18: Range is 4.18s~8.36s step 65.536ms\n");
+ for (i = 0; i < 64; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_18[i]);
+
+ printf("\nGroup 19: Range is 8.36s~ step 131.072ms\n");
+ for (i = 0; i < 64; i++)
+ printf("Bucket %2d: %u\n", i, stats->bucket_19[i]);
+
+ printf("\nAverage latency statistics %" PRIu64 "\n",
+ (uint64_t)stats->average);
+}
+
+
+static int get_lat_stats_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ struct sfx_lat_stats stats;
+ char *desc = "Get ScaleFlux Latency Statistics log and show it.";
+ const char *raw = "dump output in binary format";
+ const char *write = "Get write statistics (read default)";
+ struct nvme_dev *dev;
+ struct config {
+ bool raw_binary;
+ bool write;
+ };
+ int err;
+
+ struct config cfg = {
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("write", 'w', &cfg.write, write),
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = nvme_get_log_simple(dev_fd(dev), cfg.write ? 0xc3 : 0xc1,
+ sizeof(stats), (void *)&stats);
+ if (!err) {
+ if ((stats.ver.maj == VANDA_MAJOR_IDX) && (stats.ver.min == VANDA_MINOR_IDX)) {
+ if (!cfg.raw_binary)
+ show_lat_stats_vanda(&stats.vanda, cfg.write);
+ else
+ d_raw((unsigned char *)&stats.vanda, sizeof(struct sfx_lat_stats_vanda));
+ } else if ((stats.ver.maj == MYRTLE_MAJOR_IDX) && (stats.ver.min == MYRTLE_MINOR_IDX)) {
+ if (!cfg.raw_binary)
+ show_lat_stats_myrtle(&stats.myrtle, cfg.write);
+ else
+ d_raw((unsigned char *)&stats.myrtle, sizeof(struct sfx_lat_stats_myrtle));
+ } else {
+ printf("ScaleFlux IO %s Command Latency Statistics Invalid Version Maj %d Min %d\n",
+ cfg.write ? "Write" : "Read", stats.ver.maj, stats.ver.min);
+ }
+ } else if (err > 0) {
+ nvme_show_status(err);
+ }
+ dev_close(dev);
+ return err;
+}
+
+int sfx_nvme_get_log(int fd, __u32 nsid, __u8 log_id, __u32 data_len, void *data)
+{
+ struct nvme_passthru_cmd cmd = {
+ .opcode = nvme_admin_get_log_page,
+ .nsid = nsid,
+ .addr = (__u64)(uintptr_t) data,
+ .data_len = data_len,
+ };
+ __u32 numd = (data_len >> 2) - 1;
+ __u16 numdu = numd >> 16, numdl = numd & 0xffff;
+
+ cmd.cdw10 = log_id | (numdl << 16);
+ cmd.cdw11 = numdu;
+
+ return nvme_submit_admin_passthru(fd, &cmd, NULL);
+}
+
+/**
+ * @brief get bb table through admin_passthru
+ *
+ * @param fd
+ * @param buf
+ * @param size
+ *
+ * @return -1 fail ; 0 success
+ */
+static int get_bb_table(int fd, __u32 nsid, unsigned char *buf, __u64 size)
+{
+ if (fd < 0 || !buf || size != 256*4096*sizeof(unsigned char)) {
+ fprintf(stderr, "Invalid Param \r\n");
+ return -EINVAL;
+ }
+
+ return sfx_nvme_get_log(fd, nsid, SFX_LOG_BBT, size, (void *)buf);
+}
+
+/**
+ * @brief display bb table
+ *
+ * @param bd_table buffer that contain bb table dumped from drvier
+ * @param table_size buffer size (BYTES), should at least has 8 bytes for mf_bb_count and grown_bb_count
+ */
+static void bd_table_show(unsigned char *bd_table, __u64 table_size)
+{
+ __u32 mf_bb_count = 0;
+ __u32 grown_bb_count = 0;
+ __u32 total_bb_count = 0;
+ __u32 remap_mfbb_count = 0;
+ __u32 remap_gbb_count = 0;
+ __u64 *bb_elem;
+ __u64 *elem_end = (__u64 *)(bd_table + table_size);
+ __u64 i;
+
+ /*buf should at least have 8bytes for mf_bb_count & total_bb_count*/
+ if (!bd_table || table_size < sizeof(__u64))
+ return;
+
+ mf_bb_count = *((__u32 *)bd_table);
+ grown_bb_count = *((__u32 *)(bd_table + sizeof(__u32)));
+ total_bb_count = *((__u32 *)(bd_table + 2 * sizeof(__u32)));
+ remap_mfbb_count = *((__u32 *)(bd_table + 3 * sizeof(__u32)));
+ remap_gbb_count = *((__u32 *)(bd_table + 4 * sizeof(__u32)));
+ bb_elem = (__u64 *)(bd_table + 5 * sizeof(__u32));
+
+ printf("Bad Block Table\n");
+ printf("MF_BB_COUNT: %u\n", mf_bb_count);
+ printf("GROWN_BB_COUNT: %u\n", grown_bb_count);
+ printf("TOTAL_BB_COUNT: %u\n", total_bb_count);
+ printf("REMAP_MFBB_COUNT: %u\n", remap_mfbb_count);
+ printf("REMAP_GBB_COUNT: %u\n", remap_gbb_count);
+
+ printf("REMAP_MFBB_TABLE [");
+ i = 0;
+ while (bb_elem < elem_end && i < remap_mfbb_count) {
+ printf(" 0x%"PRIx64"", (uint64_t)*(bb_elem++));
+ i++;
+ }
+ printf(" ]\n");
+
+ printf("REMAP_GBB_TABLE [");
+ i = 0;
+ while (bb_elem < elem_end && i < remap_gbb_count) {
+ printf(" 0x%"PRIx64"", (uint64_t)*(bb_elem++));
+ i++;
+ }
+ printf(" ]\n");
+}
+
+/**
+ * @brief "hooks of sfx get-bad-block"
+ *
+ * @param argc
+ * @param argv
+ * @param cmd
+ * @param plugin
+ *
+ * @return
+ */
+static int sfx_get_bad_block(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const __u64 buf_size = 256*4096*sizeof(unsigned char);
+ unsigned char *data_buf;
+ struct nvme_dev *dev;
+ int err = 0;
+
+ char *desc = "Get bad block table of sfx block device.";
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ data_buf = malloc(buf_size);
+ if (!data_buf) {
+ fprintf(stderr, "malloc fail, errno %d\r\n", errno);
+ dev_close(dev);
+ return -1;
+ }
+
+ err = get_bb_table(dev_fd(dev), 0xffffffff, data_buf, buf_size);
+ if (err < 0) {
+ perror("get-bad-block");
+ } else if (err) {
+ nvme_show_status(err);
+ } else {
+ bd_table_show(data_buf, buf_size);
+ printf("ScaleFlux get bad block table: success\n");
+ }
+
+ free(data_buf);
+ dev_close(dev);
+ return 0;
+}
+
+static void show_cap_info(struct sfx_freespace_ctx *ctx)
+{
+
+ printf("logic capacity:%5lluGB(0x%"PRIx64")\n",
+ IDEMA_CAP2GB(ctx->user_space), (uint64_t)ctx->user_space);
+ printf("provisioned capacity:%5lluGB(0x%"PRIx64")\n",
+ IDEMA_CAP2GB(ctx->phy_space), (uint64_t)ctx->phy_space);
+ printf("free provisioned capacity:%5lluGB(0x%"PRIx64")\n",
+ IDEMA_CAP2GB(ctx->free_space), (uint64_t)ctx->free_space);
+ printf("used provisioned capacity:%5lluGB(0x%"PRIx64")\n",
+ IDEMA_CAP2GB(ctx->phy_space) - IDEMA_CAP2GB(ctx->free_space),
+ (uint64_t)(ctx->phy_space - ctx->free_space));
+ printf("map_unit :0x%"PRIx64"K\n", (uint64_t)(ctx->map_unit * 4));
+}
+
+static int query_cap_info(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ struct sfx_freespace_ctx ctx = { 0 };
+ char *desc = "query current capacity info";
+ const char *raw = "dump output in binary format";
+ struct nvme_dev *dev;
+ struct config {
+ bool raw_binary;
+ };
+ struct config cfg;
+ int err = 0;
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ if (nvme_query_cap(dev_fd(dev), 0xffffffff, sizeof(ctx), &ctx)) {
+ perror("sfx-query-cap");
+ err = -1;
+ }
+
+ if (!err) {
+ if (!cfg.raw_binary)
+ show_cap_info(&ctx);
+ else
+ d_raw((unsigned char *)&ctx, sizeof(ctx));
+ }
+ dev_close(dev);
+ return err;
+}
+
+static int change_sanity_check(int fd, __u64 trg_in_4k, int *shrink)
+{
+ struct sfx_freespace_ctx freespace_ctx = { 0 };
+ struct sysinfo s_info;
+ __u64 mem_need = 0;
+ __u64 cur_in_4k = 0;
+ __u64 provisoned_cap_4k = 0;
+ int extend = 0;
+
+ if (nvme_query_cap(fd, 0xffffffff, sizeof(freespace_ctx), &freespace_ctx))
+ return -1;
+
+ /*
+ * capacity illegal check
+ */
+ provisoned_cap_4k = freespace_ctx.phy_space >>
+ (SFX_PAGE_SHIFT - SECTOR_SHIFT);
+ if (trg_in_4k < provisoned_cap_4k ||
+ trg_in_4k > ((__u64)provisoned_cap_4k * 4)) {
+ fprintf(stderr,
+ "WARNING: Only support 1.0~4.0 x provisioned capacity!\n");
+ if (trg_in_4k < provisoned_cap_4k)
+ fprintf(stderr,
+ "WARNING: The target capacity is less than 1.0 x provisioned capacity!\n");
+ else
+ fprintf(stderr,
+ "WARNING: The target capacity is larger than 4.0 x provisioned capacity!\n");
+ return -1;
+ }
+ if (trg_in_4k > ((__u64)provisoned_cap_4k*4)) {
+ fprintf(stderr, "WARNING: the target capacity is too large\n");
+ return -1;
+ }
+
+ /*
+ * check whether mem enough if extend
+ */
+ cur_in_4k = freespace_ctx.user_space >> (SFX_PAGE_SHIFT - SECTOR_SHIFT);
+ extend = (cur_in_4k <= trg_in_4k);
+ if (extend) {
+ if (sysinfo(&s_info) < 0) {
+ printf("change-cap query mem info fail\n");
+ return -1;
+ }
+ mem_need = (trg_in_4k - cur_in_4k) * 8;
+ if (s_info.freeram <= 10 || mem_need > s_info.freeram) {
+ fprintf(stderr,
+ "WARNING: Free memory is not enough! Please drop cache or extend more memory and retry\n"
+ "WARNING: Memory needed is %"PRIu64", free memory is %"PRIu64"\n",
+ (uint64_t)mem_need, (uint64_t)s_info.freeram);
+ return -1;
+ }
+ }
+ *shrink = !extend;
+
+ return 0;
+}
+
+/**
+ * @brief prompt and get user confirm input
+ *
+ * @param str, prompt string
+ *
+ * @return 0, canceled; 1 confirmed
+ */
+static int sfx_confirm_change(const char *str)
+{
+ unsigned char confirm;
+
+ fprintf(stderr, "WARNING: %s.\n"
+ "Use the force [--force] option to suppress this warning.\n", str);
+
+ fprintf(stderr, "Confirm Y/y, Others cancel:\n");
+ confirm = (unsigned char)fgetc(stdin);
+ if (confirm != 'y' && confirm != 'Y') {
+ fprintf(stderr, "Canceled.\n");
+ return 0;
+ }
+ fprintf(stderr, "Sending operation ...\n");
+ return 1;
+}
+
+static int change_cap(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ char *desc = "dynamic change capacity";
+ const char *cap_gb = "cap size in GB";
+ const char *cap_byte = "cap size in byte";
+ const char *force = "The \"I know what I'm doing\" flag, skip confirmation before sending command";
+ struct nvme_dev *dev;
+ __u64 cap_in_4k = 0;
+ __u64 cap_in_sec = 0;
+ int shrink = 0;
+ int err = -1;
+
+ struct config {
+ __u64 cap_in_byte;
+ __u32 capacity_in_gb;
+ bool force;
+ };
+
+ struct config cfg = {
+ .cap_in_byte = 0,
+ .capacity_in_gb = 0,
+ .force = 0,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("cap", 'c', &cfg.capacity_in_gb, cap_gb),
+ OPT_SUFFIX("cap-byte", 'z', &cfg.cap_in_byte, cap_byte),
+ OPT_FLAG("force", 'f', &cfg.force, force),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ cap_in_sec = IDEMA_CAP(cfg.capacity_in_gb);
+ cap_in_4k = cap_in_sec >> 3;
+ if (cfg.cap_in_byte)
+ cap_in_4k = cfg.cap_in_byte >> 12;
+ printf("%dG %"PRIu64"B %"PRIu64" 4K\n",
+ cfg.capacity_in_gb, (uint64_t)cfg.cap_in_byte, (uint64_t)cap_in_4k);
+
+ if (change_sanity_check(dev_fd(dev), cap_in_4k, &shrink)) {
+ printf("ScaleFlux change-capacity: fail\n");
+ dev_close(dev);
+ return err;
+ }
+
+ if (!cfg.force && shrink && !sfx_confirm_change("Changing Cap may irrevocably delete this device's data")) {
+ dev_close(dev);
+ return 0;
+ }
+
+ err = nvme_change_cap(dev_fd(dev), 0xffffffff, cap_in_4k);
+ if (err < 0) {
+ perror("sfx-change-cap");
+ } else if (err) {
+ nvme_show_status(err);
+ } else {
+ printf("ScaleFlux change-capacity: success\n");
+ ioctl(dev_fd(dev), BLKRRPART);
+ }
+ dev_close(dev);
+ return err;
+}
+
+static int sfx_verify_chr(int fd)
+{
+ static struct stat nvme_stat;
+ int err = fstat(fd, &nvme_stat);
+
+ if (err < 0) {
+ perror("fstat");
+ return errno;
+ }
+ if (!S_ISCHR(nvme_stat.st_mode)) {
+ fprintf(stderr,
+ "Error: requesting clean card on non-controller handle\n");
+ return -ENOTBLK;
+ }
+ return 0;
+}
+
+static int sfx_clean_card(int fd)
+{
+ int ret;
+
+ ret = sfx_verify_chr(fd);
+ if (ret)
+ return ret;
+ ret = ioctl(fd, NVME_IOCTL_CLR_CARD);
+ if (ret)
+ perror("Ioctl Fail.");
+ else
+ printf("ScaleFlux clean card success\n");
+
+ return ret;
+}
+
+char *sfx_feature_to_string(int feature)
+{
+ switch (feature) {
+ case SFX_FEAT_ATOMIC:
+ return "ATOMIC";
+ case SFX_FEAT_UP_P_CAP:
+ return "UPDATE_PROVISION_CAPACITY";
+ default:
+ return "Unknown";
+ }
+}
+
+static int sfx_set_feature(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ char *desc = "ScaleFlux internal set features\n"
+ "feature id 1: ATOMIC\n"
+ "value 0: Disable atomic write\n"
+ " 1: Enable atomic write";
+ const char *value = "new value of feature (required)";
+ const char *feature_id = "hex feature name (required)";
+ const char *namespace_id = "desired namespace";
+ const char *force = "The \"I know what I'm doing\" flag, skip confirmation before sending command";
+ struct nvme_dev *dev;
+ struct nvme_id_ns ns;
+ int err = 0;
+
+ struct config {
+ __u32 namespace_id;
+ __u32 feature_id;
+ __u32 value;
+ bool force;
+ };
+ struct config cfg = {
+ .namespace_id = 1,
+ .feature_id = 0,
+ .value = 0,
+ .force = 0,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_UINT("feature-id", 'f', &cfg.feature_id, feature_id),
+ OPT_UINT("value", 'v', &cfg.value, value),
+ OPT_FLAG("force", 's', &cfg.force, force),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ if (!cfg.feature_id) {
+ fprintf(stderr, "feature-id required param\n");
+ dev_close(dev);
+ return -EINVAL;
+ }
+
+ if (cfg.feature_id == SFX_FEAT_CLR_CARD) {
+ /*Warning for clean card*/
+ if (!cfg.force && !sfx_confirm_change("Going to clean device's data, confirm umount fs and try again")) {
+ dev_close(dev);
+ return 0;
+ } else {
+ return sfx_clean_card(dev_fd(dev));
+ }
+
+ }
+
+ if (cfg.feature_id == SFX_FEAT_ATOMIC && cfg.value) {
+ if (cfg.namespace_id != 0xffffffff) {
+ err = nvme_identify_ns(dev_fd(dev), cfg.namespace_id,
+ &ns);
+ if (err) {
+ if (err < 0)
+ perror("identify-namespace");
+ else
+ nvme_show_status(err);
+ dev_close(dev);
+ return err;
+ }
+ /*
+ * atomic only support with sector-size = 4k now
+ */
+ if ((ns.flbas & 0xf) != 1) {
+ printf("Please change-sector size to 4K, then retry\n");
+ dev_close(dev);
+ return -EFAULT;
+ }
+ }
+ } else if (cfg.feature_id == SFX_FEAT_UP_P_CAP) {
+ if (cfg.value <= 0) {
+ fprintf(stderr, "Invalid Param\n");
+ dev_close(dev);
+ return -EINVAL;
+ }
+
+ /*Warning for change pacp by GB*/
+ if (!cfg.force && !sfx_confirm_change("Changing physical capacity may irrevocably delete this device's data")) {
+ dev_close(dev);
+ return 0;
+ }
+ }
+
+ err = nvme_sfx_set_features(dev_fd(dev), cfg.namespace_id,
+ cfg.feature_id,
+ cfg.value);
+
+ if (err < 0) {
+ perror("ScaleFlux-set-feature");
+ dev_close(dev);
+ return errno;
+ } else if (!err) {
+ printf("ScaleFlux set-feature:%#02x (%s), value:%d\n", cfg.feature_id,
+ sfx_feature_to_string(cfg.feature_id), cfg.value);
+ } else if (err > 0) {
+ nvme_show_status(err);
+ }
+
+ dev_close(dev);
+ return err;
+}
+
+static int sfx_get_feature(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ char *desc = "ScaleFlux internal set features\n"
+ "feature id 1: ATOMIC";
+ const char *feature_id = "hex feature name (required)";
+ const char *namespace_id = "desired namespace";
+ struct nvme_dev *dev;
+ __u32 result = 0;
+ int err = 0;
+
+ struct config {
+ __u32 namespace_id;
+ __u32 feature_id;
+ };
+ struct config cfg = {
+ .namespace_id = 0,
+ .feature_id = 0,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_UINT("feature-id", 'f', &cfg.feature_id, feature_id),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ if (!cfg.feature_id) {
+ fprintf(stderr, "feature-id required param\n");
+ dev_close(dev);
+ return -EINVAL;
+ }
+
+ err = nvme_sfx_get_features(dev_fd(dev), cfg.namespace_id,
+ cfg.feature_id, &result);
+ if (err < 0) {
+ perror("ScaleFlux-get-feature");
+ dev_close(dev);
+ return errno;
+ } else if (!err) {
+ printf("ScaleFlux get-feature:%02x (%s), value:%d\n", cfg.feature_id,
+ sfx_feature_to_string(cfg.feature_id), result);
+ } else if (err > 0) {
+ nvme_show_status(err);
+ }
+
+ dev_close(dev);
+ return err;
+
+}
+
+static int nvme_parse_evtlog(void *pevent_log_info, __u32 log_len, char *output)
+{
+ __u32 offset = 0;
+ __u32 length = log_len;
+ __u16 fw_core;
+ __u64 fw_time;
+ __u8 code_level;
+ __u8 code_type;
+ char str_buffer[512];
+ __u32 str_pos;
+ FILE *fd;
+ int err = 0;
+
+ enum sfx_evtlog_level {
+ sfx_evtlog_level_warning,
+ sfx_evtlog_level_error,
+ };
+
+ const char *sfx_evtlog_warning[4] = {
+ "RESERVED",
+ "TOO_MANY_BB",
+ "LOW_SPACE",
+ "HIGH_TEMPERATURE"
+ };
+
+ const char *sfx_evtlog_error[14] = {
+ "RESERVED",
+ "HAS_ASSERT",
+ "HAS_PANIC_DUMP",
+ "INVALID_FORMAT_CAPACITY",
+ "MAT_FAILED",
+ "FREEZE_DUE_TO_RECOVERY_FAILED",
+ "RFS_BROKEN",
+ "MEDIA_ERR_ON_PAGE_IN",
+ "MEDIA_ERR_ON_MPAGE_HEADER",
+ "CAPACITOR_BROKEN",
+ "READONLY_DUE_TO_RECOVERY_FAILED",
+ "RD_ERR_IN_GSD_RECOVERY",
+ "RD_ERR_ON_PF_RECOVERY",
+ "MEDIA_ERR_ON_FULL_RECOVERY"
+ };
+
+ struct sfx_nvme_evtlog_info {
+ __u16 time_stamp[4];
+ __u64 magic1;
+ __u8 reverse[10];
+ char evt_name[32];
+ __u64 magic2;
+ char fw_ver[24];
+ char bl2_ver[32];
+ __u16 code;
+ __u16 assert_id;
+ } __packed;
+
+ struct sfx_nvme_evtlog_info *info = NULL;
+
+ fd = fopen(output, "w+");
+ if (!fd) {
+ fprintf(stderr, "Failed to open %s file to write\n", output);
+ err = ENOENT;
+ goto ret;
+ }
+
+ while (length > 0) {
+ info = (struct sfx_nvme_evtlog_info *)(pevent_log_info + offset);
+
+ if ((info->magic1 == 0x474F4C545645) &&
+ (info->magic2 == 0x38B0B3ABA9BA)) {
+
+ memset(str_buffer, 0, 512);
+ str_pos = 0;
+
+ fw_core = info->time_stamp[3];
+ snprintf(str_buffer + str_pos, 16, "[%d-", fw_core);
+ str_pos = strlen(str_buffer);
+
+ fw_time = ((__u64)info->time_stamp[2] << 32) + ((__u64)info->time_stamp[1] << 16) + (__u64)info->time_stamp[0];
+ convert_ts(fw_time, str_buffer + str_pos);
+ str_pos = strlen(str_buffer);
+
+ strcpy(str_buffer + str_pos, "] event-log:\n");
+ str_pos = strlen(str_buffer);
+
+ snprintf(str_buffer + str_pos, 128,
+ " > fw_version: %s\n > bl2_version: %s\n",
+ info->fw_ver, info->bl2_ver);
+ str_pos = strlen(str_buffer);
+
+ code_level = (info->code & 0x100) >> 8;
+ code_type = (info->code % 0x100);
+ if (code_level == sfx_evtlog_level_warning) {
+ snprintf(str_buffer + str_pos, 128,
+ " > error_str: [WARNING][%s]\n\n",
+ sfx_evtlog_warning[code_type]);
+ } else {
+ if (info->assert_id)
+ snprintf(str_buffer + str_pos, 128,
+ " > error_str: [ERROR][%s]\n > assert_id: %d\n\n",
+ sfx_evtlog_error[code_type], info->assert_id);
+ else
+ snprintf(str_buffer + str_pos, 128,
+ " > error_str: [ERROR][%s]\n\n",
+ sfx_evtlog_error[code_type]);
+ }
+ str_pos = strlen(str_buffer);
+
+ if (fwrite(str_buffer, 1, str_pos, fd) != str_pos) {
+ fprintf(stderr, "Failed to write parse result to output file\n");
+ goto close_fd;
+ }
+ }
+
+ offset++;
+ length--;
+
+ if (!(offset % (log_len / 100)) || (offset == log_len))
+ util_spinner("Parse", (float) (offset) / (float) (log_len));
+ }
+
+ printf("\nParse-evtlog: Success\n");
+
+close_fd:
+ fclose(fd);
+ret:
+ return err;
+}
+
+static int nvme_dump_evtlog(struct nvme_dev *dev, __u32 namespace_id, __u32 storage_medium,
+ char *file, bool parse, char *output)
+{
+ struct nvme_persistent_event_log *pevent;
+ void *pevent_log_info;
+ _cleanup_huge_ struct nvme_mem_huge mh = { 0, };
+ __u8 lsp_base;
+ __u32 offset = 0;
+ __u32 length = 0;
+ __u32 log_len;
+ __u32 single_len;
+ int err = 0;
+ FILE *fd = NULL;
+ struct nvme_get_log_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .lid = NVME_LOG_LID_PERSISTENT_EVENT,
+ .nsid = namespace_id,
+ .lpo = NVME_LOG_LPO_NONE,
+ .lsp = NVME_LOG_LSP_NONE,
+ .lsi = NVME_LOG_LSI_NONE,
+ .rae = false,
+ .uuidx = NVME_UUID_NONE,
+ .csi = NVME_CSI_NVM,
+ .ot = false,
+ .len = 0,
+ .log = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ };
+
+ if (!storage_medium) {
+ lsp_base = 0;
+ single_len = 64 * 1024 - 4;
+ } else {
+ lsp_base = 4;
+ single_len = 32 * 1024;
+ }
+
+ pevent = calloc(sizeof(*pevent), sizeof(__u8));
+ if (!pevent) {
+ err = -ENOMEM;
+ goto ret;
+ }
+
+ args.lsp = lsp_base + NVME_PEVENT_LOG_RELEASE_CTX;
+ args.log = pevent;
+ args.len = sizeof(*pevent);
+
+ err = nvme_get_log(&args);
+ if (err) {
+ fprintf(stderr, "Unable to get evtlog lsp=0x%x, ret = 0x%x\n", args.lsp, err);
+ goto free_pevent;
+ }
+
+ args.lsp = lsp_base + NVME_PEVENT_LOG_EST_CTX_AND_READ;
+ err = nvme_get_log(&args);
+ if (err) {
+ fprintf(stderr, "Unable to get evtlog lsp=0x%x, ret = 0x%x\n", args.lsp, err);
+ goto free_pevent;
+ }
+
+ log_len = le64_to_cpu(pevent->tll);
+ if (log_len % 4)
+ log_len = (log_len / 4 + 1) * 4;
+
+ pevent_log_info = nvme_alloc_huge(single_len, &mh);
+ if (!pevent_log_info) {
+ err = -ENOMEM;
+ goto free_pevent;
+ }
+
+ fd = fopen(file, "wb+");
+ if (!fd) {
+ fprintf(stderr, "Failed to open %s file to write\n", file);
+ err = ENOENT;
+ goto free_pevent;
+ }
+
+ args.lsp = lsp_base + NVME_PEVENT_LOG_READ;
+ args.log = pevent_log_info;
+ length = log_len;
+ while (length > 0) {
+ args.lpo = offset;
+ if (length > single_len) {
+ args.len = single_len;
+ } else {
+ memset(args.log, 0, args.len);
+ args.len = length;
+ }
+ err = nvme_get_log(&args);
+ if (err) {
+ fprintf(stderr, "Unable to get evtlog offset=0x%x len 0x%x ret = 0x%x\n", offset, args.len, err);
+ goto close_fd;
+ }
+
+ if (fwrite(args.log, 1, args.len, fd) != args.len) {
+ fprintf(stderr, "Failed to write evtlog to file\n");
+ goto close_fd;
+ }
+
+ offset += args.len;
+ length -= args.len;
+ util_spinner("Parse", (float) (offset) / (float) (log_len));
+ }
+
+ printf("\nDump-evtlog: Success\n");
+
+ if (parse) {
+ nvme_free_huge(&mh);
+ pevent_log_info = nvme_alloc_huge(log_len, &mh);
+ if (!pevent_log_info) {
+ fprintf(stderr, "Failed to alloc enough memory 0x%x to parse evtlog\n", log_len);
+ err = -ENOMEM;
+ goto close_fd;
+ }
+
+ fclose(fd);
+ fd = fopen(file, "rb");
+ if (!fd) {
+ fprintf(stderr, "Failed to open %s file to read\n", file);
+ err = ENOENT;
+ goto free_pevent;
+ }
+ if (fread(pevent_log_info, 1, log_len, fd) != log_len) {
+ fprintf(stderr, "Failed to read evtlog to buffer\n");
+ goto close_fd;
+ }
+
+ err = nvme_parse_evtlog(pevent_log_info, log_len, output);
+ }
+
+close_fd:
+ fclose(fd);
+free_pevent:
+ free(pevent);
+ret:
+ return err;
+}
+
+static int sfx_dump_evtlog(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ char *desc = "dump evtlog into file and parse";
+ const char *file = "evtlog file(required)";
+ const char *namespace_id = "desired namespace";
+ const char *storage_medium = "evtlog storage medium\n"
+ "0: nand(default) 1: nor";
+ const char *parse = "parse error & warning evtlog from evtlog file";
+ const char *output = "parse result output file";
+ struct nvme_dev *dev;
+ int err = 0;
+
+ struct config {
+ char *file;
+ __u32 namespace_id;
+ __u32 storage_medium;
+ bool parse;
+ char *output;
+ };
+ struct config cfg = {
+ .file = NULL,
+ .namespace_id = 0xffffffff,
+ .storage_medium = 0,
+ .parse = false,
+ .output = NULL,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FILE("file", 'f', &cfg.file, file),
+ OPT_UINT("namespace_id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_UINT("storage_medium", 's', &cfg.storage_medium, storage_medium),
+ OPT_FLAG("parse", 'p', &cfg.parse, parse),
+ OPT_FILE("output", 'o', &cfg.output, output),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ goto ret;
+
+ if (!cfg.file) {
+ fprintf(stderr, "file required param\n");
+ err = EINVAL;
+ goto close_dev;
+ }
+
+ if (cfg.parse && !cfg.output) {
+ fprintf(stderr, "output file required if evtlog need be parsed\n");
+ err = EINVAL;
+ goto close_dev;
+ }
+
+ err = nvme_dump_evtlog(dev, cfg.namespace_id, cfg.storage_medium, cfg.file, cfg.parse, cfg.output);
+
+close_dev:
+ dev_close(dev);
+ret:
+ return err;
+}
+
+static int nvme_expand_cap(struct nvme_dev *dev, __u32 namespace_id, __u64 namespace_size,
+ __u64 namespace_cap, __u32 lbaf, __u32 units)
+{
+ struct dirent **devices;
+ char dev_name[32] = "";
+ int i = 0;
+ int num = 0;
+ int err = 0;
+
+ struct sfx_expand_cap_info {
+ __u64 namespace_size;
+ __u64 namespace_cap;
+ __u8 reserve[10];
+ __u8 lbaf;
+ __u8 reserve1[5];
+ } __packed;
+
+ if (S_ISCHR(dev->direct.stat.st_mode))
+ snprintf(dev_name, 32, "%sn%u", dev->name, namespace_id);
+ else
+ strcpy(dev_name, dev->name);
+
+ num = scandir("/dev", &devices, nvme_namespace_filter, alphasort);
+ if (num <= 0) {
+ err = num;
+ goto ret;
+ }
+
+ if (strcmp(dev_name, devices[num-1]->d_name)) {
+ fprintf(stderr, "Expand namespace not the last one\n");
+ err = EINVAL;
+ goto free_devices;
+ }
+
+ if (!units) {
+ namespace_size = IDEMA_CAP(namespace_size) / (1 << (lbaf * 3));
+ namespace_cap = IDEMA_CAP(namespace_cap) / (1 << (lbaf * 3));
+ }
+
+ struct sfx_expand_cap_info info = {
+ .namespace_size = namespace_size,
+ .namespace_cap = namespace_cap,
+ .lbaf = lbaf,
+ };
+
+ struct nvme_passthru_cmd cmd = {
+ .opcode = nvme_admin_ns_mgmt,
+ .nsid = namespace_id,
+ .addr = (__u64)(uintptr_t)&info,
+ .data_len = sizeof(info),
+ .cdw10 = 0x0e,
+ };
+
+ err = nvme_submit_admin_passthru(dev_fd(dev), &cmd, NULL);
+ if (err) {
+ fprintf(stderr, "Create ns failed\n");
+ nvme_show_status(err);
+ goto free_devices;
+ }
+
+free_devices:
+ for (i = 0; i < num; i++)
+ free(devices[i]);
+ free(devices);
+ret:
+ return err;
+}
+
+static int sfx_expand_cap(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ char *desc = "expand capacity";
+ const char *namespace_id = "desired namespace";
+ const char *namespace_size = "namespace size(required)";
+ const char *namespace_cap = "namespace capacity(required)";
+ const char *lbaf = "LBA format to apply\n"
+ "0: 512(default) 1: 4096";
+ const char *units = "namespace size/capacity units\n"
+ "0: GB(default) 1: LBA";
+ struct nvme_dev *dev;
+ int err = 0;
+
+ struct config {
+ __u32 namespace_id;
+ __u64 namespace_size;
+ __u64 namespace_cap;
+ __u32 lbaf;
+ __u32 units;
+ };
+ struct config cfg = {
+ .namespace_id = 0xffffffff,
+ .lbaf = 0,
+ .units = 0,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace_id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_LONG("namespace_size", 's', &cfg.namespace_size, namespace_size),
+ OPT_LONG("namespace_cap", 'c', &cfg.namespace_cap, namespace_cap),
+ OPT_UINT("lbaf", 'l', &cfg.lbaf, lbaf),
+ OPT_UINT("units", 'u', &cfg.units, units),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ goto ret;
+
+ if (cfg.namespace_id == 0xffffffff) {
+ if (S_ISCHR(dev->direct.stat.st_mode)) {
+ fprintf(stderr, "namespace_id or blk device required\n");
+ err = EINVAL;
+ goto ret;
+ } else {
+ cfg.namespace_id = atoi(&dev->name[strlen(dev->name) - 1]);
+ }
+ }
+
+ if (!cfg.namespace_size) {
+ fprintf(stderr, "namespace_size required param\n");
+ err = EINVAL;
+ goto close_dev;
+ }
+
+ if (!cfg.namespace_cap) {
+ fprintf(stderr, "namespace_cap required param\n");
+ err = EINVAL;
+ goto close_dev;
+ }
+
+ err = nvme_expand_cap(dev, cfg.namespace_id, cfg.namespace_size, cfg.namespace_cap, cfg.lbaf, cfg.units);
+ if (err)
+ goto close_dev;
+
+ printf("%s: Success, create nsid:%d\n", cmd->name, cfg.namespace_id);
+
+close_dev:
+ dev_close(dev);
+ret:
+ return err;
+}
diff --git a/plugins/scaleflux/sfx-nvme.h b/plugins/scaleflux/sfx-nvme.h
new file mode 100644
index 0000000..53e2217
--- /dev/null
+++ b/plugins/scaleflux/sfx-nvme.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/scaleflux/sfx-nvme
+
+#if !defined(SFX_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define SFX_NVME
+
+#include "cmd.h"
+
+PLUGIN(NAME("sfx", "ScaleFlux vendor specific extensions", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("smart-log-add", "Retrieve ScaleFlux SMART Log, show it", get_additional_smart_log)
+ ENTRY("lat-stats", "Retrieve ScaleFlux IO Latency Statistics log, show it", get_lat_stats_log)
+ ENTRY("get-bad-block", "Retrieve bad block table of block device, show it", sfx_get_bad_block)
+ ENTRY("query-cap", "Query current capacity info", query_cap_info)
+ ENTRY("change-cap", "Dynamic change capacity", change_cap)
+ ENTRY("set-feature", "Set a feature", sfx_set_feature)
+ ENTRY("get-feature", "Get a feature", sfx_get_feature)
+ ENTRY("dump-evtlog", "dump evtlog into file and parse warning & error log", sfx_dump_evtlog)
+ ENTRY("expand-cap", "expand the last namespace capacity lossless", sfx_expand_cap)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/seagate/seagate-diag.h b/plugins/seagate/seagate-diag.h
new file mode 100644
index 0000000..39f0d39
--- /dev/null
+++ b/plugins/seagate/seagate-diag.h
@@ -0,0 +1,388 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Do NOT modify or remove this copyright and license
+ *
+ * Copyright (c) 2017-2018 Seagate Technology LLC and/or its Affiliates, All Rights Reserved
+ *
+ * ******************************************************************************************
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * \file seagate-diag.h
+ * \brief This file defines the functions and macros to make building a nvme-cli seagate plug-in.
+ *
+ * Author: Debabrata Bardhan <debabrata.bardhan@seagate.com>
+ */
+
+
+#ifndef SEAGATE_NVME_H
+#define SEAGATE_NVME_H
+
+#define SEAGATE_PLUGIN_VERSION_MAJOR 1
+#define SEAGATE_PLUGIN_VERSION_MINOR 2
+
+#define SEAGATE_OCP_PLUGIN_VERSION_MAJOR 1
+#define SEAGATE_OCP_PLUGIN_VERSION_MINOR 0
+
+#define PERSIST_FILE_SIZE (2764800)
+#define ONE_MB (1048576) /* (1024 * 1024) */
+#define PERSIST_CHUNK (65536) /* (1024 * 64) */
+#define FOUR_KB (4096)
+#define STX_NUM_LEGACY_DRV (123)
+
+
+const char* stx_jag_pan_mn[STX_NUM_LEGACY_DRV] = {"ST1000KN0002", "ST1000KN0012", "ST2000KN0002",
+ "ST2000KN0012", "ST4000KN0002", "XP1600HE10002",
+ "XP1600HE10012", "XP1600HE30002", "XP1600HE30012",
+ "XP1920LE10002", "XP1920LE10012", "XP1920LE30002",
+ "XP1920LE30012", "XP3200HE10002", "XP3200HE10012",
+ "XP3840LE10002", "XP3840LE10012", "XP400HE30002",
+ "XP400HE30012", "XP400HE30022", "XP400HE30032",
+ "XP480LE30002", "XP480LE30012", "XP480LE30022",
+ "XP480LE30032", "XP800HE10002", "XP800HE10012",
+ "XP800HE30002", "XP800HE30012", "XP800HE30022",
+ "XP800HE30032", "XP960LE10002", "XP960LE10012",
+ "XP960LE30002", "XP960LE30012", "XP960LE30022",
+ "XP960LE30032", "XP256LE30011", "XP256LE30021",
+ "XP7680LE80002", "XP7680LE80003", "XP15360LE80003",
+ "XP30720LE80003", "XP7200-1A2048", "XP7200-1A4096",
+ "XP7201-2A2048", "XP7201-2A4096", "XP7200-1A8192",
+ "ST1000HM0021", "ST1000HM0031", "ST1000HM0061",
+ "ST1000HM0071", "ST1000HM0081", "ST1200HM0001",
+ "ST1600HM0031", "ST1800HM0001", "ST1800HM0011",
+ "ST2000HM0011", "ST2000HM0031", "ST400HM0061",
+ "ST400HM0071", "ST500HM0021", "ST500HM0031",
+ "ST500HM0061", "ST500HM0071", "ST500HM0081",
+ "ST800HM0061", "ST800HM0071", "ST1600HM0011",
+ "ST1600KN0001", "ST1600KN0011", "ST1920HM0001",
+ "ST1920KN0001", "ST1920KN0011", "ST400HM0021",
+ "ST400KN0001", "ST400KN0011", "ST480HM0001",
+ "ST480KN0001", "ST480KN0011", "ST800HM0021",
+ "ST800KN0001", "ST800KN0011", "ST960HM0001",
+ "ST960KN0001", "ST960KN0011", "XF1441-1AA251024",
+ "XF1441-1AA252048", "XF1441-1AA25512", "XF1441-1AB251024",
+ "XF1441-1AB252048", "XF1441-1AB25512", "XF1441-1BA251024",
+ "XF1441-1BA252048", "XF1441-1BA25512", "XF1441-1BB251024",
+ "XF1441-1BB252048", "XF1441-1BB25512", "ST400HM0031",
+ "ST400KN0021", "ST400KN0031", "ST480HM0011",
+ "ST480KN0021", "ST480KN0031", "ST800HM0031",
+ "ST800KN0021", "ST800KN0031", "ST960HM0011",
+ "ST960KN0021", "ST960KN0031", "XM1441-1AA111024",
+ "XM1441-1AA112048", "XM1441-1AA11512", "XM1441-1AA801024",
+ "XM1441-1AA80512", "XM1441-1AB111024", "XM1441-1AB112048",
+ "XM1441-1BA111024", "XM1441-1BA112048", "XM1441-1BA11512",
+ "XM1441-1BA801024", "XM1441-1BA80512", "XM1441-1BB112048"};
+
+
+/***************************
+*Supported Log-Pages from FW
+***************************/
+
+typedef struct __attribute__((__packed__)) log_page_map_entry {
+ __u32 LogPageID;
+ __u32 LogPageSignature;
+ __u32 LogPageVersion;
+} log_page_map_entry;
+
+#define MAX_SUPPORTED_LOG_PAGE_ENTRIES ((4096 - sizeof(__u32)) / sizeof(log_page_map_entry))
+
+typedef struct __attribute__((__packed__)) log_page_map {
+ __u32 NumLogPages;
+ log_page_map_entry LogPageEntry[ MAX_SUPPORTED_LOG_PAGE_ENTRIES ];
+} log_page_map;
+/* EOF Supported Log-Pages from FW */
+
+
+/***************************
+* Extended-SMART Information
+***************************/
+#define NUMBER_EXTENDED_SMART_ATTRIBUTES 42
+
+typedef enum _EXTENDED_SMART_VERSION_
+{
+ EXTENDED_SMART_VERSION_NONE,
+ EXTENDED_SMART_VERSION_GEN,
+ EXTENDED_SMART_VERSION_VENDOR1,
+} EXTENDED_SMART_VERSION;
+
+typedef struct __attribute__((__packed__)) _SmartVendorSpecific
+{
+ __u8 AttributeNumber;
+ __u16 SmartStatus;
+ __u8 NominalValue;
+ __u8 LifetimeWorstValue;
+ __u32 Raw0_3;
+ __u8 RawHigh[3];
+} SmartVendorSpecific;
+
+typedef struct __attribute__((__packed__)) _EXTENDED_SMART_INFO_T
+{
+ __u16 Version;
+ SmartVendorSpecific vendorData[NUMBER_EXTENDED_SMART_ATTRIBUTES];
+ __u8 vendor_specific_reserved[6];
+} EXTENDED_SMART_INFO_T;
+
+typedef struct __attribute__((__packed__)) vendor_smart_attribute_data
+{
+ __u8 AttributeNumber;
+ __u8 Rsvd[3];
+ __u32 LSDword;
+ __u32 MSDword;
+} vendor_smart_attribute_data;
+
+struct __attribute__((__packed__)) nvme_temetry_log_hdr
+{
+ __u8 log_id;
+ __u8 rsvd1[4];
+ __u8 ieee_id[3];
+ __le16 tele_data_area1;
+ __le16 tele_data_area2;
+ __le16 tele_data_area3;
+ __u8 rsvd14[368];
+ __u8 tele_data_aval;
+ __u8 tele_data_gen_num;
+ __u8 reason_identifier[128];
+};
+
+typedef struct __attribute__((__packed__)) _U128
+{
+ __u64 LS__u64;
+ __u64 MS__u64;
+} U128;
+
+typedef struct __attribute__((__packed__)) _vendor_log_page_CF_Attr
+{
+ __u16 SuperCapCurrentTemperature;
+ __u16 SuperCapMaximumTemperature;
+ __u8 SuperCapStatus;
+ __u8 Reserved5to7[3];
+ U128 DataUnitsReadToDramNamespace;
+ U128 DataUnitsWrittenToDramNamespace;
+ __u64 DramCorrectableErrorCount;
+ __u64 DramUncorrectableErrorCount;
+} vendor_log_page_CF_Attr;
+
+typedef struct __attribute__((__packed__)) _vendor_log_page_CF
+{
+ vendor_log_page_CF_Attr AttrCF;
+ __u8 Vendor_Specific_Reserved[ 456 ]; /* 56-511 */
+} vendor_log_page_CF;
+
+typedef struct __attribute__((__packed__)) _STX_EXT_SMART_LOG_PAGE_C0
+{
+ U128 phyMediaUnitsWrt;
+ U128 phyMediaUnitsRd;
+ __u64 badUsrNandBlocks;
+ __u64 badSysNandBlocks;
+ __u64 xorRecoveryCnt;
+ __u64 ucRdEc;
+ __u64 softEccEc;
+ __u64 etoeCrrCnt;
+ __u64 sysDataUsed : 8;
+ __u64 refreshCount : 56;
+ __u64 usrDataEraseCnt;
+ __u16 thermalThrottling;
+ __u8 dssdSpecVerErrata;
+ __u16 dssdSpecVerPoint;
+ __u16 dssdSpecVerMinor;
+ __u8 dssdSpecVerMajor;
+ __u64 pcieCorrEc;
+ __u32 incompleteShutdowns;
+ __u32 rsvd_116_119;
+ __u8 freeBlocks;
+ __u8 rsvd_121_127[7];
+ __u16 capHealth;
+ __u8 nvmeErrataVer;
+ __u8 rsvd_131_135[5];
+ __u64 unalignedIO;
+ __u64 secVerNum;
+ __u64 totalNUSE;
+ U128 plpStartCnt;
+ U128 enduranceEstimate;
+ __u64 pcieLinkRetCnt;
+ __u64 powStateChangeCnt;
+ __u8 rsvd_208_493[286];
+ __u16 logPageVer;
+ U128 logPageGUID;
+} STX_EXT_SMART_LOG_PAGE_C0;
+
+/* EOF Extended-SMART Information*/
+
+
+/**************************
+* PCIE ERROR INFORMATION
+**************************/
+typedef struct __attribute__((__packed__)) pcie_error_log_page
+{
+ __u32 Version;
+ __u32 BadDllpErrCnt;
+ __u32 BadTlpErrCnt;
+ __u32 RcvrErrCnt;
+ __u32 ReplayTOErrCnt;
+ __u32 ReplayNumRolloverErrCnt;
+ __u32 FCProtocolErrCnt;
+ __u32 DllpProtocolErrCnt;
+ __u32 CmpltnTOErrCnt;
+ __u32 RcvrQOverflowErrCnt;
+ __u32 UnexpectedCplTlpErrCnt;
+ __u32 CplTlpURErrCnt;
+ __u32 CplTlpCAErrCnt;
+ __u32 ReqCAErrCnt;
+ __u32 ReqURErrCnt;
+ __u32 EcrcErrCnt;
+ __u32 MalformedTlpErrCnt;
+ __u32 CplTlpPoisonedErrCnt;
+ __u32 MemRdTlpPoisonedErrCnt;
+} pcie_error_log_page;
+/*EOF PCIE ERROR INFORMATION */
+
+/**************************************
+* FW Activation History Log INFORMATION
+***************************************/
+
+typedef struct __attribute__((__packed__)) _stx_fw_activ_his_ele
+{
+ __u8 entryVerNum;
+ __u8 entryLen;
+ __u16 rev02_03;
+ __u16 fwActivCnt;
+ __u64 timeStamp;
+ __u64 rev14_21;
+ __u64 powCycleCnt;
+ __u8 previousFW[8];
+ __u8 newFW[8];
+ __u8 slotNum;
+ __u8 commitActionType;
+ __u16 result;
+ __u8 rev50_63[14];
+} stx_fw_activ_his_ele;
+
+typedef struct __attribute__((__packed__)) _stx_fw_activ_history_log_page
+{
+ __u8 logID;
+ __u8 rev01_03[3];
+ __u32 numValidFwActHisEnt;
+ stx_fw_activ_his_ele fwActHisEnt[20];
+ __u8 rev1288_4077[2790];
+ __u16 logPageVer;
+ __u8 logPageGUID[16];
+} stx_fw_activ_history_log_page;
+
+/* FW Activation History Log INFORMATION */
+
+
+typedef enum
+{
+ VS_ATTR_SOFT_READ_ERROR_RATE, /* 0 OFFSET : 02 -13 bytes */
+ VS_ATTR_REALLOCATED_SECTOR_COUNT, /* 1 OFFSET : 14 -25 bytes */
+ VS_ATTR_POWER_ON_HOURS, /* 2 OFFSET : 26 -37 bytes */
+ VS_ATTR_POWER_FAIL_EVENT_COUNT, /* 3 OFFSET : 38 -49 bytes */
+ VS_ATTR_DEVICE_POWER_CYCLE_COUNT, /* 4 OFFSET : 50 -61 bytes */
+ VS_ATTR_GB_ERASED, /* 5 OFFSET : 62 -73 bytes */
+ VS_ATTR_LIFETIME_DEVSLEEP_EXIT_COUNT, /* 6 OFFSET : 74 -85 bytes */
+ VS_ATTR_LIFETIME_ENTERING_PS4_COUNT, /* 7 OFFSET : 86 -97 bytes */
+ VS_ATTR_LIFETIME_ENTERING_PS3_COUNT, /* 8 OFFSET : 98 -109 bytes */
+ VS_ATTR_RETIRED_BLOCK_COUNT, /* 9 OFFSET : 110 -121 bytes */
+ VS_ATTR_PROGRAM_FAILURE_COUNT, /* 10 OFFSET : 122 -133 bytes */
+ VS_ATTR_ERASE_FAIL_COUNT, /* 11 OFFSET : 134 -145 bytes */
+ VS_ATTR_AVG_ERASE_COUNT, /* 12 OFFSET : 146 -157 bytes */
+ VS_ATTR_UNEXPECTED_POWER_LOSS_COUNT, /* 13 OFFSET : 158 -169 bytes */
+ VS_ATTR_WEAR_RANGE_DELTA, /* 14 OFFSET : 170 -181 bytes */
+ VS_ATTR_SATA_INTERFACE_DOWNSHIFT_COUNT, /* 15 OFFSET : 182 -193 bytes */
+ VS_ATTR_END_TO_END_CRC_ERROR_COUNT, /* 16 OFFSET : 194 -205 bytes */
+ VS_ATTR_MAX_LIFE_TEMPERATURE, /* 17 OFFSET : 206 -217 bytes */
+ VS_ATTR_UNCORRECTABLE_RAISE_ERRORS, /* 18 OFFSET : 218 -229 bytes */
+ VS_ATTR_DRIVE_LIFE_PROTECTION_STATUS, /* 19 OFFSET : 230 -241 bytes */
+ VS_ATTR_REMAINING_SSD_LIFE, /* 20 OFFSET : 242 -253 bytes */
+ VS_ATTR_LIFETIME_WRITES_TO_FLASH, /* 21 OFFSET : 254 -265 bytes */
+ VS_ATTR_LIFETIME_WRITES_FROM_HOST, /* 22 OFFSET : 266 -277 bytes */
+ VS_ATTR_LIFETIME_READS_TO_HOST, /* 23 OFFSET : 278 -289 bytes */
+ VS_ATTR_FREE_SPACE, /* 24 OFFSET : 290 -301 bytes */
+ VS_ATTR_TRIM_COUNT_LSB, /* 25 OFFSET : 302 -313 bytes */
+ VS_ATTR_TRIM_COUNT_MSB, /* 26 OFFSET : 314 -325 bytes */
+ VS_ATTR_OP_PERCENTAGE, /* 27 OFFSET : 326 -337 bytes */
+ VS_ATTR_RAISE_ECC_CORRECTABLE_ERROR_COUNT, /* 28 OFFSET : 338 -349 bytes */
+ VS_ATTR_UNCORRECTABLE_ECC_ERRORS , /* 29 OFFSET : 350 -361 bytes */
+ VS_ATTR_LIFETIME_WRITES0_TO_FLASH, /* 30 OFFSET : 362-372 bytes */
+ VS_ATTR_LIFETIME_WRITES1_TO_FLASH, /* 31 OFFSET : 374-385 bytes */
+ VS_ATTR_LIFETIME_WRITES0_FROM_HOST, /* 32 OFFSET : 386-397 bytes */
+ VS_ATTR_LIFETIME_WRITES1_FROM_HOST, /* 33 OFFSET : 398-409 bytes */
+ VS_ATTR_LIFETIME_READ0_FROM_HOST, /* 34 OFFSET : 410-421 bytes */
+ VS_ATTR_LIFETIME_READ1_FROM_HOST, /* 35 OFFSET : 422-433 bytes */
+ VS_ATTR_PCIE_PHY_CRC_ERROR, /* 36 OFFSET : 434-445 bytes */
+ VS_ATTR_BAD_BLOCK_COUNT_SYSTEM, /* 37 OFFSET : 446-457 bytes */
+ VS_ATTR_BAD_BLOCK_COUNT_USER, /* 38 OFFSET : 458-469 bytes */
+ VS_ATTR_THERMAL_THROTTLING_STATUS, /* 39 OFFSET : 470-481 bytes */
+ VS_ATTR_POWER_CONSUMPTION, /* 40 OFFSET : 482-493 bytes */
+ VS_ATTR_MAX_SOC_LIFE_TEMPERATURE, /* 41 OFFSET : 494-505 bytes */
+ VS_MAX_ATTR_NUMBER
+} extended_smart_attributes;
+
+/*Smart attribute IDs */
+
+typedef enum
+{
+ VS_ATTR_ID_SOFT_READ_ERROR_RATE = 1,
+ VS_ATTR_ID_REALLOCATED_SECTOR_COUNT = 5,
+ VS_ATTR_ID_POWER_ON_HOURS = 9,
+ VS_ATTR_ID_POWER_FAIL_EVENT_COUNT = 11,
+ VS_ATTR_ID_DEVICE_POWER_CYCLE_COUNT = 12,
+ VS_ATTR_ID_RAW_READ_ERROR_RATE = 13,
+ VS_ATTR_ID_GROWN_BAD_BLOCK_COUNT = 40,
+ VS_ATTR_ID_END_2_END_CORRECTION_COUNT = 41,
+ VS_ATTR_ID_MIN_MAX_WEAR_RANGE_COUNT = 42,
+ VS_ATTR_ID_REFRESH_COUNT = 43,
+ VS_ATTR_ID_BAD_BLOCK_COUNT_USER = 44,
+ VS_ATTR_ID_BAD_BLOCK_COUNT_SYSTEM = 45,
+ VS_ATTR_ID_THERMAL_THROTTLING_STATUS = 46,
+ VS_ATTR_ID_ALL_PCIE_CORRECTABLE_ERROR_COUNT = 47,
+ VS_ATTR_ID_ALL_PCIE_UNCORRECTABLE_ERROR_COUNT = 48,
+ VS_ATTR_ID_INCOMPLETE_SHUTDOWN_COUNT = 49,
+ VS_ATTR_ID_GB_ERASED_LSB = 100,
+ VS_ATTR_ID_GB_ERASED_MSB = 101,
+ VS_ATTR_ID_LIFETIME_ENTERING_PS4_COUNT = 102,
+ VS_ATTR_ID_LIFETIME_ENTERING_PS3_COUNT = 103,
+ VS_ATTR_ID_LIFETIME_DEVSLEEP_EXIT_COUNT = 104,
+ VS_ATTR_ID_RETIRED_BLOCK_COUNT = 170,
+ VS_ATTR_ID_PROGRAM_FAILURE_COUNT = 171,
+ VS_ATTR_ID_ERASE_FAIL_COUNT = 172,
+ VS_ATTR_ID_AVG_ERASE_COUNT = 173,
+ VS_ATTR_ID_UNEXPECTED_POWER_LOSS_COUNT = 174,
+ VS_ATTR_ID_WEAR_RANGE_DELTA = 177,
+ VS_ATTR_ID_SATA_INTERFACE_DOWNSHIFT_COUNT = 183,
+ VS_ATTR_ID_END_TO_END_CRC_ERROR_COUNT = 184,
+ VS_ATTR_ID_UNCORRECTABLE_READ_ERRORS = 188,
+ VS_ATTR_ID_MAX_LIFE_TEMPERATURE = 194,
+ VS_ATTR_ID_RAISE_ECC_CORRECTABLE_ERROR_COUNT = 195,
+ VS_ATTR_ID_UNCORRECTABLE_RAISE_ERRORS = 198,
+ VS_ATTR_ID_DRIVE_LIFE_PROTECTION_STATUS = 230,
+ VS_ATTR_ID_REMAINING_SSD_LIFE = 231,
+ VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB = 233,
+ VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_MSB = 234,
+ VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB = 241,
+ VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_MSB = 242,
+ VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB = 243,
+ VS_ATTR_ID_LIFETIME_READS_TO_HOST_MSB = 244,
+ VS_ATTR_ID_FREE_SPACE = 245,
+ VS_ATTR_ID_TRIM_COUNT_LSB = 250,
+ VS_ATTR_ID_TRIM_COUNT_MSB = 251,
+ VS_ATTR_ID_OP_PERCENTAGE = 252,
+ VS_ATTR_ID_MAX_SOC_LIFE_TEMPERATURE = 253,
+} smart_attributes_ids;
+
+#define TELEMETRY_BLOCKS_TO_READ 8
+
+void seaget_d_raw(unsigned char *buf, int len, int fd);
+
+
+#define DP_CLASS_ID_FULL 0
+
+#endif
diff --git a/plugins/seagate/seagate-nvme.c b/plugins/seagate/seagate-nvme.c
new file mode 100644
index 0000000..887e5bc
--- /dev/null
+++ b/plugins/seagate/seagate-nvme.c
@@ -0,0 +1,1968 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Do NOT modify or remove this copyright and license
+ *
+ * Copyright (c) 2017-2018 Seagate Technology LLC and/or its Affiliates, All Rights Reserved
+ *
+ * ******************************************************************************************
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * \file seagate-nvme.c
+ * \brief This file defines the functions and macros to make building a nvme-cli seagate plug-in.
+ *
+ * Author: Debabrata Bardhan <debabrata.bardhan@seagate.com>
+ */
+
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "nvme-print.h"
+#include <time.h>
+
+#define CREATE_CMD
+
+#include "seagate-nvme.h"
+#include "seagate-diag.h"
+
+
+/***************************************
+ * Command for "log-pages-supp"
+ ***************************************/
+static char *log_pages_supp_print(__u32 pageID)
+{
+ switch (pageID) {
+ case 0x01:
+ return "ERROR_INFORMATION";
+ case 0x02:
+ return "SMART_INFORMATION";
+ case 0x03:
+ return "FW_SLOT_INFORMATION";
+ case 0x04:
+ return "CHANGED_NAMESPACE_LIST";
+ case 0x05:
+ return "COMMANDS_SUPPORTED_AND_EFFECTS";
+ case 0x06:
+ return "DEVICE_SELF_TEST";
+ case 0x07:
+ return "TELEMETRY_HOST_INITIATED";
+ case 0x08:
+ return "TELEMETRY_CONTROLLER_INITIATED";
+ case 0xC0:
+ return "VS_MEDIA_SMART_LOG";
+ case 0xC1:
+ return "VS_DEBUG_LOG1";
+ case 0xC2:
+ return "VS_SEC_ERROR_LOG_PAGE";
+ case 0xC3:
+ return "VS_LIFE_TIME_DRIVE_HISTORY";
+ case 0xC4:
+ return "VS_EXTENDED_SMART_INFO";
+ case 0xC5:
+ return "VS_LIST_SUPPORTED_LOG_PAGE";
+ case 0xC6:
+ return "VS_POWER_MONITOR_LOG_PAGE";
+ case 0xC7:
+ return "VS_CRITICAL_EVENT_LOG_PAGE";
+ case 0xC8:
+ return "VS_RECENT_DRIVE_HISTORY";
+ case 0xC9:
+ return "VS_SEC_ERROR_LOG_PAGE";
+ case 0xCA:
+ return "VS_LIFE_TIME_DRIVE_HISTORY";
+ case 0xCB:
+ return "VS_PCIE_ERROR_LOG_PAGE";
+ case 0xCF:
+ return "DRAM Supercap SMART Attributes";
+ case 0xD6:
+ return "VS_OEM2_WORK_LOAD";
+ case 0xD7:
+ return "VS_OEM2_FW_SECURITY";
+ case 0xD8:
+ return "VS_OEM2_REVISION";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static int stx_is_jag_pan(char *devMN)
+{
+ int match_found = 1; /* found = 0, not_found = 1 */
+
+ for (int i = 0; i < STX_NUM_LEGACY_DRV; i++) {
+ match_found = strncmp(devMN, stx_jag_pan_mn[i], strlen(stx_jag_pan_mn[i]));
+ if (!match_found)
+ break;
+ }
+
+ return match_found;
+}
+
+
+static void json_log_pages_supp(log_page_map *logPageMap)
+{
+ struct json_object *root;
+ struct json_object *logPages;
+ __u32 i = 0;
+
+ root = json_create_object();
+ logPages = json_create_array();
+ json_object_add_value_array(root, "supported_log_pages", logPages);
+
+ for (i = 0; i < le32_to_cpu(logPageMap->NumLogPages); i++) {
+ struct json_object *lbaf = json_create_object();
+
+ json_object_add_value_int(lbaf, "logpage_id",
+ le32_to_cpu(logPageMap->LogPageEntry[i].LogPageID));
+ json_object_add_value_string(lbaf, "logpage_name",
+ log_pages_supp_print(le32_to_cpu(logPageMap->LogPageEntry[i].LogPageID)));
+
+ json_array_add_value_object(logPages, lbaf);
+ }
+ json_print_object(root, NULL);
+ json_free_object(root);
+}
+
+static int log_pages_supp(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ int err = 0;
+ __u32 i = 0;
+ log_page_map logPageMap;
+ const char *desc = "Retrieve Seagate Supported Log-Page information for the given device ";
+ const char *output_format = "output in binary format";
+ struct nvme_dev *dev;
+ int fmt;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+ err = nvme_get_log_simple(dev_fd(dev), 0xc5,
+ sizeof(logPageMap), &logPageMap);
+ if (!err) {
+ if (strcmp(cfg.output_format, "json")) {
+ printf("Seagate Supported Log-pages count :%d\n",
+ le32_to_cpu(logPageMap.NumLogPages));
+ printf("%-15s %-30s\n", "LogPage-Id", "LogPage-Name");
+
+ for (fmt = 0; fmt < 45; fmt++)
+ printf("-");
+ printf("\n");
+ } else
+ json_log_pages_supp(&logPageMap);
+
+ for (i = 0; i < le32_to_cpu(logPageMap.NumLogPages); i++) {
+ if (strcmp(cfg.output_format, "json")) {
+ printf("0x%-15X",
+ le32_to_cpu(logPageMap.LogPageEntry[i].LogPageID));
+ printf("%-30s\n",
+ log_pages_supp_print(le32_to_cpu(logPageMap.LogPageEntry[i].LogPageID)));
+ }
+ }
+ }
+
+ if (err > 0)
+ nvme_show_status(err);
+ dev_close(dev);
+ return err;
+}
+
+/* EOF Command for "log-pages-supp" */
+
+/***************************************
+ * Extended-SMART Information
+ ***************************************/
+static char *print_ext_smart_id(__u8 attrId)
+{
+ switch (attrId) {
+ case VS_ATTR_ID_SOFT_READ_ERROR_RATE:
+ return "Soft ECC error count";
+ case VS_ATTR_ID_REALLOCATED_SECTOR_COUNT:
+ return "Bad NAND block count";
+ case VS_ATTR_ID_POWER_ON_HOURS:
+ return "Power On Hours";
+ case VS_ATTR_ID_POWER_FAIL_EVENT_COUNT:
+ return "Power Fail Event Count";
+ case VS_ATTR_ID_DEVICE_POWER_CYCLE_COUNT:
+ return "Device Power Cycle Count";
+ case VS_ATTR_ID_RAW_READ_ERROR_RATE:
+ return "Raw Read Error Count";
+ case VS_ATTR_ID_GROWN_BAD_BLOCK_COUNT:
+ return "Bad NAND block count";
+ case VS_ATTR_ID_END_2_END_CORRECTION_COUNT:
+ return "SSD End to end correction counts";
+ case VS_ATTR_ID_MIN_MAX_WEAR_RANGE_COUNT:
+ return "User data erase counts";
+ case VS_ATTR_ID_REFRESH_COUNT:
+ return "Refresh count";
+ case VS_ATTR_ID_BAD_BLOCK_COUNT_USER:
+ return "User data erase fail count";
+ case VS_ATTR_ID_BAD_BLOCK_COUNT_SYSTEM:
+ return "System area erase fail count";
+ case VS_ATTR_ID_THERMAL_THROTTLING_STATUS:
+ return "Thermal throttling status and count";
+ case VS_ATTR_ID_ALL_PCIE_CORRECTABLE_ERROR_COUNT:
+ return "PCIe Correctable Error count";
+ case VS_ATTR_ID_ALL_PCIE_UNCORRECTABLE_ERROR_COUNT:
+ return "PCIe Uncorrectable Error count";
+ case VS_ATTR_ID_INCOMPLETE_SHUTDOWN_COUNT:
+ return "Incomplete shutdowns";
+ case VS_ATTR_ID_GB_ERASED_LSB:
+ return "LSB of Flash GB erased";
+ case VS_ATTR_ID_GB_ERASED_MSB:
+ return "MSB of Flash GB erased";
+ case VS_ATTR_ID_LIFETIME_DEVSLEEP_EXIT_COUNT:
+ return "LIFETIME_DEV_SLEEP_EXIT_COUNT";
+ case VS_ATTR_ID_LIFETIME_ENTERING_PS4_COUNT:
+ return "LIFETIME_ENTERING_PS4_COUNT";
+ case VS_ATTR_ID_LIFETIME_ENTERING_PS3_COUNT:
+ return "LIFETIME_ENTERING_PS3_COUNT";
+ case VS_ATTR_ID_RETIRED_BLOCK_COUNT:
+ return "Retired block count";
+ case VS_ATTR_ID_PROGRAM_FAILURE_COUNT:
+ return "Program fail count";
+ case VS_ATTR_ID_ERASE_FAIL_COUNT:
+ return "Erase Fail Count";
+ case VS_ATTR_ID_AVG_ERASE_COUNT:
+ return "System data % used";
+ case VS_ATTR_ID_UNEXPECTED_POWER_LOSS_COUNT:
+ return "Unexpected power loss count";
+ case VS_ATTR_ID_WEAR_RANGE_DELTA:
+ return "Wear range delta";
+ case VS_ATTR_ID_SATA_INTERFACE_DOWNSHIFT_COUNT:
+ return "PCIE_INTF_DOWNSHIFT_COUNT";
+ case VS_ATTR_ID_END_TO_END_CRC_ERROR_COUNT:
+ return "E2E_CRC_ERROR_COUNT";
+ case VS_ATTR_ID_UNCORRECTABLE_READ_ERRORS:
+ return "Uncorrectable Read Error Count";
+ case VS_ATTR_ID_MAX_LIFE_TEMPERATURE:
+ return "Max lifetime temperature";
+ case VS_ATTR_ID_RAISE_ECC_CORRECTABLE_ERROR_COUNT:
+ return "RAIS_ECC_CORRECT_ERR_COUNT";
+ case VS_ATTR_ID_UNCORRECTABLE_RAISE_ERRORS:
+ return "Uncorrectable RAISE error count";
+ case VS_ATTR_ID_DRIVE_LIFE_PROTECTION_STATUS:
+ return "DRIVE_LIFE_PROTECTION_STATUS";
+ case VS_ATTR_ID_REMAINING_SSD_LIFE:
+ return "Remaining SSD life";
+ case VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB:
+ return "LSB of Physical (NAND) bytes written";
+ case VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_MSB:
+ return "MSB of Physical (NAND) bytes written";
+ case VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB:
+ return "LSB of Physical (HOST) bytes written";
+ case VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_MSB:
+ return "MSB of Physical (HOST) bytes written";
+ case VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB:
+ return "LSB of Physical (NAND) bytes read";
+ case VS_ATTR_ID_LIFETIME_READS_TO_HOST_MSB:
+ return "MSB of Physical (NAND) bytes read";
+ case VS_ATTR_ID_FREE_SPACE:
+ return "Free Space";
+ case VS_ATTR_ID_TRIM_COUNT_LSB:
+ return "LSB of Trim count";
+ case VS_ATTR_ID_TRIM_COUNT_MSB:
+ return "MSB of Trim count";
+ case VS_ATTR_ID_OP_PERCENTAGE:
+ return "OP percentage";
+ case VS_ATTR_ID_MAX_SOC_LIFE_TEMPERATURE:
+ return "Max lifetime SOC temperature";
+ default:
+ return "Un-Known";
+ }
+}
+
+static __u64 smart_attribute_vs(__u16 verNo, SmartVendorSpecific attr)
+{
+ __u64 val = 0;
+ vendor_smart_attribute_data *attrVendor;
+
+ /**
+ * These are all Vendor A specific attributes.
+ */
+ if (verNo >= EXTENDED_SMART_VERSION_VENDOR1) {
+ attrVendor = (vendor_smart_attribute_data *)&attr;
+ memcpy(&val, &(attrVendor->LSDword), sizeof(val));
+ return val;
+ } else
+ return le32_to_cpu(attr.Raw0_3);
+}
+
+static void print_smart_log(__u16 verNo, SmartVendorSpecific attr, int lastAttr)
+{
+ static __u64 lsbGbErased = 0, msbGbErased = 0, lsbLifWrtToFlash = 0, msbLifWrtToFlash = 0,
+ lsbLifWrtFrmHost = 0, msbLifWrtFrmHost = 0, lsbLifRdToHost = 0, msbLifRdToHost = 0, lsbTrimCnt = 0, msbTrimCnt = 0;
+ char buf[40] = {0};
+ char strBuf[35] = {0};
+ int hideAttr = 0;
+
+ if (attr.AttributeNumber == VS_ATTR_ID_GB_ERASED_LSB) {
+ lsbGbErased = smart_attribute_vs(verNo, attr);
+ hideAttr = 1;
+ }
+
+ if (attr.AttributeNumber == VS_ATTR_ID_GB_ERASED_MSB) {
+ msbGbErased = smart_attribute_vs(verNo, attr);
+ hideAttr = 1;
+ }
+
+ if (attr.AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB) {
+ lsbLifWrtToFlash = smart_attribute_vs(verNo, attr);
+ hideAttr = 1;
+ }
+
+ if (attr.AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_MSB) {
+ msbLifWrtToFlash = smart_attribute_vs(verNo, attr);
+ hideAttr = 1;
+ }
+
+ if (attr.AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB) {
+ lsbLifWrtFrmHost = smart_attribute_vs(verNo, attr);
+ hideAttr = 1;
+ }
+
+ if (attr.AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_MSB) {
+ msbLifWrtFrmHost = smart_attribute_vs(verNo, attr);
+ hideAttr = 1;
+ }
+
+ if (attr.AttributeNumber == VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB) {
+ lsbLifRdToHost = smart_attribute_vs(verNo, attr);
+ hideAttr = 1;
+ }
+
+ if (attr.AttributeNumber == VS_ATTR_ID_LIFETIME_READS_TO_HOST_MSB) {
+ msbLifRdToHost = smart_attribute_vs(verNo, attr);
+ hideAttr = 1;
+ }
+
+ if (attr.AttributeNumber == VS_ATTR_ID_TRIM_COUNT_LSB) {
+ lsbTrimCnt = smart_attribute_vs(verNo, attr);
+ hideAttr = 1;
+ }
+
+ if (attr.AttributeNumber == VS_ATTR_ID_TRIM_COUNT_MSB) {
+ msbTrimCnt = smart_attribute_vs(verNo, attr);
+ hideAttr = 1;
+ }
+
+ if ((attr.AttributeNumber) && (hideAttr != 1)) {
+ printf("%-40s", print_ext_smart_id(attr.AttributeNumber));
+ printf("%-15d", attr.AttributeNumber);
+ printf(" 0x%016"PRIx64"\n", (uint64_t)smart_attribute_vs(verNo, attr));
+ }
+
+ if (lastAttr == 1) {
+ sprintf(strBuf, "%s", (print_ext_smart_id(VS_ATTR_ID_GB_ERASED_LSB) + 7));
+ printf("%-40s", strBuf);
+
+ printf("%-15d", VS_ATTR_ID_GB_ERASED_MSB << 8 | VS_ATTR_ID_GB_ERASED_LSB);
+
+ sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbGbErased, (uint64_t)lsbGbErased);
+ printf(" %s\n", buf);
+
+ sprintf(strBuf, "%s", (print_ext_smart_id(VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB) + 7));
+ printf("%-40s", strBuf);
+
+ printf("%-15d", VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_MSB << 8 | VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB);
+
+ sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbLifWrtToFlash, (uint64_t)lsbLifWrtToFlash);
+ printf(" %s\n", buf);
+
+ sprintf(strBuf, "%s", (print_ext_smart_id(VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB) + 7));
+ printf("%-40s", strBuf);
+
+ printf("%-15d", VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_MSB << 8 | VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB);
+
+ sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbLifWrtFrmHost, (uint64_t)lsbLifWrtFrmHost);
+ printf(" %s\n", buf);
+
+ sprintf(strBuf, "%s", (print_ext_smart_id(VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB) + 7));
+ printf("%-40s", strBuf);
+
+ printf("%-15d", VS_ATTR_ID_LIFETIME_READS_TO_HOST_MSB << 8 | VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB);
+
+ sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbLifRdToHost, (uint64_t)lsbLifRdToHost);
+ printf(" %s\n", buf);
+
+ sprintf(strBuf, "%s", (print_ext_smart_id(VS_ATTR_ID_TRIM_COUNT_LSB) + 7));
+ printf("%-40s", strBuf);
+
+ printf("%-15d", VS_ATTR_ID_TRIM_COUNT_MSB << 8 | VS_ATTR_ID_TRIM_COUNT_LSB);
+
+ sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbTrimCnt, (uint64_t)lsbTrimCnt);
+ printf(" %s\n", buf);
+
+ }
+}
+
+static void json_print_smart_log(struct json_object *root, EXTENDED_SMART_INFO_T *ExtdSMARTInfo)
+{
+ struct json_object *lbafs;
+ int index = 0;
+
+ static __u64 lsbGbErased = 0, msbGbErased = 0, lsbLifWrtToFlash = 0, msbLifWrtToFlash = 0,
+ lsbLifWrtFrmHost = 0, msbLifWrtFrmHost = 0, lsbLifRdToHost = 0, msbLifRdToHost = 0, lsbTrimCnt = 0, msbTrimCnt = 0;
+ char buf[40] = {0};
+
+ lbafs = json_create_array();
+ json_object_add_value_array(root, "Extended-SMART-Attributes", lbafs);
+
+ for (index = 0; index < NUMBER_EXTENDED_SMART_ATTRIBUTES; index++) {
+ struct json_object *lbaf = json_create_object();
+
+ if (ExtdSMARTInfo->vendorData[index].AttributeNumber) {
+ json_object_add_value_string(lbaf, "attribute_name", print_ext_smart_id(ExtdSMARTInfo->vendorData[index].AttributeNumber));
+ json_object_add_value_int(lbaf, "attribute_id", ExtdSMARTInfo->vendorData[index].AttributeNumber);
+ json_object_add_value_int(lbaf, "attribute_value", smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]));
+ json_array_add_value_object(lbafs, lbaf);
+
+ if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_GB_ERASED_LSB)
+ lsbGbErased = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]);
+
+ if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_GB_ERASED_MSB)
+ msbGbErased = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]);
+
+ if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB)
+ lsbLifWrtToFlash = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]);
+
+ if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_MSB)
+ msbLifWrtToFlash = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]);
+
+ if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB)
+ lsbLifWrtFrmHost = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]);
+
+ if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_MSB)
+ msbLifWrtFrmHost = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]);
+
+ if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB)
+ lsbLifRdToHost = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]);
+
+ if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_LIFETIME_READS_TO_HOST_MSB)
+ msbLifRdToHost = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]);
+
+ if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_TRIM_COUNT_LSB)
+ lsbTrimCnt = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]);
+
+ if (ExtdSMARTInfo->vendorData[index].AttributeNumber == VS_ATTR_ID_TRIM_COUNT_MSB)
+ msbTrimCnt = smart_attribute_vs(ExtdSMARTInfo->Version, ExtdSMARTInfo->vendorData[index]);
+ }
+ }
+
+ struct json_object *lbaf = json_create_object();
+
+ json_object_add_value_string(lbaf, "attribute_name", (print_ext_smart_id(VS_ATTR_ID_GB_ERASED_LSB) + 7));
+
+ json_object_add_value_int(lbaf, "attribute_id", VS_ATTR_ID_GB_ERASED_MSB << 8 | VS_ATTR_ID_GB_ERASED_LSB);
+
+ sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbGbErased, (uint64_t)lsbGbErased);
+ json_object_add_value_string(lbaf, "attribute_value", buf);
+ json_array_add_value_object(lbafs, lbaf);
+
+
+ lbaf = json_create_object();
+
+ json_object_add_value_string(lbaf, "attribute_name", (print_ext_smart_id(VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB) + 7));
+
+ json_object_add_value_int(lbaf, "attribute_id", VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_MSB << 8 | VS_ATTR_ID_LIFETIME_WRITES_TO_FLASH_LSB);
+
+ sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbLifWrtToFlash, (uint64_t)lsbLifWrtToFlash);
+ json_object_add_value_string(lbaf, "attribute_value", buf);
+ json_array_add_value_object(lbafs, lbaf);
+
+
+ lbaf = json_create_object();
+
+ json_object_add_value_string(lbaf, "attribute_name", (print_ext_smart_id(VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB) + 7));
+
+ json_object_add_value_int(lbaf, "attribute_id", VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_MSB << 8 | VS_ATTR_ID_LIFETIME_WRITES_FROM_HOST_LSB);
+
+ sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbLifWrtFrmHost, (uint64_t)lsbLifWrtFrmHost);
+ json_object_add_value_string(lbaf, "attribute_value", buf);
+ json_array_add_value_object(lbafs, lbaf);
+
+
+ lbaf = json_create_object();
+
+ json_object_add_value_string(lbaf, "attribute_name", (print_ext_smart_id(VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB) + 7));
+
+ json_object_add_value_int(lbaf, "attribute_id", VS_ATTR_ID_LIFETIME_READS_TO_HOST_MSB << 8 | VS_ATTR_ID_LIFETIME_READS_TO_HOST_LSB);
+
+ sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbLifRdToHost, (uint64_t)lsbLifRdToHost);
+ json_object_add_value_string(lbaf, "attribute_value", buf);
+ json_array_add_value_object(lbafs, lbaf);
+
+
+ lbaf = json_create_object();
+
+ json_object_add_value_string(lbaf, "attribute_name", (print_ext_smart_id(VS_ATTR_ID_TRIM_COUNT_LSB) + 7));
+
+ json_object_add_value_int(lbaf, "attribute_id", VS_ATTR_ID_TRIM_COUNT_MSB << 8 | VS_ATTR_ID_TRIM_COUNT_LSB);
+
+ sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", (uint64_t)msbTrimCnt, (uint64_t)lsbTrimCnt);
+ json_object_add_value_string(lbaf, "attribute_value", buf);
+ json_array_add_value_object(lbafs, lbaf);
+}
+
+static void print_smart_log_CF(vendor_log_page_CF *pLogPageCF)
+{
+ __u64 currentTemp, maxTemp;
+
+ printf("\n\nSeagate DRAM Supercap SMART Attributes :\n");
+ printf("%-39s %-19s\n", "Description", "Supercap Attributes");
+
+ printf("%-40s", "Super-cap current temperature");
+ currentTemp = pLogPageCF->AttrCF.SuperCapCurrentTemperature;
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(currentTemp));
+
+ maxTemp = pLogPageCF->AttrCF.SuperCapMaximumTemperature;
+ printf("%-40s", "Super-cap maximum temperature");
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(maxTemp));
+
+ printf("%-40s", "Super-cap status");
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageCF->AttrCF.SuperCapStatus));
+
+ printf("%-40s", "Data units read to DRAM namespace");
+ printf(" 0x%016"PRIx64"%016"PRIx64"\n", le64_to_cpu(pLogPageCF->AttrCF.DataUnitsReadToDramNamespace.MS__u64),
+ le64_to_cpu(pLogPageCF->AttrCF.DataUnitsReadToDramNamespace.LS__u64));
+
+ printf("%-40s", "Data units written to DRAM namespace");
+ printf(" 0x%016"PRIx64"%016"PRIx64"\n", le64_to_cpu(pLogPageCF->AttrCF.DataUnitsWrittenToDramNamespace.MS__u64),
+ le64_to_cpu(pLogPageCF->AttrCF.DataUnitsWrittenToDramNamespace.LS__u64));
+
+ printf("%-40s", "DRAM correctable error count");
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageCF->AttrCF.DramCorrectableErrorCount));
+
+ printf("%-40s", "DRAM uncorrectable error count");
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageCF->AttrCF.DramUncorrectableErrorCount));
+}
+
+static void json_print_smart_log_CF(struct json_object *root, vendor_log_page_CF *pLogPageCF)
+{
+ struct json_object *logPages;
+ unsigned int currentTemp, maxTemp;
+ char buf[40];
+
+ logPages = json_create_array();
+ json_object_add_value_array(root, "DRAM Supercap SMART Attributes", logPages);
+ struct json_object *lbaf = json_create_object();
+
+ currentTemp = pLogPageCF->AttrCF.SuperCapCurrentTemperature;
+ json_object_add_value_string(lbaf, "attribute_name", "Super-cap current temperature");
+ json_object_add_value_int(lbaf, "attribute_value", currentTemp);
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ maxTemp = pLogPageCF->AttrCF.SuperCapMaximumTemperature;
+ json_object_add_value_string(lbaf, "attribute_name", "Super-cap maximum temperature");
+ json_object_add_value_int(lbaf, "attribute_value", maxTemp);
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Super-cap status");
+ json_object_add_value_int(lbaf, "attribute_value", pLogPageCF->AttrCF.SuperCapStatus);
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Data units read to DRAM namespace");
+ memset(buf, 0, sizeof(buf));
+ sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", le64_to_cpu(pLogPageCF->AttrCF.DataUnitsReadToDramNamespace.MS__u64),
+ le64_to_cpu(pLogPageCF->AttrCF.DataUnitsReadToDramNamespace.LS__u64));
+ json_object_add_value_string(lbaf, "attribute_value", buf);
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Data units written to DRAM namespace");
+ memset(buf, 0, sizeof(buf));
+ sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", le64_to_cpu(pLogPageCF->AttrCF.DataUnitsWrittenToDramNamespace.MS__u64),
+ le64_to_cpu(pLogPageCF->AttrCF.DataUnitsWrittenToDramNamespace.LS__u64));
+ json_object_add_value_string(lbaf, "attribute_value", buf);
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "DRAM correctable error count");
+ json_object_add_value_int(lbaf, "attribute_value", pLogPageCF->AttrCF.DramCorrectableErrorCount);
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "DRAM uncorrectable error count");
+ json_object_add_value_int(lbaf, "attribute_value", pLogPageCF->AttrCF.DramUncorrectableErrorCount);
+ json_array_add_value_object(logPages, lbaf);
+}
+
+
+static void print_stx_smart_log_C0(STX_EXT_SMART_LOG_PAGE_C0 *pLogPageC0)
+{
+ printf("\n\nSeagate SMART Health Attributes :\n");
+ printf("%-39s %-19s\n", "Description", "Health Attributes");
+
+ printf("%-40s", "Physical Media Units Written");
+ printf(" 0x%016"PRIx64"%016"PRIx64"\n", le64_to_cpu(pLogPageC0->phyMediaUnitsWrt.MS__u64),
+ le64_to_cpu(pLogPageC0->phyMediaUnitsWrt.LS__u64));
+
+ printf("%-40s", "Physical Media Units Read");
+ printf(" 0x%016"PRIx64"%016"PRIx64"\n", le64_to_cpu(pLogPageC0->phyMediaUnitsRd.MS__u64),
+ le64_to_cpu(pLogPageC0->phyMediaUnitsRd.LS__u64));
+
+ printf("%-40s", "Bad User NAND Blocks");
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->badUsrNandBlocks));
+
+ printf("%-40s", "Bad System NAND Blocks");
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->badSysNandBlocks));
+
+ printf("%-40s", "XOR Recovery Count");
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->xorRecoveryCnt));
+
+ printf("%-40s", "Uncorrectable Read Error Count");
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->ucRdEc));
+
+ printf("%-40s", "Soft ECC Error Count");
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->softEccEc));
+
+ printf("%-40s", "End to End Correction Counts");
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->etoeCrrCnt));
+
+ printf("%-40s", "System Data Used in Parcent");
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->sysDataUsed));
+
+ printf("%-40s", "Refresh Counts");
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->refreshCount));
+
+ printf("%-40s", "User Data Erase Counts");
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->usrDataEraseCnt));
+
+ printf("%-40s", "Thermal Throttling Status and Count");
+ printf(" 0x%04x\n", le16_to_cpu(pLogPageC0->thermalThrottling));
+
+ printf("%-40s", "DSSD Specification Version");
+ printf(" %d.%d.%d.%d\n", pLogPageC0->dssdSpecVerMajor,
+ le16_to_cpu(pLogPageC0->dssdSpecVerMinor),
+ le16_to_cpu(pLogPageC0->dssdSpecVerPoint),
+ pLogPageC0->dssdSpecVerErrata);
+
+ printf("%-40s", "PCIe Correctable Error Count");
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->pcieCorrEc));
+
+ printf("%-40s", "Incomplete Shutdowns");
+ printf(" 0x%08x\n", le32_to_cpu(pLogPageC0->incompleteShutdowns));
+
+ printf("%-40s", "Free Blocks in Percent");
+ printf(" %d\n", pLogPageC0->freeBlocks);
+
+ printf("%-40s", "Capacitor Health");
+ printf(" 0x%04x\n", le16_to_cpu(pLogPageC0->capHealth));
+
+ printf("%-40s", "NVMe Errata Version");
+ printf(" %c\n", pLogPageC0->nvmeErrataVer);
+
+ printf("%-40s", "Unaligned IO");
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->unalignedIO));
+
+ printf("%-40s", "Security Version Number");
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->secVerNum));
+
+ printf("%-40s", "Total Namespace Utilization");
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->totalNUSE));
+
+ printf("%-40s", "PLP Start Count");
+ printf(" 0x%016"PRIx64"%016"PRIx64"\n", le64_to_cpu(pLogPageC0->plpStartCnt.MS__u64),
+ le64_to_cpu(pLogPageC0->plpStartCnt.LS__u64));
+
+ printf("%-40s", "Endurance Estimate");
+ printf(" 0x%016"PRIx64"%016"PRIx64"\n", le64_to_cpu(pLogPageC0->enduranceEstimate.MS__u64),
+ le64_to_cpu(pLogPageC0->enduranceEstimate.LS__u64));
+
+ printf("%-40s", "PCIe Link Retraining Count");
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->pcieLinkRetCnt));
+
+ printf("%-40s", "Power State Change Count");
+ printf(" 0x%016"PRIx64"\n", le64_to_cpu(pLogPageC0->powStateChangeCnt));
+
+ printf("%-40s", "Log Page Version");
+ printf(" 0x%04x\n", le16_to_cpu(pLogPageC0->logPageVer));
+
+ printf("%-40s", "Log Page GUID");
+ printf(" 0x%016"PRIx64"%016"PRIx64"\n", le64_to_cpu(pLogPageC0->logPageGUID.MS__u64),
+ le64_to_cpu(pLogPageC0->logPageGUID.LS__u64));
+}
+
+static void json_print_stx_smart_log_C0(struct json_object *root, STX_EXT_SMART_LOG_PAGE_C0 *pLogPageC0)
+{
+ struct json_object *logPages;
+ char buf[40];
+
+ logPages = json_create_array();
+ json_object_add_value_array(root, "Seagate SMART Health Attributes", logPages);
+
+ struct json_object *lbaf = json_create_object();
+
+ json_object_add_value_string(lbaf, "attribute_name", "Physical Media Units Written");
+ memset(buf, 0, sizeof(buf));
+ sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", le64_to_cpu(pLogPageC0->phyMediaUnitsWrt.MS__u64),
+ le64_to_cpu(pLogPageC0->phyMediaUnitsWrt.LS__u64));
+ json_object_add_value_string(lbaf, "attribute_value", buf);
+ json_array_add_value_object(logPages, lbaf);
+
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Physical Media Units Read");
+ memset(buf, 0, sizeof(buf));
+ sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", le64_to_cpu(pLogPageC0->phyMediaUnitsRd.MS__u64),
+ le64_to_cpu(pLogPageC0->phyMediaUnitsRd.LS__u64));
+ json_object_add_value_string(lbaf, "attribute_value", buf);
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Bad User NAND Blocks");
+ json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->badUsrNandBlocks));
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Bad System NAND Blocks");
+ json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->badSysNandBlocks));
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "XOR Recovery Count");
+ json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->xorRecoveryCnt));
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Uncorrectable Read Error Count");
+ json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->ucRdEc));
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Soft ECC Error Count");
+ json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->softEccEc));
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "End to End Correction Counts");
+ json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->etoeCrrCnt));
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "System Data Used in Parcent");
+ json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->sysDataUsed));
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Refresh Counts");
+ json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->refreshCount));
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "User Data Erase Counts");
+ json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->usrDataEraseCnt));
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Thermal Throttling Status and Count");
+ json_object_add_value_int(lbaf, "attribute_value", le16_to_cpu(pLogPageC0->thermalThrottling));
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "DSSD Specification Version");
+ memset(buf, 0, sizeof(buf));
+ sprintf(buf, "%d.%d.%d.%d", pLogPageC0->dssdSpecVerMajor,
+ le16_to_cpu(pLogPageC0->dssdSpecVerMinor),
+ le16_to_cpu(pLogPageC0->dssdSpecVerPoint),
+ pLogPageC0->dssdSpecVerErrata);
+ json_object_add_value_string(lbaf, "attribute_value", buf);
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "PCIe Correctable Error Count");
+ json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->pcieCorrEc));
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Incomplete Shutdowns");
+ json_object_add_value_int(lbaf, "attribute_value", le32_to_cpu(pLogPageC0->incompleteShutdowns));
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Free Blocks in Percent");
+ json_object_add_value_int(lbaf, "attribute_value", pLogPageC0->freeBlocks);
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Capacitor Health");
+ json_object_add_value_int(lbaf, "attribute_value", le16_to_cpu(pLogPageC0->capHealth));
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "NVMe Errata Version");
+ json_object_add_value_int(lbaf, "attribute_value", pLogPageC0->nvmeErrataVer);
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Unaligned IO");
+ json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->unalignedIO));
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Security Version Number");
+ json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->secVerNum));
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Total Namespace Utilization");
+ json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->totalNUSE));
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "PLP Start Count");
+ memset(buf, 0, sizeof(buf));
+ sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", le64_to_cpu(pLogPageC0->plpStartCnt.MS__u64),
+ le64_to_cpu(pLogPageC0->plpStartCnt.LS__u64));
+ json_object_add_value_string(lbaf, "attribute_value", buf);
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Endurance Estimate");
+ memset(buf, 0, sizeof(buf));
+ sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", le64_to_cpu(pLogPageC0->enduranceEstimate.MS__u64),
+ le64_to_cpu(pLogPageC0->enduranceEstimate.LS__u64));
+ json_object_add_value_string(lbaf, "attribute_value", buf);
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "PCIe Link Retraining Count");
+ json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->pcieLinkRetCnt));
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Power State Change Count");
+ json_object_add_value_int(lbaf, "attribute_value", le64_to_cpu(pLogPageC0->powStateChangeCnt));
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Log Page Version");
+ json_object_add_value_int(lbaf, "attribute_value", le16_to_cpu(pLogPageC0->logPageVer));
+ json_array_add_value_object(logPages, lbaf);
+
+ lbaf = json_create_object();
+ json_object_add_value_string(lbaf, "attribute_name", "Log Page GUID");
+ memset(buf, 0, sizeof(buf));
+ sprintf(buf, "0x%016"PRIx64"%016"PRIx64"", le64_to_cpu(pLogPageC0->logPageGUID.MS__u64),
+ le64_to_cpu(pLogPageC0->logPageGUID.LS__u64));
+ json_object_add_value_string(lbaf, "attribute_value", buf);
+ json_array_add_value_object(logPages, lbaf);
+}
+
+static int vs_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ struct nvme_id_ctrl ctrl;
+ char modelNo[40];
+ STX_EXT_SMART_LOG_PAGE_C0 ehExtSmart;
+ EXTENDED_SMART_INFO_T ExtdSMARTInfo;
+ vendor_log_page_CF logPageCF;
+ struct json_object *root = json_create_object();
+ struct json_object *lbafs = json_create_array();
+ struct json_object *lbafs_ExtSmart, *lbafs_DramSmart;
+
+ const char *desc = "Retrieve the Firmware Activation History for Seagate NVMe drives";
+ const char *output_format = "output in binary format";
+ struct nvme_dev *dev;
+ int err, index = 0;
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err) {
+ printf("\nDevice not found\n");
+ return -1;
+ }
+
+ if (strcmp(cfg.output_format, "json"))
+ printf("Seagate Extended SMART Information :\n");
+
+
+ /**
+ * Here we should identify if the drive is a Panthor or Jaguar.
+ * Here we need to extract the model no from ctrl-id abd use it
+ * to determine drive family.
+ */
+
+ err = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (!err) {
+ memcpy(modelNo, ctrl.mn, sizeof(modelNo));
+ } else {
+ nvme_show_status(err);
+ return err;
+ }
+
+ if (!stx_is_jag_pan(modelNo)) {
+ err = nvme_get_log_simple(dev_fd(dev), 0xC4, sizeof(ExtdSMARTInfo), &ExtdSMARTInfo);
+ if (!err) {
+ if (strcmp(cfg.output_format, "json")) {
+ printf("%-39s %-15s %-19s\n", "Description", "Ext-Smart-Id", "Ext-Smart-Value");
+ for (index = 0; index < 80; index++)
+ printf("-");
+ printf("\n");
+ for (index = 0; index < NUMBER_EXTENDED_SMART_ATTRIBUTES; index++)
+ print_smart_log(ExtdSMARTInfo.Version, ExtdSMARTInfo.vendorData[index], index == (NUMBER_EXTENDED_SMART_ATTRIBUTES - 1));
+
+ } else {
+ lbafs_ExtSmart = json_create_object();
+ json_print_smart_log(lbafs_ExtSmart, &ExtdSMARTInfo);
+
+ json_object_add_value_array(root, "SMART-Attributes", lbafs);
+ json_array_add_value_object(lbafs, lbafs_ExtSmart);
+ }
+
+ /**
+ * Next get Log Page 0xCF
+ */
+
+ err = nvme_get_log_simple(dev_fd(dev), 0xCF, sizeof(logPageCF), &logPageCF);
+ if (!err) {
+ if (strcmp(cfg.output_format, "json")) {
+ print_smart_log_CF(&logPageCF);
+ } else {
+ lbafs_DramSmart = json_create_object();
+ json_print_smart_log_CF(lbafs_DramSmart, &logPageCF);
+ json_array_add_value_object(lbafs, lbafs_DramSmart);
+ json_print_object(root, NULL);
+ }
+ } else if (!strcmp(cfg.output_format, "json")) {
+ json_print_object(root, NULL);
+ json_free_object(root);
+ }
+ } else if (err > 0) {
+ nvme_show_status(err);
+ }
+ } else {
+ err = nvme_get_log_simple(dev_fd(dev), 0xC0, sizeof(ehExtSmart), &ehExtSmart);
+
+ if (!err) {
+ if (strcmp(cfg.output_format, "json")) {
+ print_stx_smart_log_C0(&ehExtSmart);
+ } else {
+ lbafs_ExtSmart = json_create_object();
+ json_print_stx_smart_log_C0(lbafs_ExtSmart, &ehExtSmart);
+
+ json_object_add_value_array(root, "SMART-Attributes", lbafs);
+ json_array_add_value_object(lbafs, lbafs_ExtSmart);
+
+ json_print_object(root, NULL);
+ json_free_object(root);
+ }
+ }
+
+ if (err > 0)
+ nvme_show_status(err);
+ }
+
+ err = nvme_get_log_simple(dev_fd(dev), 0xC4,
+ sizeof(ExtdSMARTInfo), &ExtdSMARTInfo);
+ if (!err) {
+ if (strcmp(cfg.output_format, "json")) {
+ printf("%-39s %-15s %-19s\n", "Description", "Ext-Smart-Id", "Ext-Smart-Value");
+ for (index = 0; index < 80; index++)
+ printf("-");
+ printf("\n");
+ for (index = 0; index < NUMBER_EXTENDED_SMART_ATTRIBUTES; index++)
+ print_smart_log(ExtdSMARTInfo.Version, ExtdSMARTInfo.vendorData[index], index == (NUMBER_EXTENDED_SMART_ATTRIBUTES - 1));
+
+ } else {
+ lbafs_ExtSmart = json_create_object();
+ json_print_smart_log(lbafs_ExtSmart, &ExtdSMARTInfo);
+
+ json_object_add_value_array(root, "SMART-Attributes", lbafs);
+ json_array_add_value_object(lbafs, lbafs_ExtSmart);
+ }
+
+ /**
+ * Next get Log Page 0xCF
+ */
+
+ err = nvme_get_log_simple(dev_fd(dev), 0xCF,
+ sizeof(logPageCF), &logPageCF);
+ if (!err) {
+ if (strcmp(cfg.output_format, "json")) {
+ print_smart_log_CF(&logPageCF);
+ } else {
+ lbafs_DramSmart = json_create_object();
+ json_print_smart_log_CF(lbafs_DramSmart, &logPageCF);
+ json_array_add_value_object(lbafs, lbafs_DramSmart);
+ json_print_object(root, NULL);
+ }
+ } else if (!strcmp(cfg.output_format, "json")) {
+ json_print_object(root, NULL);
+ }
+ } else if (err > 0) {
+ nvme_show_status(err);
+ }
+
+ dev_close(dev);
+
+ return err;
+}
+
+/*EOF Extended-SMART Information */
+
+/***************************************
+ * Temperature-Stats information
+ ***************************************/
+static void json_temp_stats(__u32 temperature, __u32 PcbTemp, __u32 SocTemp, __u32 maxTemperature,
+ __u32 MaxSocTemp, __u32 cf_err, __u32 scCurrentTemp, __u32 scMaxTem)
+{
+ struct json_object *root = json_create_object();
+
+ json_object_add_value_int(root, "Current temperature", temperature);
+ json_object_add_value_int(root, "Current PCB temperature", PcbTemp);
+ json_object_add_value_int(root, "Current SOC temperature", SocTemp);
+ json_object_add_value_int(root, "Highest temperature", maxTemperature);
+ json_object_add_value_int(root, "Max SOC temperature", MaxSocTemp);
+ if (!cf_err) {
+ json_object_add_value_int(root, "SuperCap Current temperature", scCurrentTemp);
+ json_object_add_value_int(root, "SuperCap Max temperature", scMaxTem);
+ }
+
+ json_print_object(root, NULL);
+}
+
+static int temp_stats(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ struct nvme_smart_log smart_log;
+ EXTENDED_SMART_INFO_T ExtdSMARTInfo;
+ vendor_log_page_CF logPageCF;
+
+ int err, cf_err;
+ int index;
+ const char *desc = "Retrieve Seagate Temperature Stats information for the given device ";
+ const char *output_format = "output in binary format";
+ unsigned int temperature = 0, PcbTemp = 0, SocTemp = 0, scCurrentTemp = 0, scMaxTemp = 0;
+ unsigned long long maxTemperature = 0, MaxSocTemp = 0;
+ struct nvme_dev *dev;
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err) {
+ printf("\nDevice not found\n");
+ return -1;
+ }
+
+ if (strcmp(cfg.output_format, "json"))
+ printf("Seagate Temperature Stats Information :\n");
+ /*STEP-1 : Get Current Temperature from SMART */
+ err = nvme_get_log_smart(dev_fd(dev), 0xffffffff, false, &smart_log);
+ if (!err) {
+ temperature = ((smart_log.temperature[1] << 8) | smart_log.temperature[0]);
+ temperature = temperature ? temperature - 273 : 0;
+ PcbTemp = le16_to_cpu(smart_log.temp_sensor[0]);
+ PcbTemp = PcbTemp ? PcbTemp - 273 : 0;
+ SocTemp = le16_to_cpu(smart_log.temp_sensor[1]);
+ SocTemp = SocTemp ? SocTemp - 273 : 0;
+ if (strcmp(cfg.output_format, "json")) {
+ printf("%-20s : %u C\n", "Current Temperature", temperature);
+ printf("%-20s : %u C\n", "Current PCB Temperature", PcbTemp);
+ printf("%-20s : %u C\n", "Current SOC Temperature", SocTemp);
+ }
+ }
+
+ /* STEP-2 : Get Max temperature form Ext SMART-id 194 */
+ err = nvme_get_log_simple(dev_fd(dev), 0xC4,
+ sizeof(ExtdSMARTInfo), &ExtdSMARTInfo);
+ if (!err) {
+ for (index = 0; index < NUMBER_EXTENDED_SMART_ATTRIBUTES; index++) {
+ if (ExtdSMARTInfo.vendorData[index].AttributeNumber == VS_ATTR_ID_MAX_LIFE_TEMPERATURE) {
+ maxTemperature = smart_attribute_vs(ExtdSMARTInfo.Version, ExtdSMARTInfo.vendorData[index]);
+ maxTemperature = maxTemperature ? maxTemperature - 273 : 0;
+ if (strcmp(cfg.output_format, "json"))
+ printf("%-20s : %d C\n", "Highest Temperature", (unsigned int)maxTemperature);
+ }
+
+ if (ExtdSMARTInfo.vendorData[index].AttributeNumber == VS_ATTR_ID_MAX_SOC_LIFE_TEMPERATURE) {
+ MaxSocTemp = smart_attribute_vs(ExtdSMARTInfo.Version, ExtdSMARTInfo.vendorData[index]);
+ MaxSocTemp = MaxSocTemp ? MaxSocTemp - 273 : 0;
+ if (strcmp(cfg.output_format, "json"))
+ printf("%-20s : %d C\n", "Max SOC Temperature", (unsigned int)MaxSocTemp);
+ }
+ }
+ } else {
+ if (err > 0)
+ nvme_show_status(err);
+ }
+
+ cf_err = nvme_get_log_simple(dev_fd(dev), 0xCF,
+ sizeof(ExtdSMARTInfo), &logPageCF);
+
+ if (!cf_err) {
+ scCurrentTemp = logPageCF.AttrCF.SuperCapCurrentTemperature;
+ scCurrentTemp = scCurrentTemp ? scCurrentTemp - 273 : 0;
+ printf("%-20s : %d C\n", "Super-cap Current Temperature", scCurrentTemp);
+
+ scMaxTemp = logPageCF.AttrCF.SuperCapMaximumTemperature;
+ scMaxTemp = scMaxTemp ? scMaxTemp - 273 : 0;
+ printf("%-20s : %d C\n", "Super-cap Max Temperature", scMaxTemp);
+ }
+
+ if (!strcmp(cfg.output_format, "json"))
+ json_temp_stats(temperature, PcbTemp, SocTemp, maxTemperature, MaxSocTemp, cf_err, scCurrentTemp, scMaxTemp);
+
+ dev_close(dev);
+ return err;
+}
+/* EOF Temperature Stats information */
+
+/***************************************
+ * PCIe error-log information
+ ***************************************/
+static void print_vs_pcie_error_log(pcie_error_log_page pcieErrorLog)
+{
+ __u32 correctPcieEc = pcieErrorLog.BadDllpErrCnt + pcieErrorLog.BadTlpErrCnt +
+ pcieErrorLog.RcvrErrCnt + pcieErrorLog.ReplayTOErrCnt +
+ pcieErrorLog.ReplayNumRolloverErrCnt;
+ __u32 uncorrectPcieEc = pcieErrorLog.FCProtocolErrCnt + pcieErrorLog.DllpProtocolErrCnt +
+ pcieErrorLog.CmpltnTOErrCnt + pcieErrorLog.RcvrQOverflowErrCnt +
+ pcieErrorLog.UnexpectedCplTlpErrCnt + pcieErrorLog.CplTlpURErrCnt +
+ pcieErrorLog.CplTlpCAErrCnt + pcieErrorLog.ReqCAErrCnt +
+ pcieErrorLog.ReqURErrCnt + pcieErrorLog.EcrcErrCnt +
+ pcieErrorLog.MalformedTlpErrCnt + pcieErrorLog.CplTlpPoisonedErrCnt +
+ pcieErrorLog.MemRdTlpPoisonedErrCnt;
+
+ printf("%-45s : %u\n", "PCIe Correctable Error Count", correctPcieEc);
+ printf("%-45s : %u\n", "PCIe Un-Correctable Error Count", uncorrectPcieEc);
+ printf("%-45s : %u\n", "Unsupported Request Error Status (URES)", pcieErrorLog.ReqURErrCnt);
+ printf("%-45s : %u\n", "ECRC Error Status (ECRCES)", pcieErrorLog.EcrcErrCnt);
+ printf("%-45s : %u\n", "Malformed TLP Status (MTS)", pcieErrorLog.MalformedTlpErrCnt);
+ printf("%-45s : %u\n", "Receiver Overflow Status (ROS)", pcieErrorLog.RcvrQOverflowErrCnt);
+ printf("%-45s : %u\n", "Unexpected Completion Status(UCS)", pcieErrorLog.UnexpectedCplTlpErrCnt);
+ printf("%-45s : %u\n", "Completion Timeout Status (CTS)", pcieErrorLog.CmpltnTOErrCnt);
+ printf("%-45s : %u\n", "Flow Control Protocol Error Status (FCPES)", pcieErrorLog.FCProtocolErrCnt);
+ printf("%-45s : %u\n", "Poisoned TLP Status (PTS)", pcieErrorLog.MemRdTlpPoisonedErrCnt);
+ printf("%-45s : %u\n", "Data Link Protocol Error Status(DLPES)", pcieErrorLog.DllpProtocolErrCnt);
+ printf("%-45s : %u\n", "Replay Timer Timeout Status(RTS)", pcieErrorLog.ReplayTOErrCnt);
+ printf("%-45s : %u\n", "Replay_NUM Rollover Status(RRS)", pcieErrorLog.ReplayNumRolloverErrCnt);
+ printf("%-45s : %u\n", "Bad DLLP Status (BDS)", pcieErrorLog.BadDllpErrCnt);
+ printf("%-45s : %u\n", "Bad TLP Status (BTS)", pcieErrorLog.BadTlpErrCnt);
+ printf("%-45s : %u\n", "Receiver Error Status (RES)", pcieErrorLog.RcvrErrCnt);
+ printf("%-45s : %u\n", "Cpl TLP Unsupported Request Error Count", pcieErrorLog.CplTlpURErrCnt);
+ printf("%-45s : %u\n", "Cpl TLP Completion Abort Error Count", pcieErrorLog.CplTlpCAErrCnt);
+ printf("%-45s : %u\n", "Cpl TLP Poisoned Error Count", pcieErrorLog.CplTlpPoisonedErrCnt);
+ printf("%-45s : %u\n", "Request Completion Abort Error Count", pcieErrorLog.ReqCAErrCnt);
+ printf("%-45s : %s\n", "Advisory Non-Fatal Error Status(ANFES)", "Not Supported");
+ printf("%-45s : %s\n", "Completer Abort Status (CAS)", "Not Supported");
+}
+
+static void json_vs_pcie_error_log(pcie_error_log_page pcieErrorLog)
+{
+ struct json_object *root = json_create_object();
+ __u32 correctPcieEc = pcieErrorLog.BadDllpErrCnt + pcieErrorLog.BadTlpErrCnt +
+ pcieErrorLog.RcvrErrCnt + pcieErrorLog.ReplayTOErrCnt +
+ pcieErrorLog.ReplayNumRolloverErrCnt;
+ __u32 uncorrectPcieEc = pcieErrorLog.FCProtocolErrCnt + pcieErrorLog.DllpProtocolErrCnt +
+ pcieErrorLog.CmpltnTOErrCnt + pcieErrorLog.RcvrQOverflowErrCnt +
+ pcieErrorLog.UnexpectedCplTlpErrCnt + pcieErrorLog.CplTlpURErrCnt +
+ pcieErrorLog.CplTlpCAErrCnt + pcieErrorLog.ReqCAErrCnt +
+ pcieErrorLog.ReqURErrCnt + pcieErrorLog.EcrcErrCnt +
+ pcieErrorLog.MalformedTlpErrCnt + pcieErrorLog.CplTlpPoisonedErrCnt +
+ pcieErrorLog.MemRdTlpPoisonedErrCnt;
+
+ json_object_add_value_int(root, "PCIe Correctable Error Count", correctPcieEc);
+ json_object_add_value_int(root, "PCIe Un-Correctable Error Count", uncorrectPcieEc);
+ json_object_add_value_int(root, "Unsupported Request Error Status (URES)", pcieErrorLog.ReqURErrCnt);
+ json_object_add_value_int(root, "ECRC Error Status (ECRCES)", pcieErrorLog.EcrcErrCnt);
+ json_object_add_value_int(root, "Malformed TLP Status (MTS)", pcieErrorLog.MalformedTlpErrCnt);
+ json_object_add_value_int(root, "Receiver Overflow Status (ROS)", pcieErrorLog.RcvrQOverflowErrCnt);
+ json_object_add_value_int(root, "Unexpected Completion Status(UCS)", pcieErrorLog.UnexpectedCplTlpErrCnt);
+ json_object_add_value_int(root, "Completion Timeout Status (CTS)", pcieErrorLog.CmpltnTOErrCnt);
+ json_object_add_value_int(root, "Flow Control Protocol Error Status (FCPES)", pcieErrorLog.FCProtocolErrCnt);
+ json_object_add_value_int(root, "Poisoned TLP Status (PTS)", pcieErrorLog.MemRdTlpPoisonedErrCnt);
+ json_object_add_value_int(root, "Data Link Protocol Error Status(DLPES)", pcieErrorLog.DllpProtocolErrCnt);
+ json_object_add_value_int(root, "Replay Timer Timeout Status(RTS)", pcieErrorLog.ReplayTOErrCnt);
+ json_object_add_value_int(root, "Replay_NUM Rollover Status(RRS)", pcieErrorLog.ReplayNumRolloverErrCnt);
+ json_object_add_value_int(root, "Bad DLLP Status (BDS)", pcieErrorLog.BadDllpErrCnt);
+ json_object_add_value_int(root, "Bad TLP Status (BTS)", pcieErrorLog.BadTlpErrCnt);
+ json_object_add_value_int(root, "Receiver Error Status (RES)", pcieErrorLog.RcvrErrCnt);
+ json_object_add_value_int(root, "Cpl TLP Unsupported Request Error Count", pcieErrorLog.CplTlpURErrCnt);
+ json_object_add_value_int(root, "Cpl TLP Completion Abort Error Count", pcieErrorLog.CplTlpCAErrCnt);
+ json_object_add_value_int(root, "Cpl TLP Poisoned Error Count", pcieErrorLog.CplTlpPoisonedErrCnt);
+ json_object_add_value_int(root, "Request Completion Abort Error Count", pcieErrorLog.ReqCAErrCnt);
+ json_print_object(root, NULL);
+}
+
+static int vs_pcie_error_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ pcie_error_log_page pcieErrorLog;
+ struct nvme_dev *dev;
+
+ const char *desc = "Retrieve Seagate PCIe error counters for the given device ";
+ const char *output_format = "output in binary format";
+ int err;
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err) {
+ printf("\nDevice not found\n");
+ return -1;
+ }
+
+ if (strcmp(cfg.output_format, "json"))
+ printf("Seagate PCIe error counters Information :\n");
+
+ err = nvme_get_log_simple(dev_fd(dev), 0xCB,
+ sizeof(pcieErrorLog), &pcieErrorLog);
+ if (!err) {
+ if (strcmp(cfg.output_format, "json"))
+ print_vs_pcie_error_log(pcieErrorLog);
+ else
+ json_vs_pcie_error_log(pcieErrorLog);
+
+ } else if (err > 0) {
+ nvme_show_status(err);
+ }
+
+ dev_close(dev);
+ return err;
+}
+/* EOF PCIE error-log information */
+
+
+/***************************************
+ * FW Activation History log
+ ***************************************/
+static void print_stx_vs_fw_activate_history(stx_fw_activ_history_log_page fwActivHis)
+{
+ __u32 i;
+ char prev_fw[9] = {0};
+ char new_fw[9] = {0};
+ char buf[80];
+
+ if (fwActivHis.numValidFwActHisEnt > 0) {
+ printf("\n\nSeagate FW Activation History :\n");
+ printf("%-9s %-21s %-7s %-13s %-9s %-5s %-15s %-9s\n", "Counter ", " Timestamp ", " PCC ", "Previous FW ", "New FW ", "Slot", "Commit Action", "Result");
+
+ for (i = 0; i < fwActivHis.numValidFwActHisEnt; i++) {
+
+ printf(" %-4d ", fwActivHis.fwActHisEnt[i].fwActivCnt);
+
+ time_t t = fwActivHis.fwActHisEnt[i].timeStamp / 1000;
+ struct tm ts = *localtime(&t);
+
+ strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &ts);
+ printf(" %-20s ", buf);
+ printf("%-5" PRId64 " ",
+ (uint64_t)fwActivHis.fwActHisEnt[i].powCycleCnt);
+
+ memset(prev_fw, 0, sizeof(prev_fw));
+ memcpy(prev_fw, fwActivHis.fwActHisEnt[i].previousFW, sizeof(fwActivHis.fwActHisEnt[i].previousFW));
+ printf("%-8s ", prev_fw);
+
+ memset(new_fw, 0, sizeof(new_fw));
+ memcpy(new_fw, fwActivHis.fwActHisEnt[i].newFW, sizeof(fwActivHis.fwActHisEnt[i].newFW));
+ printf("%-8s ", new_fw);
+
+ printf(" %-2d ", fwActivHis.fwActHisEnt[i].slotNum);
+ printf(" 0x%02x ", fwActivHis.fwActHisEnt[i].commitActionType);
+ printf(" 0x%02x\n", fwActivHis.fwActHisEnt[i].result);
+ }
+ } else {
+ printf("%s\n", "Do not have valid FW Activation History");
+ }
+}
+
+static void json_stx_vs_fw_activate_history(stx_fw_activ_history_log_page fwActivHis)
+{
+ struct json_object *root = json_create_object();
+ __u32 i;
+
+ char buf[80];
+
+ struct json_object *historyLogPage = json_create_array();
+
+ json_object_add_value_array(root, "Seagate FW Activation History", historyLogPage);
+
+ if (fwActivHis.numValidFwActHisEnt > 0) {
+ for (i = 0; i < fwActivHis.numValidFwActHisEnt; i++) {
+ struct json_object *lbaf = json_create_object();
+ char prev_fw[8] = { 0 };
+ char new_fw[8] = { 0 };
+
+ json_object_add_value_int(lbaf, "Counter", fwActivHis.fwActHisEnt[i].fwActivCnt);
+
+ time_t t = fwActivHis.fwActHisEnt[i].timeStamp / 1000;
+ struct tm ts = *localtime(&t);
+
+ strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &ts);
+ printf(" %-20s ", buf);
+ json_object_add_value_string(lbaf, "Timestamp", buf);
+
+ json_object_add_value_int(lbaf, "PCC", fwActivHis.fwActHisEnt[i].powCycleCnt);
+ sprintf(prev_fw, "%s", fwActivHis.fwActHisEnt[i].previousFW);
+ json_object_add_value_string(lbaf, "Previous_FW", prev_fw);
+
+ sprintf(new_fw, "%s", fwActivHis.fwActHisEnt[i].newFW);
+ json_object_add_value_string(lbaf, "New_FW", new_fw);
+
+ json_object_add_value_int(lbaf, "Slot", fwActivHis.fwActHisEnt[i].slotNum);
+ json_object_add_value_int(lbaf, "Commit_Action", fwActivHis.fwActHisEnt[i].commitActionType);
+ json_object_add_value_int(lbaf, "Result", fwActivHis.fwActHisEnt[i].result);
+
+ json_array_add_value_object(historyLogPage, lbaf);
+ }
+ } else {
+ printf("%s\n", "Do not have valid FW Activation History");
+ }
+
+ json_print_object(root, NULL);
+ json_free_object(root);
+}
+
+static int stx_vs_fw_activate_history(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ stx_fw_activ_history_log_page fwActivHis;
+ struct nvme_dev *dev;
+
+ const char *desc = "Retrieve FW Activate History for Seagate device ";
+ const char *output_format = "output in binary format";
+ int err;
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err < 0) {
+ printf("\nDevice not found\n");
+ return -1;
+ }
+
+ if (strcmp(cfg.output_format, "json"))
+ printf("Seagate FW Activation History Information :\n");
+
+ err = nvme_get_log_simple(dev_fd(dev), 0xC2, sizeof(fwActivHis), &fwActivHis);
+ if (!err) {
+ if (strcmp(cfg.output_format, "json"))
+ print_stx_vs_fw_activate_history(fwActivHis);
+ else
+ json_stx_vs_fw_activate_history(fwActivHis);
+
+ } else if (err > 0) {
+ nvme_show_status(err);
+ }
+
+ dev_close(dev);
+ return err;
+}
+/* EOF FW Activation History log information */
+
+
+static int clear_fw_activate_history(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Clear FW Activation History for the given Seagate device ";
+ const char *save = "specifies that the controller shall save the attribute";
+ int err;
+ struct nvme_dev *dev;
+ struct nvme_id_ctrl ctrl;
+ char modelNo[40];
+ __u32 result;
+
+ struct config {
+ bool save;
+ };
+
+ struct config cfg = {
+ .save = 0,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("save", 's', &cfg.save, save),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err < 0) {
+ printf("\nDevice not found\n");
+ return -1;
+ }
+
+ err = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (!err) {
+ memcpy(modelNo, ctrl.mn, sizeof(modelNo));
+ } else {
+ nvme_show_status(err);
+ return err;
+ }
+
+ if (!stx_is_jag_pan(modelNo)) {
+ printf("\nDevice does not support Clear FW Activation History\n");
+ } else {
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = 0xC1,
+ .nsid = 0,
+ .cdw11 = 0x80000000,
+ .cdw12 = 0,
+ .save = 0,
+ .uuidx = 0,
+ .cdw15 = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+ err = nvme_set_features(&args);
+ if (err)
+ fprintf(stderr, "%s: couldn't clear PCIe correctable errors\n",
+ __func__);
+ }
+
+ if (err < 0) {
+ perror("set-feature");
+ return errno;
+ }
+
+ dev_close(dev);
+ return err;
+}
+
+
+static int vs_clr_pcie_correctable_errs(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Clear Seagate PCIe Correctable counters for the given device ";
+ const char *save = "specifies that the controller shall save the attribute";
+
+ struct nvme_id_ctrl ctrl;
+ char modelNo[40];
+
+ struct nvme_dev *dev;
+
+ __u32 result;
+ int err;
+
+ struct config {
+ bool save;
+ };
+
+ struct config cfg = {
+ .save = 0,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("save", 's', &cfg.save, save),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err) {
+ printf("\nDevice not found\n");
+ return -1;
+ }
+
+
+ err = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (!err) {
+ memcpy(modelNo, ctrl.mn, sizeof(modelNo));
+ } else {
+ nvme_show_status(err);
+ return err;
+ }
+
+ if (!stx_is_jag_pan(modelNo)) {
+ err = nvme_set_features_simple(dev_fd(dev), 0xE1, 0, 0xCB, cfg.save, &result);
+ } else {
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = 0xC3,
+ .nsid = 0,
+ .cdw11 = 0x80000000,
+ .cdw12 = 0,
+ .save = 0,
+ .uuidx = 0,
+ .cdw15 = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+ err = nvme_set_features(&args);
+ if (err)
+ fprintf(stderr, "%s: couldn't clear PCIe correctable errors\n", __func__);
+ }
+
+ err = nvme_set_features_simple(dev_fd(dev), 0xE1, 0, 0xCB, cfg.save, &result);
+
+ if (err < 0) {
+ perror("set-feature");
+ return errno;
+ }
+
+ dev_close(dev);
+ return err;
+}
+
+static int get_host_tele(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc =
+ "Capture the Telemetry Host-Initiated Data in either hex-dump (default) or binary format";
+ const char *namespace_id = "desired namespace";
+ const char *log_specific = "1 - controller shall capture Data representing the internal\n"
+ "state of the controller at the time the command is processed.\n"
+ "0 - controller shall not update the Telemetry Host Initiated Data.";
+ const char *raw = "output in raw format";
+ struct nvme_temetry_log_hdr tele_log;
+ int blkCnt, maxBlk = 0, blksToGet;
+ struct nvme_dev *dev;
+ unsigned char *log;
+ __le64 offset = 0;
+ int err, dump_fd;
+
+ struct config {
+ __u32 namespace_id;
+ __u32 log_id;
+ bool raw_binary;
+ };
+
+ struct config cfg = {
+ .namespace_id = 0xffffffff,
+ .log_id = 0,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_UINT("log_specific", 'i', &cfg.log_id, log_specific),
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ dump_fd = STDOUT_FILENO;
+ cfg.log_id = (cfg.log_id << 8) | 0x07;
+ err = nvme_get_nsid_log(dev_fd(dev), false, cfg.log_id,
+ cfg.namespace_id,
+ sizeof(tele_log), (void *)(&tele_log));
+ if (!err) {
+ maxBlk = tele_log.tele_data_area3;
+ offset += 512;
+
+ if (!cfg.raw_binary) {
+ printf("Device:%s log-id:%d namespace-id:%#x\n",
+ dev->name, cfg.log_id,
+ cfg.namespace_id);
+ printf("Data Block 1 Last Block:%d Data Block 2 Last Block:%d Data Block 3 Last Block:%d\n",
+ tele_log.tele_data_area1, tele_log.tele_data_area2, tele_log.tele_data_area3);
+
+ d((unsigned char *)(&tele_log), sizeof(tele_log), 16, 1);
+ } else
+ seaget_d_raw((unsigned char *)(&tele_log), sizeof(tele_log), dump_fd);
+ } else if (err > 0) {
+ nvme_show_status(err);
+ } else {
+ perror("log page");
+ }
+
+ blkCnt = 0;
+
+ while (blkCnt < maxBlk) {
+ unsigned long long bytesToGet;
+
+ blksToGet = ((maxBlk - blkCnt) >= TELEMETRY_BLOCKS_TO_READ) ? TELEMETRY_BLOCKS_TO_READ : (maxBlk - blkCnt);
+
+ if (!blksToGet) {
+ dev_close(dev);
+ return err;
+ }
+
+ bytesToGet = (unsigned long long)blksToGet * 512;
+ log = malloc(bytesToGet);
+
+ if (!log) {
+ fprintf(stderr, "could not alloc buffer for log\n");
+ dev_close(dev);
+ return -EINVAL;
+ }
+
+ memset(log, 0, bytesToGet);
+
+ struct nvme_get_log_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .lid = cfg.log_id,
+ .nsid = cfg.namespace_id,
+ .lpo = offset,
+ .lsp = 0,
+ .lsi = 0,
+ .rae = true,
+ .uuidx = 0,
+ .csi = NVME_CSI_NVM,
+ .ot = false,
+ .len = bytesToGet,
+ .log = (void *)log,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ };
+ err = nvme_get_log(&args);
+ if (!err) {
+ offset += (__le64)bytesToGet;
+
+ if (!cfg.raw_binary) {
+ printf("\nBlock # :%d to %d\n", blkCnt + 1, blkCnt + blksToGet);
+
+ d((unsigned char *)log, bytesToGet, 16, 1);
+ } else
+ seaget_d_raw((unsigned char *)log, bytesToGet, dump_fd);
+ } else if (err > 0) {
+ nvme_show_status(err);
+ } else {
+ perror("log page");
+ }
+
+ blkCnt += blksToGet;
+
+ free(log);
+ }
+
+ dev_close(dev);
+ return err;
+}
+
+static int get_ctrl_tele(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc =
+ "Capture the Telemetry Controller-Initiated Data in either hex-dump (default) or binary format";
+ const char *namespace_id = "desired namespace";
+ const char *raw = "output in raw format";
+ struct nvme_dev *dev;
+ int err, dump_fd;
+ struct nvme_temetry_log_hdr tele_log;
+ __le64 offset = 0;
+ __u16 log_id;
+ int blkCnt, maxBlk = 0, blksToGet;
+ unsigned char *log;
+
+ struct config {
+ __u32 namespace_id;
+ bool raw_binary;
+ };
+
+ struct config cfg = {
+ .namespace_id = 0xffffffff,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ dump_fd = STDOUT_FILENO;
+
+ log_id = 0x08;
+ err = nvme_get_nsid_log(dev_fd(dev), false, log_id, cfg.namespace_id,
+ sizeof(tele_log), (void *)(&tele_log));
+ if (!err) {
+ maxBlk = tele_log.tele_data_area3;
+ offset += 512;
+
+ if (!cfg.raw_binary) {
+ printf("Device:%s namespace-id:%#x\n",
+ dev->name, cfg.namespace_id);
+ printf("Data Block 1 Last Block:%d Data Block 2 Last Block:%d Data Block 3 Last Block:%d\n",
+ tele_log.tele_data_area1, tele_log.tele_data_area2, tele_log.tele_data_area3);
+
+ d((unsigned char *)(&tele_log), sizeof(tele_log), 16, 1);
+ } else
+ seaget_d_raw((unsigned char *)(&tele_log), sizeof(tele_log), dump_fd);
+ } else if (err > 0) {
+ nvme_show_status(err);
+ } else {
+ perror("log page");
+ }
+
+ blkCnt = 0;
+
+ while (blkCnt < maxBlk) {
+ unsigned long long bytesToGet;
+
+ blksToGet = ((maxBlk - blkCnt) >= TELEMETRY_BLOCKS_TO_READ) ? TELEMETRY_BLOCKS_TO_READ : (maxBlk - blkCnt);
+
+ if (!blksToGet)
+ return err;
+
+ bytesToGet = (unsigned long long)blksToGet * 512;
+ log = malloc(bytesToGet);
+
+ if (!log) {
+ fprintf(stderr, "could not alloc buffer for log\n");
+ return -EINVAL;
+ }
+
+ memset(log, 0, bytesToGet);
+
+ struct nvme_get_log_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .lid = log_id,
+ .nsid = cfg.namespace_id,
+ .lpo = offset,
+ .lsp = 0,
+ .lsi = 0,
+ .rae = true,
+ .uuidx = 0,
+ .csi = NVME_CSI_NVM,
+ .ot = false,
+ .len = bytesToGet,
+ .log = (void *)log,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ };
+ err = nvme_get_log(&args);
+ if (!err) {
+ offset += (__le64)bytesToGet;
+
+ if (!cfg.raw_binary) {
+ printf("\nBlock # :%d to %d\n", blkCnt + 1, blkCnt + blksToGet);
+
+ d((unsigned char *)log, bytesToGet, 16, 1);
+ } else
+ seaget_d_raw((unsigned char *)log, bytesToGet, dump_fd);
+ } else if (err > 0) {
+ nvme_show_status(err);
+ } else {
+ perror("log page");
+ }
+
+ blkCnt += blksToGet;
+
+ free(log);
+ }
+
+ dev_close(dev);
+ return err;
+}
+
+void seaget_d_raw(unsigned char *buf, int len, int fd)
+{
+ if (write(fd, (void *)buf, len) <= 0)
+ printf("%s: Write Failed\n", __func__);
+}
+
+
+static int vs_internal_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Capture the Telemetry Controller-Initiated Data in binary format";
+ const char *namespace_id = "desired namespace";
+
+ const char *file = "dump file";
+ struct nvme_dev *dev;
+ int err, dump_fd;
+ int flags = O_WRONLY | O_CREAT;
+ int mode = 0664;
+ struct nvme_temetry_log_hdr tele_log;
+ __le64 offset = 0;
+ __u16 log_id;
+ int blkCnt, maxBlk = 0, blksToGet;
+ unsigned char *log;
+
+ struct config {
+ __u32 namespace_id;
+ char *file;
+ };
+
+ struct config cfg = {
+ .namespace_id = 0xffffffff,
+ .file = "",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_FILE("dump-file", 'f', &cfg.file, file),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ dump_fd = STDOUT_FILENO;
+ if (strlen(cfg.file)) {
+ dump_fd = open(cfg.file, flags, mode);
+ if (dump_fd < 0) {
+ perror(cfg.file);
+ dev_close(dev);
+ return -EINVAL;
+ }
+ }
+
+ log_id = 0x08;
+ err = nvme_get_nsid_log(dev_fd(dev), false, log_id, cfg.namespace_id,
+ sizeof(tele_log), (void *)(&tele_log));
+ if (!err) {
+ maxBlk = tele_log.tele_data_area3;
+ offset += 512;
+
+ seaget_d_raw((unsigned char *)(&tele_log), sizeof(tele_log), dump_fd);
+ } else if (err > 0) {
+ nvme_show_status(err);
+ } else {
+ perror("log page");
+ }
+
+ blkCnt = 0;
+
+ while (blkCnt < maxBlk) {
+ unsigned long long bytesToGet;
+
+ blksToGet = ((maxBlk - blkCnt) >= TELEMETRY_BLOCKS_TO_READ) ? TELEMETRY_BLOCKS_TO_READ : (maxBlk - blkCnt);
+
+ if (!blksToGet)
+ goto out;
+
+ bytesToGet = (unsigned long long)blksToGet * 512;
+ log = malloc(bytesToGet);
+
+ if (!log) {
+ fprintf(stderr, "could not alloc buffer for log\n");
+ err = EINVAL;
+ goto out;
+ }
+
+ memset(log, 0, bytesToGet);
+
+ struct nvme_get_log_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .lid = log_id,
+ .nsid = cfg.namespace_id,
+ .lpo = offset,
+ .lsp = 0,
+ .lsi = 0,
+ .rae = true,
+ .uuidx = 0,
+ .csi = NVME_CSI_NVM,
+ .ot = false,
+ .len = bytesToGet,
+ .log = (void *)log,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ };
+ err = nvme_get_log(&args);
+ if (!err) {
+ offset += (__le64)bytesToGet;
+
+ seaget_d_raw((unsigned char *)log, bytesToGet, dump_fd);
+
+ } else if (err > 0) {
+ nvme_show_status(err);
+ } else {
+ perror("log page");
+ }
+
+ blkCnt += blksToGet;
+
+ free(log);
+ }
+out:
+ if (strlen(cfg.file))
+ close(dump_fd);
+
+ dev_close(dev);
+ return err;
+}
+
+/*SEAGATE-PLUGIN Version */
+static int seagate_plugin_version(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ printf("Seagate-Plugin version : %d.%d\n",
+ SEAGATE_PLUGIN_VERSION_MAJOR,
+ SEAGATE_PLUGIN_VERSION_MINOR);
+ return 0;
+}
+/*EOF SEAGATE-PLUGIN Version */
+
+/*OCP SEAGATE-PLUGIN Version */
+static int stx_ocp_plugin_version(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ printf("Seagate-OCP-Plugin version : %d.%d\n",
+ SEAGATE_OCP_PLUGIN_VERSION_MAJOR,
+ SEAGATE_OCP_PLUGIN_VERSION_MINOR);
+ return 0;
+}
+/*EOF OCP SEAGATE-PLUGIN Version */
diff --git a/plugins/seagate/seagate-nvme.h b/plugins/seagate/seagate-nvme.h
new file mode 100644
index 0000000..99f6327
--- /dev/null
+++ b/plugins/seagate/seagate-nvme.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Do NOT modify or remove this copyright and license
+ *
+ * Copyright (c) 2017-2018 Seagate Technology LLC and/or its Affiliates, All Rights Reserved
+ *
+ * ******************************************************************************************
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * \file seagate-nvme.h
+ * \brief This file defines the functions and macros to make building a nvme-cli seagate plug-in.
+ *
+ * Author: Debabrata Bardhan <debabrata.bardhan@seagate.com>
+ */
+
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/seagate/seagate-nvme
+
+#if !defined(SEAGATE_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define SEAGATE_NVME
+
+#include "cmd.h"
+
+PLUGIN(NAME("seagate", "Seagate vendor specific extensions", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("vs-temperature-stats", "Retrieve Seagate temperature statistics ", temp_stats)
+ ENTRY("vs-log-page-sup", "Retrieve Seagate Supported Log-pages Information ", log_pages_supp)
+ ENTRY("vs-smart-add-log", "Retrieve Seagate extended-SMART Information ", vs_smart_log)
+ ENTRY("vs-pcie-stats", "Retrieve Seagate PCIe error statistics ", vs_pcie_error_log)
+ ENTRY("clear-pcie-correctable-errors", "Clear Seagate PCIe error statistics ", vs_clr_pcie_correctable_errs)
+ ENTRY("get-host-tele", "Retrieve Seagate Host-Initiated Telemetry ", get_host_tele)
+ ENTRY("get-ctrl-tele", "Retrieve Seagate Controller-Initiated Telemetry ", get_ctrl_tele)
+ ENTRY("vs-internal-log", "Retrieve Seagate Controller-Initiated Telemetry in binary format", vs_internal_log)
+ ENTRY("vs-fw-activate-history", "Retrieve the Firmware Activation History", stx_vs_fw_activate_history)
+ ENTRY("clear-fw-activate-history", "Clear Firmware Activation History", clear_fw_activate_history)
+ ENTRY("plugin-version", "Shows Seagate plugin's version information ", seagate_plugin_version)
+ ENTRY("cloud-SSD-plugin-version", "Shows OCP Seagate plugin's version information ", stx_ocp_plugin_version)
+ )
+);
+
+#endif
+#include "define_cmd.h"
diff --git a/plugins/sed/meson.build b/plugins/sed/meson.build
new file mode 100644
index 0000000..4a2e544
--- /dev/null
+++ b/plugins/sed/meson.build
@@ -0,0 +1,4 @@
+sources += [
+ 'plugins/sed/sed.c',
+ 'plugins/sed/sedopal_cmd.c',
+]
diff --git a/plugins/sed/sed.c b/plugins/sed/sed.c
new file mode 100644
index 0000000..0471e54
--- /dev/null
+++ b/plugins/sed/sed.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <linux/fs.h>
+#include <sys/stat.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "nvme-print.h"
+#include "sedopal_cmd.h"
+#include <linux/sed-opal.h>
+
+#define CREATE_CMD
+#include "sed.h"
+
+OPT_ARGS(no_opts) = {
+ OPT_END()
+};
+
+OPT_ARGS(key_opts) = {
+ OPT_FLAG("ask-key", 'k', &sedopal_ask_key,
+ "prompt for SED authentication key"),
+ OPT_END()
+};
+
+OPT_ARGS(revert_opts) = {
+ OPT_FLAG("destructive", 'e', &sedopal_destructive_revert,
+ "destructive revert"),
+ OPT_FLAG("psid", 'p', &sedopal_psid_revert, "PSID revert"),
+ OPT_END()
+};
+
+
+/*
+ * Open the NVMe device specified on the command line. It must be the
+ * NVMe block device (e.g. /dev/nvme0n1).
+ */
+static int sed_opal_open_device(struct nvme_dev **dev, int argc, char **argv,
+ const char *desc, struct argconfig_commandline_options *opts)
+{
+ int err;
+
+ err = parse_and_open(dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ if (!S_ISBLK((*dev)->direct.stat.st_mode)) {
+ fprintf(stderr,
+ "ERROR : The NVMe block device must be specified\n");
+ err = -EINVAL;
+ dev_close(*dev);
+ }
+
+ return err;
+}
+
+static int sed_opal_discover(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ int err;
+ const char *desc = "Query SED device and display locking features";
+ struct nvme_dev *dev;
+
+ err = sed_opal_open_device(&dev, argc, argv, desc, no_opts);
+ if (err)
+ return err;
+
+ err = sedopal_cmd_discover(dev->direct.fd);
+
+ dev_close(dev);
+ return err;
+}
+
+static int sed_opal_initialize(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ int err;
+ const char *desc = "Initialize a SED device for locking";
+ struct nvme_dev *dev;
+
+ err = sed_opal_open_device(&dev, argc, argv, desc, no_opts);
+ if (err)
+ return err;
+
+ err = sedopal_cmd_initialize(dev->direct.fd);
+ if (err != 0)
+ fprintf(stderr, "initialize: SED error - %s\n",
+ sedopal_error_to_text(err));
+
+ dev_close(dev);
+ return err;
+}
+
+static int sed_opal_revert(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ int err;
+ const char *desc = "Revert a SED device from locking state";
+ struct nvme_dev *dev;
+
+ err = sed_opal_open_device(&dev, argc, argv, desc, revert_opts);
+ if (err)
+ return err;
+
+ err = sedopal_cmd_revert(dev->direct.fd);
+ if (err != 0)
+ fprintf(stderr, "revert: SED error - %s\n",
+ sedopal_error_to_text(err));
+
+ dev_close(dev);
+ return err;
+}
+
+static int sed_opal_lock(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ int err;
+ const char *desc = "Lock a SED device";
+ struct nvme_dev *dev;
+
+ err = sed_opal_open_device(&dev, argc, argv, desc, key_opts);
+ if (err)
+ return err;
+
+ err = sedopal_cmd_lock(dev->direct.fd);
+ if (err != 0)
+ fprintf(stderr, "lock: SED error - %s\n",
+ sedopal_error_to_text(err));
+
+ dev_close(dev);
+ return err;
+}
+
+static int sed_opal_unlock(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ int err;
+ const char *desc = "Unlock a SED device";
+ struct nvme_dev *dev;
+
+ err = sed_opal_open_device(&dev, argc, argv, desc, key_opts);
+ if (err)
+ return err;
+
+ err = sedopal_cmd_unlock(dev->direct.fd);
+ if (err != 0)
+ fprintf(stderr, "unlock: SED error - %s\n",
+ sedopal_error_to_text(err));
+
+ dev_close(dev);
+ return err;
+}
+
+static int sed_opal_password(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ int err;
+ const char *desc = "Change the locking password of a SED device";
+ struct nvme_dev *dev;
+
+ err = sed_opal_open_device(&dev, argc, argv, desc, no_opts);
+ if (err)
+ return err;
+
+ err = sedopal_cmd_password(dev->direct.fd);
+ if (err != 0)
+ fprintf(stderr, "password: SED error - %s\n",
+ sedopal_error_to_text(err));
+
+ dev_close(dev);
+ return err;
+}
diff --git a/plugins/sed/sed.h b/plugins/sed/sed.h
new file mode 100644
index 0000000..1618272
--- /dev/null
+++ b/plugins/sed/sed.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/sed/sed
+
+#include "cmd.h"
+#include <linux/sed-opal.h>
+
+PLUGIN(NAME("sed", "SED Opal Command Set", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("discover", "Discover SED Opal Locking Features", sed_opal_discover, "1")
+ ENTRY("initialize", "Initialize a SED Opal Device for locking", sed_opal_initialize)
+ ENTRY("revert", "Revert a SED Opal Device from locking", sed_opal_revert)
+ ENTRY("lock", "Lock a SED Opal Device", sed_opal_lock)
+ ENTRY("unlock", "Unlock a SED Opal Device", sed_opal_unlock)
+ ENTRY("password", "Change the SED Opal Device password", sed_opal_password)
+ )
+);
+
+#include "define_cmd.h"
diff --git a/plugins/sed/sedopal_cmd.c b/plugins/sed/sedopal_cmd.c
new file mode 100644
index 0000000..649e0b2
--- /dev/null
+++ b/plugins/sed/sedopal_cmd.c
@@ -0,0 +1,512 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/mount.h>
+#include <linux/sed-opal.h>
+#include "sedopal_spec.h"
+#include "sedopal_cmd.h"
+
+/*
+ * ask user for key rather than obtaining it from kernel keyring
+ */
+bool sedopal_ask_key;
+
+/*
+ * initiate dialog to ask for and confirm new password
+ */
+bool sedopal_ask_new_key;
+
+/*
+ * perform a destructive drive revert
+ */
+bool sedopal_destructive_revert;
+
+/*
+ * perform a PSID drive revert
+ */
+bool sedopal_psid_revert;
+
+/*
+ * Map method status codes to error text
+ */
+static const char * const sedopal_errors[] = {
+ [SED_STATUS_SUCCESS] = "Success",
+ [SED_STATUS_NOT_AUTHORIZED] = "Host Not Authorized",
+ [SED_STATUS_OBSOLETE_1] = "Obsolete",
+ [SED_STATUS_SP_BUSY] = "SP Session Busy",
+ [SED_STATUS_SP_FAILED] = "SP Failed",
+ [SED_STATUS_SP_DISABLED] = "SP Disabled",
+ [SED_STATUS_SP_FROZEN] = "SP Frozen",
+ [SED_STATUS_NO_SESSIONS_AVAILABLE] = "No Sessions Available",
+ [SED_STATUS_UNIQUENESS_CONFLICT] = "Uniqueness Conflict",
+ [SED_STATUS_INSUFFICIENT_SPACE] = "Insufficient Space",
+ [SED_STATUS_INSUFFICIENT_ROWS] = "Insufficient Rows",
+ [SED_STATUS_OBSOLETE_2] = "Obsolete",
+ [SED_STATUS_INVALID_PARAMETER] = "Invalid Parameter",
+ [SED_STATUS_OBSOLETE_3] = "Obsolete",
+ [SED_STATUS_OBSOLETE_4] = "Obsolete",
+ [SED_STATUS_TPER_MALFUNCTION] = "TPER Malfunction",
+ [SED_STATUS_TRANSACTION_FAILURE] = "Transaction Failure",
+ [SED_STATUS_RESPONSE_OVERFLOW] = "Response Overflow",
+ [SED_STATUS_AUTHORITY_LOCKED_OUT] = "Authority Locked Out",
+};
+
+const char *sedopal_error_to_text(int code)
+{
+ if (code == SED_STATUS_FAIL)
+ return "Failed";
+
+ if (code == SED_STATUS_NO_METHOD_STATUS)
+ return "Method returned no status";
+
+ if (code < SED_STATUS_SUCCESS ||
+ code > SED_STATUS_AUTHORITY_LOCKED_OUT)
+ return("Unknown Error");
+
+ return sedopal_errors[code];
+}
+
+/*
+ * Read a user entered password and do some basic validity checks.
+ */
+char *sedopal_get_password(char *prompt)
+{
+ char *pass;
+ int len;
+
+ pass = getpass(prompt);
+ if (pass == NULL)
+ return NULL;
+
+ len = strlen(pass);
+ if (len < SEDOPAL_MIN_PASSWORD_LEN)
+ return NULL;
+
+ if (len > SEDOPAL_MAX_PASSWORD_LEN)
+ return NULL;
+
+ return pass;
+}
+
+/*
+ * Initialize a SED Opal key. The key can either specify that the actual
+ * key should be looked up in the kernel keyring, or it should be
+ * populated in the key by prompting the user.
+ */
+int sedopal_set_key(struct opal_key *key)
+{
+#if !HAVE_KEY_TYPE
+ /*
+ * If key_type isn't avaialable, force key prompt
+ */
+ sedopal_ask_key = true;
+#endif
+
+ if (sedopal_ask_key) {
+ char *pass;
+ char *prompt;
+
+ /*
+ * set proper prompt
+ */
+ if (sedopal_ask_new_key)
+ prompt = SEDOPAL_NEW_PW_PROMPT;
+ else {
+ if (sedopal_psid_revert)
+ prompt = SEDOPAL_PSID_PROMPT;
+ else
+ prompt = SEDOPAL_CURRENT_PW_PROMPT;
+ }
+
+ pass = sedopal_get_password(prompt);
+ if (pass == NULL)
+ return -EINVAL;
+
+#if HAVE_KEY_TYPE
+ key->key_type = OPAL_INCLUDED;
+#endif
+ key->key_len = strlen(pass);
+ memcpy(key->key, pass, key->key_len);
+
+ /*
+ * If getting a new key, ask for it to be re-entered
+ * and verify the two entries are the same.
+ */
+ if (sedopal_ask_new_key) {
+ pass = sedopal_get_password(SEDOPAL_REENTER_PW_PROMPT);
+ if (strncmp((char *)key->key, pass, key->key_len)) {
+ fprintf(stderr,
+ "Error: passwords don't match\n");
+ return -EINVAL;
+ }
+ }
+ } else {
+#if HAVE_KEY_TYPE
+ key->key_type = OPAL_KEYRING;
+#endif
+ key->key_len = 0;
+ }
+
+ key->lr = 0;
+
+ return 0;
+}
+
+/*
+ * Prepare a drive for SED Opal locking.
+ */
+int sedopal_cmd_initialize(int fd)
+{
+ int rc;
+ struct opal_key key;
+ struct opal_lr_act lr_act = {};
+ struct opal_user_lr_setup lr_setup = {};
+
+ sedopal_ask_key = true;
+ rc = sedopal_set_key(&key);
+ if (rc != 0)
+ return rc;
+
+ /*
+ * take ownership of the device
+ */
+ rc = ioctl(fd, IOC_OPAL_TAKE_OWNERSHIP, &key);
+ if (rc != 0) {
+ fprintf(stderr,
+ "Error: failed to take device ownership - %d\n", rc);
+ return rc;
+ }
+
+ /*
+ * activate lsp
+ */
+ lr_act.num_lrs = 1;
+ lr_act.sum = false;
+ lr_act.key = key;
+
+ rc = ioctl(fd, IOC_OPAL_ACTIVATE_LSP, &lr_act);
+ if (rc != 0) {
+ fprintf(stderr, "Error: failed to activate LSP - %d\n", rc);
+ return rc;
+ }
+
+ /*
+ * setup global locking range
+ */
+ lr_setup.range_start = 0;
+ lr_setup.range_length = 0;
+ lr_setup.RLE = true;
+ lr_setup.WLE = true;
+
+ lr_setup.session.opal_key = key;
+ lr_setup.session.sum = 0;
+ lr_setup.session.who = OPAL_ADMIN1;
+
+ rc = ioctl(fd, IOC_OPAL_LR_SETUP, &lr_setup);
+ if (rc != 0) {
+ fprintf(stderr,
+ "Error: failed to setup locking range - %d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+/*
+ * Lock a SED Opal drive
+ */
+int sedopal_cmd_lock(int fd)
+{
+
+ return sedopal_lock_unlock(fd, OPAL_LK);
+}
+
+/*
+ * Unlock a SED Opal drive
+ */
+int sedopal_cmd_unlock(int fd)
+{
+
+ return sedopal_lock_unlock(fd, OPAL_RW);
+}
+
+/*
+ * Prepare and issue an ioctl to lock/unlock a drive
+ */
+int sedopal_lock_unlock(int fd, int lock_state)
+{
+ int rc;
+ struct opal_lock_unlock opal_lu = {};
+
+ rc = sedopal_set_key(&opal_lu.session.opal_key);
+ if (rc != 0)
+ return rc;
+
+ opal_lu.session.sum = 0;
+ opal_lu.session.who = OPAL_ADMIN1;
+ opal_lu.l_state = lock_state;
+
+ rc = ioctl(fd, IOC_OPAL_LOCK_UNLOCK, &opal_lu);
+ if (rc != 0)
+ fprintf(stderr,
+ "Error: failed locking or unlocking - %d\n", rc);
+
+ /*
+ * If the unlock was successful, force a re-read of the
+ * partition table.
+ */
+ if (rc == 0) {
+ rc = ioctl(fd, BLKRRPART, 0);
+ if (rc != 0)
+ fprintf(stderr,
+ "Error: failed re-reading partition\n");
+ }
+
+ return rc;
+}
+
+/*
+ * Confirm a destructive drive so that data is inadvertently erased
+ */
+static bool sedopal_confirm_revert(void)
+{
+ int rc;
+ char ans;
+ bool confirmed = false;
+
+ /*
+ * verify that destructive revert is really the intention
+ */
+ fprintf(stdout,
+ "Destructive revert erases drive data. Continue (y/n)? ");
+ rc = fscanf(stdin, " %c", &ans);
+ if ((rc == 1) && (ans == 'y' || ans == 'Y')) {
+ fprintf(stdout, "Are you sure (y/n)? ");
+ rc = fscanf(stdin, " %c", &ans);
+ if ((rc == 1) && (ans == 'y' || ans == 'Y'))
+ confirmed = true;
+ }
+
+ return confirmed;
+}
+
+/*
+ * perform a destructive drive revert
+ */
+static int sedopal_revert_destructive(int fd)
+{
+ struct opal_key key;
+ int rc;
+
+ if (!sedopal_confirm_revert()) {
+ fprintf(stderr, "Aborting destructive revert\n");
+ return -1;
+ }
+
+ /*
+ * for destructive revert, require that key is provided
+ */
+ sedopal_ask_key = true;
+
+ rc = sedopal_set_key(&key);
+ if (rc == 0)
+ rc = ioctl(fd, IOC_OPAL_REVERT_TPR, &key);
+
+ return rc;
+}
+
+/*
+ * perform a PSID drive revert
+ */
+static int sedopal_revert_psid(int fd)
+{
+#ifdef IOC_OPAL_PSID_REVERT_TPR
+ struct opal_key key;
+ int rc;
+
+ if (!sedopal_confirm_revert()) {
+ fprintf(stderr, "Aborting PSID revert\n");
+ return -1;
+ }
+
+ rc = sedopal_set_key(&key);
+ if (rc == 0) {
+ rc = ioctl(fd, IOC_OPAL_PSID_REVERT_TPR, &key);
+ if (rc != 0)
+ fprintf(stderr, "PSID_REVERT_TPR rc %d\n", rc);
+ }
+
+ return rc;
+#else
+ fprintf(stderr, "ERROR : PSID revert is not supported\n");
+ return -EOPNOTSUPP;
+#endif /* IOC_OPAL_PSID_REVERT_TPR */
+}
+
+/*
+ * revert a drive from the provisioned state to a state where locking
+ * is disabled.
+ */
+int sedopal_cmd_revert(int fd)
+{
+ int rc;
+
+ /*
+ * for revert, require that key/PSID is provided
+ */
+ sedopal_ask_key = true;
+
+ if (sedopal_psid_revert) {
+ rc = sedopal_revert_psid(fd);
+ } else if (sedopal_destructive_revert) {
+ rc = sedopal_revert_destructive(fd);
+ } else {
+#ifdef IOC_OPAL_REVERT_LSP
+ struct opal_revert_lsp revert_lsp;
+
+ rc = sedopal_set_key(&revert_lsp.key);
+ if (rc != 0)
+ return rc;
+
+ revert_lsp.options = OPAL_PRESERVE;
+ revert_lsp.__pad = 0;
+
+ rc = ioctl(fd, IOC_OPAL_REVERT_LSP, &revert_lsp);
+#else
+ rc = -EOPNOTSUPP;
+#endif
+ }
+
+ if (rc != 0)
+ fprintf(stderr, "Error: failed reverting drive - %d\n", rc);
+
+ return rc;
+}
+
+/*
+ * Change the password of a drive. The existing password must be
+ * provided and the new password is confirmed by re-entry.
+ */
+int sedopal_cmd_password(int fd)
+{
+ int rc;
+ struct opal_new_pw new_pw = {};
+
+ new_pw.new_user_pw.who = OPAL_ADMIN1;
+ new_pw.new_user_pw.opal_key.lr = 0;
+ new_pw.session.who = OPAL_ADMIN1;
+ new_pw.session.sum = 0;
+ new_pw.session.opal_key.lr = 0;
+
+ /*
+ * get current key
+ */
+ sedopal_ask_key = true;
+ if (sedopal_set_key(&new_pw.session.opal_key) != 0)
+ return -EINVAL;
+
+ /*
+ * get new key
+ */
+ sedopal_ask_new_key = true;
+ if (sedopal_set_key(&new_pw.new_user_pw.opal_key) != 0)
+ return -EINVAL;
+
+ rc = ioctl(fd, IOC_OPAL_SET_PW, &new_pw);
+ if (rc != 0)
+ fprintf(stderr, "Error: failed setting password - %d\n", rc);
+
+ return rc;
+}
+
+/*
+ * Print the state of locking features.
+ */
+void sedopal_print_locking_features(uint8_t features)
+{
+ printf("Locking Features:\n");
+ printf("\tLocking Supported: %s\n",
+ (features & OPAL_FEATURE_LOCKING_SUPPORTED) ? "Yes" : "No");
+ printf("\tLocking Feature Enabled: %s\n",
+ (features & OPAL_FEATURE_LOCKING_ENABLED) ? "Yes" : "No");
+ printf("\tLocked: %s\n",
+ (features & OPAL_FEATURE_LOCKED) ? "Yes" : "No");
+}
+
+/*
+ * Query a drive to determine if it's SED Opal capable and
+ * it's current locking status.
+ */
+int sedopal_cmd_discover(int fd)
+{
+#ifdef IOC_OPAL_DISCOVERY
+ int rc;
+ bool sedopal_locking_supported = false;
+ struct opal_discovery discover;
+ struct level_0_discovery_header *dh;
+ struct level_0_discovery_features *feat;
+ struct level_0_discovery_features *feat_end;
+ uint16_t code;
+ uint8_t locking_flags;
+ char buf[4096];
+
+ discover.data = (__u64)buf;
+ discover.size = sizeof(buf);
+
+ rc = ioctl(fd, IOC_OPAL_DISCOVERY, &discover);
+ if (rc < 0) {
+ fprintf(stderr, "Error: ioctl IOC_OPAL_DISCOVERY failed\n");
+ return rc;
+ }
+
+ /*
+ * The returned buffer contains a level 0 discovery header
+ * folowed by an array of level 0 feature records.
+ *
+ * TCG Opal Specification v2.0.2 section 3.1.1
+ */
+ dh = (struct level_0_discovery_header *)buf;
+ feat = (struct level_0_discovery_features *)(dh + 1);
+ feat_end = (struct level_0_discovery_features *)
+ (buf + be32toh(dh->parameter_length));
+
+ /*
+ * iterate through all the features that were returned
+ */
+ while (feat < feat_end) {
+ code = be16toh(feat->code);
+ switch (code) {
+ case OPAL_FEATURE_CODE_LOCKING:
+ locking_flags = feat->feature;
+ break;
+ case OPAL_FEATURE_CODE_OPALV2:
+ sedopal_locking_supported = true;
+ break;
+ default:
+ break;
+ }
+
+ feat++;
+ }
+
+ rc = 0;
+ if (!sedopal_locking_supported) {
+ fprintf(stderr, "Error: device does not support SED Opal\n");
+ rc = -1;
+ } else
+ sedopal_print_locking_features(locking_flags);
+
+ return rc;
+#else /* IOC_OPAL_DISCOVERY */
+ fprintf(stderr, "ERROR : NVMe device discovery is not supported\n");
+ return -EOPNOTSUPP;
+#endif
+}
diff --git a/plugins/sed/sedopal_cmd.h b/plugins/sed/sedopal_cmd.h
new file mode 100644
index 0000000..3b6eae2
--- /dev/null
+++ b/plugins/sed/sedopal_cmd.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef _SED_OPAL_CMD_H
+#define _SED_OPAL_CMD_H
+
+#define SEDOPAL_CURRENT_PW_PROMPT "Password: "
+#define SEDOPAL_NEW_PW_PROMPT "New Password: "
+#define SEDOPAL_REENTER_PW_PROMPT "Re-enter New Password: "
+#define SEDOPAL_PSID_PROMPT "PSID: "
+
+#define SEDOPAL_MIN_PASSWORD_LEN 8
+#define SEDOPAL_MAX_PASSWORD_LEN 32
+
+#define NVME_DEV_PATH "/dev/nvme"
+
+extern bool sedopal_ask_key;
+extern bool sedopal_ask_new_key;
+extern bool sedopal_destructive_revert;
+extern bool sedopal_psid_revert;
+
+/*
+ * Sub-commands supported by the sedopal command
+ */
+enum sedopal_cmds {
+ SEDOPAL_CMD_NOT_SPECIFIED = -1,
+ SEDOPAL_CMD_INITIALIZE = 0,
+ SEDOPAL_CMD_LOCK = 1,
+ SEDOPAL_CMD_UNLOCK = 2,
+ SEDOPAL_CMD_REVERT = 3,
+ SEDOPAL_CMD_PASSWORD = 4,
+ SEDOPAL_CMD_DISCOVER = 5,
+};
+
+struct cmd_table {
+ int (*cmd_handler)(int fd);
+};
+
+/*
+ * command handlers
+ */
+int sedopal_cmd_initialize(int fd);
+int sedopal_cmd_lock(int fd);
+int sedopal_cmd_unlock(int fd);
+int sedopal_cmd_revert(int fd);
+int sedopal_cmd_password(int fd);
+int sedopal_cmd_discover(int fd);
+
+/*
+ * utility functions
+ */
+int sedopal_open_nvme_device(char *device);
+int sedopal_lock_unlock(int fd, int lock_state);
+const char *sedopal_error_to_text(int code);
+
+#endif /* _SED_OPAL_CMD_H */
diff --git a/plugins/sed/sedopal_spec.h b/plugins/sed/sedopal_spec.h
new file mode 100644
index 0000000..7523060
--- /dev/null
+++ b/plugins/sed/sedopal_spec.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef _SED_OPAL_SPEC_H
+#define _SED_OPAL_SPEC_H
+
+/*
+ * TCP Storage Architecture Core Specification Version 2.01
+ * section 5.1.5 Method Status Codes
+ */
+enum sed_status_codes {
+ SED_STATUS_SUCCESS = 0x00,
+ SED_STATUS_NOT_AUTHORIZED = 0x01,
+ SED_STATUS_OBSOLETE_1 = 0x02,
+ SED_STATUS_SP_BUSY = 0x03,
+ SED_STATUS_SP_FAILED = 0x04,
+ SED_STATUS_SP_DISABLED = 0x05,
+ SED_STATUS_SP_FROZEN = 0x06,
+ SED_STATUS_NO_SESSIONS_AVAILABLE = 0x07,
+ SED_STATUS_UNIQUENESS_CONFLICT = 0x08,
+ SED_STATUS_INSUFFICIENT_SPACE = 0x09,
+ SED_STATUS_INSUFFICIENT_ROWS = 0x0A,
+ SED_STATUS_OBSOLETE_2 = 0x0B,
+ SED_STATUS_INVALID_PARAMETER = 0x0C,
+ SED_STATUS_OBSOLETE_3 = 0x0D,
+ SED_STATUS_OBSOLETE_4 = 0x0E,
+ SED_STATUS_TPER_MALFUNCTION = 0x0F,
+ SED_STATUS_TRANSACTION_FAILURE = 0x10,
+ SED_STATUS_RESPONSE_OVERFLOW = 0x11,
+ SED_STATUS_AUTHORITY_LOCKED_OUT = 0x12,
+ SED_STATUS_FAIL = 0x3F,
+ SED_STATUS_NO_METHOD_STATUS = 0x89,
+};
+
+/*
+ * Definitions from TCG Opal Specification v2.0.2
+ */
+
+/*
+ * level 0 feature codes - section 3.1.1
+ */
+#define OPAL_FEATURE_CODE_LOCKING 0x0002
+#define OPAL_FEATURE_CODE_OPALV2 0x0203
+
+/* locking features */
+#define OPAL_FEATURE_LOCKING_SUPPORTED 0x01
+#define OPAL_FEATURE_LOCKING_ENABLED 0x02
+#define OPAL_FEATURE_LOCKED 0x04
+
+
+/*
+ * discovery header as specified in section 3.1.1.1
+ */
+struct level_0_discovery_header {
+ uint32_t parameter_length;
+ uint32_t revision;
+ uint64_t reserved;
+ uint8_t vendor_specific[32];
+};
+
+/*
+ * level 0 features as specified in section 3.1.1.3
+ */
+struct level_0_discovery_features {
+ uint16_t code;
+ uint8_t version;
+ uint8_t length;
+ uint8_t feature;
+ uint8_t reserved[11];
+};
+
+#endif /* _SED_OPAL_SPEC_H */
diff --git a/plugins/shannon/shannon-nvme.c b/plugins/shannon/shannon-nvme.c
new file mode 100644
index 0000000..d4db8c7
--- /dev/null
+++ b/plugins/shannon/shannon-nvme.c
@@ -0,0 +1,381 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "nvme-print.h"
+
+#define CREATE_CMD
+#include "shannon-nvme.h"
+
+enum {
+ PROGRAM_FAIL_CNT,
+ ERASE_FAIL_CNT,
+ WEARLEVELING_COUNT,
+ E2E_ERR_CNT,
+ CRC_ERR_CNT,
+ TIME_WORKLOAD_MEDIA_WEAR,
+ TIME_WORKLOAD_HOST_READS,
+ TIME_WORKLOAD_TIMER,
+ THERMAL_THROTTLE,
+ RETRY_BUFFER_OVERFLOW,
+ PLL_LOCK_LOSS,
+ NAND_WRITE,
+ HOST_WRITE,
+ SRAM_ERROR_CNT,
+ ADD_SMART_ITEMS,
+};
+
+#pragma pack(push, 1)
+struct nvme_shannon_smart_log_item {
+ __u8 rsv1[3];
+ __u8 norm;
+ __u8 rsv2;
+ union {
+ __u8 item_val[6];
+ struct wear_level {
+ __le16 min;
+ __le16 max;
+ __le16 avg;
+ } wear_level;
+ struct thermal_throttle {
+ __u8 st;
+ __u32 count;
+ } thermal_throttle;
+ };
+ __u8 _resv;
+};
+#pragma pack(pop)
+
+struct nvme_shannon_smart_log {
+ struct nvme_shannon_smart_log_item items[ADD_SMART_ITEMS];
+ __u8 vend_spec_resv;
+};
+
+static void show_shannon_smart_log(struct nvme_shannon_smart_log *smart, unsigned int nsid,
+ const char *devname)
+{
+ printf("Additional Smart Log for NVME device:%s namespace-id:%x\n",
+ devname, nsid);
+ printf("key normalized value\n");
+ printf("program_fail_count : %3d%% %"PRIu64"\n",
+ smart->items[PROGRAM_FAIL_CNT].norm,
+ int48_to_long(smart->items[PROGRAM_FAIL_CNT].item_val));
+ printf("erase_fail_count : %3d%% %"PRIu64"\n",
+ smart->items[ERASE_FAIL_CNT].norm,
+ int48_to_long(smart->items[ERASE_FAIL_CNT].item_val));
+ printf("wear_leveling : %3d%% min: %u, max: %u, avg: %u\n",
+ smart->items[WEARLEVELING_COUNT].norm,
+ le16_to_cpu(smart->items[WEARLEVELING_COUNT].wear_level.min),
+ le16_to_cpu(smart->items[WEARLEVELING_COUNT].wear_level.max),
+ le16_to_cpu(smart->items[WEARLEVELING_COUNT].wear_level.avg));
+ printf("end_to_end_error_detection_count: %3d%% %"PRIu64"\n",
+ smart->items[E2E_ERR_CNT].norm,
+ int48_to_long(smart->items[E2E_ERR_CNT].item_val));
+ printf("crc_error_count : %3d%% %"PRIu64"\n",
+ smart->items[CRC_ERR_CNT].norm,
+ int48_to_long(smart->items[CRC_ERR_CNT].item_val));
+ printf("timed_workload_media_wear : %3d%% %.3f%%\n",
+ smart->items[TIME_WORKLOAD_MEDIA_WEAR].norm,
+ ((float)int48_to_long(smart->items[TIME_WORKLOAD_MEDIA_WEAR].item_val)) / 1024);
+ printf("timed_workload_host_reads : %3d%% %"PRIu64"%%\n",
+ smart->items[TIME_WORKLOAD_HOST_READS].norm,
+ int48_to_long(smart->items[TIME_WORKLOAD_HOST_READS].item_val));
+ printf("timed_workload_timer : %3d%% %"PRIu64" min\n",
+ smart->items[TIME_WORKLOAD_TIMER].norm,
+ int48_to_long(smart->items[TIME_WORKLOAD_TIMER].item_val));
+ printf("thermal_throttle_status : %3d%% CurTTSta: %u%%, TTCnt: %u\n",
+ smart->items[THERMAL_THROTTLE].norm,
+ smart->items[THERMAL_THROTTLE].thermal_throttle.st,
+ smart->items[THERMAL_THROTTLE].thermal_throttle.count);
+ printf("retry_buffer_overflow_count : %3d%% %"PRIu64"\n",
+ smart->items[RETRY_BUFFER_OVERFLOW].norm,
+ int48_to_long(smart->items[RETRY_BUFFER_OVERFLOW].item_val));
+ printf("pll_lock_loss_count : %3d%% %"PRIu64"\n",
+ smart->items[PLL_LOCK_LOSS].norm,
+ int48_to_long(smart->items[PLL_LOCK_LOSS].item_val));
+ printf("nand_bytes_written : %3d%% sectors: %"PRIu64"\n",
+ smart->items[NAND_WRITE].norm,
+ int48_to_long(smart->items[NAND_WRITE].item_val));
+ printf("host_bytes_written : %3d%% sectors: %"PRIu64"\n",
+ smart->items[HOST_WRITE].norm,
+ int48_to_long(smart->items[HOST_WRITE].item_val));
+ printf("sram_error_count : %3d%% %"PRIu64"\n",
+ smart->items[RETRY_BUFFER_OVERFLOW].norm,
+ int48_to_long(smart->items[SRAM_ERROR_CNT].item_val));
+}
+
+static int get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ struct nvme_shannon_smart_log smart_log;
+ char *desc =
+ "Get Shannon vendor specific additional smart log (optionally, for the specified namespace), and show it.";
+ const char *namespace = "(optional) desired namespace";
+ const char *raw = "dump output in binary format";
+ struct nvme_dev *dev;
+ struct config {
+ __u32 namespace_id;
+ bool raw_binary;
+ };
+ int err;
+
+ struct config cfg = {
+ .namespace_id = NVME_NSID_ALL,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace),
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+ err = nvme_get_nsid_log(dev_fd(dev), false, 0xca, cfg.namespace_id,
+ sizeof(smart_log), &smart_log);
+ if (!err) {
+ if (!cfg.raw_binary)
+ show_shannon_smart_log(&smart_log, cfg.namespace_id, dev->name);
+ else
+ d_raw((unsigned char *)&smart_log, sizeof(smart_log));
+ } else if (err > 0) {
+ nvme_show_status(err);
+ }
+ dev_close(dev);
+ return err;
+}
+
+static int get_additional_feature(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Read operating parameters of the\n"
+ "specified controller. Operating parameters are grouped\n"
+ "and identified by Feature Identifiers; each Feature\n"
+ "Identifier contains one or more attributes that may affect\n"
+ "behavior of the feature. Each Feature has three possible\n"
+ "settings: default, saveable, and current. If a Feature is\n"
+ "saveable, it may be modified by set-feature. Default values\n"
+ "are vendor-specific and not changeable. Use set-feature to\n"
+ "change saveable Features.\n\n"
+ "Available additional feature id:\n"
+ "0x02: Shannon power management\n";
+ const char *raw = "show infos in binary format";
+ const char *namespace_id = "identifier of desired namespace";
+ const char *feature_id = "hexadecimal feature name";
+ const char *sel = "[0-3]: curr./default/saved/supp.";
+ const char *data_len = "buffer len (if) data is returned";
+ const char *cdw11 = "dword 11 for interrupt vector config";
+ const char *human_readable = "show infos in readable format";
+ struct nvme_dev *dev;
+ void *buf = NULL;
+ __u32 result;
+ int err;
+
+ struct config {
+ __u32 namespace_id;
+ enum nvme_features_id feature_id;
+ __u8 sel;
+ __u32 cdw11;
+ __u32 data_len;
+ bool raw_binary;
+ bool human_readable;
+ };
+
+ struct config cfg = {
+ .namespace_id = 1,
+ .feature_id = 0,
+ .sel = 0,
+ .cdw11 = 0,
+ .data_len = 0,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_UINT("feature-id", 'f', &cfg.feature_id, feature_id),
+ OPT_BYTE("sel", 's', &cfg.sel, sel),
+ OPT_UINT("data-len", 'l', &cfg.data_len, data_len),
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_UINT("cdw11", 'c', &cfg.cdw11, cdw11),
+ OPT_FLAG("human-readable", 'H', &cfg.human_readable, human_readable),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ if (cfg.sel > 7) {
+ fprintf(stderr, "invalid 'select' param:%d\n", cfg.sel);
+ dev_close(dev);
+ return -EINVAL;
+ }
+ if (!cfg.feature_id) {
+ fprintf(stderr, "feature-id required param\n");
+ dev_close(dev);
+ return -EINVAL;
+ }
+ if (cfg.data_len) {
+ if (posix_memalign(&buf, getpagesize(), cfg.data_len)) {
+ dev_close(dev);
+ exit(ENOMEM);
+ }
+ memset(buf, 0, cfg.data_len);
+ }
+
+ struct nvme_get_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = cfg.feature_id,
+ .nsid = cfg.namespace_id,
+ .sel = cfg.sel,
+ .cdw11 = cfg.cdw11,
+ .uuidx = 0,
+ .data_len = cfg.data_len,
+ .data = buf,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+ err = nvme_get_features(&args);
+ if (err > 0)
+ nvme_show_status(err);
+ if (buf)
+ free(buf);
+ return err;
+}
+
+static int set_additional_feature(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Modify the saveable or changeable\n"
+ "current operating parameters of the controller. Operating\n"
+ "parameters are grouped and identified by Feature\n"
+ "Identifiers. Feature settings can be applied to the entire\n"
+ "controller and all associated namespaces, or to only a few\n"
+ "namespace(s) associated with the controller. Default values\n"
+ "for each Feature are vendor-specific and may not be modified.\n"
+ "Use get-feature to determine which Features are supported by\n"
+ "the controller and are saveable/changeable.\n\n"
+ "Available additional feature id:\n"
+ "0x02: Shannon power management\n";
+ const char *namespace_id = "desired namespace";
+ const char *feature_id = "hex feature name (required)";
+ const char *data_len = "buffer length if data required";
+ const char *data = "optional file for feature data (default stdin)";
+ const char *value = "new value of feature (required)";
+ const char *save = "specifies that the controller shall save the attribute";
+ int ffd = STDIN_FILENO;
+ struct nvme_dev *dev;
+ void *buf = NULL;
+ __u32 result;
+ int err;
+
+ struct config {
+ char *file;
+ __u32 namespace_id;
+ __u32 feature_id;
+ __u32 value;
+ __u32 data_len;
+ bool save;
+ };
+
+ struct config cfg = {
+ .file = "",
+ .namespace_id = 0,
+ .feature_id = 0,
+ .value = 0,
+ .data_len = 0,
+ .save = 0,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_UINT("feature-id", 'f', &cfg.feature_id, feature_id),
+ OPT_UINT("value", 'v', &cfg.value, value),
+ OPT_UINT("data-len", 'l', &cfg.data_len, data_len),
+ OPT_FILE("data", 'd', &cfg.file, data),
+ OPT_FLAG("save", 's', &cfg.save, save),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ if (!cfg.feature_id) {
+ fprintf(stderr, "feature-id required param\n");
+ dev_close(dev);
+ return -EINVAL;
+ }
+
+ if (cfg.data_len) {
+ if (posix_memalign(&buf, getpagesize(), cfg.data_len)) {
+ fprintf(stderr, "can not allocate feature payload\n");
+ dev_close(dev);
+ return -ENOMEM;
+ }
+ memset(buf, 0, cfg.data_len);
+ }
+
+ if (buf) {
+ if (strlen(cfg.file)) {
+ ffd = open(cfg.file, O_RDONLY);
+ if (ffd <= 0) {
+ fprintf(stderr, "no firmware file provided\n");
+ err = EINVAL;
+ goto free;
+ }
+ }
+ err = read(ffd, (void *)buf, cfg.data_len);
+ if (err < 0) {
+ fprintf(stderr, "failed to read data buffer from input file\n");
+ err = EINVAL;
+ goto free;
+ }
+ }
+
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = cfg.feature_id,
+ .nsid = cfg.namespace_id,
+ .cdw11 = cfg.value,
+ .cdw12 = 0,
+ .save = cfg.save,
+ .uuidx = 0,
+ .cdw15 = 0,
+ .data_len = cfg.data_len,
+ .data = buf,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+ err = nvme_set_features(&args);
+ if (err < 0) {
+ perror("set-feature");
+ goto free;
+ }
+ if (!err) {
+ if (buf)
+ d(buf, cfg.data_len, 16, 1);
+ } else if (err > 0)
+ nvme_show_status(err);
+
+free:
+ if (buf)
+ free(buf);
+ return err;
+}
+
+static int shannon_id_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return __id_ctrl(argc, argv, cmd, plugin, NULL);
+}
+
+
+
diff --git a/plugins/shannon/shannon-nvme.h b/plugins/shannon/shannon-nvme.h
new file mode 100644
index 0000000..255bb6b
--- /dev/null
+++ b/plugins/shannon/shannon-nvme.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/shannon/shannon-nvme
+
+#if !defined(SHANNON_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define SHANNON_NVME
+
+#include "cmd.h"
+
+PLUGIN(NAME("shannon", "Shannon vendor specific extensions", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("smart-log-add", "Retrieve Shannon SMART Log, show it", get_additional_smart_log)
+ ENTRY("set-additioal-feature", "Set additional Shannon feature", set_additional_feature)
+ ENTRY("get-additional-feature", "Get additional Shannon feature", get_additional_feature)
+ ENTRY("id-ctrl", "Retrieve Shannon ctrl id, show it", shannon_id_ctrl)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/solidigm/meson.build b/plugins/solidigm/meson.build
new file mode 100644
index 0000000..052afa1
--- /dev/null
+++ b/plugins/solidigm/meson.build
@@ -0,0 +1,16 @@
+sources += [
+ 'plugins/solidigm/solidigm-id-ctrl.c',
+ 'plugins/solidigm/solidigm-util.c',
+ 'plugins/solidigm/solidigm-smart.c',
+ 'plugins/solidigm/solidigm-garbage-collection.c',
+ 'plugins/solidigm/solidigm-latency-tracking.c',
+ 'plugins/solidigm/solidigm-log-page-dir.c',
+ 'plugins/solidigm/solidigm-telemetry.c',
+ 'plugins/solidigm/solidigm-internal-logs.c',
+ 'plugins/solidigm/solidigm-market-log.c',
+ 'plugins/solidigm/solidigm-temp-stats.c',
+ 'plugins/solidigm/solidigm-get-drive-info.c',
+ 'plugins/solidigm/solidigm-ocp-version.c',
+]
+subdir('solidigm-telemetry')
+
diff --git a/plugins/solidigm/solidigm-garbage-collection.c b/plugins/solidigm/solidigm-garbage-collection.c
new file mode 100644
index 0000000..a37e9c5
--- /dev/null
+++ b/plugins/solidigm/solidigm-garbage-collection.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "nvme-print.h"
+#include "solidigm-garbage-collection.h"
+#include "solidigm-util.h"
+
+struct __packed gc_item {
+ __le32 timer_type;
+ __le64 timestamp;
+};
+
+#define VU_GC_MAX_ITEMS 100
+struct garbage_control_collection_log {
+ __le16 version_major;
+ __le16 version_minor;
+ struct __packed gc_item item[VU_GC_MAX_ITEMS];
+ __u8 reserved[2892];
+};
+
+static void vu_gc_log_show_json(struct garbage_control_collection_log *payload, const char *devname)
+{
+ struct json_object *gc_entries = json_create_array();
+
+ for (int i = 0; i < VU_GC_MAX_ITEMS; i++) {
+ struct __packed gc_item item = payload->item[i];
+ struct json_object *entry = json_create_object();
+
+ json_object_add_value_int(entry, "timestamp", le64_to_cpu(item.timestamp));
+ json_object_add_value_int(entry, "timer_type", le32_to_cpu(item.timer_type));
+ json_array_add_value_object(gc_entries, entry);
+ }
+
+ json_print_object(gc_entries, NULL);
+ json_free_object(gc_entries);
+}
+
+static void vu_gc_log_show(struct garbage_control_collection_log *payload, const char *devname,
+ __u8 uuid_index)
+{
+ printf("Solidigm Garbage Collection Log for NVME device:%s UUID-idx:%d\n", devname,
+ uuid_index);
+ printf("Timestamp Timer Type\n");
+
+ for (int i = 0; i < VU_GC_MAX_ITEMS; i++) {
+ struct __packed gc_item item = payload->item[i];
+
+ printf("%-13" PRIu64 " %d\n", le64_to_cpu(item.timestamp), le32_to_cpu(item.timer_type));
+ }
+}
+
+int solidigm_get_garbage_collection_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Get and parse Solidigm vendor specific garbage collection event log.";
+ enum nvme_print_flags flags;
+ struct nvme_dev *dev;
+ int err;
+ __u8 uuid_index;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = validate_output_format(cfg.output_format, &flags);
+ if (err) {
+ fprintf(stderr, "Invalid output format '%s'\n", cfg.output_format);
+ dev_close(dev);
+ return -EINVAL;
+ }
+
+ uuid_index = solidigm_get_vu_uuid_index(dev);
+
+ struct garbage_control_collection_log gc_log;
+ const int solidigm_vu_gc_log_id = 0xfd;
+ struct nvme_get_log_args args = {
+ .lpo = 0,
+ .result = NULL,
+ .log = &gc_log,
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .lid = solidigm_vu_gc_log_id,
+ .len = sizeof(gc_log),
+ .nsid = NVME_NSID_ALL,
+ .csi = NVME_CSI_NVM,
+ .lsi = NVME_LOG_LSI_NONE,
+ .lsp = NVME_LOG_LSP_NONE,
+ .uuidx = uuid_index,
+ .rae = false,
+ .ot = false,
+ };
+
+ err = nvme_get_log(&args);
+ if (!err) {
+ if (flags & BINARY)
+ d_raw((unsigned char *)&gc_log, sizeof(gc_log));
+ else if (flags & JSON)
+ vu_gc_log_show_json(&gc_log, dev->name);
+ else
+ vu_gc_log_show(&gc_log, dev->name, uuid_index);
+ } else if (err > 0) {
+ nvme_show_status(err);
+ }
+
+ /* Redundant close() to make static code analysis happy */
+ close(dev->direct.fd);
+ dev_close(dev);
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-garbage-collection.h b/plugins/solidigm/solidigm-garbage-collection.h
new file mode 100644
index 0000000..a3e34b2
--- /dev/null
+++ b/plugins/solidigm/solidigm-garbage-collection.h
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+int solidigm_get_garbage_collection_log(int argc, char **argv, struct command *cmd, struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-get-drive-info.c b/plugins/solidigm/solidigm-get-drive-info.c
new file mode 100644
index 0000000..21f59bb
--- /dev/null
+++ b/plugins/solidigm/solidigm-get-drive-info.c
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Authors: leonardo.da.cunha@solidigm.com
+ */
+
+#include <errno.h>
+#include "nvme-print.h"
+#include "nvme-wrap.h"
+#include "common.h"
+
+int sldgm_get_drive_info(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ _cleanup_nvme_dev_ struct nvme_dev *dev = NULL;
+ const char *desc = "Get drive HW information";
+ const char *FTL_unit_size_str = "FTL_unit_size";
+ char *output_format = "normal";
+ enum nvme_print_flags flags;
+ nvme_root_t r;
+ nvme_ctrl_t c;
+ nvme_ns_t n;
+ struct nvme_id_ns ns = { 0 };
+ __u8 flbaf_inUse;
+ __u16 lba_size;
+ __u16 ftl_unit_size;
+ int err;
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &output_format, "normal|json"),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = validate_output_format(output_format, &flags);
+ if ((err < 0) || !(flags == NORMAL || flags == JSON)) {
+ nvme_show_error("Invalid output format");
+ return err;
+ }
+
+ r = nvme_scan(NULL);
+ c = nvme_scan_ctrl(r, dev->name);
+ n = c ? nvme_ctrl_first_ns(c) : nvme_scan_namespace(dev->name);
+ if (!n) {
+ nvme_show_error("solidigm-vs-drive-info: drive missing namespace");
+ return -EINVAL;
+ }
+
+ err = nvme_ns_identify(n, &ns);
+ if (err) {
+ nvme_show_error("identify namespace: %s", nvme_strerror(errno));
+ return err;
+ }
+
+ if (!(ns.nsfeat & 0x10)) {
+ nvme_show_error("solidigm-vs-drive-info: performance options not available");
+ return -EINVAL;
+ }
+
+ nvme_id_ns_flbas_to_lbaf_inuse(ns.flbas, &flbaf_inUse);
+ lba_size = 1 << ns.lbaf[flbaf_inUse].ds;
+ ftl_unit_size = (le16_to_cpu(ns.npwg) + 1) * lba_size / 1024;
+
+ if (flags == JSON) {
+ struct json_object *root = json_create_object();
+
+ json_object_add_value_int(root, FTL_unit_size_str, ftl_unit_size);
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+ } else {
+ printf("%s: %d\n", FTL_unit_size_str, ftl_unit_size);
+ }
+
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-get-drive-info.h b/plugins/solidigm/solidigm-get-drive-info.h
new file mode 100644
index 0000000..ffc1bd2
--- /dev/null
+++ b/plugins/solidigm/solidigm-get-drive-info.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+int sldgm_get_drive_info(int argc, char **argv, struct command *cmd, struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-id-ctrl.c b/plugins/solidigm/solidigm-id-ctrl.c
new file mode 100644
index 0000000..f45e758
--- /dev/null
+++ b/plugins/solidigm/solidigm-id-ctrl.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <inttypes.h>
+#include "common.h"
+#include "solidigm-id-ctrl.h"
+
+struct __packed nvme_vu_id_ctrl_field { /* CDR MR5 */
+ __u8 rsvd1[3];
+ __u8 ss;
+ char health[20];
+ __u8 cls;
+ __u8 nlw;
+ __u8 scap;
+ __u8 sstat;
+ char bl[8];
+ __u8 rsvd2[38];
+ __le64 ww;
+ char mic_bl[4];
+ char mic_fw[4];
+};
+
+void sldgm_id_ctrl(uint8_t *vs, struct json_object *root)
+{
+ // text output aligns nicely with property name up to 10 chars
+ const char *str_ss = "stripeSize";
+ const char *str_health = "health";
+ const char *str_cls = "linkSpeed";
+ const char *str_nlw = "negLnkWdth";
+ const char *str_scap = "secCapab";
+ const char *str_sstat = "secStatus";
+ const char *str_bl = "bootLoader";
+ const char *str_ww = "wwid";
+ const char *str_mic_bl = "bwLimGran";
+ const char *str_mic_fw = "ioLimGran";
+
+ struct nvme_vu_id_ctrl_field *id = (struct nvme_vu_id_ctrl_field *)vs;
+
+ const char str_heathy[sizeof(id->health)] = "healthy";
+ const char *health = id->health[0] ? id->health : str_heathy;
+
+ if (root == NULL) {
+ printf("%-10s: %u\n", str_ss, id->ss);
+ printf("%-10s: %.*s\n", str_health, (int)sizeof(id->health), health);
+ printf("%-10s: %u\n", str_cls, id->cls);
+ printf("%-10s: %u\n", str_nlw, id->nlw);
+ printf("%-10s: %u\n", str_scap, id->scap);
+ printf("%-10s: %u\n", str_sstat, id->sstat);
+ printf("%-10s: %.*s\n", str_bl, (int)sizeof(id->bl), id->bl);
+ printf("%-10s: 0x%016"PRIx64"\n", str_ww, le64_to_cpu(id->ww));
+ printf("%-10s: %.*s\n", str_mic_bl, (int)sizeof(id->mic_bl), id->mic_bl);
+ printf("%-10s: %.*s\n", str_mic_fw, (int)sizeof(id->mic_fw), id->mic_fw);
+ return;
+ }
+
+ json_object_add_value_uint(root, str_ss, id->ss);
+ json_object_object_add(root, str_health,
+ json_object_new_string_len(health, sizeof(id->health)));
+ json_object_add_value_uint(root, str_cls, id->cls);
+ json_object_add_value_uint(root, str_nlw, id->nlw);
+ json_object_add_value_uint(root, str_scap, id->scap);
+ json_object_add_value_uint(root, str_sstat, id->sstat);
+ json_object_object_add(root, str_bl, json_object_new_string_len(id->bl, sizeof(id->bl)));
+ json_object_add_value_uint64(root, str_ww, le64_to_cpu(id->ww));
+ json_object_object_add(root, str_mic_bl,
+ json_object_new_string_len(id->mic_bl, sizeof(id->mic_bl)));
+ json_object_object_add(root, str_mic_fw,
+ json_object_new_string_len(id->mic_fw, sizeof(id->mic_fw)));
+}
diff --git a/plugins/solidigm/solidigm-id-ctrl.h b/plugins/solidigm/solidigm-id-ctrl.h
new file mode 100644
index 0000000..ed6e438
--- /dev/null
+++ b/plugins/solidigm/solidigm-id-ctrl.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <inttypes.h>
+#include "util/json.h"
+void sldgm_id_ctrl(uint8_t *vs, struct json_object *root);
diff --git a/plugins/solidigm/solidigm-internal-logs.c b/plugins/solidigm/solidigm-internal-logs.c
new file mode 100644
index 0000000..c604761
--- /dev/null
+++ b/plugins/solidigm/solidigm-internal-logs.c
@@ -0,0 +1,657 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Authors: leonardo.da.cunha@solidigm.com
+ * shankaralingegowda.singonahalli@solidigm.com
+ */
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <linux/limits.h>
+#include <time.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "nvme-print.h"
+#include "solidigm-util.h"
+
+#define DWORD_SIZE 4
+
+enum log_type {
+ NLOG = 0,
+ EVENTLOG = 1,
+ ASSERTLOG = 2,
+};
+
+#pragma pack(push, internal_logs, 1)
+struct version {
+ __u16 major;
+ __u16 minor;
+};
+
+struct event_dump_instance {
+ __u32 numeventdumps;
+ __u32 coresize;
+ __u32 coreoffset;
+ __u32 eventidoffset[16];
+ __u8 eventIdValidity[16];
+};
+
+struct commom_header {
+ struct version ver;
+ __u32 header_size;
+ __u32 log_size;
+ __u32 numcores;
+};
+
+struct event_dump_header {
+ struct commom_header header;
+ __u32 eventidsize;
+ struct event_dump_instance edumps[0];
+};
+
+struct assert_dump_core {
+ __u32 coreoffset;
+ __u32 assertsize;
+ __u8 assertdumptype;
+ __u8 assertvalid;
+ __u8 reserved[2];
+};
+
+struct assert_dump_header {
+ struct commom_header header;
+ struct assert_dump_core core[];
+};
+
+struct nlog_dump_header_common {
+ struct version ver;
+ __u32 logselect;
+ __u32 totalnlogs;
+ __u32 nlognum;
+ char nlogname[4];
+ __u32 nlogbytesize;
+ __u32 nlogprimarybuffsize;
+ __u32 tickspersecond;
+ __u32 corecount;
+};
+
+struct nlog_dump_header3_0 {
+ struct nlog_dump_header_common common;
+ __u32 nlogpausestatus;
+ __u32 selectoffsetref;
+ __u32 selectnlogpause;
+ __u32 selectaddedoffset;
+ __u32 nlogbufnum;
+ __u32 nlogbufnummax;
+};
+
+struct nlog_dump_header4_0 {
+ struct nlog_dump_header_common common;
+ __u64 nlogpausestatus;
+ __u32 selectoffsetref;
+ __u32 selectnlogpause;
+ __u32 selectaddedoffset;
+ __u32 nlogbufnum;
+ __u32 nlogbufnummax;
+ __u32 coreselected;
+ __u32 reserved[2];
+};
+
+struct nlog_dump_header4_1 {
+ struct nlog_dump_header_common common;
+ __u64 nlogpausestatus;
+ __u32 selectoffsetref;
+ __u32 selectnlogpause;
+ __u32 selectaddedoffset;
+ __u32 nlogbufnum;
+ __u32 nlogbufnummax;
+ __u32 coreselected;
+ __u32 lpaPointer1High;
+ __u32 lpaPointer1Low;
+ __u32 lpaPointer2High;
+ __u32 lpaPointer2Low;
+};
+
+#pragma pack(pop, internal_logs)
+
+struct config {
+ __u32 namespace_id;
+ char *dir_prefix;
+ char *type;
+ bool verbose;
+};
+
+static void print_nlog_header(__u8 *buffer)
+{
+ struct nlog_dump_header_common *nlog_header = (struct nlog_dump_header_common *) buffer;
+
+ if (nlog_header->ver.major >= 3) {
+ printf("Version Major %u\n", nlog_header->ver.major);
+ printf("Version Minor %u\n", nlog_header->ver.minor);
+ printf("Log_select %u\n", nlog_header->logselect);
+ printf("totalnlogs %u\n", nlog_header->totalnlogs);
+ printf("nlognum %u\n", nlog_header->nlognum);
+ printf("nlogname %c%c%c%c\n", nlog_header->nlogname[3], nlog_header->nlogname[2],
+ nlog_header->nlogname[1], nlog_header->nlogname[0]);
+ printf("nlogbytesize %u\n", nlog_header->nlogbytesize);
+ printf("nlogprimarybuffsize %u\n", nlog_header->nlogprimarybuffsize);
+ printf("tickspersecond %u\n", nlog_header->tickspersecond);
+ printf("corecount %u\n", nlog_header->corecount);
+ }
+ if (nlog_header->ver.major >= 4) {
+ struct nlog_dump_header4_0 *nlog_header = (struct nlog_dump_header4_0 *) buffer;
+
+ printf("nlogpausestatus %"PRIu64"\n", (uint64_t)nlog_header->nlogpausestatus);
+ printf("selectoffsetref %u\n", nlog_header->selectoffsetref);
+ printf("selectnlogpause %u\n", nlog_header->selectnlogpause);
+ printf("selectaddedoffset %u\n", nlog_header->selectaddedoffset);
+ printf("nlogbufnum %u\n", nlog_header->nlogbufnum);
+ printf("nlogbufnummax %u\n", nlog_header->nlogbufnummax);
+ printf("coreselected %u\n\n", nlog_header->coreselected);
+ }
+}
+
+#define INTERNAL_LOG_MAX_BYTE_TRANSFER 4096
+#define INTERNAL_LOG_MAX_DWORD_TRANSFER (INTERNAL_LOG_MAX_BYTE_TRANSFER / 4)
+
+static int cmd_dump_repeat(struct nvme_passthru_cmd *cmd, __u32 total_dw_size,
+ int out_fd, int ioctl_fd, bool force_max_transfer)
+{
+ int err = 0;
+
+ while (total_dw_size > 0) {
+ size_t dword_tfer = min(INTERNAL_LOG_MAX_DWORD_TRANSFER, total_dw_size);
+
+ cmd->cdw10 = force_max_transfer ? INTERNAL_LOG_MAX_DWORD_TRANSFER : dword_tfer;
+ cmd->data_len = dword_tfer * 4;
+ err = nvme_submit_admin_passthru(ioctl_fd, cmd, NULL);
+ if (err)
+ return err;
+
+ if (out_fd > 0) {
+ err = write(out_fd, (const void *)(uintptr_t)cmd->addr, cmd->data_len);
+ if (err < 0) {
+ perror("write failure");
+ return err;
+ }
+ err = 0;
+ }
+ total_dw_size -= dword_tfer;
+ cmd->cdw13 += dword_tfer;
+ }
+ return err;
+}
+
+static int write_header(__u8 *buf, int fd, size_t amnt)
+{
+ if (write(fd, buf, amnt) < 0)
+ return 1;
+ return 0;
+}
+
+static int read_header(struct nvme_passthru_cmd *cmd, int ioctl_fd)
+{
+ memset((void *)(uintptr_t)cmd->addr, 0, INTERNAL_LOG_MAX_BYTE_TRANSFER);
+ return cmd_dump_repeat(cmd, INTERNAL_LOG_MAX_DWORD_TRANSFER, -1, ioctl_fd, false);
+}
+
+static int get_serial_number(char *str, int fd)
+{
+ struct nvme_id_ctrl ctrl = {0};
+ int err;
+
+ err = nvme_identify_ctrl(fd, &ctrl);
+ if (err)
+ return err;
+
+ /* Remove trailing spaces */
+ for (int i = sizeof(ctrl.sn) - 1; i && ctrl.sn[i] == ' '; i--)
+ ctrl.sn[i] = '\0';
+ sprintf(str, "%-.*s", (int)sizeof(ctrl.sn), ctrl.sn);
+ return err;
+}
+
+static int dump_assert_logs(struct nvme_dev *dev, struct config cfg)
+{
+ __u8 buf[INTERNAL_LOG_MAX_BYTE_TRANSFER];
+ __u8 head_buf[INTERNAL_LOG_MAX_BYTE_TRANSFER];
+ char file_path[PATH_MAX];
+ char file_name[] = "AssertLog.bin";
+ struct assert_dump_header *ad = (struct assert_dump_header *) head_buf;
+ struct nvme_passthru_cmd cmd = {
+ .opcode = 0xd2,
+ .nsid = cfg.namespace_id,
+ .addr = (unsigned long)(void *)head_buf,
+ .cdw12 = ASSERTLOG,
+ .cdw13 = 0,
+ };
+ int output, err;
+
+ err = read_header(&cmd, dev_fd(dev));
+ if (err)
+ return err;
+
+ snprintf(file_path, sizeof(file_path), "%.*s/%s",
+ (int) (sizeof(file_path) - sizeof(file_name) - 1), cfg.dir_prefix, file_name);
+ output = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (output < 0)
+ return -errno;
+ err = write_header((__u8 *)ad, output, ad->header.header_size * DWORD_SIZE);
+ if (err) {
+ perror("write failure");
+ close(output);
+ return err;
+ }
+ cmd.addr = (unsigned long)(void *)buf;
+
+ if (cfg.verbose) {
+ printf("Assert Log, cores: %d log size: %d header size: %d\n", ad->header.numcores,
+ ad->header.log_size * DWORD_SIZE, ad->header.header_size * DWORD_SIZE);
+ for (__u32 i = 0; i < ad->header.numcores; i++)
+ printf("core %d assert size: %d\n", i, ad->core[i].assertsize * DWORD_SIZE);
+ }
+
+ for (__u32 i = 0; i < ad->header.numcores; i++) {
+ if (!ad->core[i].assertvalid)
+ continue;
+ cmd.cdw13 = ad->core[i].coreoffset;
+ err = cmd_dump_repeat(&cmd, ad->core[i].assertsize,
+ output,
+ dev_fd(dev), false);
+ if (err) {
+ close(output);
+ return err;
+ }
+ }
+ close(output);
+ printf("Successfully wrote log to %s\n", file_path);
+ return err;
+}
+
+static int dump_event_logs(struct nvme_dev *dev, struct config cfg)
+{
+ __u8 buf[INTERNAL_LOG_MAX_BYTE_TRANSFER];
+ __u8 head_buf[INTERNAL_LOG_MAX_BYTE_TRANSFER];
+ char file_path[PATH_MAX];
+ struct event_dump_header *ehdr = (struct event_dump_header *) head_buf;
+ struct nvme_passthru_cmd cmd = {
+ .opcode = 0xd2,
+ .nsid = cfg.namespace_id,
+ .addr = (unsigned long)(void *)head_buf,
+ .cdw12 = EVENTLOG,
+ .cdw13 = 0,
+ };
+ int output;
+ int core_num, err;
+
+ err = read_header(&cmd, dev_fd(dev));
+ if (err)
+ return err;
+ snprintf(file_path, sizeof(file_path), "%s/EventLog.bin", cfg.dir_prefix);
+ output = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (output < 0)
+ return -errno;
+ err = write_header(head_buf, output, INTERNAL_LOG_MAX_BYTE_TRANSFER);
+
+ core_num = ehdr->header.numcores;
+
+ if (err) {
+ close(output);
+ return err;
+ }
+ cmd.addr = (unsigned long)(void *)buf;
+
+ if (cfg.verbose)
+ printf("Event Log, cores: %d log size: %d\n", core_num, ehdr->header.log_size * 4);
+
+ for (__u32 j = 0; j < core_num; j++) {
+ if (cfg.verbose) {
+ for (int k = 0 ; k < 16; k++) {
+ printf("core: %d event: %d ", j, k);
+ printf("validity: %d ", ehdr->edumps[j].eventIdValidity[k]);
+ printf("offset: %d\n", ehdr->edumps[j].eventidoffset[k]);
+ }
+ }
+ cmd.cdw13 = ehdr->edumps[j].coreoffset;
+ err = cmd_dump_repeat(&cmd, ehdr->edumps[j].coresize,
+ output, dev_fd(dev), false);
+ if (err) {
+ close(output);
+ return err;
+ }
+ }
+ close(output);
+ printf("Successfully wrote log to %s\n", file_path);
+ return err;
+}
+
+static size_t get_nlog_header_size(struct nlog_dump_header_common *nlog_header)
+{
+ switch (nlog_header->ver.major) {
+ case 3:
+ return sizeof(struct nlog_dump_header3_0);
+ case 4:
+ if (nlog_header->ver.minor == 0)
+ return sizeof(struct nlog_dump_header4_0);
+ return sizeof(struct nlog_dump_header4_1);
+ default:
+ return INTERNAL_LOG_MAX_BYTE_TRANSFER;
+ }
+
+}
+
+/* dumps nlogs from specified core or all cores when core = -1 */
+static int dump_nlogs(struct nvme_dev *dev, struct config cfg, int core)
+{
+ int err = 0;
+ __u32 count, core_num;
+ __u8 buf[INTERNAL_LOG_MAX_BYTE_TRANSFER];
+ char file_path[PATH_MAX];
+ struct nlog_dump_header_common *nlog_header = (struct nlog_dump_header_common *)buf;
+ struct nvme_passthru_cmd cmd = {
+ .opcode = 0xd2,
+ .nsid = cfg.namespace_id,
+ .addr = (unsigned long)(void *)buf
+ };
+
+ struct dump_select {
+ union {
+ struct {
+ __u32 selectLog : 3;
+ __u32 selectCore : 2;
+ __u32 selectNlog : 8;
+ };
+ __u32 raw;
+ };
+ } log_select;
+ int output;
+ bool is_open = false;
+ size_t header_size = 0;
+
+ log_select.selectCore = core < 0 ? 0 : core;
+ do {
+ log_select.selectNlog = 0;
+ do {
+ cmd.cdw13 = 0;
+ cmd.cdw12 = log_select.raw;
+ err = read_header(&cmd, dev_fd(dev));
+ if (err) {
+ if (is_open)
+ close(output);
+ return err;
+ }
+ count = nlog_header->totalnlogs;
+ core_num = core < 0 ? nlog_header->corecount : 0;
+ if (!header_size) {
+ snprintf(file_path, sizeof(file_path), "%s/NLog.bin",
+ cfg.dir_prefix);
+ output = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (output < 0)
+ return -errno;
+ header_size = get_nlog_header_size(nlog_header);
+ is_open = true;
+ }
+ err = write_header(buf, output, header_size);
+ if (err)
+ break;
+ if (cfg.verbose)
+ print_nlog_header(buf);
+ cmd.cdw13 = 0x400;
+ err = cmd_dump_repeat(&cmd, nlog_header->nlogbytesize / 4,
+ output, dev_fd(dev), true);
+ if (err)
+ break;
+ } while (++log_select.selectNlog < count);
+ if (err)
+ break;
+ } while (++log_select.selectCore < core_num);
+ if (is_open) {
+ close(output);
+ printf("Successfully wrote log to %s\n", file_path);
+ }
+ return err;
+}
+
+enum telemetry_type {
+ HOSTGENOLD,
+ HOSTGENNEW,
+ CONTROLLER
+};
+
+static int dump_telemetry(struct nvme_dev *dev, struct config cfg, enum telemetry_type ttype)
+{
+ _cleanup_free_ struct nvme_telemetry_log *log = NULL;
+ size_t log_size = 0;
+ int err = 0;
+ __u8 *buffer = NULL;
+ size_t bytes_remaining = 0;
+ enum nvme_telemetry_da da;
+ size_t max_data_tx;
+ char file_path[PATH_MAX];
+ char *file_name;
+ char *log_descr;
+ struct stat sb;
+
+ _cleanup_file_ int output = -1;
+
+ switch (ttype) {
+ case HOSTGENNEW:
+ file_name = "lid_0x07_lsp_0x01_lsi_0x0000.bin";
+ log_descr = "Generated Host Initiated";
+ break;
+ case HOSTGENOLD:
+ file_name = "lid_0x07_lsp_0x00_lsi_0x0000.bin";
+ log_descr = "Existing Host Initiated";
+ break;
+ case CONTROLLER:
+ file_name = "lid_0x08_lsp_0x00_lsi_0x0000.bin";
+ log_descr = "Controller Initiated";
+ break;
+ default:
+ return -EINVAL;
+ }
+ err = nvme_get_telemetry_max(dev_fd(dev), &da, &max_data_tx);
+ if (err)
+ return err;
+
+ if (max_data_tx > DRIVER_MAX_TX_256K)
+ max_data_tx = DRIVER_MAX_TX_256K;
+
+ switch (ttype) {
+ case HOSTGENNEW:
+ err = nvme_get_telemetry_log(dev_fd(dev), true, false, false, max_data_tx, da,
+ &log, &log_size);
+ break;
+ case HOSTGENOLD:
+ err = nvme_get_telemetry_log(dev_fd(dev), false, false, false, max_data_tx, da,
+ &log, &log_size);
+ break;
+ case CONTROLLER:
+ err = nvme_get_telemetry_log(dev_fd(dev), false, true, true, max_data_tx, da, &log,
+ &log_size);
+ break;
+ }
+
+ if (err)
+ return err;
+
+ snprintf(file_path, sizeof(file_path), "%s/log_pages", cfg.dir_prefix);
+ if (!(stat(file_path, &sb) == 0 && S_ISDIR(sb.st_mode))) {
+ if (mkdir(file_path, 777) != 0) {
+ perror(file_path);
+ return -errno;
+ }
+ }
+
+ snprintf(file_path, sizeof(file_path), "%s/log_pages/%s", cfg.dir_prefix, file_name);
+ output = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (output < 0)
+ return -errno;
+
+ bytes_remaining = log_size;
+ buffer = (__u8 *)log;
+
+ while (bytes_remaining) {
+ ssize_t bytes_written = write(output, buffer, bytes_remaining);
+
+ if (bytes_written < 0) {
+ err = -errno;
+ goto tele_close_output;
+ }
+
+ bytes_remaining -= bytes_written;
+ buffer += bytes_written;
+ }
+ printf("Successfully wrote %s Telemetry log to %s\n", log_descr, file_path);
+
+tele_close_output:
+ close(output);
+ return err;
+}
+
+int solidigm_get_internal_log(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ char folder[PATH_MAX];
+ char zip_name[PATH_MAX];
+ char *output_path;
+ char sn_prefix[sizeof(((struct nvme_id_ctrl *)0)->sn)+1];
+ int log_count = 0;
+ int err;
+ _cleanup_nvme_dev_ struct nvme_dev *dev = NULL;
+ bool all = false;
+ time_t t;
+ struct tm tm;
+
+ const char *desc = "Get Debug Firmware Logs and save them.";
+ const char *type =
+ "Log type: ALL, CONTROLLERINITTELEMETRY, HOSTINITTELEMETRY, HOSTINITTELEMETRYNOGEN, NLOG, ASSERT, EVENT. Defaults to ALL.";
+ const char *prefix = "Output dir prefix; defaults to device serial number.";
+ const char *verbose = "To print out verbose info.";
+ const char *namespace_id = "Namespace to get logs from.";
+
+
+ struct config cfg = {
+ .namespace_id = NVME_NSID_ALL,
+ .dir_prefix = NULL,
+ .type = NULL,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_STR("type", 't', &cfg.type, type),
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_FILE("dir-prefix", 'p', &cfg.dir_prefix, prefix),
+ OPT_FLAG("verbose", 'v', &cfg.verbose, verbose),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ if (!cfg.dir_prefix) {
+ err = get_serial_number(sn_prefix, dev_fd(dev));
+ if (err)
+ return err;
+ cfg.dir_prefix = sn_prefix;
+ }
+ t = time(NULL);
+ tm = *localtime(&t);
+ snprintf(folder, sizeof(folder), "%s-%d%02d%02d%02d%02d%02d", cfg.dir_prefix,
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
+ if (mkdir(folder, 0777) != 0) {
+ perror("mkdir");
+ return -errno;
+ }
+ cfg.dir_prefix = folder;
+ output_path = folder;
+
+ if (!cfg.type)
+ cfg.type = "ALL";
+ else {
+ for (char *p = cfg.type; *p; ++p)
+ *p = toupper(*p);
+ }
+
+ if (!strcmp(cfg.type, "ALL")) {
+ all = true;
+ }
+ if (all || !strcmp(cfg.type, "ASSERT")) {
+ err = dump_assert_logs(dev, cfg);
+ if (err == 0)
+ log_count++;
+ else if (err < 0)
+ perror("Error retrieving Assert log");
+ }
+ if (all || !strcmp(cfg.type, "EVENT")) {
+ err = dump_event_logs(dev, cfg);
+ if (err == 0)
+ log_count++;
+ else if (err < 0)
+ perror("Error retrieving Event log");
+ }
+ if (all || !strcmp(cfg.type, "NLOG")) {
+ err = dump_nlogs(dev, cfg, -1);
+ if (err == 0)
+ log_count++;
+ else if (err < 0)
+ perror("Error retrieving Nlog");
+ }
+ if (all || !strcmp(cfg.type, "CONTROLLERINITTELEMETRY")) {
+ err = dump_telemetry(dev, cfg, CONTROLLER);
+ if (err == 0)
+ log_count++;
+ else if (err < 0)
+ perror("Error retrieving Telemetry Controller Initiated");
+ }
+ if (all || !strcmp(cfg.type, "HOSTINITTELEMETRYNOGEN")) {
+ err = dump_telemetry(dev, cfg, HOSTGENOLD);
+ if (err == 0)
+ log_count++;
+ else if (err < 0)
+ perror("Error retrieving previously existing Telemetry Host Initiated");
+ }
+ if (all || !strcmp(cfg.type, "HOSTINITTELEMETRY")) {
+ err = dump_telemetry(dev, cfg, HOSTGENNEW);
+ if (err == 0)
+ log_count++;
+ else if (err < 0)
+ perror("Error retrieving Telemetry Host Initiated");
+ }
+
+ if (log_count > 0) {
+ int ret_cmd;
+ char cmd[ARG_MAX];
+ char *where_err = cfg.verbose ? "" : ">/dev/null 2>&1";
+
+ snprintf(zip_name, sizeof(zip_name), "%s.zip", cfg.dir_prefix);
+ snprintf(cmd, sizeof(cmd), "cd \"%s\" && zip -r \"../%s\" ./* %s", cfg.dir_prefix,
+ zip_name, where_err);
+ printf("Compressing logs to %s\n", zip_name);
+ ret_cmd = system(cmd);
+ if (ret_cmd == -1)
+ perror(cmd);
+ else {
+ output_path = zip_name;
+ snprintf(cmd, sizeof(cmd), "rm -rf %s", cfg.dir_prefix);
+ printf("Removing %s\n", cfg.dir_prefix);
+ if (system(cmd) != 0)
+ perror("Failed removing logs folder");
+ }
+ }
+
+ if (log_count == 0) {
+ if (err > 0)
+ nvme_show_status(err);
+ } else if ((log_count > 1) || cfg.verbose)
+ printf("Total: %d log files in %s\n", log_count, output_path);
+
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-internal-logs.h b/plugins/solidigm/solidigm-internal-logs.h
new file mode 100644
index 0000000..801af24
--- /dev/null
+++ b/plugins/solidigm/solidigm-internal-logs.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+int solidigm_get_internal_log(int argc, char **argv, struct command *cmd, struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-latency-tracking.c b/plugins/solidigm/solidigm-latency-tracking.c
new file mode 100644
index 0000000..66f3c56
--- /dev/null
+++ b/plugins/solidigm/solidigm-latency-tracking.c
@@ -0,0 +1,474 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "nvme-print.h"
+#include "solidigm-util.h"
+
+#define BUCKET_LIST_SIZE_4_0 152
+#define BUCKET_LIST_SIZE_4_1 1216
+
+#define BASE_RANGE_BITS_4_0 3
+#define BASE_RANGE_BITS_4_1 6
+
+struct latency_statistics {
+ __u16 version_major;
+ __u16 version_minor;
+ __u32 data[BUCKET_LIST_SIZE_4_1];
+ __u64 average_latency;
+};
+
+struct config {
+ bool enable;
+ bool disable;
+ bool read;
+ bool write;
+ unsigned char type;
+ char *output_format;
+};
+
+struct latency_tracker {
+ int fd;
+ __u8 uuid_index;
+ struct config cfg;
+ enum nvme_print_flags print_flags;
+ struct latency_statistics stats;
+ struct json_object *bucket_list;
+ __u32 bucket_list_size;
+ __u8 base_range_bits;
+ bool has_average_latency_field;
+};
+
+/* COL_WIDTH controls width of columns in NORMAL output. */
+#define COL_WIDTH 12
+#define BUCKET_LABEL_MAX_SIZE 10
+
+#define US_IN_S 1000000
+#define US_IN_MS 1000
+
+/*
+ * Edge buckets may have range [#s, inf) in some
+ * latency statistics formats.
+ */
+static void get_time_unit_label(char *label, __u32 microseconds,
+ bool bonded)
+{
+ char *string = "us";
+ int divisor = 1;
+
+ if (!bonded) {
+ snprintf(label, BUCKET_LABEL_MAX_SIZE, "%s", "+INF");
+ return;
+ }
+
+ if (microseconds > US_IN_S) {
+ string = "s";
+ divisor = US_IN_S;
+ } else if (microseconds > US_IN_MS) {
+ string = "ms";
+ divisor = US_IN_MS;
+ }
+
+ snprintf(label, BUCKET_LABEL_MAX_SIZE, "%4.2f%s", (float) microseconds / divisor,
+ string);
+}
+
+static void latency_tracker_bucket_parse(const struct latency_tracker *lt, int id,
+ __u32 lower_us, __u32 upper_us, bool upper_bounded)
+{
+ char buffer[BUCKET_LABEL_MAX_SIZE] = "";
+ __u32 bucket_data = le32_to_cpu(lt->stats.data[id]);
+
+ if (lt->print_flags == NORMAL) {
+ printf("%-*d", COL_WIDTH, id);
+
+ get_time_unit_label(buffer, lower_us, true);
+ printf("%-*s", COL_WIDTH, buffer);
+
+ get_time_unit_label(buffer, upper_us, upper_bounded);
+ printf("%-*s", COL_WIDTH, buffer);
+
+ printf("%-*d\n", COL_WIDTH, bucket_data);
+ }
+
+ if (lt->print_flags == JSON) {
+ /*
+ * Creates a bucket under the "values" json_object. Format is:
+ * "values" : {
+ * "bucket" : {
+ * "id" : #,
+ * "start" : string,
+ * "end" : string,
+ * "value" : 0,
+ * },
+ */
+ struct json_object *bucket = json_create_object();
+
+ json_object_array_add(lt->bucket_list, bucket);
+ json_object_add_value_int(bucket, "id", id);
+
+ get_time_unit_label(buffer, lower_us, true);
+ json_object_add_value_string(bucket, "start", buffer);
+
+ get_time_unit_label(buffer, upper_us, upper_bounded);
+ json_object_add_value_string(bucket, "end", buffer);
+
+ json_object_add_value_int(bucket, "value", bucket_data);
+ }
+}
+
+static void latency_tracker_parse_linear(const struct latency_tracker *lt,
+ __u32 start_offset, __u32 end_offset,
+ __u32 bytes_per, __u32 us_step,
+ bool nonzero_print)
+{
+ for (int i = (start_offset / bytes_per) - 1; i < end_offset / bytes_per; i++) {
+ if (nonzero_print && !lt->stats.data[i])
+ continue;
+ latency_tracker_bucket_parse(lt, i, us_step * i, us_step * (i + 1), true);
+ }
+}
+
+/*
+ * Calculates bucket time slot. Valid starting on 4.0 revision.
+ */
+
+static int latency_tracker_bucket_pos2us(const struct latency_tracker *lt, int i)
+{
+ __u32 base_val = 1 << lt->base_range_bits;
+
+ if (i < (base_val << 1))
+ return i;
+
+ int error_bits = (i >> lt->base_range_bits) - 1;
+ int base = 1 << (error_bits + lt->base_range_bits);
+ int k = i % base_val;
+
+ return base + ((k + 0.5) * (1 << error_bits));
+}
+
+/*
+ * Creates a subroot in the following manner:
+ * {
+ * "latstats" : {
+ * "type" : "write" or "read",
+ * "values" : {
+ */
+static void latency_tracker_populate_json_root(const struct latency_tracker *lt,
+ struct json_object *root)
+{
+ struct json_object *subroot = json_create_object();
+
+ json_object_add_value_object(root, "latstats", subroot);
+ json_object_add_value_string(subroot, "type", lt->cfg.write ? "write" : "read");
+ if (lt->has_average_latency_field)
+ json_object_add_value_uint64(subroot, "average_latency",
+ le64_to_cpu(lt->stats.average_latency));
+ json_object_add_value_object(subroot, "values", lt->bucket_list);
+}
+
+static void latency_tracker_parse_3_0(const struct latency_tracker *lt)
+{
+ latency_tracker_parse_linear(lt, 4, 131, 4, 32, false);
+ latency_tracker_parse_linear(lt, 132, 255, 4, 1024, false);
+ latency_tracker_parse_linear(lt, 256, 379, 4, 32768, false);
+ latency_tracker_parse_linear(lt, 380, 383, 4, 32, true);
+ latency_tracker_parse_linear(lt, 384, 387, 4, 32, true);
+ latency_tracker_parse_linear(lt, 388, 391, 4, 32, true);
+}
+
+static void latency_tracker_parse_4_0(const struct latency_tracker *lt)
+{
+ for (unsigned int i = 0; i < lt->bucket_list_size; i++) {
+ int lower_us = latency_tracker_bucket_pos2us(lt, i);
+ int upper_us = latency_tracker_bucket_pos2us(lt, i + 1);
+
+ latency_tracker_bucket_parse(lt, i, lower_us, upper_us,
+ i < (lt->bucket_list_size - 1));
+ }
+}
+
+static void print_dash_separator(void)
+{
+ printf("--------------------------------------------------\n");
+}
+
+static void latency_tracker_pre_parse(struct latency_tracker *lt)
+{
+ if (lt->print_flags == NORMAL) {
+ printf("Solidigm IO %s Command Latency Tracking Statistics type %d\n",
+ lt->cfg.write ? "Write" : "Read", lt->cfg.type);
+ printf("UUID-idx: %d\n", lt->uuid_index);
+ printf("Major Revision: %u\nMinor Revision: %u\n",
+ le16_to_cpu(lt->stats.version_major), le16_to_cpu(lt->stats.version_minor));
+ if (lt->has_average_latency_field)
+ printf("Average Latency: %" PRIu64 "\n", le64_to_cpu(lt->stats.average_latency));
+ print_dash_separator();
+ printf("%-12s%-12s%-12s%-20s\n", "Bucket", "Start", "End", "Value");
+ print_dash_separator();
+ }
+ if (lt->print_flags == JSON)
+ lt->bucket_list = json_object_new_array();
+}
+
+static void latency_tracker_post_parse(struct latency_tracker *lt)
+{
+ if (lt->print_flags == JSON) {
+ struct json_object *root = json_create_object();
+
+ latency_tracker_populate_json_root(lt, root);
+ json_print_object(root, NULL);
+ json_free_object(root);
+ printf("\n");
+ }
+}
+
+static void latency_tracker_parse(struct latency_tracker *lt)
+{
+ __u16 version_major = le16_to_cpu(lt->stats.version_major);
+ __u16 version_minor = le16_to_cpu(lt->stats.version_minor);
+
+ switch (version_major) {
+ case 3:
+ latency_tracker_pre_parse(lt);
+ latency_tracker_parse_3_0(lt);
+ break;
+ case 4:
+ if (version_minor >= 8)
+ lt->has_average_latency_field = true;
+ latency_tracker_pre_parse(lt);
+ if (!version_minor) {
+ lt->base_range_bits = BASE_RANGE_BITS_4_0;
+ lt->bucket_list_size = BUCKET_LIST_SIZE_4_0;
+ }
+ latency_tracker_parse_4_0(lt);
+ break;
+ default:
+ printf("Unsupported revision (%u.%u)\n",
+ version_major, version_minor);
+ break;
+ }
+
+ latency_tracker_post_parse(lt);
+}
+
+#define LATENCY_TRACKING_FID 0xe2
+#define LATENCY_TRACKING_FID_DATA_LEN 32
+
+static int latency_tracking_is_enable(struct latency_tracker *lt, __u32 *enabled)
+{
+ struct nvme_get_features_args args_get = {
+ .args_size = sizeof(args_get),
+ .fd = lt->fd,
+ .uuidx = lt->uuid_index,
+ .fid = LATENCY_TRACKING_FID,
+ .nsid = 0,
+ .sel = 0,
+ .cdw11 = 0,
+ .data_len = LATENCY_TRACKING_FID_DATA_LEN,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = enabled,
+ };
+ return nvme_get_features(&args_get);
+}
+
+static int latency_tracking_enable(struct latency_tracker *lt)
+{
+ __u32 result;
+ int err;
+
+ if (!(lt->cfg.enable || lt->cfg.disable))
+ return 0;
+
+ if (lt->cfg.enable && lt->cfg.disable) {
+ fprintf(stderr, "Cannot enable and disable simultaneously.\n");
+ return -EINVAL;
+ }
+
+ struct nvme_set_features_args args_set = {
+ .args_size = sizeof(args_set),
+ .fd = lt->fd,
+ .uuidx = lt->uuid_index,
+ .fid = LATENCY_TRACKING_FID,
+ .nsid = 0,
+ .cdw11 = lt->cfg.enable,
+ .cdw12 = 0,
+ .save = 0,
+ .cdw15 = 0,
+ .data_len = LATENCY_TRACKING_FID_DATA_LEN,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ err = nvme_set_features(&args_set);
+ if (err > 0) {
+ nvme_show_status(err);
+ } else if (err < 0) {
+ perror("Enable latency tracking");
+ fprintf(stderr, "Command failed while parsing.\n");
+ } else {
+ if (lt->print_flags == NORMAL) {
+ printf("Successfully set enable bit for UUID-idx:%d FID:0x%X, to %i.\n",
+ lt->uuid_index, LATENCY_TRACKING_FID, lt->cfg.enable);
+ }
+ }
+ return err;
+}
+
+#define READ_LOG_ID 0xc1
+#define WRITE_LOG_ID 0xc2
+
+static int latency_tracker_get_log(struct latency_tracker *lt)
+{
+ int err;
+
+ if (lt->cfg.read && lt->cfg.write) {
+ fprintf(stderr, "Cannot capture read and write logs simultaneously.\n");
+ return -EINVAL;
+ }
+
+ if (!(lt->cfg.read || lt->cfg.write))
+ return 0;
+
+ struct nvme_get_log_args args = {
+ .lpo = 0,
+ .result = NULL,
+ .log = &lt->stats,
+ .args_size = sizeof(args),
+ .fd = lt->fd,
+ .uuidx = lt->uuid_index,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .lid = lt->cfg.write ? WRITE_LOG_ID : READ_LOG_ID,
+ .len = sizeof(lt->stats),
+ .nsid = NVME_NSID_ALL,
+ .csi = NVME_CSI_NVM,
+ .lsi = NVME_LOG_LSI_NONE,
+ .lsp = lt->cfg.type,
+ .rae = false,
+ .ot = false,
+ };
+
+ err = nvme_get_log(&args);
+ if (err)
+ return err;
+
+ if (lt->print_flags & BINARY)
+ d_raw((unsigned char *)&lt->stats,
+ sizeof(lt->stats));
+ else {
+ latency_tracker_parse(lt);
+ }
+ return err;
+}
+
+int solidigm_get_latency_tracking_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ const char *desc = "Get and Parse Solidigm Latency Tracking Statistics log.";
+ struct nvme_dev *dev;
+ __u32 enabled;
+ int err;
+
+ struct latency_tracker lt = {
+ .uuid_index = 0,
+ .cfg = {
+ .output_format = "normal",
+ },
+ .base_range_bits = BASE_RANGE_BITS_4_1,
+ .bucket_list_size = BUCKET_LIST_SIZE_4_1,
+ .has_average_latency_field = false,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("enable", 'e', &lt.cfg.enable, "Enable Latency Tracking"),
+ OPT_FLAG("disable", 'd', &lt.cfg.disable, "Disable Latency Tracking"),
+ OPT_FLAG("read", 'r', &lt.cfg.read, "Get read statistics"),
+ OPT_FLAG("write", 'w', &lt.cfg.write, "Get write statistics"),
+ OPT_BYTE("type", 't', &lt.cfg.type, "Log type to get"),
+ OPT_FMT("output-format", 'o', &lt.cfg.output_format, output_format),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ lt.fd = dev_fd(dev);
+
+ err = validate_output_format(lt.cfg.output_format, &lt.print_flags);
+ if (err < 0) {
+ fprintf(stderr, "Invalid output format '%s'\n", lt.cfg.output_format);
+ dev_close(dev);
+ return -EINVAL;
+ }
+
+ if (lt.cfg.type > 0xf) {
+ fprintf(stderr, "Invalid Log type value '%d'\n", lt.cfg.type);
+ dev_close(dev);
+ return -EINVAL;
+ }
+
+ if (lt.cfg.type && !(lt.cfg.read || lt.cfg.write)) {
+ fprintf(stderr, "Log type option valid only when retrieving statistics\n");
+ dev_close(dev);
+ return -EINVAL;
+ }
+
+ lt.uuid_index = solidigm_get_vu_uuid_index(dev);
+
+ err = latency_tracking_enable(&lt);
+ if (err) {
+ dev_close(dev);
+ return err;
+ }
+
+ err = latency_tracker_get_log(&lt);
+ 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(&lt, &enabled);
+ if (!err) {
+ if (lt.print_flags == JSON) {
+ struct json_object *root = json_create_object();
+
+ json_object_add_value_int(root, "enabled", enabled);
+ json_print_object(root, NULL);
+ json_free_object(root);
+ printf("\n");
+ } else if (lt.print_flags == BINARY) {
+ putchar(enabled);
+ } else {
+ printf("Latency Statistics Tracking (UUID-idx:%d, FID:0x%X) is currently %i.\n",
+ lt.uuid_index, LATENCY_TRACKING_FID, enabled);
+ }
+ } else {
+ fprintf(stderr, "Could not read feature id 0xE2.\n");
+ }
+ /* Redundant close() to make static code analysis happy */
+ close(dev->direct.fd);
+ dev_close(dev);
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-latency-tracking.h b/plugins/solidigm/solidigm-latency-tracking.h
new file mode 100644
index 0000000..9a763a9
--- /dev/null
+++ b/plugins/solidigm/solidigm-latency-tracking.h
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+int solidigm_get_latency_tracking_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-log-page-dir.c b/plugins/solidigm/solidigm-log-page-dir.c
new file mode 100644
index 0000000..bf272f8
--- /dev/null
+++ b/plugins/solidigm/solidigm-log-page-dir.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: karl.dedow@solidigm.com
+ */
+
+#include "solidigm-log-page-dir.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "nvme-print.h"
+
+#include "plugins/ocp/ocp-utils.h"
+
+#define MIN_VENDOR_LID 0xC0
+#define SOLIDIGM_MAX_UUID 2
+
+static const char dash[100] = {[0 ... 99] = '-'};
+
+struct lid_dir {
+ struct __packed {
+ bool supported;
+ const char *str;
+ } lid[NVME_LOG_SUPPORTED_LOG_PAGES_MAX];
+};
+
+static void init_lid_dir(struct lid_dir *lid_dir)
+{
+ static const char *unknown_str = "Unknown";
+
+ for (int lid = 0; lid < NVME_LOG_SUPPORTED_LOG_PAGES_MAX; lid++) {
+ lid_dir->lid[lid].supported = false;
+ lid_dir->lid[lid].str = unknown_str;
+ }
+}
+
+static bool is_invalid_uuid(const struct nvme_id_uuid_list_entry entry)
+{
+ static const unsigned char ALL_ZERO_UUID[NVME_UUID_LEN] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ return memcmp(ALL_ZERO_UUID, entry.uuid, NVME_UUID_LEN) == 0;
+}
+
+static bool is_solidigm_uuid(const struct nvme_id_uuid_list_entry entry)
+{
+ static const unsigned char SOLIDIGM_UUID[NVME_UUID_LEN] = {
+ 0x96, 0x19, 0x58, 0x6e, 0xc1, 0x1b, 0x43, 0xad,
+ 0xaa, 0xaa, 0x65, 0x41, 0x87, 0xf6, 0xbb, 0xb2
+ };
+
+ return memcmp(SOLIDIGM_UUID, entry.uuid, NVME_UUID_LEN) == 0;
+}
+
+static bool is_ocp_uuid(const struct nvme_id_uuid_list_entry entry)
+{
+ static const unsigned char OCP_UUID[NVME_UUID_LEN] = {
+ 0xc1, 0x94, 0xd5, 0x5b, 0xe0, 0x94, 0x47, 0x94,
+ 0xa2, 0x1d, 0x29, 0x99, 0x8f, 0x56, 0xbe, 0x6f
+ };
+
+ return memcmp(OCP_UUID, entry.uuid, NVME_UUID_LEN) == 0;
+}
+
+static int get_supported_log_pages_log(struct nvme_dev *dev, int uuid_index,
+ struct nvme_supported_log_pages *supported)
+{
+ static const __u8 LID;
+
+ memset(supported, 0, sizeof(*supported));
+ struct nvme_get_log_args args = {
+ .lpo = 0,
+ .result = NULL,
+ .log = supported,
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .lid = LID,
+ .len = sizeof(*supported),
+ .nsid = NVME_NSID_ALL,
+ .csi = NVME_CSI_NVM,
+ .lsi = NVME_LOG_LSI_NONE,
+ .lsp = 0,
+ .uuidx = uuid_index,
+ .rae = false,
+ .ot = false,
+ };
+
+ return nvme_get_log(&args);
+}
+
+static struct lid_dir *get_standard_lids(struct nvme_supported_log_pages *supported)
+{
+ static struct lid_dir standard_dir = { 0 };
+
+ init_lid_dir(&standard_dir);
+
+ for (int lid = 0; lid < NVME_LOG_SUPPORTED_LOG_PAGES_MAX; lid++) {
+ if (!supported->lid_support[lid] || lid >= MIN_VENDOR_LID)
+ continue;
+
+ standard_dir.lid[lid].supported = true;
+ standard_dir.lid[lid].str = nvme_log_to_string(lid);
+ }
+
+ return &standard_dir;
+}
+
+static void update_vendor_lid_supported(struct nvme_supported_log_pages *supported,
+ struct lid_dir *lid_dir)
+{
+ for (int lid = 0; lid < NVME_LOG_SUPPORTED_LOG_PAGES_MAX; lid++) {
+ if (!supported->lid_support[lid] || lid < MIN_VENDOR_LID)
+ continue;
+
+ lid_dir->lid[lid].supported = true;
+ }
+}
+
+static struct lid_dir *get_solidigm_lids(struct nvme_supported_log_pages *supported)
+{
+ static struct lid_dir solidigm_dir = { 0 };
+
+ init_lid_dir(&solidigm_dir);
+ solidigm_dir.lid[0xC1].str = "Read Commands Latency Statistics";
+ solidigm_dir.lid[0xC2].str = "Write Commands Latency Statistics";
+ solidigm_dir.lid[0xC4].str = "Endurance Manager Statistics";
+ solidigm_dir.lid[0xC5].str = "Temperature Statistics";
+ solidigm_dir.lid[0xCA].str = "SMART Attributes";
+ solidigm_dir.lid[0xCB].str = "VU NVMe IO Queue Metrics Log Page";
+ solidigm_dir.lid[0xDD].str = "VU Marketing Description Log Page";
+ solidigm_dir.lid[0xEF].str = "Performance Rating and LBA Access Histogram";
+ solidigm_dir.lid[0xF2].str = "Get Power Usage Log Page";
+ solidigm_dir.lid[0xF6].str = "Vt Histo Get Log Page";
+ solidigm_dir.lid[0xF9].str = "Workload Tracker Get Log Page";
+ solidigm_dir.lid[0xFD].str = "Garbage Control Collection Log Page";
+ solidigm_dir.lid[0xFE].str = "Latency Outlier Log Page";
+
+ update_vendor_lid_supported(supported, &solidigm_dir);
+
+ return &solidigm_dir;
+}
+
+static struct lid_dir *get_ocp_lids(struct nvme_supported_log_pages *supported)
+{
+ static struct lid_dir ocp_dir = { 0 };
+
+ init_lid_dir(&ocp_dir);
+ ocp_dir.lid[0xC0].str = "OCP SMART / Health Information Extended";
+ ocp_dir.lid[0xC1].str = "OCP Error Recovery";
+ ocp_dir.lid[0xC2].str = "OCP Firmware Activation History";
+ ocp_dir.lid[0xC3].str = "OCP Latency Monitor";
+ ocp_dir.lid[0xC4].str = "OCP Device Capabilities";
+ ocp_dir.lid[0xC5].str = "OCP Unsupported Requirements";
+
+ update_vendor_lid_supported(supported, &ocp_dir);
+
+ return &ocp_dir;
+}
+
+static void supported_log_pages_normal(struct lid_dir *lid_dir[SOLIDIGM_MAX_UUID + 1])
+{
+ printf("%-5s %-4s %-42s\n", "uuidx", "LID", "Description");
+ printf("%-.5s %-.4s %-.42s\n", dash, dash, dash);
+
+ for (int uuid_index = 0; uuid_index <= SOLIDIGM_MAX_UUID; uuid_index++) {
+ if (!lid_dir[uuid_index])
+ continue;
+
+ for (int lid = 0; lid < NVME_LOG_SUPPORTED_LOG_PAGES_MAX; lid++) {
+ if (!lid_dir[uuid_index]->lid[lid].supported)
+ continue;
+
+ printf("%-5d 0x%02x %s\n", le32_to_cpu(uuid_index), le32_to_cpu(lid),
+ lid_dir[uuid_index]->lid[lid].str);
+ }
+ }
+}
+
+static void supported_log_pages_json(struct lid_dir *lid_dir[SOLIDIGM_MAX_UUID + 1])
+{
+ struct json_object *root = json_create_array();
+
+ for (int uuid_index = 0; uuid_index <= SOLIDIGM_MAX_UUID; uuid_index++) {
+ if (!lid_dir[uuid_index])
+ continue;
+
+ for (int lid = 0; lid < NVME_LOG_SUPPORTED_LOG_PAGES_MAX; lid++) {
+ if (!lid_dir[uuid_index]->lid[lid].supported)
+ continue;
+
+ struct json_object *lid_obj = json_create_object();
+
+ json_object_add_value_uint(lid_obj, "uuidx", le32_to_cpu(uuid_index));
+ json_object_add_value_uint(lid_obj, "lid", le32_to_cpu(lid));
+ json_object_add_value_string(lid_obj, "description",
+ lid_dir[uuid_index]->lid[lid].str);
+ json_array_add_value_object(root, lid_obj);
+ }
+ }
+
+ json_print_object(root, NULL);
+ json_free_object(root);
+ printf("\n");
+}
+
+int solidigm_get_log_page_directory_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ const int NO_UUID_INDEX = 0;
+ const char *description = "Retrieves list of supported log pages for each UUID index.";
+ char *format = "normal";
+
+ OPT_ARGS(options) = {
+ OPT_FMT("output-format", 'o', &format, "output format : normal | json"),
+ OPT_END()
+ };
+
+ struct nvme_dev *dev = NULL;
+ int err = parse_and_open(&dev, argc, argv, description, options);
+
+ if (err)
+ return err;
+
+ struct lid_dir *lid_dirs[SOLIDIGM_MAX_UUID + 1] = { 0 };
+ struct nvme_id_uuid_list uuid_list = { 0 };
+ struct nvme_supported_log_pages supported = { 0 };
+
+ err = get_supported_log_pages_log(dev, NO_UUID_INDEX, &supported);
+
+ if (!err) {
+ lid_dirs[NO_UUID_INDEX] = get_standard_lids(&supported);
+
+ // Assume VU logs are the Solidigm log pages if UUID not supported.
+ if (nvme_identify_uuid(dev_fd(dev), &uuid_list)) {
+ struct lid_dir *solidigm_lid_dir = get_solidigm_lids(&supported);
+
+ // Transfer supported Solidigm lids to lid directory at UUID index 0
+ for (int lid = 0; lid < NVME_LOG_SUPPORTED_LOG_PAGES_MAX; lid++) {
+ if (solidigm_lid_dir->lid[lid].supported)
+ lid_dirs[NO_UUID_INDEX]->lid[lid] = solidigm_lid_dir->lid[lid];
+ }
+ } else {
+ for (int uuid_index = 1; uuid_index <= SOLIDIGM_MAX_UUID; uuid_index++) {
+ if (is_invalid_uuid(uuid_list.entry[uuid_index - 1]))
+ break;
+ else if (get_supported_log_pages_log(dev, uuid_index, &supported))
+ continue;
+
+ if (is_solidigm_uuid(uuid_list.entry[uuid_index - 1]))
+ lid_dirs[uuid_index] = get_solidigm_lids(&supported);
+ else if (is_ocp_uuid(uuid_list.entry[uuid_index - 1]))
+ lid_dirs[uuid_index] = get_ocp_lids(&supported);
+ }
+ }
+ } else {
+ nvme_show_status(err);
+ }
+
+ if (!err) {
+ enum nvme_print_flags print_flag;
+
+ err = validate_output_format(format, &print_flag);
+ if (err < 0) {
+ fprintf(stderr, "Error: Invalid output format specified: %s.\n", format);
+ return err;
+ }
+
+ if (print_flag == NORMAL) {
+ supported_log_pages_normal(lid_dirs);
+ } else if (print_flag == JSON) {
+ supported_log_pages_json(lid_dirs);
+ }
+ }
+
+ /* Redundant close() to make static code analysis happy */
+ close(dev->direct.fd);
+ dev_close(dev);
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-log-page-dir.h b/plugins/solidigm/solidigm-log-page-dir.h
new file mode 100644
index 0000000..48777df
--- /dev/null
+++ b/plugins/solidigm/solidigm-log-page-dir.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Authors: karl.dedow@solidigm.com
+ */
+
+#ifndef SOLIDIGM_LOG_PAGE_DIRECTORY_H
+#define SOLIDIGM_LOG_PAGE_DIRECTORY_H
+
+struct command;
+struct plugin;
+
+int solidigm_get_log_page_directory_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin);
+
+#endif
diff --git a/plugins/solidigm/solidigm-market-log.c b/plugins/solidigm/solidigm-market-log.c
new file mode 100644
index 0000000..d7d38da
--- /dev/null
+++ b/plugins/solidigm/solidigm-market-log.c
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Authors: leonardo.da.cunha@solidigm.com
+ * Hardeep.Dhillon@solidigm.com
+ */
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <linux/limits.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "nvme-print.h"
+
+#define MARKET_LOG_MAX_SIZE 512
+
+int sldgm_get_market_log(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Get Solidigm Marketing Name log and show it.";
+ const char *raw = "dump output in binary format";
+ struct nvme_dev *dev;
+ char log[MARKET_LOG_MAX_SIZE];
+ int err;
+
+ struct config {
+ bool raw_binary;
+ };
+
+ struct config cfg = {
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = nvme_get_log_simple(dev_fd(dev), 0xdd, sizeof(log), log);
+ if (!err) {
+ if (!cfg.raw_binary)
+ printf("Solidigm Marketing Name Log:\n%s\n", log);
+ else
+ d_raw((unsigned char *)&log, sizeof(log));
+ } else if (err > 0)
+
+ nvme_show_status(err);
+ /* Redundant close() to make static code analysis happy */
+ close(dev->direct.fd);
+ dev_close(dev);
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-market-log.h b/plugins/solidigm/solidigm-market-log.h
new file mode 100644
index 0000000..6f808c4
--- /dev/null
+++ b/plugins/solidigm/solidigm-market-log.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: hardeep.dhillon@solidigm.com
+ */
+
+int sldgm_get_market_log(int argc, char **argv, struct command *cmd, struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-nvme.c b/plugins/solidigm/solidigm-nvme.c
new file mode 100644
index 0000000..3fb86f5
--- /dev/null
+++ b/plugins/solidigm/solidigm-nvme.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022-2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include "nvme.h"
+
+#define CREATE_CMD
+#include "solidigm-nvme.h"
+
+#include "solidigm-id-ctrl.h"
+#include "solidigm-smart.h"
+#include "solidigm-internal-logs.h"
+#include "solidigm-garbage-collection.h"
+#include "solidigm-latency-tracking.h"
+#include "solidigm-telemetry.h"
+#include "solidigm-log-page-dir.h"
+#include "solidigm-market-log.h"
+#include "solidigm-temp-stats.h"
+#include "solidigm-get-drive-info.h"
+#include "solidigm-ocp-version.h"
+
+#include "plugins/ocp/ocp-clear-features.h"
+#include "plugins/ocp/ocp-smart-extended-log.h"
+#include "plugins/ocp/ocp-fw-activation-history.h"
+
+static int id_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return __id_ctrl(argc, argv, cmd, plugin, sldgm_id_ctrl);
+}
+
+static int get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return solidigm_get_additional_smart_log(argc, argv, cmd, plugin);
+}
+
+static int get_internal_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return solidigm_get_internal_log(argc, argv, cmd, plugin);
+}
+
+static int get_garbage_collection_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return solidigm_get_garbage_collection_log(argc, argv, cmd, plugin);
+}
+
+static int get_latency_tracking_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return solidigm_get_latency_tracking_log(argc, argv, cmd, plugin);
+}
+
+static int get_telemetry_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return solidigm_get_telemetry_log(argc, argv, cmd, plugin);
+}
+
+static int clear_fw_update_history(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ return ocp_clear_fw_update_history(argc, argv, cmd, plugin);
+}
+
+static int clear_pcie_correctable_error_counters(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ return ocp_clear_pcie_correctable_errors(argc, argv, cmd, plugin);
+}
+
+static int smart_cloud(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ return ocp_smart_add_log(argc, argv, cmd, plugin);
+}
+
+static int fw_activation_history(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ return ocp_fw_activation_history_log(argc, argv, cmd, plugin);
+}
+
+static int get_log_page_directory_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ return solidigm_get_log_page_directory_log(argc, argv, cmd, plugin);
+}
+
+static int get_market_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ return sldgm_get_market_log(argc, argv, cmd, plugin);
+}
+
+static int get_temp_stats_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return sldgm_get_temp_stats_log(argc, argv, cmd, plugin);
+}
+
+static int get_drive_info(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return sldgm_get_drive_info(argc, argv, cmd, plugin);
+}
+
+static int get_cloud_SSDplugin_version(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ return sldgm_ocp_version(argc, argv, cmd, plugin);
+}
diff --git a/plugins/solidigm/solidigm-nvme.h b/plugins/solidigm/solidigm-nvme.h
new file mode 100644
index 0000000..bee8266
--- /dev/null
+++ b/plugins/solidigm/solidigm-nvme.h
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022-2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/solidigm/solidigm-nvme
+
+#if !defined(SOLIDIGM_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define SOLIDIGM_NVME
+
+#include "cmd.h"
+
+#define SOLIDIGM_PLUGIN_VERSION "1.1"
+
+PLUGIN(NAME("solidigm", "Solidigm vendor specific extensions", SOLIDIGM_PLUGIN_VERSION),
+ COMMAND_LIST(
+ ENTRY("id-ctrl", "Send NVMe Identify Controller", id_ctrl)
+ ENTRY("smart-log-add", "Retrieve Solidigm SMART Log", get_additional_smart_log)
+ ENTRY("vs-smart-add-log", "Get SMART / health extended log (redirects to ocp plug-in)", smart_cloud)
+ ENTRY("vs-internal-log", "Retrieve Debug log binaries", get_internal_log)
+ ENTRY("garbage-collect-log", "Retrieve Garbage Collection Log", get_garbage_collection_log)
+ ENTRY("market-log", "Retrieve Market Log", get_market_log)
+ ENTRY("latency-tracking-log", "Enable/Retrieve Latency tracking Log", get_latency_tracking_log)
+ ENTRY("parse-telemetry-log", "Parse Telemetry Log binary", get_telemetry_log)
+ ENTRY("clear-pcie-correctable-errors ", "Clear PCIe Correctable Error Counters (redirects to ocp plug-in)", clear_pcie_correctable_error_counters)
+ ENTRY("clear-fw-activate-history", "Clear firmware update history log (redirects to ocp plug-in)", clear_fw_update_history)
+ ENTRY("vs-fw-activate-history", "Get firmware activation history log (redirects to ocp plug-in)", fw_activation_history)
+ ENTRY("log-page-directory", "Retrieve log page directory", get_log_page_directory_log)
+ ENTRY("temp-stats", "Retrieve Temperature Statistics log", get_temp_stats_log)
+ ENTRY("vs-drive-info", "Retrieve drive information", get_drive_info)
+ ENTRY("cloud-SSDplugin-version", "Prints plug-in OCP version", get_cloud_SSDplugin_version)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/solidigm/solidigm-ocp-version.c b/plugins/solidigm/solidigm-ocp-version.c
new file mode 100644
index 0000000..4048cc1
--- /dev/null
+++ b/plugins/solidigm/solidigm-ocp-version.c
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <stdio.h>
+#include "nvme.h"
+
+int sldgm_ocp_version(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Prints OCP extensions version of Solidigm plugin";
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ int err = argconfig_parse(argc, argv, desc, opts);
+
+ if (!err)
+ printf("1.0\n");
+
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-ocp-version.h b/plugins/solidigm/solidigm-ocp-version.h
new file mode 100644
index 0000000..d79452c
--- /dev/null
+++ b/plugins/solidigm/solidigm-ocp-version.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+int sldgm_ocp_version(int argc, char **argv, struct command *cmd, struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-smart.c b/plugins/solidigm/solidigm-smart.c
new file mode 100644
index 0000000..62245fa
--- /dev/null
+++ b/plugins/solidigm/solidigm-smart.c
@@ -0,0 +1,271 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "nvme-print.h"
+
+#include "solidigm-smart.h"
+#include "solidigm-util.h"
+
+struct __packed nvme_additional_smart_log_item {
+ __u8 id;
+ __u8 _kp[2];
+ __u8 normalized;
+ __u8 _np;
+ union __packed {
+ __u8 raw[6];
+ struct __packed wear_level {
+ __le16 min;
+ __le16 max;
+ __le16 avg;
+ } wear_level;
+ struct __packed thermal_throttle {
+ __u8 pct;
+ __u32 count;
+ } thermal_throttle;
+ };
+ __u8 _rp;
+};
+
+#define VU_SMART_PAGE_SIZE 512
+#define VU_SMART_MAX_ITEMS (VU_SMART_PAGE_SIZE / sizeof(struct nvme_additional_smart_log_item))
+struct vu_smart_log {
+ struct nvme_additional_smart_log_item item[VU_SMART_MAX_ITEMS];
+};
+
+static char *id_to_name(__u8 id)
+{
+ switch (id) {
+ case 0x0D:
+ return "soft_ecc_error_rate";
+ case 0x05:
+ return "relocatable_sector_count";
+ case 0xAB:
+ return "program_fail_count";
+ case 0xAC:
+ return "erase_fail_count";
+ case 0xAD:
+ return "wear_leveling_count";
+ case 0xAE:
+ return "unexpected_power_loss";
+ case 0xB8:
+ return "e2e_error_detect_count";
+ case 0xC7:
+ return "crc_error_count";
+ case 0xE2:
+ return "media_wear_percentage";
+ case 0xE3:
+ return "timed_work_load_host_reads";
+ case 0xE4:
+ return "timed_work_load_timer";
+ case 0xE5:
+ return "read_commands_in_flight_counter";
+ case 0xE6:
+ return "write_commands_in_flight_counter";
+ case 0xEA:
+ return "thermal_throttle_status";
+ case 0xEE:
+ return "re_sku_count";
+ case 0xF0:
+ return "retry_buffer_overflow_counter";
+ case 0xF3:
+ return "pll_lock_loss_counter";
+ case 0xF4:
+ return "nand_bytes_written";
+ case 0xF5:
+ return "host_bytes_written";
+ case 0xF6:
+ return "host_context_wear_used";
+ case 0xF7:
+ return "performance_status_indicator";
+ case 0xF8:
+ return "media_bytes_read";
+ case 0xF9:
+ return "available_fw_downgrades";
+ case 0xFA:
+ return "host_read_collision_count";
+ case 0xFB:
+ return "host_write_collision_count";
+ case 0xFC:
+ return "xor_pass_count";
+ case 0xFD:
+ return "xor_fail_count";
+ case 0xFE:
+ return "xor_invoked_count";
+ default:
+ return "unknown";
+ }
+}
+
+static void smart_log_item_print(struct nvme_additional_smart_log_item *item)
+{
+ if (!item->id)
+ return;
+
+ printf("%#x %-45s %3d ",
+ item->id, id_to_name(item->id), item->normalized);
+
+ switch (item->id) {
+ case 0xAD:
+ printf("min: %u, max: %u, avg: %u\n",
+ le16_to_cpu(item->wear_level.min),
+ le16_to_cpu(item->wear_level.max),
+ le16_to_cpu(item->wear_level.avg));
+ return;
+ case 0xEA:
+ printf("%u%%, cnt: %u\n",
+ item->thermal_throttle.pct,
+ le32_to_cpu(item->thermal_throttle.count));
+ return;
+ default:
+ printf("%"PRIu64"\n", int48_to_long(item->raw));
+ }
+}
+
+static void smart_log_item_add_json(struct nvme_additional_smart_log_item *item, struct json_object *dev_stats)
+{
+ struct json_object *entry_stats = json_create_object();
+
+ if (!item->id)
+ return;
+
+ json_object_add_value_int(entry_stats, "normalized", item->normalized);
+
+ switch (item->id) {
+ case 0xAD:
+ json_object_add_value_int(entry_stats, "min", le16_to_cpu(item->wear_level.min));
+ json_object_add_value_int(entry_stats, "max", le16_to_cpu(item->wear_level.max));
+ json_object_add_value_int(entry_stats, "avg", le16_to_cpu(item->wear_level.avg));
+ break;
+ case 0xEA:
+ json_object_add_value_int(entry_stats, "percentage", item->thermal_throttle.pct);
+ json_object_add_value_int(entry_stats, "count", le32_to_cpu(item->thermal_throttle.count));
+ break;
+ default:
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(item->raw));
+ }
+ json_object_add_value_object(dev_stats, id_to_name(item->id), entry_stats);
+}
+
+static void vu_smart_log_show_json(struct vu_smart_log *payload, unsigned int nsid, const char *devname)
+{
+ struct json_object *dev_stats = json_create_object();
+ struct nvme_additional_smart_log_item *item = payload->item;
+ struct json_object *root;
+
+ for (int i = 0; i < VU_SMART_MAX_ITEMS; i++)
+ smart_log_item_add_json(&item[i], dev_stats);
+
+ root = json_create_object();
+ json_object_add_value_string(root, "Solidigm SMART log", devname);
+ json_object_add_value_object(root, "Device stats", dev_stats);
+
+ json_print_object(root, NULL);
+ json_free_object(root);
+}
+
+static void vu_smart_log_show(struct vu_smart_log *payload, unsigned int nsid, const char *devname,
+ __u8 uuid_index)
+{
+ struct nvme_additional_smart_log_item *item = payload->item;
+
+ printf("Additional Smart Log for NVMe device:%s namespace-id:%x UUID-idx:%d\n",
+ devname, nsid, uuid_index);
+ printf("ID KEY Normalized Raw\n");
+
+ for (int i = 0; i < VU_SMART_MAX_ITEMS; i++)
+ smart_log_item_print(&item[i]);
+}
+
+int solidigm_get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc =
+ "Get Solidigm vendor specific smart log (optionally, for the specified namespace), and show it.";
+ const int solidigm_vu_smart_log_id = 0xCA;
+ struct vu_smart_log smart_log_payload;
+ enum nvme_print_flags flags;
+ struct nvme_dev *dev;
+ int err;
+ __u8 uuid_index;
+
+ struct config {
+ __u32 namespace_id;
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .namespace_id = NVME_NSID_ALL,
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, "(optional) desired namespace"),
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = validate_output_format(cfg.output_format, &flags);
+ if (err < 0) {
+ fprintf(stderr, "Invalid output format '%s'\n", cfg.output_format);
+ dev_close(dev);
+ return err;
+ }
+
+ uuid_index = solidigm_get_vu_uuid_index(dev);
+
+ struct nvme_get_log_args args = {
+ .lpo = 0,
+ .result = NULL,
+ .log = &smart_log_payload,
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .lid = solidigm_vu_smart_log_id,
+ .len = sizeof(smart_log_payload),
+ .nsid = NVME_NSID_ALL,
+ .csi = NVME_CSI_NVM,
+ .lsi = NVME_LOG_LSI_NONE,
+ .lsp = NVME_LOG_LSP_NONE,
+ .uuidx = uuid_index,
+ .rae = false,
+ .ot = false,
+ };
+
+ err = nvme_get_log(&args);
+ if (!err) {
+ if (flags & JSON)
+ vu_smart_log_show_json(&smart_log_payload,
+ cfg.namespace_id, dev->name);
+ else if (flags & BINARY)
+ d_raw((unsigned char *)&smart_log_payload, sizeof(smart_log_payload));
+ else
+ vu_smart_log_show(&smart_log_payload, cfg.namespace_id,
+ dev->name, uuid_index);
+ } else if (err > 0) {
+ nvme_show_status(err);
+ }
+
+ /* Redundant close() to make static code analysis happy */
+ close(dev->direct.fd);
+ dev_close(dev);
+ return err;
+}
+
diff --git a/plugins/solidigm/solidigm-smart.h b/plugins/solidigm/solidigm-smart.h
new file mode 100644
index 0000000..e19ebe5
--- /dev/null
+++ b/plugins/solidigm/solidigm-smart.h
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+int solidigm_get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-telemetry.c b/plugins/solidigm/solidigm-telemetry.c
new file mode 100644
index 0000000..2bebccc
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "nvme-print.h"
+#include "solidigm-telemetry.h"
+#include "solidigm-telemetry/telemetry-log.h"
+#include "solidigm-telemetry/cod.h"
+#include "solidigm-telemetry/header.h"
+#include "solidigm-telemetry/config.h"
+#include "solidigm-telemetry/data-area.h"
+#include "solidigm-util.h"
+
+static int read_file2buffer(char *file_name, char **buffer, size_t *length)
+{
+ FILE *fd = fopen(file_name, "rb");
+
+ if (!fd)
+ return errno;
+
+ fseek(fd, 0, SEEK_END);
+ size_t length_bytes = ftell(fd);
+
+ fseek(fd, 0, SEEK_SET);
+
+ *buffer = malloc(length_bytes);
+ if (!*buffer) {
+ fclose(fd);
+ return errno;
+ }
+ *length = fread(*buffer, 1, length_bytes, fd);
+ fclose(fd);
+ return 0;
+}
+
+struct config {
+ __u32 host_gen;
+ bool ctrl_init;
+ int data_area;
+ char *cfg_file;
+ bool is_input_file;
+};
+
+int solidigm_get_telemetry_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Parse Solidigm Telemetry log";
+ const char *hgen = "Controls when to generate new host initiated report. Default value '1' generates new host initiated report, value '0' causes retrieval of existing log.";
+ const char *cgen = "Gather report generated by the controller.";
+ const char *dgen = "Pick which telemetry data area to report. Default is 3 to fetch areas 1-3. Valid options are 1, 2, 3, 4.";
+ const char *cfile = "JSON configuration file";
+ const char *sfile = "data source <device> is binary file containing log dump instead of block or character device";
+ struct nvme_dev *dev;
+
+ struct telemetry_log tl = {
+ .root = json_create_object(),
+ .log = NULL,
+ };
+
+ struct config cfg = {
+ .host_gen = 1,
+ .ctrl_init = false,
+ .data_area = -1,
+ .cfg_file = NULL,
+ .is_input_file = false,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("host-generate", 'g', &cfg.host_gen, hgen),
+ OPT_FLAG("controller-init", 'c', &cfg.ctrl_init, cgen),
+ OPT_UINT("data-area", 'd', &cfg.data_area, dgen),
+ OPT_FILE("config-file", 'j', &cfg.cfg_file, cfile),
+ OPT_FLAG("source-file", 's', &cfg.is_input_file, sfile),
+ OPT_END()
+ };
+
+ int err = argconfig_parse(argc, argv, desc, opts);
+
+ if (err)
+ goto ret;
+
+ /* When not selected on the command line, get minimum data area required */
+ if (cfg.data_area == -1)
+ cfg.data_area = cfg.cfg_file ? 3 : 1;
+
+ if (cfg.is_input_file) {
+ if (optind >= argc) {
+ err = errno = EINVAL;
+ perror(argv[0]);
+ goto ret;
+ }
+ char *binary_file_name = argv[optind];
+
+ err = read_file2buffer(binary_file_name, (char **)&tl.log, &tl.log_size);
+ } else {
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ }
+ if (err)
+ goto ret;
+
+ if (cfg.host_gen > 1) {
+ SOLIDIGM_LOG_WARNING("Invalid host-generate value '%d'", cfg.host_gen);
+ err = EINVAL;
+ goto close_fd;
+ }
+
+ if (cfg.cfg_file) {
+ char *conf_str = NULL;
+ size_t length = 0;
+
+ err = read_file2buffer(cfg.cfg_file, &conf_str, &length);
+ if (err) {
+ SOLIDIGM_LOG_WARNING("Failed to open JSON configuration file %s: %s!",
+ cfg.cfg_file, strerror(err));
+ goto close_fd;
+ }
+ struct json_tokener *jstok = json_tokener_new();
+
+ tl.configuration = json_tokener_parse_ex(jstok, conf_str, length);
+ free(conf_str);
+ if (jstok->err != json_tokener_success) {
+ SOLIDIGM_LOG_WARNING("Parsing error on JSON configuration file %s: %s (at offset %d)",
+ cfg.cfg_file,
+ json_tokener_error_desc(jstok->err),
+ jstok->char_offset);
+ json_tokener_free(jstok);
+ err = EINVAL;
+ goto close_fd;
+ }
+ json_tokener_free(jstok);
+ }
+
+ if (!cfg.is_input_file) {
+ size_t max_data_tx;
+
+ err = nvme_get_telemetry_max(dev_fd(dev), NULL, &max_data_tx);
+ if (err < 0) {
+ SOLIDIGM_LOG_WARNING("identify_ctrl: %s",
+ nvme_strerror(errno));
+ goto close_fd;
+ } else if (err > 0) {
+ nvme_show_status(err);
+ SOLIDIGM_LOG_WARNING("Failed to acquire identify ctrl %d!", err);
+ goto close_fd;
+ }
+ if (max_data_tx > DRIVER_MAX_TX_256K)
+ max_data_tx = DRIVER_MAX_TX_256K;
+
+ err = nvme_get_telemetry_log(dev_fd(dev), cfg.host_gen, cfg.ctrl_init, true,
+ max_data_tx, cfg.data_area, &tl.log, &tl.log_size);
+ if (err < 0) {
+ SOLIDIGM_LOG_WARNING("get-telemetry-log: %s",
+ nvme_strerror(errno));
+ goto close_fd;
+ } else if (err > 0) {
+ nvme_show_status(err);
+ SOLIDIGM_LOG_WARNING("Failed to acquire telemetry log %d!", err);
+ goto close_fd;
+ }
+ }
+ solidigm_telemetry_log_data_areas_parse(&tl, cfg.data_area);
+
+ json_print_object(tl.root, NULL);
+ json_free_object(tl.root);
+ printf("\n");
+
+close_fd:
+ if (!cfg.is_input_file) {
+ /* Redundant close() to make static code analysis happy */
+ close(dev->direct.fd);
+ dev_close(dev);
+ }
+ret:
+ json_free_object(tl.configuration);
+ free(tl.log);
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-telemetry.h b/plugins/solidigm/solidigm-telemetry.h
new file mode 100644
index 0000000..971ee2a
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry.h
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+int solidigm_get_telemetry_log(int argc, char **argv, struct command *cmd, struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-telemetry/cod.c b/plugins/solidigm/solidigm-telemetry/cod.c
new file mode 100644
index 0000000..363822a
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/cod.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+#include "common.h"
+#include "cod.h"
+
+const char *oemDataMapDesc[] = {
+ "Media Read Count", //Uid 0x00
+ "Host Read count", //Uid 0x01
+ "Media Write Count", //Uid 0x02
+ "Host Write Count", //Uid 0x03
+ "Device Model", // 0x04
+ "Serial Number", // 0x05
+ "Firmware Revision", // 0x06
+ "Drive Status", // 0x07
+ "Minimum Temperature", // 0x08
+ "Maximum Temperature", // 0x09
+ "Power Loss Protection Status", // 0x0a
+ "Lifetime Unsafe Shutdown Count", // 0x0b
+ "Lifetime Power Cycle Count", // 0x0c
+ "Minimum Read Latency", // 0x0d
+ "Maximum Read Latency", // 0x0e
+ "Average Read Latency", // 0x0f
+ "Minimum Write Latency", // 0x10
+ "Maximum Write Latency", // 0x11
+ "Average Write Latency", // 0x12
+ "Grown Defects Count", // 0x13
+ "DQS Recovery Count", // 0x14
+ "Program Fail Count", // 0x15
+ "Erase Fail Count", // 0x16
+ "Defrag Writes in Progress Count", // 0x17
+ "Total Defrag Writes Count", // 0x18
+ "Max Die Offline Number", // 0x19
+ "Current Die Offline Number", // 0x1A
+ "XOR Enable Status", // 0x1B
+ "Media Life Used", // 0x1C
+ "Uncorrectable Error Count", // 0x1D
+ "Current Wear Range Delta", // 0x1E
+ "Read Errors Corrected by XOR", // 0x1F
+ "Background Data Refresh", // 0x20
+ "Pmic Vin History Data 1 Min", // 0x21
+ "Pmic Vin History Data 1 Max", // 0x22
+ "Pmic Vin History Data 1 Avg", // 0x23
+ "Pmic Vin History Data 2 Min", // 0x24
+ "Pmic Vin History Data 2 Max", // 0x25
+ "Pmic Vin History Data 2 Avg", // 0x26
+ "Pmic Vin History Data Total Readings", // 0x27
+ "All Time Current Max Wear Level", // 0x28
+ "Media Wear Remaining", // 0x29
+ "Total Non-Defrag Writes", // 0x2A
+ "Media Health Relocations" //Uid 0x2B = 43
+};
+
+static const char *getOemDataMapDescription(uint32_t id)
+{
+ if (id < ARRAY_SIZE(oemDataMapDesc))
+ return oemDataMapDesc[id];
+ return "unknown";
+}
+
+#define OEMSIGNATURE 0x504D4443
+
+#pragma pack(push, cod, 1)
+struct cod_header {
+ uint32_t versionMajor;
+ uint32_t versionMinor;
+ uint32_t Signature; //!Fixed signature value (0x504D4443) for identification and validation
+ uint32_t MapSizeInBytes; //!Total size of the map data structure in bytes
+ uint32_t EntryCount; //!Total number of entries in the entry list
+ uint8_t Reserved[12];
+};
+
+struct cod_item {
+ uint32_t DataFieldMapUid; //!The data field unique identifier value
+ uint32_t reserved1 : 8;
+ uint32_t dataFieldType : 8;
+ uint32_t issigned : 1;
+ uint32_t bigEndian : 1;
+ uint32_t dataInvalid : 1;
+ uint32_t reserved2 : 13;
+ uint32_t DataFieldSizeInBytes;
+ uint8_t Reserved1[4];
+ uint64_t DataFieldOffset;
+ uint8_t Reserved2[8];
+};
+
+struct cod_map {
+ struct cod_header header;
+ struct cod_item items[];
+};
+
+#pragma pack(pop, cod)
+
+void solidigm_telemetry_log_cod_parse(struct telemetry_log *tl)
+{
+ enum cod_field_type {
+ INTEGER,
+ FLOAT,
+ STRING,
+ TWO_BYTE_ASCII,
+ FOUR_BYTE_ASCII,
+
+ UNKNOWN = 0xFF,
+ };
+ struct json_object *telemetry_header = NULL;
+ struct json_object *COD_offset = NULL;
+ struct json_object *reason_id = NULL;
+
+ if (!json_object_object_get_ex(tl->root, "telemetryHeader", &telemetry_header))
+ return;
+ if (!json_object_object_get_ex(telemetry_header, "reasonIdentifier", &reason_id))
+ return;
+ if (!json_object_object_get_ex(reason_id, "oemDataMapOffset", &COD_offset))
+ return;
+
+ uint64_t offset = json_object_get_int(COD_offset);
+
+ if (!offset)
+ return;
+
+ if ((offset + sizeof(struct cod_header)) > tl->log_size) {
+ SOLIDIGM_LOG_WARNING("Warning: COD map header out of bounds.");
+ return;
+ }
+
+ const struct cod_map *data = (struct cod_map *) (((uint8_t *)tl->log) + offset);
+
+ uint32_t signature = be32_to_cpu(data->header.Signature);
+
+ if (signature != OEMSIGNATURE) {
+ SOLIDIGM_LOG_WARNING("Warning: Unsupported COD data signature %x!", signature);
+ return;
+ }
+ if ((offset + data->header.MapSizeInBytes) > tl->log_size) {
+ SOLIDIGM_LOG_WARNING("Warning: COD map data out of bounds.");
+ return;
+ }
+
+ struct json_object *cod = json_create_object();
+
+ json_object_object_add(tl->root, "cod", cod);
+
+ for (uint64_t i = 0; i < data->header.EntryCount; i++) {
+ if ((offset + sizeof(struct cod_header) + (i + 1) * sizeof(struct cod_item)) >
+ tl->log_size) {
+ SOLIDIGM_LOG_WARNING("Warning: COD data out of bounds at item %"PRIu64"!",
+ i);
+ return;
+ }
+ struct cod_item item = data->items[i];
+
+ if (item.DataFieldOffset + item.DataFieldOffset > tl->log_size)
+ continue;
+ if (item.dataInvalid)
+ continue;
+ uint8_t *val = ((uint8_t *)tl->log) + item.DataFieldOffset;
+ const char *key = getOemDataMapDescription(item.DataFieldMapUid);
+
+ switch (item.dataFieldType) {
+ case INTEGER:
+ if (item.issigned)
+ json_object_object_add(cod, key,
+ json_object_new_int64(le64_to_cpu(*(uint64_t *)val)));
+ else
+ json_object_add_value_uint64(cod, key, le64_to_cpu(*(uint64_t *)val));
+ break;
+ case FLOAT:
+ json_object_add_value_float(cod, key, *(float *)val);
+ break;
+ case STRING:
+ json_object_object_add(cod, key,
+ json_object_new_string_len((const char *)val, item.DataFieldSizeInBytes));
+ break;
+ case TWO_BYTE_ASCII:
+ json_object_object_add(cod, key,
+ json_object_new_string_len((const char *)val, 2));
+ break;
+ case FOUR_BYTE_ASCII:
+ json_object_object_add(cod, key,
+ json_object_new_string_len((const char *)val, 4));
+ break;
+ default:
+ SOLIDIGM_LOG_WARNING("Warning: Unknown COD field type (%d)", item.DataFieldMapUid);
+ break;
+ }
+ }
+}
diff --git a/plugins/solidigm/solidigm-telemetry/cod.h b/plugins/solidigm/solidigm-telemetry/cod.h
new file mode 100644
index 0000000..032ccdc
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/cod.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include "telemetry-log.h"
+void solidigm_telemetry_log_cod_parse(struct telemetry_log *tl);
diff --git a/plugins/solidigm/solidigm-telemetry/config.c b/plugins/solidigm/solidigm-telemetry/config.c
new file mode 100644
index 0000000..eceeede
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/config.c
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "config.h"
+
+// max 16 bit unsigned integer number 65535
+#define MAX_16BIT_NUM_AS_STRING_SIZE 6
+
+#define OBJ_NAME_PREFIX "UID_"
+#define NLOG_OBJ_PREFIX OBJ_NAME_PREFIX "NLOG_"
+
+static bool config_get_by_version(const struct json_object *obj, int version_major,
+ int version_minor, struct json_object **value)
+{
+ char str_key[MAX_16BIT_NUM_AS_STRING_SIZE];
+ char str_subkey[MAX_16BIT_NUM_AS_STRING_SIZE];
+
+ snprintf(str_key, sizeof(str_key), "%d", version_major);
+ snprintf(str_subkey, sizeof(str_subkey), "%d", version_minor);
+ struct json_object *major_obj = NULL;
+
+ if (!json_object_object_get_ex(obj, str_key, &major_obj))
+ return false;
+ if (!json_object_object_get_ex(major_obj, str_subkey, value))
+ return false;
+ return value != NULL;
+}
+
+bool solidigm_config_get_struct_by_token_version(const struct json_object *config, int token_id,
+ int version_major, int version_minor,
+ struct json_object **value)
+{
+ struct json_object *token = NULL;
+ char str_key[MAX_16BIT_NUM_AS_STRING_SIZE];
+
+ snprintf(str_key, sizeof(str_key), "%d", token_id);
+ if (!json_object_object_get_ex(config, str_key, &token))
+ return false;
+ if (!config_get_by_version(token, version_major, version_minor, value))
+ return false;
+ return value != NULL;
+}
+
+const char *solidigm_config_get_nlog_obj_name(const struct json_object *config, uint32_t token)
+{
+ struct json_object *nlog_names = NULL;
+ struct json_object *obj_name;
+ char hex_header[STR_HEX32_SIZE];
+ const char *name;
+
+ if (!json_object_object_get_ex(config, "TELEMETRY_OBJECT_UIDS", &nlog_names))
+ return NULL;
+ snprintf(hex_header, STR_HEX32_SIZE, "0x%08X", token);
+
+ if (!json_object_object_get_ex(nlog_names, hex_header, &obj_name))
+ return NULL;
+ name = json_object_get_string(obj_name);
+ if (strncmp(NLOG_OBJ_PREFIX, name, strlen(NLOG_OBJ_PREFIX)))
+ return NULL;
+
+ return &name[strlen(OBJ_NAME_PREFIX)];
+}
+
+struct json_object *solidigm_config_get_nlog_formats(const struct json_object *config)
+{
+ struct json_object *nlog_formats = NULL;
+
+ json_object_object_get_ex(config, "NLOG_FORMATS", &nlog_formats);
+ return nlog_formats;
+}
diff --git a/plugins/solidigm/solidigm-telemetry/config.h b/plugins/solidigm/solidigm-telemetry/config.h
new file mode 100644
index 0000000..4e56ba3
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/config.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+#include <stdbool.h>
+#include "util/json.h"
+
+#define STR_HEX32_SIZE sizeof("0x00000000")
+
+bool solidigm_config_get_struct_by_token_version(const struct json_object *obj,
+ int key, int subkey,
+ int subsubkey,
+ struct json_object **value);
+
+const char *solidigm_config_get_nlog_obj_name(const struct json_object *config, uint32_t token);
+struct json_object *solidigm_config_get_nlog_formats(const struct json_object *config);
+
diff --git a/plugins/solidigm/solidigm-telemetry/data-area.c b/plugins/solidigm/solidigm-telemetry/data-area.c
new file mode 100644
index 0000000..14d612b
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/data-area.c
@@ -0,0 +1,472 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include "common.h"
+#include "header.h"
+#include "cod.h"
+#include "data-area.h"
+#include "config.h"
+#include "nlog.h"
+#include <ctype.h>
+
+#define SIGNED_INT_PREFIX "int"
+#define BITS_IN_BYTE 8
+
+#define MAX_WARNING_SIZE 1024
+#define MAX_ARRAY_RANK 16
+
+static bool telemetry_log_get_value(const struct telemetry_log *tl,
+ uint64_t offset_bit, uint32_t size_bit,
+ bool is_signed, struct json_object **val_obj)
+{
+ uint32_t offset_bit_from_byte;
+ uint32_t additional_size_byte;
+ uint32_t offset_byte;
+ uint64_t val;
+
+ if (!size_bit) {
+ char err_msg[MAX_WARNING_SIZE];
+
+ snprintf(err_msg, MAX_WARNING_SIZE,
+ "Value with size_bit=0 not supported.");
+ *val_obj = json_object_new_string(err_msg);
+
+ return false;
+ }
+ additional_size_byte = (size_bit - 1) ? (size_bit - 1) / BITS_IN_BYTE : 0;
+ offset_byte = (uint32_t)offset_bit / BITS_IN_BYTE;
+
+ if (offset_byte > (tl->log_size - additional_size_byte)) {
+ char err_msg[MAX_WARNING_SIZE];
+
+ snprintf(err_msg, MAX_WARNING_SIZE,
+ "Value offset greater than binary size (%u > %zu).",
+ offset_byte, tl->log_size);
+ *val_obj = json_object_new_string(err_msg);
+
+ return false;
+ }
+
+ offset_bit_from_byte = (uint32_t) (offset_bit - ((uint64_t)offset_byte * BITS_IN_BYTE));
+
+ if ((size_bit + offset_bit_from_byte) > (sizeof(uint64_t) * BITS_IN_BYTE)) {
+ char err_msg[MAX_WARNING_SIZE];
+
+ snprintf(err_msg, MAX_WARNING_SIZE,
+ "Value crossing 64 bit, byte aligned boundary, not supported. size_bit=%u, offset_bit_from_byte=%u.",
+ size_bit, offset_bit_from_byte);
+ *val_obj = json_object_new_string(err_msg);
+
+ return false;
+ }
+
+ val = *(uint64_t *)(((char *)tl->log) + offset_byte);
+ val >>= offset_bit_from_byte;
+ if (size_bit < 64)
+ val &= (1ULL << size_bit) - 1;
+ if (is_signed) {
+ if (val >> (size_bit - 1))
+ val |= (0ULL - 1) << size_bit;
+ *val_obj = json_object_new_int64(val);
+ } else {
+ *val_obj = json_object_new_uint64(val);
+ }
+
+ return true;
+}
+
+static int telemetry_log_structure_parse(const struct telemetry_log *tl,
+ struct json_object *struct_def,
+ uint64_t parent_offset_bit,
+ struct json_object *output,
+ struct json_object *metadata)
+{
+ struct json_object *obj_arraySizeArray = NULL;
+ struct json_object *obj = NULL;
+ struct json_object *obj_memberList;
+ struct json_object *major_dimension = NULL;
+ struct json_object *sub_output;
+ bool is_enumeration = false;
+ bool has_member_list;
+ const char *type = "";
+ const char *name;
+ size_t array_rank;
+ uint64_t offset_bit;
+ uint32_t size_bit;
+ uint64_t linear_array_pos_bit;
+ uint32_t array_size_dimension[MAX_ARRAY_RANK];
+
+ if (!json_object_object_get_ex(struct_def, "name", &obj)) {
+ SOLIDIGM_LOG_WARNING("Warning: Structure definition missing property 'name': %s",
+ json_object_to_json_string(struct_def));
+ return -1;
+ }
+
+ name = json_object_get_string(obj);
+
+ if (metadata) {
+ json_object_get(obj);
+ json_object_object_add(metadata, "objName", obj);
+ }
+
+ if (json_object_object_get_ex(struct_def, "type", &obj))
+ type = json_object_get_string(obj);
+
+ if (!json_object_object_get_ex(struct_def, "offsetBit", &obj)) {
+ SOLIDIGM_LOG_WARNING(
+ "Warning: Structure definition missing property 'offsetBit': %s",
+ json_object_to_json_string(struct_def));
+ return -1;
+ }
+
+ offset_bit = json_object_get_uint64(obj);
+
+ if (!json_object_object_get_ex(struct_def, "sizeBit", &obj)) {
+ SOLIDIGM_LOG_WARNING(
+ "Warning: Structure definition missing property 'sizeBit': %s",
+ json_object_to_json_string(struct_def));
+ return -1;
+ }
+
+ size_bit = (uint32_t)json_object_get_uint64(obj);
+
+ if (json_object_object_get_ex(struct_def, "enum", &obj))
+ is_enumeration = json_object_get_boolean(obj);
+
+ has_member_list = json_object_object_get_ex(struct_def,
+ "memberList",
+ &obj_memberList);
+
+ if (!json_object_object_get_ex(struct_def, "arraySize",
+ &obj_arraySizeArray)) {
+ SOLIDIGM_LOG_WARNING(
+ "Warning: Structure definition missing property 'arraySize': %s",
+ json_object_to_json_string(struct_def));
+ return -1;
+ }
+
+ array_rank = json_object_array_length(obj_arraySizeArray);
+ if (!array_rank) {
+ SOLIDIGM_LOG_WARNING(
+ "Warning: Structure property 'arraySize' don't support flexible array: %s",
+ json_object_to_json_string(struct_def));
+ return -1;
+ }
+ if (array_rank > MAX_ARRAY_RANK) {
+ SOLIDIGM_LOG_WARNING(
+ "Warning: Structure property 'arraySize' don't support more than %d dimensions: %s",
+ MAX_ARRAY_RANK, json_object_to_json_string(struct_def));
+ return -1;
+ }
+
+ for (size_t i = 0; i < array_rank; i++) {
+ struct json_object *dimension = json_object_array_get_idx(obj_arraySizeArray, i);
+
+ array_size_dimension[i] = json_object_get_int(dimension);
+ major_dimension = dimension;
+ }
+ if (array_rank > 1) {
+ uint32_t linear_pos_per_index = array_size_dimension[0];
+ uint32_t prev_index_offset_bit = 0;
+ struct json_object *dimension_output;
+
+ for (unsigned int i = 1; i < (array_rank - 1); i++)
+ linear_pos_per_index *= array_size_dimension[i];
+
+ dimension_output = json_create_array();
+ if (json_object_get_type(output) == json_type_array)
+ json_object_array_add(output, dimension_output);
+ else
+ json_object_add_value_array(output, name, dimension_output);
+
+ /*
+ * Make sure major_dimension object will not be
+ * deleted from memory when deleted from array
+ */
+ json_object_get(major_dimension);
+ json_object_array_del_idx(obj_arraySizeArray, array_rank - 1, 1);
+
+ for (unsigned int i = 0 ; i < array_size_dimension[0]; i++) {
+ struct json_object *sub_array = json_create_array();
+ uint64_t offset;
+
+ offset = parent_offset_bit + prev_index_offset_bit;
+
+ json_object_array_add(dimension_output, sub_array);
+ telemetry_log_structure_parse(tl, struct_def,
+ offset, sub_array, NULL);
+ prev_index_offset_bit += linear_pos_per_index * size_bit;
+ }
+
+ json_object_array_put_idx(obj_arraySizeArray, array_rank - 1,
+ major_dimension);
+
+ return 0;
+ }
+
+ linear_array_pos_bit = 0;
+ sub_output = output;
+
+ if (array_size_dimension[0] > 1) {
+ sub_output = json_create_array();
+ if (json_object_get_type(output) == json_type_array)
+ json_object_array_add(output, sub_output);
+ else
+ json_object_add_value_array(output, name, sub_output);
+ }
+
+ for (uint32_t j = 0; j < array_size_dimension[0]; j++) {
+ if (is_enumeration || !has_member_list) {
+ bool is_signed = !strncmp(type, SIGNED_INT_PREFIX, sizeof(SIGNED_INT_PREFIX)-1);
+ struct json_object *val_obj;
+ uint64_t offset;
+
+ offset = parent_offset_bit + offset_bit + linear_array_pos_bit;
+ if (telemetry_log_get_value(tl, offset, size_bit, is_signed, &val_obj)) {
+ if (array_size_dimension[0] > 1)
+ json_object_array_put_idx(sub_output, j, val_obj);
+ else
+ json_object_object_add(sub_output, name, val_obj);
+ } else {
+ SOLIDIGM_LOG_WARNING(
+ "Warning: %s From property '%s', array index %u, structure definition: %s",
+ json_object_get_string(val_obj), name, j,
+ json_object_to_json_string(struct_def));
+ json_free_object(val_obj);
+ }
+ } else {
+ struct json_object *sub_sub_output = json_create_object();
+ int num_members;
+
+ if (array_size_dimension[0] > 1)
+ json_object_array_put_idx(sub_output, j, sub_sub_output);
+ else
+ json_object_add_value_object(sub_output, name, sub_sub_output);
+
+ num_members = json_object_array_length(obj_memberList);
+ for (int k = 0; k < num_members; k++) {
+ struct json_object *member = json_object_array_get_idx(obj_memberList, k);
+ uint64_t offset;
+
+ offset = parent_offset_bit + offset_bit + linear_array_pos_bit;
+ telemetry_log_structure_parse(tl, member, offset,
+ sub_sub_output, NULL);
+ }
+ }
+ linear_array_pos_bit += size_bit;
+ }
+ return 0;
+}
+
+static int telemetry_log_data_area_get_offset(const struct telemetry_log *tl,
+ enum nvme_telemetry_da da,
+ uint32_t *offset, uint32_t *size)
+{
+ uint32_t offset_blocks = 1;
+ uint32_t last_block = tl->log->dalb1;
+ uint32_t last;
+
+ switch (da) {
+ case NVME_TELEMETRY_DA_1:
+ offset_blocks = 1;
+ last_block = tl->log->dalb1;
+ break;
+ case NVME_TELEMETRY_DA_2:
+ offset_blocks = tl->log->dalb1 + 1;
+ last_block = tl->log->dalb2;
+ break;
+ case NVME_TELEMETRY_DA_3:
+ offset_blocks = tl->log->dalb2 + 1;
+ last_block = tl->log->dalb3;
+ break;
+ case NVME_TELEMETRY_DA_4:
+ offset_blocks = tl->log->dalb3 + 1;
+ last_block = tl->log->dalb4;
+ break;
+ default:
+ return -1;
+ }
+
+ *offset = offset_blocks * NVME_LOG_TELEM_BLOCK_SIZE;
+ last = (last_block + 1) * NVME_LOG_TELEM_BLOCK_SIZE;
+ *size = last - *offset;
+ if ((*offset > tl->log_size) || (last > tl->log_size) || (last <= *offset)) {
+ SOLIDIGM_LOG_WARNING("Warning: Data Area %d don't fit this Telemetry log.", da);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int telemetry_log_nlog_parse(const struct telemetry_log *tl, struct json_object *formats,
+ uint64_t nlog_file_offset, uint64_t nlog_size,
+ struct json_object *output, struct json_object *metadata)
+{
+ /* boundary check */
+ if (tl->log_size < (nlog_file_offset + nlog_size)) {
+ const char *name = "";
+ int media_bank = -1;
+ struct json_object *jobj;
+
+ if (json_object_object_get_ex(metadata, "objName", &jobj))
+ name = json_object_get_string(jobj);
+ if (json_object_object_get_ex(metadata, "mediaBankId", &jobj))
+ media_bank = json_object_get_int(jobj);
+ SOLIDIGM_LOG_WARNING("%s:%d do not fit this log dump.", name, media_bank);
+ return -1;
+ }
+ return solidigm_nlog_parse(((char *) tl->log) + nlog_file_offset,
+ nlog_size, formats, metadata, output);
+}
+
+struct toc_item {
+ uint32_t OffsetBytes;
+ uint32_t ContentSizeBytes;
+};
+
+struct data_area_header {
+ uint8_t versionMajor;
+ uint8_t versionMinor;
+ uint16_t TableOfContentsCount;
+ uint32_t DataAreaSize;
+ uint8_t Reserved[8];
+};
+
+struct table_of_contents {
+ struct data_area_header header;
+ struct toc_item items[];
+};
+
+struct telemetry_object_header {
+ uint16_t versionMajor;
+ uint16_t versionMinor;
+ uint32_t Token;
+ uint8_t CoreId;
+ uint8_t Reserved[3];
+};
+
+static void telemetry_log_data_area_toc_parse(const struct telemetry_log *tl,
+ enum nvme_telemetry_da da,
+ struct json_object *toc_array,
+ struct json_object *tele_obj_array)
+{
+
+ const struct telemetry_object_header *header;
+ const struct table_of_contents *toc;
+ char *payload;
+ uint32_t da_offset;
+ uint32_t da_size;
+ struct json_object *nlog_formats;
+
+ if (telemetry_log_data_area_get_offset(tl, da, &da_offset, &da_size))
+ return;
+
+ toc = (struct table_of_contents *)(((char *)tl->log) + da_offset);
+ payload = (char *) tl->log;
+ nlog_formats = solidigm_config_get_nlog_formats(tl->configuration);
+
+ for (int i = 0; i < toc->header.TableOfContentsCount; i++) {
+ struct json_object *structure_definition = NULL;
+ struct json_object *toc_item;
+ uint32_t obj_offset;
+ bool has_struct;
+ const char *nlog_name = NULL;
+ uint32_t header_offset = sizeof(const struct telemetry_object_header);
+
+ if ((char *)&toc->items[i] >
+ (((char *)toc) + da_size - sizeof(const struct toc_item))) {
+ SOLIDIGM_LOG_WARNING(
+ "Warning: Data Area %d, Table of Contents item %d crossed Data Area size.",
+ da, i);
+ return;
+ }
+
+ obj_offset = toc->items[i].OffsetBytes;
+ if ((obj_offset + sizeof(const struct telemetry_object_header)) > da_size) {
+ SOLIDIGM_LOG_WARNING(
+ "Warning: Data Area %d, item %d data, crossed Data Area size.", da, i);
+ continue;
+ }
+
+ toc_item = json_create_object();
+ json_object_array_add(toc_array, toc_item);
+ json_object_add_value_uint(toc_item, "dataArea", da);
+ json_object_add_value_uint(toc_item, "dataAreaIndex", i);
+ json_object_add_value_uint(toc_item, "dataAreaOffset", obj_offset);
+ json_object_add_value_uint(toc_item, "fileOffset", obj_offset + da_offset);
+ json_object_add_value_uint(toc_item, "size", toc->items[i].ContentSizeBytes);
+
+ header = (const struct telemetry_object_header *) (payload + da_offset + obj_offset);
+ json_object_add_value_uint(toc_item, "telemMajor", header->versionMajor);
+ json_object_add_value_uint(toc_item, "telemMinor", header->versionMinor);
+ json_object_add_value_uint(toc_item, "objectId", header->Token);
+ json_object_add_value_uint(toc_item, "mediaBankId", header->CoreId);
+
+ has_struct = solidigm_config_get_struct_by_token_version(tl->configuration,
+ header->Token,
+ header->versionMajor,
+ header->versionMinor,
+ &structure_definition);
+ if (!has_struct) {
+ if (!nlog_formats)
+ continue;
+ nlog_name = solidigm_config_get_nlog_obj_name(tl->configuration,
+ header->Token);
+ if (!nlog_name)
+ continue;
+ }
+ struct json_object *tele_obj_item = json_create_object();
+
+ json_object_array_add(tele_obj_array, tele_obj_item);
+ json_object_get(toc_item);
+ json_object_add_value_object(tele_obj_item, "metadata", toc_item);
+ struct json_object *parsed_struct = json_create_object();
+
+ json_object_add_value_object(tele_obj_item, "objectData", parsed_struct);
+ struct json_object *obj_hasTelemObjHdr = NULL;
+ uint64_t object_file_offset;
+
+ if (json_object_object_get_ex(structure_definition,
+ "hasTelemObjHdr",
+ &obj_hasTelemObjHdr)) {
+ bool hasHeader = json_object_get_boolean(obj_hasTelemObjHdr);
+
+ if (hasHeader)
+ header_offset = 0;
+ }
+ object_file_offset = ((uint64_t)da_offset) + obj_offset + header_offset;
+ if (has_struct) {
+ telemetry_log_structure_parse(tl, structure_definition,
+ BITS_IN_BYTE * object_file_offset,
+ parsed_struct, toc_item);
+ } else if (nlog_formats) {
+ json_object_object_add(toc_item, "objName",
+ json_object_new_string(nlog_name));
+ telemetry_log_nlog_parse(tl, nlog_formats, object_file_offset,
+ toc->items[i].ContentSizeBytes - header_offset,
+ parsed_struct, toc_item);
+ }
+ }
+}
+
+int solidigm_telemetry_log_data_areas_parse(struct telemetry_log *tl,
+ enum nvme_telemetry_da last_da)
+{
+ struct json_object *tele_obj_array = json_create_array();
+ struct json_object *toc_array = json_create_array();
+
+ solidigm_telemetry_log_header_parse(tl);
+ solidigm_telemetry_log_cod_parse(tl);
+ if (tl->configuration) {
+ json_object_add_value_array(tl->root, "tableOfContents", toc_array);
+ json_object_add_value_array(tl->root, "telemetryObjects", tele_obj_array);
+
+ for (enum nvme_telemetry_da da = NVME_TELEMETRY_DA_1; da <= last_da; da++)
+ telemetry_log_data_area_toc_parse(tl, da, toc_array, tele_obj_array);
+ }
+ return 0;
+}
diff --git a/plugins/solidigm/solidigm-telemetry/data-area.h b/plugins/solidigm/solidigm-telemetry/data-area.h
new file mode 100644
index 0000000..6b690d8
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/data-area.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+#include "telemetry-log.h"
+
+int solidigm_telemetry_log_data_areas_parse(struct telemetry_log *tl,
+ enum nvme_telemetry_da last_da);
diff --git a/plugins/solidigm/solidigm-telemetry/header.c b/plugins/solidigm/solidigm-telemetry/header.c
new file mode 100644
index 0000000..866ebff
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/header.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include "common.h"
+#include "header.h"
+
+#pragma pack(push, reason_indentifier, 1)
+struct reason_indentifier_1_0 {
+ uint16_t versionMajor;
+ uint16_t versionMinor;
+ uint32_t reasonCode; //! 0 denotes no issue. All other values denote a potential issue.
+ char DriveStatus[20]; //! Drive Status String (for example: "Healthy", "*BAD_CONTEXT_2020")
+ char FirmwareVersion[12]; //! Similar to IdentifyController.FR
+ char BootloaderVersion[12]; //! Bootloader version string
+ char SerialNumber[20]; //! Device serial number
+ uint8_t Reserved[56]; //! Reserved for future usage
+};
+static_assert(sizeof(const struct reason_indentifier_1_0) ==
+ MEMBER_SIZE(struct nvme_telemetry_log, rsnident),
+ "Size mismatch for reason_indentifier_1_0");
+
+struct reason_indentifier_1_1 {
+ uint16_t versionMajor;
+ uint16_t versionMinor;
+ uint32_t reasonCode; //! 0 denotes no issue. All other values denote a potential issue.
+ char DriveStatus[20]; //! Drive Status String (for example: "Healthy", "*BAD_CONTEXT_2020")
+ char FirmwareVersion[12]; //! Similar to IdentifyController.FR
+ char BootloaderVersion[12]; //! Bootloader version string
+ char SerialNumber[20]; //! Device serial number
+ uint64_t OemDataMapOffset; //! Customer Data Map Object Log Offset
+ uint8_t TelemetryMajorVersion; //! Shadow of version in TOC
+ uint8_t TelemetryMinorVersion; //! Shadow of version in TOC
+ uint8_t Reserved[46]; //! Reserved for future usage
+};
+static_assert(sizeof(const struct reason_indentifier_1_1) ==
+ MEMBER_SIZE(struct nvme_telemetry_log, rsnident),
+ "Size mismatch for reason_indentifier_1_1");
+
+struct reason_indentifier_1_2 {
+ uint16_t versionMajor;
+ uint16_t versionMinor;
+ uint32_t reasonCode; //! 0 denotes no issue. All other values denote a potential issue.
+ char DriveStatus[20]; //! Drive Status String (for example: "Healthy", "*BAD_CONTEXT_2020")
+ uint8_t Reserved1[24]; //! pad over Fields removed from version 1.1
+ char SerialNumber[20]; //! Device serial number
+ uint64_t OemDataMapOffset; //! Customer Data Map Object Log Offset
+ uint8_t TelemetryMajorVersion; //! Shadow of version in TOC
+ uint8_t TelemetryMinorVersion; //! Shadow of version in TOC
+ uint8_t ProductFamilyId;
+ uint8_t Reserved2[5]; //! Reserved for future usage
+ uint8_t DualPortReserved[40]; //! Reserved for dual port
+};
+static_assert(sizeof(const struct reason_indentifier_1_2) ==
+ MEMBER_SIZE(struct nvme_telemetry_log, rsnident),
+ "Size mismatch for reason_indentifier_1_2");
+#pragma pack(pop, reason_indentifier)
+
+static void telemetry_log_reason_id_parse1_0_ext(const struct telemetry_log *tl,
+ struct json_object *reason_id)
+{
+ const struct reason_indentifier_1_0 *ri;
+ struct json_object *reserved;
+
+ ri = (struct reason_indentifier_1_0 *) tl->log->rsnident;
+ json_object_object_add(reason_id, "firmwareVersion",
+ json_object_new_string_len(ri->FirmwareVersion,
+ sizeof(ri->FirmwareVersion)));
+ json_object_object_add(reason_id, "bootloaderVersion",
+ json_object_new_string_len(ri->BootloaderVersion,
+ sizeof(ri->BootloaderVersion)));
+ json_object_object_add(reason_id, "serialNumber",
+ json_object_new_string_len(ri->SerialNumber,
+ sizeof(ri->SerialNumber)));
+
+ reserved = json_create_array();
+ json_object_add_value_array(reason_id, "reserved", reserved);
+ for (int i = 0; i < sizeof(ri->Reserved); i++) {
+ struct json_object *val = json_object_new_int(ri->Reserved[i]);
+
+ json_object_array_add(reserved, val);
+ }
+}
+
+static void telemetry_log_reason_id_parse1_1_ext(const struct telemetry_log *tl,
+ struct json_object *reason_id)
+{
+ const struct reason_indentifier_1_1 *ri;
+ struct json_object *reserved;
+
+ ri = (struct reason_indentifier_1_1 *) tl->log->rsnident;
+ json_object_object_add(reason_id, "firmwareVersion",
+ json_object_new_string_len(ri->FirmwareVersion,
+ sizeof(ri->FirmwareVersion)));
+ json_object_object_add(reason_id, "bootloaderVersion",
+ json_object_new_string_len(ri->BootloaderVersion,
+ sizeof(ri->BootloaderVersion)));
+ json_object_object_add(reason_id, "serialNumber",
+ json_object_new_string_len(ri->SerialNumber,
+ sizeof(ri->SerialNumber)));
+ json_object_add_value_uint64(reason_id, "oemDataMapOffset",
+ le64_to_cpu(ri->OemDataMapOffset));
+ json_object_add_value_uint(reason_id, "telemetryMajorVersion",
+ le16_to_cpu(ri->TelemetryMajorVersion));
+ json_object_add_value_uint(reason_id, "telemetryMinorVersion",
+ le16_to_cpu(ri->TelemetryMinorVersion));
+
+ reserved = json_create_array();
+ json_object_add_value_array(reason_id, "reserved", reserved);
+ for (int i = 0; i < sizeof(ri->Reserved); i++) {
+ struct json_object *val = json_object_new_int(ri->Reserved[i]);
+
+ json_object_array_add(reserved, val);
+ }
+}
+
+static void telemetry_log_reason_id_parse1_2_ext(const struct telemetry_log *tl,
+ struct json_object *reason_id)
+{
+ const struct reason_indentifier_1_2 *ri;
+ struct json_object *dp_reserved;
+ struct json_object *reserved;
+
+ ri = (struct reason_indentifier_1_2 *) tl->log->rsnident;
+
+ json_object_object_add(reason_id, "serialNumber",
+ json_object_new_string_len(ri->SerialNumber,
+ sizeof(ri->SerialNumber)));
+ json_object_add_value_uint64(reason_id, "oemDataMapOffset",
+ le64_to_cpu(ri->OemDataMapOffset));
+ json_object_add_value_uint(reason_id, "telemetryMajorVersion",
+ le16_to_cpu(ri->TelemetryMajorVersion));
+ json_object_add_value_uint(reason_id, "telemetryMinorVersion",
+ le16_to_cpu(ri->TelemetryMinorVersion));
+ json_object_add_value_uint(reason_id, "productFamilyId", ri->ProductFamilyId);
+
+ reserved = json_create_array();
+ json_object_add_value_array(reason_id, "reserved2", reserved);
+ for (int i = 0; i < sizeof(ri->Reserved2); i++) {
+ struct json_object *val = json_object_new_int(ri->Reserved2[i]);
+
+ json_object_array_add(reserved, val);
+ }
+
+ dp_reserved = json_create_array();
+ json_object_add_value_array(reason_id, "dualPortReserved", dp_reserved);
+ for (int i = 0; i < sizeof(ri->DualPortReserved); i++) {
+ struct json_object *val = json_object_new_int(ri->DualPortReserved[i]);
+
+ json_object_array_add(dp_reserved, val);
+ }
+}
+
+static void solidigm_telemetry_log_reason_id_parse(const struct telemetry_log *tl, struct json_object *reason_id)
+{
+ const struct reason_indentifier_1_0 *ri1_0 =
+ (struct reason_indentifier_1_0 *) tl->log->rsnident;
+ uint16_t version_major = le16_to_cpu(ri1_0->versionMajor);
+ uint16_t version_minor = le16_to_cpu(ri1_0->versionMinor);
+
+ json_object_add_value_uint(reason_id, "versionMajor", version_major);
+ json_object_add_value_uint(reason_id, "versionMinor", version_minor);
+ json_object_add_value_uint(reason_id, "reasonCode", le32_to_cpu(ri1_0->reasonCode));
+ json_object_add_value_object(reason_id, "driveStatus",
+ json_object_new_string_len(ri1_0->DriveStatus,
+ sizeof(ri1_0->DriveStatus)));
+ if (version_major == 1) {
+ switch (version_minor) {
+ case 0:
+ telemetry_log_reason_id_parse1_0_ext(tl, reason_id);
+ break;
+ case 1:
+ telemetry_log_reason_id_parse1_1_ext(tl, reason_id);
+ break;
+ default:
+ telemetry_log_reason_id_parse1_2_ext(tl, reason_id);
+ break;
+ }
+ }
+}
+
+bool solidigm_telemetry_log_header_parse(const struct telemetry_log *tl)
+{
+ const struct nvme_telemetry_log *log;
+ struct json_object *ieee_oui_id;
+ struct json_object *reason_id;
+ struct json_object *header;
+
+ if (tl->log_size < sizeof(const struct nvme_telemetry_log)) {
+ SOLIDIGM_LOG_WARNING("Telemetry log too short.");
+ return false;
+ }
+
+ header = json_create_object();
+
+ json_object_object_add(tl->root, "telemetryHeader", header);
+ log = tl->log;
+
+ json_object_add_value_uint(header, "logIdentifier", log->lpi);
+ ieee_oui_id = json_create_array();
+
+ json_object_object_add(header, "ieeeOuiIdentifier", ieee_oui_id);
+ for (int i = 0; i < sizeof(log->ieee); i++) {
+ struct json_object *val = json_object_new_int(log->ieee[i]);
+
+ json_object_array_add(ieee_oui_id, val);
+ }
+ json_object_add_value_uint(header, "dataArea1LastBlock", log->dalb1);
+ json_object_add_value_uint(header, "dataArea2LastBlock", log->dalb2);
+ json_object_add_value_uint(header, "dataArea3LastBlock", log->dalb3);
+ json_object_add_value_uint(header, "hostInitiatedDataGeneration", log->hostdgn);
+ json_object_add_value_uint(header, "controllerInitiatedDataAvailable", log->ctrlavail);
+ json_object_add_value_uint(header, "controllerInitiatedDataGeneration", log->ctrldgn);
+
+ reason_id = json_create_object();
+ json_object_add_value_object(header, "reasonIdentifier", reason_id);
+ solidigm_telemetry_log_reason_id_parse(tl, reason_id);
+
+ return true;
+}
diff --git a/plugins/solidigm/solidigm-telemetry/header.h b/plugins/solidigm/solidigm-telemetry/header.h
new file mode 100644
index 0000000..027af55
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/header.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include "telemetry-log.h"
+bool solidigm_telemetry_log_header_parse(const struct telemetry_log *tl);
diff --git a/plugins/solidigm/solidigm-telemetry/meson.build b/plugins/solidigm/solidigm-telemetry/meson.build
new file mode 100644
index 0000000..96273b7
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/meson.build
@@ -0,0 +1,7 @@
+sources += [
+ 'plugins/solidigm/solidigm-telemetry/cod.c',
+ 'plugins/solidigm/solidigm-telemetry/header.c',
+ 'plugins/solidigm/solidigm-telemetry/config.c',
+ 'plugins/solidigm/solidigm-telemetry/data-area.c',
+ 'plugins/solidigm/solidigm-telemetry/nlog.c',
+]
diff --git a/plugins/solidigm/solidigm-telemetry/nlog.c b/plugins/solidigm/solidigm-telemetry/nlog.c
new file mode 100644
index 0000000..926772b
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/nlog.c
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include "nlog.h"
+#include "config.h"
+#include <string.h>
+#include <stdio.h>
+
+#include "ccan/ilog/ilog.h"
+
+#define LOG_ENTRY_HEADER_SIZE 1
+#define LOG_ENTRY_TIMESTAMP_SIZE 2
+#define LOG_ENTRY_NUM_ARGS_MAX 8
+#define LOG_ENTRY_MAX_SIZE (LOG_ENTRY_HEADER_SIZE + LOG_ENTRY_TIMESTAMP_SIZE + \
+ LOG_ENTRY_NUM_ARGS_MAX)
+#define NUM_ARGS_MASK ((1 << ((int)STATIC_ILOG_32(LOG_ENTRY_NUM_ARGS_MAX))) - 1)
+#define MAX_HEADER_MISMATCH_TRACK 10
+
+static int formats_find(struct json_object *formats, uint32_t val, struct json_object **format)
+{
+ char hex_header[STR_HEX32_SIZE];
+
+ snprintf(hex_header, STR_HEX32_SIZE, "0x%08X", val);
+ return json_object_object_get_ex(formats, hex_header, format);
+}
+
+static uint32_t nlog_get_pos(const uint32_t *nlog, const uint32_t nlog_size, int pos)
+{
+ return nlog[pos % nlog_size];
+}
+
+static uint32_t nlog_get_events(const uint32_t *nlog, const uint32_t nlog_size, int start_offset,
+ struct json_object *formats, struct json_object *events, uint32_t *tail_mismatches)
+{
+ uint32_t event_count = 0;
+ int last_bad_header_pos = nlog_size + 1; // invalid nlog offset
+ uint32_t tail_count = 0;
+
+ for (int i = nlog_size - start_offset - 1; i >= -start_offset; i--) {
+ struct json_object *format;
+ uint32_t header = nlog_get_pos(nlog, nlog_size, i);
+ uint32_t num_data;
+
+ if (header == 0 || !formats_find(formats, header, &format)) {
+ if (event_count > 0) {
+ //check if fould circular buffer tail
+ if (i != (last_bad_header_pos - 1)) {
+ if (tail_mismatches &&
+ (tail_count < MAX_HEADER_MISMATCH_TRACK))
+ tail_mismatches[tail_count] = header;
+ tail_count++;
+ }
+ last_bad_header_pos = i;
+ }
+ continue;
+ }
+ num_data = header & NUM_ARGS_MASK;
+ if (events) {
+ struct json_object *event = json_object_new_array();
+ struct json_object *param = json_object_new_array();
+ uint32_t val = nlog_get_pos(nlog, nlog_size, i - 1);
+
+ json_object_array_add(events, event);
+ json_object_array_add(event, json_object_new_int64(val));
+ val = nlog_get_pos(nlog, nlog_size, i - 2);
+ json_object_array_add(event, json_object_new_int64(val));
+ json_object_array_add(event, json_object_new_int64(header));
+ json_object_array_add(event, param);
+ for (uint32_t j = 0; j < num_data; j++) {
+ val = nlog_get_pos(nlog, nlog_size, i - 3 - j);
+ json_object_array_add(param, json_object_new_int64(val));
+ }
+ json_object_get(format);
+ json_object_array_add(event, format);
+ }
+ i -= 2 + num_data;
+ event_count++;
+ }
+ return tail_count;
+}
+
+int solidigm_nlog_parse(const char *buffer, uint64_t buff_size, struct json_object *formats,
+ struct json_object *metadata, struct json_object *output)
+{
+ uint32_t smaller_tail_count = UINT32_MAX;
+ int best_offset = 0;
+ uint32_t offset_tail_mismatches[LOG_ENTRY_MAX_SIZE][MAX_HEADER_MISMATCH_TRACK];
+ struct json_object *events = json_object_new_array();
+ const uint32_t *nlog = (uint32_t *)buffer;
+ const uint32_t nlog_size = buff_size / sizeof(uint32_t);
+
+ for (int i = 0; i < LOG_ENTRY_MAX_SIZE; i++) {
+ uint32_t tail_count = nlog_get_events(nlog, nlog_size, i, formats, NULL,
+ offset_tail_mismatches[i]);
+ if (tail_count < smaller_tail_count) {
+ best_offset = i;
+ smaller_tail_count = tail_count;
+ }
+ if (tail_count == 0)
+ break;
+ }
+ if (smaller_tail_count > 1) {
+ const char *name = "";
+ int media_bank = -1;
+ char str_mismatches[(STR_HEX32_SIZE + 1) * MAX_HEADER_MISMATCH_TRACK];
+ int pos = 0;
+ int show_mismatch_num = smaller_tail_count < MAX_HEADER_MISMATCH_TRACK ?
+ smaller_tail_count : MAX_HEADER_MISMATCH_TRACK;
+ struct json_object *jobj;
+
+ if (json_object_object_get_ex(metadata, "objName", &jobj))
+ name = json_object_get_string(jobj);
+ if (json_object_object_get_ex(metadata, "mediaBankId", &jobj))
+ media_bank = json_object_get_int(jobj);
+
+ for (int i = 0; i < show_mismatch_num; i++)
+ pos += snprintf(&str_mismatches[pos], STR_HEX32_SIZE + 1, "0x%08X ",
+ offset_tail_mismatches[best_offset][i]);
+
+ SOLIDIGM_LOG_WARNING("%s:%d with %d header mismatches ( %s). Configuration file may be missing format headers.",
+ name, media_bank, smaller_tail_count, str_mismatches);
+ }
+ nlog_get_events(nlog, nlog_size, best_offset, formats, events, NULL);
+
+ json_object_object_add(output, "events", events);
+ return 0;
+}
diff --git a/plugins/solidigm/solidigm-telemetry/nlog.h b/plugins/solidigm/solidigm-telemetry/nlog.h
new file mode 100644
index 0000000..f33aa45
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/nlog.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+#include "telemetry-log.h"
+
+int solidigm_nlog_parse(const char *buffer, uint64_t bufer_size,
+ struct json_object *formats, struct json_object *metadata,
+ struct json_object *output);
diff --git a/plugins/solidigm/solidigm-telemetry/telemetry-log.h b/plugins/solidigm/solidigm-telemetry/telemetry-log.h
new file mode 100644
index 0000000..e9eff73
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/telemetry-log.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#ifndef _SOLIDIGM_TELEMETRY_LOG_H
+#define _SOLIDIGM_TELEMETRY_LOG_H
+
+#include "libnvme.h"
+#include "util/json.h"
+#include <assert.h>
+
+#if !defined __cplusplus
+#define static_assert _Static_assert
+#endif
+
+#define VA_ARGS(...), ##__VA_ARGS__
+#define SOLIDIGM_LOG_WARNING(format, ...) fprintf(stderr, format"\n" VA_ARGS(__VA_ARGS__))
+
+#define MEMBER_SIZE(type, member) sizeof(((type *)0)->member)
+
+struct telemetry_log {
+ struct nvme_telemetry_log *log;
+ size_t log_size;
+ struct json_object *root;
+ struct json_object *configuration;
+};
+
+#endif /* _SOLIDIGM_TELEMETRY_LOG_H */
diff --git a/plugins/solidigm/solidigm-temp-stats.c b/plugins/solidigm/solidigm-temp-stats.c
new file mode 100644
index 0000000..85a3c37
--- /dev/null
+++ b/plugins/solidigm/solidigm-temp-stats.c
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <errno.h>
+
+#include "common.h"
+#include "nvme-print.h"
+#include "solidigm-util.h"
+
+#define SLDGM_TEMP_STATS_LID 0xC5
+
+struct temp_stats {
+ __le64 curr;
+ __le64 last_overtemp;
+ __le64 life_overtemp;
+ __le64 highest_temp;
+ __le64 lowest_temp;
+ __u8 rsvd[40];
+ __le64 max_operating_temp;
+ __le64 min_operating_temp;
+ __le64 est_offset;
+};
+
+static void show_temp_stats(struct temp_stats *stats)
+{
+ printf("Current temperature : %"PRIu64"\n", le64_to_cpu(stats->curr));
+ printf("Last critical overtemp flag : %"PRIu64"\n", le64_to_cpu(stats->last_overtemp));
+ printf("Life critical overtemp flag : %"PRIu64"\n", le64_to_cpu(stats->life_overtemp));
+ printf("Highest temperature : %"PRIu64"\n", le64_to_cpu(stats->highest_temp));
+ printf("Lowest temperature : %"PRIu64"\n", le64_to_cpu(stats->lowest_temp));
+ printf("Max operating temperature : %"PRIu64"\n", le64_to_cpu(stats->max_operating_temp));
+ printf("Min operating temperature : %"PRIu64"\n", le64_to_cpu(stats->min_operating_temp));
+ printf("Estimated offset : %"PRIu64"\n", le64_to_cpu(stats->est_offset));
+}
+
+int sldgm_get_temp_stats_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ unsigned char buffer[4096] = {0};
+ struct nvme_dev *dev;
+ __u8 uuid_idx;
+ int err;
+
+ const char *desc = "Get/show Temperature Statistics log.";
+ const char *raw = "dump output in binary format";
+ struct config {
+ bool raw_binary;
+ };
+
+ struct config cfg = {
+ .raw_binary = false,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ uuid_idx = solidigm_get_vu_uuid_index(dev);
+
+ struct nvme_get_log_args args = {
+ .lpo = 0,
+ .result = NULL,
+ .log = buffer,
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .uuidx = uuid_idx,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .lid = SLDGM_TEMP_STATS_LID,
+ .len = sizeof(buffer),
+ .nsid = NVME_NSID_ALL,
+ .csi = NVME_CSI_NVM,
+ .lsi = NVME_LOG_LSI_NONE,
+ .lsp = NVME_LOG_LSP_NONE,
+ .rae = false,
+ .ot = false,
+ };
+
+ err = nvme_get_log(&args);
+ if (!err) {
+ uint64_t *guid = (uint64_t *)&buffer[4080];
+
+ if (guid[1] == 0xC7BB98B7D0324863 && guid[0] == 0xBB2C23990E9C722F) {
+ fprintf(stderr, "Error: Log page has 'OCP unsupported Requirements' GUID\n");
+ err = -EBADMSG;
+ goto closefd;
+ }
+ if (!cfg.raw_binary)
+ show_temp_stats((struct temp_stats *) buffer);
+ else
+ d_raw(buffer, sizeof(struct temp_stats));
+ } else if (err > 0) {
+ nvme_show_status(err);
+ }
+
+closefd:
+ /* Redundant close() to make static code analysis happy */
+ close(dev->direct.fd);
+ dev_close(dev);
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-temp-stats.h b/plugins/solidigm/solidigm-temp-stats.h
new file mode 100644
index 0000000..58d5a86
--- /dev/null
+++ b/plugins/solidigm/solidigm-temp-stats.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+int sldgm_get_temp_stats_log(int argc, char **argv, struct command *cmd, struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-util.c b/plugins/solidigm/solidigm-util.c
new file mode 100644
index 0000000..0171a49
--- /dev/null
+++ b/plugins/solidigm/solidigm-util.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include "plugins/ocp/ocp-utils.h"
+#include "solidigm-util.h"
+
+__u8 solidigm_get_vu_uuid_index(struct nvme_dev *dev)
+{
+ int ocp_uuid_index = 0;
+
+ if (ocp_get_uuid_index(dev, &ocp_uuid_index) == 0)
+ if (ocp_uuid_index == 2)
+ return 1;
+
+ return 0;
+}
diff --git a/plugins/solidigm/solidigm-util.h b/plugins/solidigm/solidigm-util.h
new file mode 100644
index 0000000..fa5032f
--- /dev/null
+++ b/plugins/solidigm/solidigm-util.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include "nvme.h"
+
+#define DRIVER_MAX_TX_256K (256 * 1024)
+
+__u8 solidigm_get_vu_uuid_index(struct nvme_dev *dev);
diff --git a/plugins/toshiba/toshiba-nvme.c b/plugins/toshiba/toshiba-nvme.c
new file mode 100644
index 0000000..4927012
--- /dev/null
+++ b/plugins/toshiba/toshiba-nvme.c
@@ -0,0 +1,573 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "nvme-print.h"
+
+#define CREATE_CMD
+#include "toshiba-nvme.h"
+
+static const __u32 OP_SCT_STATUS = 0xE0;
+static const __u32 OP_SCT_COMMAND_TRANSFER = 0xE0;
+static const __u32 OP_SCT_DATA_TRANSFER = 0xE1;
+
+static const __u32 DW10_SCT_STATUS_COMMAND;
+static const __u32 DW10_SCT_COMMAND_TRANSFER = 0x1;
+
+static const __u32 DW11_SCT_STATUS_COMMAND;
+static const __u32 DW11_SCT_COMMAND_TRANSFER;
+
+static const __u16 INTERNAL_LOG_ACTION_CODE = 0xFFFB;
+static const __u16 CURRENT_LOG_FUNCTION_CODE = 0x0001;
+static const __u16 SAVED_LOG_FUNCTION_CODE = 0x0002;
+
+/* A bitmask field for supported devices */
+enum {
+ MASK_0 = 1 << 0,
+ MASK_1 = 1 << 1,
+ /*
+ * Future devices can use the remaining 31 bits from this field
+ * and should use 1 << 2, 1 << 3, etc.
+ */
+ MASK_IGNORE = 0
+};
+
+/* Internal device codes */
+enum {
+ CODE_0 = 0x0D,
+ CODE_1 = 0x10
+};
+
+
+static int nvme_sct_op(int fd, __u32 opcode, __u32 cdw10, __u32 cdw11, void *data, __u32 data_len)
+{
+ void *metadata = NULL;
+ const __u32 cdw2 = 0;
+ const __u32 cdw3 = 0;
+ const __u32 cdw12 = 0;
+ const __u32 cdw13 = 0;
+ const __u32 cdw14 = 0;
+ const __u32 cdw15 = 0;
+ const __u32 timeout = 0;
+ const __u32 metadata_len = 0;
+ const __u32 namespace_id = 0x0;
+ const __u32 flags = 0;
+ const __u32 rsvd = 0;
+ __u32 result;
+
+ return nvme_admin_passthru(fd, opcode, flags, rsvd, namespace_id, cdw2, cdw3, cdw10, cdw11,
+ cdw12, cdw13, cdw14, cdw15, data_len, data, metadata_len,
+ metadata, timeout, &result);
+}
+
+static int nvme_get_sct_status(int fd, __u32 device_mask)
+{
+ int err;
+ void *data = NULL;
+ size_t data_len = 512;
+ unsigned char *status;
+ __u32 supported = 0;
+
+ if (posix_memalign(&data, getpagesize(), data_len))
+ return -ENOMEM;
+
+ memset(data, 0, data_len);
+ err = nvme_sct_op(fd, OP_SCT_STATUS, DW10_SCT_STATUS_COMMAND, DW11_SCT_STATUS_COMMAND, data, data_len);
+ if (err) {
+ fprintf(stderr, "%s: SCT status failed :%d\n", __func__, err);
+ goto end;
+ }
+
+ status = data;
+ if (status[0] != 1U) {
+ /* Eek, wrong version in status header */
+ fprintf(stderr, "%s: unexpected value in SCT status[0]:(%x)\n", __func__, status[0]);
+ err = -1;
+ errno = EINVAL;
+ goto end;
+ }
+
+ /* Check if device is supported */
+ if (device_mask != MASK_IGNORE) {
+ switch (status[1]) {
+ case CODE_0:
+ supported = (device_mask & MASK_0);
+ break;
+ case CODE_1:
+ supported = (device_mask & MASK_1);
+ break;
+ default:
+ break;
+ };
+
+ if (!supported) {
+ fprintf(stderr, "%s: command unsupported on this device: (0x%x)\n", __func__, status[1]);
+ err = -1;
+ errno = EINVAL;
+ goto end;
+ }
+ }
+end:
+ if (data)
+ free(data);
+ return err;
+}
+
+static int nvme_sct_command_transfer_log(int fd, bool current)
+{
+ int err;
+ void *data = NULL;
+ size_t data_len = 512;
+ __u16 function_code, action_code = INTERNAL_LOG_ACTION_CODE;
+
+ if (current)
+ function_code = CURRENT_LOG_FUNCTION_CODE;
+ else
+ function_code = SAVED_LOG_FUNCTION_CODE;
+
+ if (posix_memalign(&data, getpagesize(), data_len))
+ return -ENOMEM;
+
+ memset(data, 0, data_len);
+ memcpy(data, &action_code, sizeof(action_code));
+ memcpy(data + 2, &function_code, sizeof(function_code));
+
+ err = nvme_sct_op(fd, OP_SCT_COMMAND_TRANSFER, DW10_SCT_COMMAND_TRANSFER, DW11_SCT_COMMAND_TRANSFER, data, data_len);
+ free(data);
+ return err;
+}
+
+static int nvme_sct_data_transfer(int fd, void *data, size_t data_len, size_t offset)
+{
+ __u32 dw10, dw11, lba_count = (data_len) / 512;
+
+ if (lba_count) {
+ /*
+ * the count is a 0-based value, which seems to mean
+ * that it's actually last lba
+ */
+ --lba_count;
+ }
+
+ dw10 = (offset << 16) | lba_count;
+ dw11 = (offset >> 16);
+ return nvme_sct_op(fd, OP_SCT_DATA_TRANSFER, dw10, dw11, data, data_len);
+}
+
+static int d_raw_to_fd(const unsigned char *buf, unsigned int len, int fd)
+{
+ int written = 0;
+ int remaining = len;
+
+ while (remaining) {
+ written = write(fd, buf, remaining);
+ if (written < 0) {
+ remaining = written;
+ break;
+ } else if (written <= remaining) {
+ remaining -= written;
+ } else {
+ /* Unexpected overwrite */
+ break;
+ }
+ }
+
+ /* return 0 on success or remaining/error */
+ return remaining;
+}
+
+/* Display progress (incoming 0->1.0) */
+static void progress_runner(float progress)
+{
+ const size_t barWidth = 70;
+ size_t i, pos;
+
+ fprintf(stdout, "[");
+ pos = barWidth * progress;
+ for (i = 0; i < barWidth; ++i) {
+ if (i <= pos)
+ fprintf(stdout, "=");
+ else
+ fprintf(stdout, " ");
+ }
+
+ fprintf(stdout, "] %d %%\r", (int)(progress * 100.0));
+ fflush(stdout);
+}
+
+static int nvme_get_internal_log(int fd, const char *const filename, bool current)
+{
+ int err;
+ int o_fd = -1;
+ void *page_data = NULL;
+ const size_t page_sector_len = 32;
+ const size_t page_data_len = page_sector_len * 512; /* 32 sectors per page */
+ uint32_t *area1_last_page;
+ uint32_t *area2_last_page;
+ uint32_t *area3_last_page;
+ uint32_t log_sectors = 0;
+ size_t pages;
+ __u32 pages_chunk;
+ /*
+ * By trial and error it seems that the largest transfer chunk size
+ * is 128 * 32 = 4k sectors = 2MB
+ */
+ const __u32 max_pages = 128;
+ size_t i;
+ unsigned int j;
+ float progress = 0.0;
+
+ err = nvme_sct_command_transfer_log(fd, current);
+ if (err) {
+ fprintf(stderr, "%s: SCT command transfer failed\n", __func__);
+ goto end;
+ }
+
+ if (posix_memalign(&page_data, getpagesize(), max_pages * page_data_len)) {
+ err = ENOMEM;
+ goto end;
+ }
+ memset(page_data, 0, max_pages * page_data_len);
+
+ /* Read the header to get the last log page - offsets 8->11, 12->15, 16->19 */
+ err = nvme_sct_data_transfer(fd, page_data, page_data_len, 0);
+ if (err) {
+ fprintf(stderr, "%s: SCT data transfer failed, page 0\n", __func__);
+ goto end;
+ }
+
+ area1_last_page = (uint32_t *) (page_data + 8);
+ area2_last_page = (uint32_t *) (page_data + 12);
+ area3_last_page = (uint32_t *) (page_data + 16);
+
+ /* The number of total log sectors is the maximum + 1; */
+ if (*area1_last_page > log_sectors)
+ log_sectors = *area1_last_page;
+ if (*area2_last_page > log_sectors)
+ log_sectors = *area2_last_page;
+ if (*area3_last_page > log_sectors)
+ log_sectors = *area3_last_page;
+
+ ++log_sectors;
+ pages = log_sectors / page_sector_len;
+ if (filename == NULL) {
+ fprintf(stdout, "Page: %u of %zu\n", 0u, pages);
+ d(page_data, page_data_len, 16, 1);
+ } else {
+ progress_runner(progress);
+ o_fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (o_fd < 0) {
+ fprintf(stderr, "%s: couldn't output file %s\n", __func__, filename);
+ err = -EINVAL;
+ goto end;
+ }
+ err = d_raw_to_fd(page_data, page_data_len, o_fd);
+ if (err) {
+ fprintf(stderr, "%s: couldn't write all data to output file\n", __func__);
+ goto end;
+ }
+ }
+
+ /* Now read the rest */
+ for (i = 1; i < pages;) {
+ pages_chunk = max_pages;
+ if (pages_chunk + i >= pages)
+ pages_chunk = pages - i;
+
+ err = nvme_sct_data_transfer(fd, page_data,
+ pages_chunk * page_data_len,
+ i * page_sector_len);
+ if (err) {
+ fprintf(stderr, "%s: SCT data transfer command failed\n", __func__);
+ goto end;
+ }
+
+ progress = (float) (i) / (float) (pages);
+ progress_runner(progress);
+ if (filename == NULL) {
+ for (j = 0; j < pages_chunk; ++j) {
+ fprintf(stdout, "Page: %zu of %zu\n", i + j, pages);
+ d(page_data + (j * page_data_len), page_data_len, 16, 1);
+ }
+ } else {
+ progress_runner(progress);
+ err = d_raw_to_fd(page_data, pages_chunk * page_data_len, o_fd);
+ if (err) {
+ fprintf(stderr, "%s: couldn't write all data to output file\n",
+ __func__);
+ goto end;
+ }
+ }
+ i += pages_chunk;
+ }
+ progress = 1.0f;
+ progress_runner(progress);
+ fprintf(stdout, "\n");
+ err = nvme_get_sct_status(fd, MASK_IGNORE);
+ if (err) {
+ fprintf(stderr, "%s: bad SCT status\n", __func__);
+ goto end;
+ }
+end:
+ if (o_fd >= 0)
+ close(o_fd);
+ if (page_data)
+ free(page_data);
+ return err;
+}
+
+static int nvme_get_internal_log_file(int fd, const char *const filename, bool current)
+{
+ int err;
+
+ /* Check device supported */
+ err = nvme_get_sct_status(fd, MASK_0 | MASK_1);
+ if (!err)
+ err = nvme_get_internal_log(fd, filename, current);
+ return err;
+}
+
+enum LOG_PAGE_C0 {
+ ERROR_LOG_C0 = 0,
+ SMART_HEALTH_LOG_C0,
+ FIRMWARE_SLOT_INFO_C0,
+ COMMAND_EFFECTS_C0,
+ DEVICE_SELF_TEST_C0,
+ LOG_PAGE_DIRECTORY_C0,
+ SMART_ATTRIBUTES_C0,
+ NR_SMART_ITEMS_C0,
+};
+
+struct nvme_xdn_smart_log_c0 {
+ __u8 items[NR_SMART_ITEMS_C0];
+ __u8 resv[512 - NR_SMART_ITEMS_C0];
+};
+
+static void default_show_vendor_log_c0(struct nvme_dev *dev, __u32 nsid,
+ struct nvme_xdn_smart_log_c0 *smart)
+{
+ printf("Vendor Log Page Directory 0xC0 for NVME device:%s namespace-id:%x\n",
+ dev->name, nsid);
+ printf("Error Log : %u\n", smart->items[ERROR_LOG_C0]);
+ printf("SMART Health Log : %u\n", smart->items[SMART_HEALTH_LOG_C0]);
+ printf("Firmware Slot Info : %u\n", smart->items[FIRMWARE_SLOT_INFO_C0]);
+ printf("Command Effects : %u\n", smart->items[COMMAND_EFFECTS_C0]);
+ printf("Device Self Test : %u\n", smart->items[DEVICE_SELF_TEST_C0]);
+ printf("Log Page Directory : %u\n", smart->items[LOG_PAGE_DIRECTORY_C0]);
+ printf("SMART Attributes : %u\n", smart->items[SMART_ATTRIBUTES_C0]);
+}
+
+static int nvme_get_vendor_log(struct nvme_dev *dev, __u32 namespace_id,
+ int log_page, const char *const filename)
+{
+ int err;
+ void *log = NULL;
+ size_t log_len = 512;
+
+ if (posix_memalign(&log, getpagesize(), log_len)) {
+ err = ENOMEM;
+ goto end;
+ }
+
+ /* Check device supported */
+ err = nvme_get_sct_status(dev_fd(dev), MASK_0 | MASK_1);
+ if (err)
+ goto end;
+ err = nvme_get_nsid_log(dev_fd(dev), false, log_page, namespace_id,
+ log_len, log);
+ if (err) {
+ fprintf(stderr, "%s: couldn't get log 0x%x\n", __func__,
+ log_page);
+ goto end;
+ }
+ if (filename) {
+ int o_fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+
+ if (o_fd < 0) {
+ fprintf(stderr, "%s: couldn't output file %s\n",
+ __func__, filename);
+ err = -EINVAL;
+ goto end;
+ }
+ err = d_raw_to_fd(log, log_len, o_fd);
+ if (err) {
+ fprintf(stderr, "%s: couldn't write all data to output file %s\n",
+ __func__, filename);
+ /* Attempt following close */
+ }
+ if (close(o_fd)) {
+ err = errno;
+ goto end;
+ }
+ } else {
+ if (log_page == 0xc0)
+ default_show_vendor_log_c0(dev, namespace_id, log);
+ else
+ d(log, log_len, 16, 1);
+ }
+end:
+ if (log)
+ free(log);
+ return err;
+}
+
+static int vendor_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ char *desc = "Get extended SMART information and show it.";
+ const char *namespace = "(optional) desired namespace";
+ const char *output_file = "(optional) binary output filename";
+ const char *log = "(optional) log ID (0xC0, or 0xCA), default 0xCA";
+ struct nvme_dev *dev;
+ int err;
+
+ struct config {
+ __u32 namespace_id;
+ const char *output_file;
+ int log;
+ };
+
+ struct config cfg = {
+ .namespace_id = 0xffffffff,
+ .output_file = NULL,
+ .log = 0xca
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace),
+ OPT_FILE("output-file", 'o', &cfg.output_file, output_file),
+ OPT_UINT("log", 'l', &cfg.log, log),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err) {
+ fprintf(stderr, "%s: failed to parse arguments\n", __func__);
+ return -EINVAL;
+ }
+
+ if ((cfg.log != 0xC0) && (cfg.log != 0xCA)) {
+ fprintf(stderr, "%s: invalid log page 0x%x - should be 0xC0 or 0xCA\n", __func__, cfg.log);
+ err = -EINVAL;
+ goto end;
+ }
+
+ err = nvme_get_vendor_log(dev, cfg.namespace_id, cfg.log,
+ cfg.output_file);
+ if (err)
+ fprintf(stderr, "%s: couldn't get vendor log 0x%x\n", __func__, cfg.log);
+end:
+ if (err > 0)
+ nvme_show_status(err);
+ dev_close(dev);
+ return err;
+}
+
+static int internal_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ char *desc = "Get internal status log and show it.";
+ const char *output_file = "(optional) binary output filename";
+ const char *prev_log = "(optional) use previous log. Otherwise uses current log.";
+ struct nvme_dev *dev;
+ int err;
+
+ struct config {
+ const char *output_file;
+ bool prev_log;
+ };
+
+ struct config cfg = {
+ .output_file = NULL,
+ .prev_log = false
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FILE("output-file", 'o', &cfg.output_file, output_file),
+ OPT_FLAG("prev-log", 'p', &cfg.prev_log, prev_log),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err) {
+ fprintf(stderr, "%s: failed to parse arguments\n", __func__);
+ return -EINVAL;
+ }
+
+ if (cfg.prev_log)
+ printf("Getting previous log\n");
+ else
+ printf("Getting current log\n");
+
+ err = nvme_get_internal_log_file(dev_fd(dev), cfg.output_file,
+ !cfg.prev_log);
+ if (err < 0)
+ fprintf(stderr, "%s: couldn't get fw log\n", __func__);
+ if (err > 0)
+ nvme_show_status(err);
+
+ dev_close(dev);
+ return err;
+}
+
+static int clear_correctable_errors(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ char *desc = "Clear PCIe correctable error count.";
+ const __u32 namespace_id = 0xFFFFFFFF;
+ const __u32 feature_id = 0xCA;
+ const __u32 value = 1; /* Bit0 - reset clear PCIe correctable count */
+ const __u32 cdw12 = 0;
+ const bool save = false;
+ struct nvme_dev *dev;
+ __u32 result;
+ int err;
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err) {
+ fprintf(stderr, "%s: failed to parse arguments\n", __func__);
+ return -EINVAL;
+ }
+
+ /* Check device supported */
+ err = nvme_get_sct_status(dev_fd(dev), MASK_0 | MASK_1);
+ if (err)
+ goto end;
+
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = feature_id,
+ .nsid = namespace_id,
+ .cdw11 = value,
+ .cdw12 = cdw12,
+ .save = save,
+ .uuidx = 0,
+ .cdw15 = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+ err = nvme_set_features(&args);
+ if (err)
+ fprintf(stderr, "%s: couldn't clear PCIe correctable errors\n",
+ __func__);
+end:
+ if (err > 0)
+ nvme_show_status(err);
+ dev_close(dev);
+ return err;
+}
diff --git a/plugins/toshiba/toshiba-nvme.h b/plugins/toshiba/toshiba-nvme.h
new file mode 100644
index 0000000..6208f5d
--- /dev/null
+++ b/plugins/toshiba/toshiba-nvme.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/toshiba/toshiba-nvme
+
+#if !defined(TOSHIBA_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define TOSHIBA_NVME
+
+#include "cmd.h"
+#include "plugin.h"
+
+PLUGIN(NAME("toshiba", "Toshiba NVME plugin", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("vs-smart-add-log", "Extended SMART information", vendor_log)
+ ENTRY("vs-internal-log", "Get Internal Log", internal_log)
+ ENTRY("clear-pcie-correctable-errors", "Clear PCIe correctable error count", clear_correctable_errors)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/transcend/transcend-nvme.c b/plugins/transcend/transcend-nvme.c
new file mode 100644
index 0000000..547fbf4
--- /dev/null
+++ b/plugins/transcend/transcend-nvme.c
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+
+#define CREATE_CMD
+#include "transcend-nvme.h"
+
+static const __u32 OP_BAD_BLOCK = 0xc2;
+static const __u32 DW10_BAD_BLOCK = 0x400;
+static const __u32 DW12_BAD_BLOCK = 0x5a;
+
+static int getHealthValue(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ struct nvme_smart_log smart_log;
+ char *desc = "Get nvme health percentage.";
+ int percent_used = 0, healthvalue = 0;
+ struct nvme_dev *dev;
+ int result;
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ result = parse_and_open(&dev, argc, argv, desc, opts);
+ if (result) {
+ printf("\nDevice not found\n");
+ return -1;
+ }
+ result = nvme_get_log_smart(dev_fd(dev), 0xffffffff, false, &smart_log);
+ if (!result) {
+ printf("Transcend NVME heath value: ");
+ percent_used = smart_log.percent_used;
+
+ if (percent_used > 100 || percent_used < 0) {
+ printf("0%%\n");
+ } else {
+ healthvalue = 100 - percent_used;
+ printf("%d%%\n", healthvalue);
+ }
+ }
+ dev_close(dev);
+ return result;
+}
+
+static int getBadblock(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+
+ char *desc = "Get nvme bad block number.";
+ struct nvme_dev *dev;
+ int result;
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ result = parse_and_open(&dev, argc, argv, desc, opts);
+ if (result) {
+ printf("\nDevice not found\n");
+ return -1;
+ }
+ unsigned char data[1] = {0};
+ struct nvme_passthru_cmd nvmecmd;
+
+ memset(&nvmecmd, 0, sizeof(nvmecmd));
+ nvmecmd.opcode = OP_BAD_BLOCK;
+ nvmecmd.cdw10 = DW10_BAD_BLOCK;
+ nvmecmd.cdw12 = DW12_BAD_BLOCK;
+ nvmecmd.addr = (__u64)(uintptr_t)data;
+ nvmecmd.data_len = 0x1;
+ result = nvme_submit_admin_passthru(dev_fd(dev), &nvmecmd, NULL);
+ if (!result) {
+ int badblock = data[0];
+
+ printf("Transcend NVME badblock count: %d\n", badblock);
+ }
+ dev_close(dev);
+ return result;
+}
diff --git a/plugins/transcend/transcend-nvme.h b/plugins/transcend/transcend-nvme.h
new file mode 100644
index 0000000..9c89883
--- /dev/null
+++ b/plugins/transcend/transcend-nvme.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/transcend/transcend-nvme
+
+#if !defined(TRANSCEND_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define TRANSCEND_NVME
+
+#include "cmd.h"
+
+
+PLUGIN(NAME("transcend", "Transcend vendor specific extensions", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("healthvalue", "NVME health percentage", getHealthValue)
+ ENTRY("badblock", "Get NVME bad block number", getBadblock)
+
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/virtium/virtium-nvme.c b/plugins/virtium/virtium-nvme.c
new file mode 100644
index 0000000..0ba4b15
--- /dev/null
+++ b/plugins/virtium/virtium-nvme.c
@@ -0,0 +1,1051 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <time.h>
+#include <locale.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "util/types.h"
+
+#define CREATE_CMD
+#include "virtium-nvme.h"
+
+#define MIN2(a, b) (((a) < (b)) ? (a) : (b))
+
+#define HOUR_IN_SECONDS 3600
+
+#define MAX_HEADER_BUFF (20 * 1024)
+#define MAX_LOG_BUFF 4096
+#define DEFAULT_TEST_NAME "Put the name of your test here"
+
+static char vt_default_log_file_name[256];
+
+struct vtview_log_header {
+ char path[256];
+ char test_name[256];
+ long time_stamp;
+ struct nvme_id_ctrl raw_ctrl;
+ struct nvme_firmware_slot raw_fw;
+};
+
+struct vtview_smart_log_entry {
+ char path[256];
+ long time_stamp;
+ struct nvme_id_ns raw_ns;
+ struct nvme_id_ctrl raw_ctrl;
+ struct nvme_smart_log raw_smart;
+};
+
+struct vtview_save_log_settings {
+ double run_time_hrs;
+ double log_record_frequency_hrs;
+ const char *output_file;
+ const char *test_name;
+};
+
+static void vt_initialize_header_buffer(struct vtview_log_header *pbuff)
+{
+ memset(pbuff->path, 0, sizeof(pbuff->path));
+ memset(pbuff->test_name, 0, sizeof(pbuff->test_name));
+}
+
+static void vt_convert_data_buffer_to_hex_string(const unsigned char *bufPtr,
+ const unsigned int size, const bool isReverted, char *output)
+{
+ unsigned int i, pos;
+ const char hextable[16] = {
+ '0', '1', '2', '3',
+ '4', '5', '6', '7',
+ '8', '9', 'A', 'B',
+ 'C', 'D', 'E', 'F',
+ };
+
+ memset(output, 0, (size * 2) + 1);
+
+ for (i = 0; i < size; i++) {
+ if (isReverted)
+ pos = size - 1 - i;
+ else
+ pos = i;
+ output[2 * i] = hextable[(bufPtr[pos] & 0xF0) >> 4];
+ output[2 * i + 1] = hextable[(bufPtr[pos] & 0x0F)];
+ }
+}
+
+/*
+ * Generate log file name.
+ * Log file name will be generated automatically if user leave log file option blank.
+ * Log file name will be generated as vtView-Smart-log-date-time.txt
+ */
+static void vt_generate_vtview_log_file_name(char *fname)
+{
+ time_t current;
+ struct tm tstamp;
+ char temp[256];
+
+ time(&current);
+
+ tstamp = *localtime(&current);
+ snprintf(temp, sizeof(temp), "./vtView-Smart-log-");
+ strcat(fname, temp);
+ strftime(temp, sizeof(temp), "%Y-%m-%d", &tstamp);
+ strcat(fname, temp);
+ snprintf(temp, sizeof(temp), ".txt");
+ strcat(fname, temp);
+}
+
+static void vt_convert_smart_data_to_human_readable_format(struct vtview_smart_log_entry *smart, char *text)
+{
+ char tempbuff[1024] = "";
+ int i;
+ int temperature = ((smart->raw_smart.temperature[1] << 8) | smart->raw_smart.temperature[0]) - 273;
+ double capacity;
+ char *curlocale;
+ char *templocale;
+ __u8 lba_index;
+
+ nvme_id_ns_flbas_to_lbaf_inuse(smart->raw_ns.flbas, &lba_index);
+
+ curlocale = setlocale(LC_ALL, NULL);
+ templocale = strdup(curlocale);
+
+ if (!templocale)
+ printf("Cannot malloc buffer\n");
+
+ setlocale(LC_ALL, "C");
+
+ unsigned long long lba = 1ULL << smart->raw_ns.lbaf[lba_index].ds;
+
+ capacity = le64_to_cpu(smart->raw_ns.nsze) * lba;
+
+ snprintf(tempbuff, sizeof(tempbuff), "log;%s;%lu;%s;%s;%-.*s;", smart->raw_ctrl.sn, smart->time_stamp, smart->path,
+ smart->raw_ctrl.mn, (int)sizeof(smart->raw_ctrl.fr), smart->raw_ctrl.fr);
+ strcpy(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Capacity;%lf;", capacity / 1000000000);
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Critical_Warning;%u;", smart->raw_smart.critical_warning);
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Temperature;%u;", temperature);
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Available_Spare;%u;", smart->raw_smart.avail_spare);
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Available_Spare_Threshold;%u;", smart->raw_smart.spare_thresh);
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Percentage_Used;%u;", smart->raw_smart.percent_used);
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Data_Units_Read;%s;", uint128_t_to_string(le128_to_cpu(smart->raw_smart.data_units_read)));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Data_Units_Written;%s;", uint128_t_to_string(le128_to_cpu(smart->raw_smart.data_units_written)));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Host_Read_Commands;%s;", uint128_t_to_string(le128_to_cpu(smart->raw_smart.host_reads)));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Host_Write_Commands;%s;", uint128_t_to_string(le128_to_cpu(smart->raw_smart.host_writes)));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Controller_Busy_Time;%s;", uint128_t_to_string(le128_to_cpu(smart->raw_smart.ctrl_busy_time)));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Power_Cycles;%s;", uint128_t_to_string(le128_to_cpu(smart->raw_smart.power_cycles)));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Power_On_Hours;%s;", uint128_t_to_string(le128_to_cpu(smart->raw_smart.power_on_hours)));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Unsafe_Shutdowns;%s;", uint128_t_to_string(le128_to_cpu(smart->raw_smart.unsafe_shutdowns)));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Media_Errors;%s;", uint128_t_to_string(le128_to_cpu(smart->raw_smart.media_errors)));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Num_Err_Log_Entries;%s;", uint128_t_to_string(le128_to_cpu(smart->raw_smart.num_err_log_entries)));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Warning_Temperature_Time;%u;", le32_to_cpu(smart->raw_smart.warning_temp_time));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Critical_Composite_Temperature_Time;%u;", le32_to_cpu(smart->raw_smart.critical_comp_time));
+ strcat(text, tempbuff);
+
+ for (i = 0; i < 8; i++) {
+ __s32 temp = le16_to_cpu(smart->raw_smart.temp_sensor[i]);
+
+ if (!temp) {
+ snprintf(tempbuff, sizeof(tempbuff), "Temperature_Sensor_%d;NC;", i);
+ strcat(text, tempbuff);
+ continue;
+ }
+ snprintf(tempbuff, sizeof(tempbuff), "Temperature_Sensor_%d;%d;", i, temp - 273);
+ strcat(text, tempbuff);
+ }
+
+ snprintf(tempbuff, sizeof(tempbuff), "Thermal_Management_T1_Trans_Count;%u;", le32_to_cpu(smart->raw_smart.thm_temp1_trans_count));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Thermal_Management_T2_Trans_Count;%u;", le32_to_cpu(smart->raw_smart.thm_temp2_trans_count));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Thermal_Management_T1_Total_Time;%u;", le32_to_cpu(smart->raw_smart.thm_temp1_total_time));
+ strcat(text, tempbuff);
+ snprintf(tempbuff, sizeof(tempbuff), "Thermal_Management_T2_Total_Time;%u;", le32_to_cpu(smart->raw_smart.thm_temp2_total_time));
+ strcat(text, tempbuff);
+
+ snprintf(tempbuff, sizeof(tempbuff), "NandWrites;%d;\n", 0);
+ strcat(text, tempbuff);
+
+ setlocale(LC_ALL, templocale);
+ free(templocale);
+}
+
+static void vt_header_to_string(const struct vtview_log_header *header, char *text)
+{
+ char timebuff[50] = "";
+ char tempbuff[MAX_HEADER_BUFF] = "";
+ char identext[16384] = "";
+ char fwtext[2048] = "";
+
+ strftime(timebuff, 50, "%Y-%m-%d %H:%M:%S", localtime(&(header->time_stamp)));
+ snprintf(tempbuff, MAX_HEADER_BUFF, "header;{\"session\":{\"testName\":\"%s\",\"dateTime\":\"%s\"},",
+ header->test_name, timebuff);
+ strcpy(text, tempbuff);
+
+ vt_convert_data_buffer_to_hex_string((unsigned char *)&(header->raw_ctrl), sizeof(header->raw_ctrl), false, identext);
+ vt_convert_data_buffer_to_hex_string((unsigned char *)&(header->raw_fw), sizeof(header->raw_fw), false, fwtext);
+ snprintf(tempbuff, MAX_HEADER_BUFF,
+ "\"devices\":[{\"model\":\"%s\",\"port\":\"%s\",\"SN\":\"%s\",\"type\":\"NVMe\",\"identify\":\"%s\",\"firmwareSlot\":\"%s\"}]}\n",
+ header->raw_ctrl.mn, header->path, header->raw_ctrl.sn, identext, fwtext);
+ strcat(text, tempbuff);
+}
+
+static int vt_append_text_file(const char *text, const char *filename)
+{
+ FILE *f;
+
+ f = fopen(filename, "a");
+ if (!f) {
+ printf("Cannot open %s\n", filename);
+ return -1;
+ }
+
+ fprintf(f, "%s", text);
+ fclose(f);
+ return 0;
+}
+
+static int vt_append_log(struct vtview_smart_log_entry *smart, const char *filename)
+{
+ char sm_log_text[MAX_LOG_BUFF] = "";
+
+ vt_convert_smart_data_to_human_readable_format(smart, sm_log_text);
+ return vt_append_text_file(sm_log_text, filename);
+}
+
+static int vt_append_header(const struct vtview_log_header *header, const char *filename)
+{
+ char header_text[MAX_HEADER_BUFF] = "";
+
+ vt_header_to_string(header, header_text);
+ return vt_append_text_file(header_text, filename);
+}
+
+static void vt_process_string(char *str, const size_t size)
+{
+ size_t i;
+
+ if (!size)
+ return;
+
+ i = size - 1;
+ while (i && (' ' == str[i])) {
+ str[i] = 0;
+ i--;
+ }
+}
+
+static int vt_add_entry_to_log(const int fd, const char *path, const struct vtview_save_log_settings *cfg)
+{
+ struct vtview_smart_log_entry smart;
+ const char *filename;
+ int ret = 0;
+ unsigned int nsid = 0;
+
+ memset(smart.path, 0, sizeof(smart.path));
+ strncpy(smart.path, path, sizeof(smart.path) - 1);
+ if (!cfg->output_file)
+ filename = vt_default_log_file_name;
+ else
+ filename = cfg->output_file;
+
+ smart.time_stamp = time(NULL);
+ ret = nvme_get_nsid(fd, &nsid);
+
+ if (ret < 0) {
+ printf("Cannot read namespace-id\n");
+ return -1;
+ }
+
+ ret = nvme_identify_ns(fd, nsid, &smart.raw_ns);
+ if (ret) {
+ printf("Cannot read namespace identify\n");
+ return -1;
+ }
+
+ ret = nvme_identify_ctrl(fd, &smart.raw_ctrl);
+ if (ret) {
+ printf("Cannot read device identify controller\n");
+ return -1;
+ }
+
+ ret = nvme_get_log_smart(fd, NVME_NSID_ALL, false, &smart.raw_smart);
+ if (ret) {
+ printf("Cannot read device SMART log\n");
+ return -1;
+ }
+
+ vt_process_string(smart.raw_ctrl.sn, sizeof(smart.raw_ctrl.sn));
+ vt_process_string(smart.raw_ctrl.mn, sizeof(smart.raw_ctrl.mn));
+
+ ret = vt_append_log(&smart, filename);
+ return ret;
+}
+
+static int vt_update_vtview_log_header(const int fd, const char *path, const struct vtview_save_log_settings *cfg)
+{
+ struct vtview_log_header header;
+ const char *filename;
+ int ret = 0;
+
+ vt_initialize_header_buffer(&header);
+ if (strlen(path) > sizeof(header.path)) {
+ printf("filename too long\n");
+ errno = EINVAL;
+ return -1;
+ }
+ strcpy(header.path, path);
+
+ if (!cfg->test_name) {
+ strcpy(header.test_name, DEFAULT_TEST_NAME);
+ } else {
+ if (strlen(cfg->test_name) > sizeof(header.test_name)) {
+ printf("test name too long\n");
+ errno = EINVAL;
+ return -1;
+ }
+ strcpy(header.test_name, cfg->test_name);
+ }
+
+ if (!cfg->output_file)
+ filename = vt_default_log_file_name;
+ else
+ filename = cfg->output_file;
+
+ printf("Log file: %s\n", filename);
+ header.time_stamp = time(NULL);
+
+ ret = nvme_identify_ctrl(fd, &header.raw_ctrl);
+ if (ret) {
+ printf("Cannot read identify device\n");
+ return -1;
+ }
+
+ ret = nvme_get_log_fw_slot(fd, false, &header.raw_fw);
+ if (ret) {
+ printf("Cannot read device firmware log\n");
+ return -1;
+ }
+
+ vt_process_string(header.raw_ctrl.sn, sizeof(header.raw_ctrl.sn));
+ vt_process_string(header.raw_ctrl.mn, sizeof(header.raw_ctrl.mn));
+
+ ret = vt_append_header(&header, filename);
+ return ret;
+}
+
+static void vt_build_identify_lv2(unsigned int data, unsigned int start,
+ unsigned int count, const char **table,
+ bool isEnd)
+{
+ unsigned int i, end, pos, sh = 1;
+ unsigned int temp;
+
+ end = start + count;
+
+ for (i = start; i < end; i++) {
+ temp = ((data & (sh << i)) >> i);
+ pos = i * 2;
+ printf(" \"bit %u\":\"%ub %s\"\n", i, temp, table[pos]);
+ printf(" %s", table[pos + 1]);
+
+ if ((end - 1) != i || !isEnd)
+ printf(",\n");
+ else
+ printf("\n");
+ }
+
+ if (isEnd)
+ printf(" },\n");
+}
+
+static void vt_build_power_state_descriptor(const struct nvme_id_ctrl *ctrl)
+{
+ unsigned int i;
+ unsigned char *buf;
+
+ printf("{\n");
+ printf("\"Power State Descriptors\":{\n");
+ printf(" \"NOPS\":\"Non-Operational State,\"\n");
+ printf(" \"MPS\":\"Max Power Scale (0: in 0.01 Watts; 1: in 0.0001 Watts),\"\n");
+ printf(" \"ENLAT\":\"Entry Latency in microseconds,\"\n");
+ printf(" \"RWL\":\"Relative Write Latency,\"\n");
+ printf(" \"RRL\":\"Relative Read Latency,\"\n");
+ printf(" \"IPS\":\"Idle Power Scale (00b: Not reported; 01b: 0.0001 W; 10b: 0.01 W; 11b: Reserved),\"\n");
+ printf(" \"APS\":\"Active Power Scale (00b: Not reported; 01b: 0.0001 W; 10b: 0.01 W; 11b: Reserved),\"\n");
+ printf(" \"ACTP\":\"Active Power,\"\n");
+ printf(" \"MP\":\"Maximum Power,\"\n");
+ printf(" \"EXLAT\":\"Exit Latency in microsecond,\"\n");
+ printf(" \"RWT\":\"Relative Write Throughput,\"\n");
+ printf(" \"RRT\":\"Relative Read Throughput,\"\n");
+ printf(" \"IDLP\":\"Idle Power,\"\n");
+ printf(" \"APW\":\"Active Power Workload,\"\n");
+ printf(" \"Ofs\":\"BYTE Offset,\"\n");
+
+ printf(" \"Power State Descriptors\":\"\n");
+
+ printf("%6s%10s%5s%4s%6s%10s%10s%10s%4s%4s%4s%4s%10s%4s%6s%10s%4s%5s%6s\n", "Entry", "0fs 00-03", "NOPS", "MPS", "MP", "ENLAT", "EXLAT", "0fs 12-15",
+ "RWL", "RWT", "RRL", "RRT", "0fs 16-19", "IPS", "IDLP", "0fs 20-23", "APS", "APW", "ACTP");
+
+
+ printf("%6s%10s%5s%4s%6s%10s%10s%10s%4s%4s%4s%4s%10s%4s%6s%10s%4s%5s%6s\n", "=====", "=========", "====", "===", "=====", "=========", "=========",
+ "=========", "===", "===", "===", "===", "=========", "===", "=====", "=========", "===", "====", "=====");
+
+ for (i = 0; i < 32; i++) {
+ char s[100];
+ unsigned int temp;
+
+ printf("%6d", i);
+ buf = (unsigned char *) (&ctrl->psd[i]);
+ vt_convert_data_buffer_to_hex_string(&buf[0], 4, true, s);
+ printf("%9sh", s);
+
+ temp = ctrl->psd[i].flags;
+ printf("%4ub", ((unsigned char)temp & 0x02));
+ printf("%3ub", ((unsigned char)temp & 0x01));
+ vt_convert_data_buffer_to_hex_string(&buf[0], 2, true, s);
+ printf("%5sh", s);
+
+ vt_convert_data_buffer_to_hex_string(&buf[4], 4, true, s);
+ printf("%9sh", s);
+ vt_convert_data_buffer_to_hex_string(&buf[8], 4, true, s);
+ printf("%9sh", s);
+ vt_convert_data_buffer_to_hex_string(&buf[12], 4, true, s);
+ printf("%9sh", s);
+ vt_convert_data_buffer_to_hex_string(&buf[15], 1, true, s);
+ printf("%3sh", s);
+ vt_convert_data_buffer_to_hex_string(&buf[14], 1, true, s);
+ printf("%3sh", s);
+ vt_convert_data_buffer_to_hex_string(&buf[13], 1, true, s);
+ printf("%3sh", s);
+ vt_convert_data_buffer_to_hex_string(&buf[12], 1, true, s);
+ printf("%3sh", s);
+ vt_convert_data_buffer_to_hex_string(&buf[16], 4, true, s);
+ printf("%9sh", s);
+
+ temp = ctrl->psd[i].ips;
+ snprintf(s, sizeof(s), "%u%u", (((unsigned char)temp >> 6) & 0x01), (((unsigned char)temp >> 7) & 0x01));
+ printf("%3sb", s);
+
+ vt_convert_data_buffer_to_hex_string(&buf[16], 2, true, s);
+ printf("%5sh", s);
+ vt_convert_data_buffer_to_hex_string(&buf[20], 4, true, s);
+ printf("%9sh", s);
+
+ temp = ctrl->psd[i].apws;
+ snprintf(s, sizeof(s), "%u%u", (((unsigned char)temp >> 6) & 0x01), (((unsigned char)temp >> 7) & 0x01));
+ printf("%3sb", s);
+ snprintf(s, sizeof(s), "%u%u%u", (((unsigned char)temp) & 0x01), (((unsigned char)temp >> 1) & 0x01), (((unsigned char)temp >> 2) & 0x01));
+ printf("%4sb", s);
+
+ vt_convert_data_buffer_to_hex_string(&buf[20], 2, true, s);
+ printf("%5sh", s);
+ printf("\n");
+ }
+
+ printf(" \"}\n}\n");
+
+}
+
+static void vt_dump_hex_data(const unsigned char *pbuff, size_t pbuffsize)
+{
+ char textbuf[33];
+ unsigned long i, j;
+
+ textbuf[32] = '\0';
+ printf("[%08X] ", 0);
+ for (i = 0; i < pbuffsize; i++) {
+ printf("%02X ", pbuff[i]);
+
+ if (pbuff[i] >= ' ' && pbuff[i] <= '~')
+ textbuf[i % 32] = pbuff[i];
+ else
+ textbuf[i % 32] = '.';
+
+ if (!(((i + 1) % 8)) || ((i + 1) == pbuffsize)) {
+ printf(" ");
+ if (!((i + 1) % 32)) {
+ printf(" %s\n", textbuf);
+ if ((i + 1) != pbuffsize)
+ printf("[%08lX] ", (i + 1));
+ } else if (i + 1 == pbuffsize) {
+ textbuf[(i + 1) % 32] = '\0';
+ if (!((i + 1) % 8))
+ printf(" ");
+
+ for (j = ((i + 1) % 32); j < 32; j++) {
+ printf(" ");
+ if (!((j + 1) % 8))
+ printf(" ");
+ }
+
+ printf("%s\n", textbuf);
+ }
+ }
+ }
+}
+
+static void vt_parse_detail_identify(const struct nvme_id_ctrl *ctrl)
+{
+ unsigned char *buf;
+ unsigned int temp, pos;
+ char s[1024] = "";
+
+ const char *CMICtable[6] = {"0 = the NVM subsystem contains only a single NVM subsystem port",
+ "1 = the NVM subsystem may contain more than one subsystem ports",
+ "0 = the NVM subsystem contains only a single controller",
+ "1 = the NVM subsystem may contain two or more controllers (see section 1.4.1)",
+ "0 = the controller is associated with a PCI Function or a Fabrics connection",
+ "1 = the controller is associated with an SR-IOV Virtual Function"};
+
+ const char *OAEStable[20] = {"Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "0 = does not support sending the Namespace Attribute Notices event nor the associated Changed Namespace List log page",
+ "1 = supports sending the Namespace Attribute Notices & the associated Changed Namespace List log page",
+ "0 = does not support sending Firmware Activation Notices event",
+ "1 = supports sending Firmware Activation Notices"};
+
+ const char *CTRATTtable[4] = {"0 = does not support a 128-bit Host Identifier",
+ "1 = supports a 128-bit Host Identifier",
+ "0 = does not support Non-Operational Power State Permissive Mode",
+ "1 = supports Non-Operational Power State Permissive Mode"};
+
+ const char *OACStable[18] = {"0 = does not support the Security Send and Security Receive commands",
+ "1 = supports the Security Send and Security Receive commands",
+ "0 = does not support the Format NVM command",
+ "1 = supports the Format NVM command",
+ "0 = does not support the Firmware Commit and Firmware Image Download commands",
+ "1 = supports the Firmware Commit and Firmware Image Download commands",
+ "0 = does not support the Namespace Management capability",
+ "1 = supports the Namespace Management capability",
+ "0 = does not support the Device Self-test command",
+ "1 = supports the Device Self-test command",
+ "0 = does not support Directives",
+ "1 = supports Directive Send & Directive Receive commands",
+ "0 = does not support the NVMe-MI Send and NVMe-MI Receive commands",
+ "1 = supports the NVMe-MI Send and NVMe-MI Receive commands",
+ "0 = does not support the Virtualization Management command",
+ "1 = supports the Virtualization Management command",
+ "0 = does not support the Doorbell Buffer Config command",
+ "1 = supports the Doorbell Buffer Config command"};
+
+ const char *FRMWtable[10] = {"0 = the 1st firmware slot (slot 1) is read/write",
+ "1 = the 1st firmware slot (slot 1) is read only",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "0 = requires a reset for firmware to be activated",
+ "1 = supports firmware activation without a reset"};
+
+ const char *LPAtable[8] = {"0 = does not support the SMART / Health information log page on a per namespace basis",
+ "1 = supports the SMART / Health information log page on a per namespace basis",
+ "0 = does not support the Commands Supported & Effects log page",
+ "1 = supports the Commands Supported Effects log page",
+ "0 = does not support extended data for Get Log Page",
+ "1 = supports extended data for Get Log Page (including extended Number of Dwords and Log Page Offset fields)",
+ "0 = does not support the Telemetry Host-Initiated and Telemetry Controller-Initiated log pages and Telemetry Log Notices events",
+ "1 = supports the Telemetry Host-Initiated and Telemetry Controller-Initiated log pages and sending Telemetry Log Notices"};
+
+ const char *AVSCCtable[2] = {"0 = the format of all Admin Vendor Specific Commands are vendor specific",
+ "1 = all Admin Vendor Specific Commands use the format defined in NVM Express specification"};
+
+ const char *APSTAtable[2] = {"0 = does not support autonomous power state transitions",
+ "1 = supports autonomous power state transitions"};
+
+ const char *DSTOtable[2] = {"0 = the NVM subsystem supports one device self-test operation per controller at a time",
+ "1 = the NVM subsystem supports only one device self-test operation in progress at a time"};
+
+ const char *HCTMAtable[2] = {"0 = does not support host controlled thermal management",
+ "1 = supports host controlled thermal management. Supports Set Features & Get Features commands with the Feature Identifier field set to 10h"};
+
+ const char *SANICAPtable[6] = {"0 = does not support the Crypto Erase sanitize operation",
+ "1 = supports the Crypto Erase sanitize operation",
+ "0 = does not support the Block Erase sanitize operation",
+ "1 = supports the Block Erase sanitize operation",
+ "0 = does not support the Overwrite sanitize operation",
+ "1 = supports the Overwrite sanitize operation"};
+
+ const char *ONCStable[14] = {"0 = does not support the Compare command",
+ "1 = supports the Compare command",
+ "0 = does not support the Write Uncorrectable command",
+ "1 = supports the Write Uncorrectable command",
+ "0 = does not support the Dataset Management command",
+ "1 = supports the Dataset Management command",
+ "0 = does not support the Write Zeroes command",
+ "1 = supports the Write Zeroes command",
+ "0 = does not support the Save field set to a non-zero value in the Set Features and the Get Features commands",
+ "1 = supports the Save field set to a non-zero value in the Set Features and the Get Features commands",
+ "0 = does not support reservations",
+ "1 = supports reservations",
+ "0 = does not support the Timestamp feature (refer to section 5.21.1.14)",
+ "1 = supports the Timestamp feature"};
+
+ const char *FUSEStable[2] = {"0 = does not support the Compare and Write fused operation",
+ "1 = supports the Compare and Write fused operation"};
+
+ const char *FNAtable[6] = {"0 = supports format on a per namespace basis",
+ "1 = all namespaces shall be configured with the same attributes and a format (excluding secure erase) of any namespace results in a format of all namespaces in an NVM subsystem",
+ "0 = any secure erase performed as part of a format results in a secure erase of a particular namespace specified",
+ "1 = any secure erase performed as part of a format operation results in a secure erase of all namespaces in the NVM subsystem",
+ "0 = cryptographic erase is not supported",
+ "1 = cryptographic erase is supported as part of the secure erase functionality"};
+
+ const char *VWCtable[2] = {"0 = a volatile write cache is not present",
+ "1 = a volatile write cache is present"};
+
+ const char *ICSVSCCtable[2] = {"0 = the format of all NVM Vendor Specific Commands are vendor specific",
+ "1 = all NVM Vendor Specific Commands use the format defined in NVM Express specification"};
+
+ const char *SGLSSubtable[4] = {"00b = SGLs are not supported",
+ "01b = SGLs are supported. There is no alignment nor granularity requirement for Data Blocks",
+ "10b = SGLs are supported. There is a Dword alignment and granularity requirement for Data Blocks",
+ "11b = Reserved"};
+
+ const char *SGLStable[42] = {"Used",
+ "Used",
+ "Used",
+ "Used",
+ "0 = does not support the Keyed SGL Data Block descriptor",
+ "1 = supports the Keyed SGL Data Block descriptor",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "Reserved",
+ "0 = the SGL Bit Bucket descriptor is not supported",
+ "1 = the SGL Bit Bucket descriptor is supported",
+ "0 = use of a byte aligned contiguous physical buffer of metadata is not supported",
+ "1 = use of a byte aligned contiguous physical buffer of metadata is supported",
+ "0 = the SGL length shall be equal to the amount of data to be transferred",
+ "1 = supports commands that contain a data or metadata SGL of a length larger than the amount of data to be transferred",
+ "0 = use of Metadata Pointer (MPTR) that contains an address of an SGL segment containing exactly one SGL Descriptor that is Qword aligned is not supported",
+ "1 = use of Metadata Pointer (MPTR) that contains an address of an SGL segment containing exactly one SGL Descriptor that is Qword aligned is supported",
+ "0 = the Address field specifying an offset is not supported",
+ "1 = supports the Address field in SGL Data Block, SGL Segment, and SGL Last Segment descriptor types specifying an offset"};
+
+ buf = (unsigned char *)(ctrl);
+
+ printf("{\n");
+ vt_convert_data_buffer_to_hex_string(buf, 2, true, s);
+ printf(" \"PCI Vendor ID\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[2], 2, true, s);
+ printf(" \"PCI Subsystem Vendor ID\":\"%sh\",\n", s);
+ printf(" \"Serial Number\":\"%s\",\n", ctrl->sn);
+ printf(" \"Model Number\":\"%s\",\n", ctrl->mn);
+ printf(" \"Firmware Revision\":\"%-.*s\",\n", (int)sizeof(ctrl->fr), ctrl->fr);
+ vt_convert_data_buffer_to_hex_string(&buf[72], 1, true, s);
+ printf(" \"Recommended Arbitration Burst\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[73], 3, true, s);
+ printf(" \"IEEE OUI Identifier\":\"%sh\",\n", s);
+
+ temp = ctrl->cmic;
+ printf(" \"Controller Multi-Path I/O and Namespace Sharing Capabilities\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[76], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 3, CMICtable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[77], 1, true, s);
+ printf(" \"Maximum Data Transfer Size\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[78], 2, true, s);
+ printf(" \"Controller ID\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[80], 4, true, s);
+ printf(" \"Version\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[84], 4, true, s);
+ printf(" \"RTD3 Resume Latency\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[88], 4, true, s);
+ printf(" \"RTD3 Entry Latency\":\"%sh\",\n", s);
+
+ temp = le32_to_cpu(ctrl->oaes);
+ printf(" \"Optional Asynchronous Events Supported\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[92], 4, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 8, 2, OAEStable, true);
+
+ temp = le32_to_cpu(ctrl->ctratt);
+ printf(" \"Controller Attributes\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[96], 4, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 2, CTRATTtable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[122], 16, true, s);
+ printf(" \"FRU Globally Unique Identifier\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[240], 16, true, s);
+ printf(" \"NVMe Management Interface Specification\":\"%sh\",\n", s);
+
+ temp = le16_to_cpu(ctrl->oacs);
+ printf(" \"Optional Admin Command Support\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[256], 2, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 9, OACStable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[258], 1, true, s);
+ printf(" \"Abort Command Limit\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[259], 1, true, s);
+ printf(" \"Asynchronous Event Request Limit\":\"%sh\",\n", s);
+
+ temp = ctrl->frmw;
+ printf(" \"Firmware Updates\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[260], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 1, FRMWtable, false);
+ vt_convert_data_buffer_to_hex_string(&buf[260], 1, true, s);
+ printf(" \"Firmware Slot\":\"%uh\",\n", ((ctrl->frmw >> 1) & 0x07));
+ vt_build_identify_lv2(temp, 4, 1, FRMWtable, true);
+
+ temp = ctrl->lpa;
+ printf(" \"Log Page Attributes\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[261], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 4, LPAtable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[262], 1, true, s);
+ printf(" \"Error Log Page Entries\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[263], 1, true, s);
+ printf(" \"Number of Power States Support\":\"%sh\",\n", s);
+
+ temp = ctrl->avscc;
+ printf(" \"Admin Vendor Specific Command Configuration\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[264], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 1, AVSCCtable, true);
+
+ temp = ctrl->apsta;
+ printf(" \"Autonomous Power State Transition Attributes\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[265], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 1, APSTAtable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[266], 2, true, s);
+ printf(" \"Warning Composite Temperature Threshold\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[268], 2, true, s);
+ printf(" \"Critical Composite Temperature Threshold\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[270], 2, true, s);
+ printf(" \"Maximum Time for Firmware Activation\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[272], 4, true, s);
+ printf(" \"Host Memory Buffer Preferred Size\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[276], 4, true, s);
+ printf(" \"Host Memory Buffer Minimum Size\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[280], 16, true, s);
+ printf(" \"Total NVM Capacity\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[296], 16, true, s);
+ printf(" \"Unallocated NVM Capacity\":\"%sh\",\n", s);
+
+ temp = le32_to_cpu(ctrl->rpmbs);
+ printf(" \"Replay Protected Memory Block Support\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[312], 4, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ printf(" \"Number of RPMB Units\":\"%u\",\n", (temp & 0x00000003));
+ snprintf(s, sizeof(s), ((temp >> 3) & 0x00000007) ? "Reserved" : "HMAC SHA-256");
+ printf(" \"Authentication Method\":\"%u: %s\",\n", ((temp >> 3) & 0x00000007), s);
+ printf(" \"Total Size\":\"%u\",\n", ((temp >> 16) & 0x000000FF));
+ printf(" \"Access Size\":\"%u\",\n", ((temp >> 24) & 0x000000FF));
+ printf(" },\n");
+
+ vt_convert_data_buffer_to_hex_string(&buf[316], 2, true, s);
+ printf(" \"Extended Device Self-test Time\":\"%sh\",\n", s);
+
+ temp = ctrl->dsto;
+ printf(" \"Device Self-test Options\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[318], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 1, DSTOtable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[319], 1, true, s);
+ printf(" \"Firmware Update Granularity\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[320], 1, true, s);
+ printf(" \"Keep Alive Support\":\"%sh\",\n", s);
+
+ temp = le16_to_cpu(ctrl->hctma);
+ printf(" \"Host Controlled Thermal Management Attributes\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[322], 2, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 1, HCTMAtable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[324], 2, true, s);
+ printf(" \"Minimum Thermal Management Temperature\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[326], 2, true, s);
+ printf(" \"Maximum Thermal Management Temperature\":\"%sh\",\n", s);
+
+ temp = le16_to_cpu(ctrl->sanicap);
+ printf(" \"Sanitize Capabilities\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[328], 2, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 3, SANICAPtable, true);
+
+ temp = ctrl->sqes;
+ printf(" \"Submission Queue Entry Size\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[512], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ printf(" \"Maximum Size\":\"%u\",\n", (temp & 0x0000000F));
+ printf(" \"Required Size\":\"%u\",\n", ((temp >> 4) & 0x0000000F));
+ printf(" }\n");
+
+ temp = ctrl->cqes;
+ printf(" \"Completion Queue Entry Size\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[513], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ printf(" \"Maximum Size\":\"%u\",\n", (temp & 0x0000000F));
+ printf(" \"Required Size\":\"%u\",\n", ((temp >> 4) & 0x0000000F));
+ printf(" }\n");
+
+ vt_convert_data_buffer_to_hex_string(&buf[514], 2, true, s);
+ printf(" \"Maximum Outstanding Commands\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[516], 4, true, s);
+ printf(" \"Number of Namespaces\":\"%sh\",\n", s);
+
+ temp = le16_to_cpu(ctrl->oncs);
+ printf(" \"Optional NVM Command Support\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[520], 2, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 7, ONCStable, true);
+
+ temp = le16_to_cpu(ctrl->fuses);
+ printf(" \"Fused Operation Support\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[522], 2, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 1, FUSEStable, true);
+
+ temp = ctrl->fna;
+ printf(" \"Format NVM Attributes\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[524], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 3, FNAtable, true);
+
+ temp = ctrl->vwc;
+ printf(" \"Volatile Write Cache\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[525], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 1, VWCtable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[526], 2, true, s);
+ printf(" \"Atomic Write Unit Normal\":\"%sh\",\n", s);
+ vt_convert_data_buffer_to_hex_string(&buf[528], 2, true, s);
+ printf(" \"Atomic Write Unit Power Fail\":\"%sh\",\n", s);
+
+ temp = ctrl->icsvscc;
+ printf(" \"NVM Vendor Specific Command Configuration\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[530], 1, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ vt_build_identify_lv2(temp, 0, 1, ICSVSCCtable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[532], 2, true, s);
+ printf(" \"Atomic Compare 0 Write Unit\":\"%sh\",\n", s);
+
+ temp = le32_to_cpu(ctrl->sgls);
+ printf(" \"SGL Support\":{\n");
+ vt_convert_data_buffer_to_hex_string(&buf[536], 4, true, s);
+ printf(" \"Value\":\"%sh\",\n", s);
+ pos = (temp & 0x00000003);
+ printf(" \"bit 1:0\":\"%s\",\n", SGLSSubtable[pos]);
+ vt_build_identify_lv2(temp, 2, 1, SGLStable, false);
+ vt_build_identify_lv2(temp, 16, 5, SGLStable, true);
+
+ vt_convert_data_buffer_to_hex_string(&buf[768], 256, false, s);
+ printf(" \"NVM Subsystem NVMe Qualified Name\":\"%s\",\n", s);
+ printf("}\n\n");
+
+ vt_build_power_state_descriptor(ctrl);
+
+
+ printf("\n{\n");
+ printf("\"Vendor Specific\":\"\n");
+ vt_dump_hex_data(&buf[3072], 1024);
+ printf("\"}\n");
+}
+
+static int vt_save_smart_to_vtview_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ int ret, err = 0;
+ long total_time = 0;
+ long freq_time = 0;
+ long cur_time = 0;
+ long remain_time = 0;
+ long start_time = 0;
+ long end_time = 0;
+ char path[256] = "";
+ char *desc = "Save SMART data into log file with format that is easy to analyze (comma delimited). Maximum log file will be 4K.\n\n"
+ "Typical usages:\n\n"
+ "Temperature characterization:\n"
+ "\tvirtium save-smart-to-vtview-log /dev/yourDevice --run-time=100 --record-frequency=0.25 --test-name=burn-in-at-(-40)\n\n"
+ "Endurance testing :\n"
+ "\tvirtium save-smart-to-vtview-log /dev/yourDevice --run-time=100 --record-frequency=1 --test-name=Endurance-test-JEDEG-219-workload\n\n"
+ "Just logging :\n"
+ "\tvirtium save-smart-to-vtview-log /dev/yourDevice";
+
+ const char *run_time = "(optional) Number of hours to log data (default = 20 hours)";
+ const char *freq = "(optional) How often you want to log SMART data (0.25 = 15' , 0.5 = 30' , 1 = 1 hour, 2 = 2 hours, etc.). Default = 10 hours.";
+ const char *output_file = "(optional) Name of the log file (give it a name that easy for you to remember what the test is). You can leave it blank too, we will take care it for you.";
+ const char *test_name = "(optional) Name of the test you are doing. We use this as part of the name of the log file.";
+ struct nvme_dev *dev;
+
+ struct vtview_save_log_settings cfg = {
+ .run_time_hrs = 20,
+ .log_record_frequency_hrs = 10,
+ .output_file = NULL,
+ .test_name = NULL,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_DOUBLE("run-time", 'r', &cfg.run_time_hrs, run_time),
+ OPT_DOUBLE("freq", 'f', &cfg.log_record_frequency_hrs, freq),
+ OPT_FILE("output-file", 'o', &cfg.output_file, output_file),
+ OPT_STRING("test-name", 'n', "NAME", &cfg.test_name, test_name),
+ OPT_END()
+ };
+
+ vt_generate_vtview_log_file_name(vt_default_log_file_name);
+
+ if (argc >= 2) {
+ if (strlen(argv[1]) > sizeof(path) - 1) {
+ printf("Filename too long\n");
+ return -1;
+ }
+ strcpy(path, argv[1]);
+ }
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err) {
+ printf("Error parse and open (err = %d)\n", err);
+ return err;
+ }
+
+ printf("Running...\n");
+ printf("Collecting data for device %s\n", path);
+ printf("Running for %lf hour(s)\n", cfg.run_time_hrs);
+ printf("Logging SMART data for every %lf hour(s)\n", cfg.log_record_frequency_hrs);
+
+ ret = vt_update_vtview_log_header(dev_fd(dev), path, &cfg);
+ if (ret) {
+ err = EINVAL;
+ dev_close(dev);
+ return err;
+ }
+
+ total_time = cfg.run_time_hrs * (float)HOUR_IN_SECONDS;
+ freq_time = cfg.log_record_frequency_hrs * (float)HOUR_IN_SECONDS;
+
+ if (!freq_time)
+ freq_time = 1;
+
+ start_time = time(NULL);
+ end_time = start_time + total_time;
+
+ fflush(stdout);
+
+ while (1) {
+ cur_time = time(NULL);
+ if (cur_time >= end_time)
+ break;
+
+ ret = vt_add_entry_to_log(dev_fd(dev), path, &cfg);
+ if (ret) {
+ printf("Cannot update driver log\n");
+ break;
+ }
+
+ remain_time = end_time - cur_time;
+ freq_time = MIN2(freq_time, remain_time);
+ sleep(freq_time);
+ fflush(stdout);
+ }
+
+ dev_close(dev);
+ return err;
+}
+
+static int vt_show_identify(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ int ret, err = 0;
+ struct nvme_id_ctrl ctrl;
+ struct nvme_dev *dev;
+ char *desc = "Parse identify data to json format\n\n"
+ "Typical usages:\n\n"
+ "virtium show-identify /dev/yourDevice\n";
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err) {
+ printf("Error parse and open (err = %d)\n", err);
+ return err;
+ }
+
+ ret = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (ret) {
+ printf("Cannot read identify device\n");
+ dev_close(dev);
+ return -1;
+ }
+
+ vt_process_string(ctrl.sn, sizeof(ctrl.sn));
+ vt_process_string(ctrl.mn, sizeof(ctrl.mn));
+ vt_parse_detail_identify(&ctrl);
+
+ dev_close(dev);
+ return err;
+}
diff --git a/plugins/virtium/virtium-nvme.h b/plugins/virtium/virtium-nvme.h
new file mode 100644
index 0000000..0a04a35
--- /dev/null
+++ b/plugins/virtium/virtium-nvme.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/virtium/virtium-nvme
+
+#if !defined(VIRTIUM_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define VIRTIUM_NVME
+
+#include "cmd.h"
+#include "plugin.h"
+
+PLUGIN(NAME("virtium", "Virtium vendor specific extensions", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("save-smart-to-vtview-log", "Periodically save smart attributes into a log file.\n\
+ The data in this log file can be analyzed using excel or using Virtium’s vtView.\n\
+ Visit vtView.virtium.com to see full potential uses of the data", vt_save_smart_to_vtview_log)
+ ENTRY("show-identify", "Shows detail features and current settings", vt_show_identify)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/wdc/wdc-nvme.c b/plugins/wdc/wdc-nvme.c
new file mode 100644
index 0000000..8cbcf2e
--- /dev/null
+++ b/plugins/wdc/wdc-nvme.c
@@ -0,0 +1,12486 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2015-2018 Western Digital Corporation or its affiliates.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ * Author: Chaitanya Kulkarni <chaitanya.kulkarni@hgst.com>,
+ * Dong Ho <dong.ho@hgst.com>,
+ * Jeff Lien <jeff.lien@wdc.com>
+ * Brandon Paupore <brandon.paupore@wdc.com>
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "util/cleanup.h"
+#include "util/types.h"
+#include "nvme-print.h"
+
+#define CREATE_CMD
+#include "wdc-nvme.h"
+#include "wdc-utils.h"
+
+#define WRITE_SIZE (sizeof(__u8) * 4096)
+
+#define WDC_NVME_SUBCMD_SHIFT 8
+
+#define WDC_NVME_LOG_SIZE_DATA_LEN 0x08
+#define WDC_NVME_LOG_SIZE_HDR_LEN 0x08
+
+/* Enclosure */
+#define WDC_OPENFLEX_MI_DEVICE_MODEL "OpenFlex"
+#define WDC_RESULT_MORE_DATA 0x80000000
+#define WDC_RESULT_NOT_AVAILABLE 0x7FFFFFFF
+
+/* Device Config */
+#define WDC_NVME_VID 0x1c58
+#define WDC_NVME_VID_2 0x1b96
+#define WDC_NVME_SNDK_VID 0x15b7
+
+#define WDC_NVME_SN100_DEV_ID 0x0003
+#define WDC_NVME_SN200_DEV_ID 0x0023
+#define WDC_NVME_SN630_DEV_ID 0x2200
+#define WDC_NVME_SN630_DEV_ID_1 0x2201
+#define WDC_NVME_SN840_DEV_ID 0x2300
+#define WDC_NVME_SN840_DEV_ID_1 0x2500
+#define WDC_NVME_SN640_DEV_ID 0x2400
+#define WDC_NVME_SN640_DEV_ID_1 0x2401
+#define WDC_NVME_SN640_DEV_ID_2 0x2402
+#define WDC_NVME_SN640_DEV_ID_3 0x2404
+#define WDC_NVME_ZN540_DEV_ID 0x2600
+#define WDC_NVME_SN540_DEV_ID 0x2610
+#define WDC_NVME_SN650_DEV_ID 0x2700
+#define WDC_NVME_SN650_DEV_ID_1 0x2701
+#define WDC_NVME_SN650_DEV_ID_2 0x2702
+#define WDC_NVME_SN650_DEV_ID_3 0x2720
+#define WDC_NVME_SN650_DEV_ID_4 0x2721
+#define WDC_NVME_SN655_DEV_ID 0x2722
+#define WDC_NVME_SN860_DEV_ID 0x2730
+#define WDC_NVME_SN660_DEV_ID 0x2704
+#define WDC_NVME_SN560_DEV_ID_1 0x2712
+#define WDC_NVME_SN560_DEV_ID_2 0x2713
+#define WDC_NVME_SN560_DEV_ID_3 0x2714
+#define WDC_NVME_SN861_DEV_ID 0x2750
+#define WDC_NVME_SN861_DEV_ID_1 0x2751
+
+/* This id's are no longer supported, delete ?? */
+#define WDC_NVME_SN550_DEV_ID 0x2708
+
+#define WDC_NVME_SXSLCL_DEV_ID 0x2001
+#define WDC_NVME_SN520_DEV_ID 0x5003
+#define WDC_NVME_SN520_DEV_ID_1 0x5004
+#define WDC_NVME_SN520_DEV_ID_2 0x5005
+
+#define WDC_NVME_SN530_DEV_ID_1 0x5007
+#define WDC_NVME_SN530_DEV_ID_2 0x5008
+#define WDC_NVME_SN530_DEV_ID_3 0x5009
+#define WDC_NVME_SN530_DEV_ID_4 0x500b
+#define WDC_NVME_SN530_DEV_ID_5 0x501d
+
+#define WDC_NVME_SN350_DEV_ID 0x5019
+
+#define WDC_NVME_SN570_DEV_ID 0x501A
+
+#define WDC_NVME_SN850X_DEV_ID 0x5030
+
+#define WDC_NVME_SN5000_DEV_ID_1 0x5034
+#define WDC_NVME_SN5000_DEV_ID_2 0x5035
+#define WDC_NVME_SN5000_DEV_ID_3 0x5036
+#define WDC_NVME_SN5000_DEV_ID_4 0x504A
+
+#define WDC_NVME_SN7000S_DEV_ID_1 0x5039
+
+#define WDC_NVME_SN7150_DEV_ID_1 0x503b
+#define WDC_NVME_SN7150_DEV_ID_2 0x503c
+#define WDC_NVME_SN7150_DEV_ID_3 0x503d
+#define WDC_NVME_SN7150_DEV_ID_4 0x503e
+#define WDC_NVME_SN7150_DEV_ID_5 0x503f
+
+#define WDC_NVME_SN7100_DEV_ID_1 0x5043
+#define WDC_NVME_SN7100_DEV_ID_2 0x5044
+#define WDC_NVME_SN7100_DEV_ID_3 0x5045
+
+#define WDC_NVME_SN8000S_DEV_ID 0x5049
+
+#define WDC_NVME_SN720_DEV_ID 0x5002
+#define WDC_NVME_SN730_DEV_ID 0x5006
+#define WDC_NVME_SN740_DEV_ID 0x5015
+#define WDC_NVME_SN740_DEV_ID_1 0x5016
+#define WDC_NVME_SN740_DEV_ID_2 0x5017
+#define WDC_NVME_SN740_DEV_ID_3 0x5025
+#define WDC_NVME_SN340_DEV_ID 0x500d
+#define WDC_NVME_ZN350_DEV_ID 0x5010
+#define WDC_NVME_ZN350_DEV_ID_1 0x5018
+#define WDC_NVME_SN810_DEV_ID 0x5011
+#define WDC_NVME_SN820CL_DEV_ID 0x5037
+
+#define WDC_DRIVE_CAP_CAP_DIAG 0x0000000000000001
+#define WDC_DRIVE_CAP_INTERNAL_LOG 0x0000000000000002
+#define WDC_DRIVE_CAP_C1_LOG_PAGE 0x0000000000000004
+#define WDC_DRIVE_CAP_CA_LOG_PAGE 0x0000000000000008
+#define WDC_DRIVE_CAP_D0_LOG_PAGE 0x0000000000000010
+#define WDC_DRIVE_CAP_DRIVE_STATUS 0x0000000000000020
+#define WDC_DRIVE_CAP_CLEAR_ASSERT 0x0000000000000040
+#define WDC_DRIVE_CAP_CLEAR_PCIE 0x0000000000000080
+#define WDC_DRIVE_CAP_RESIZE 0x0000000000000100
+#define WDC_DRIVE_CAP_NAND_STATS 0x0000000000000200
+#define WDC_DRIVE_CAP_DRIVE_LOG 0x0000000000000400
+#define WDC_DRIVE_CAP_CRASH_DUMP 0x0000000000000800
+#define WDC_DRIVE_CAP_PFAIL_DUMP 0x0000000000001000
+#define WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY 0x0000000000002000
+#define WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY 0x0000000000004000
+#define WDC_DRVIE_CAP_DISABLE_CTLR_TELE_LOG 0x0000000000008000
+#define WDC_DRIVE_CAP_REASON_ID 0x0000000000010000
+#define WDC_DRIVE_CAP_LOG_PAGE_DIR 0x0000000000020000
+#define WDC_DRIVE_CAP_NS_RESIZE 0x0000000000040000
+#define WDC_DRIVE_CAP_INFO 0x0000000000080000
+#define WDC_DRIVE_CAP_C0_LOG_PAGE 0x0000000000100000
+#define WDC_DRIVE_CAP_TEMP_STATS 0x0000000000200000
+#define WDC_DRIVE_CAP_VUC_CLEAR_PCIE 0x0000000000400000
+#define WDC_DRIVE_CAP_VU_FID_CLEAR_PCIE 0x0000000000800000
+#define WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY_C2 0x0000000001000000
+#define WDC_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY 0x0000000002000000
+#define WDC_DRIVE_CAP_CLOUD_SSD_VERSION 0x0000000004000000
+#define WDC_DRIVE_CAP_PCIE_STATS 0x0000000008000000
+#define WDC_DRIVE_CAP_HW_REV_LOG_PAGE 0x0000000010000000
+#define WDC_DRIVE_CAP_C3_LOG_PAGE 0x0000000020000000
+#define WDC_DRIVE_CAP_CLOUD_BOOT_SSD_VERSION 0x0000000040000000
+#define WDC_DRIVE_CAP_CLOUD_LOG_PAGE 0x0000000080000000
+
+#define WDC_DRIVE_CAP_DRIVE_ESSENTIALS 0x0000000100000000
+#define WDC_DRIVE_CAP_DUI_DATA 0x0000000200000000
+#define WDC_SN730B_CAP_VUC_LOG 0x0000000400000000
+#define WDC_DRIVE_CAP_DUI 0x0000000800000000
+#define WDC_DRIVE_CAP_PURGE 0x0000001000000000
+#define WDC_DRIVE_CAP_OCP_C1_LOG_PAGE 0x0000002000000000
+#define WDC_DRIVE_CAP_OCP_C4_LOG_PAGE 0x0000004000000000
+#define WDC_DRIVE_CAP_OCP_C5_LOG_PAGE 0x0000008000000000
+#define WDC_DRIVE_CAP_DEVICE_WAF 0x0000010000000000
+#define WDC_DRIVE_CAP_SET_LATENCY_MONITOR 0x0000020000000000
+
+#define WDC_DRIVE_CAP_SMART_LOG_MASK (WDC_DRIVE_CAP_C0_LOG_PAGE | \
+ WDC_DRIVE_CAP_C1_LOG_PAGE | \
+ WDC_DRIVE_CAP_CA_LOG_PAGE | \
+ WDC_DRIVE_CAP_D0_LOG_PAGE)
+#define WDC_DRIVE_CAP_CLEAR_PCIE_MASK (WDC_DRIVE_CAP_CLEAR_PCIE | \
+ WDC_DRIVE_CAP_VUC_CLEAR_PCIE | \
+ WDC_DRIVE_CAP_VU_FID_CLEAR_PCIE)
+#define WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY_MASK (WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY | \
+ WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY_C2)
+#define WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY_MASK (WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY | \
+ WDC_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY)
+#define WDC_DRIVE_CAP_INTERNAL_LOG_MASK (WDC_DRIVE_CAP_INTERNAL_LOG | \
+ WDC_DRIVE_CAP_DUI | \
+ WDC_DRIVE_CAP_DUI_DATA | \
+ WDC_SN730B_CAP_VUC_LOG)
+
+/* SN730 Get Log Capabilities */
+#define SN730_NVME_GET_LOG_OPCODE 0xc2
+#define SN730_GET_FULL_LOG_LENGTH 0x00080009
+#define SN730_GET_KEY_LOG_LENGTH 0x00090009
+#define SN730_GET_COREDUMP_LOG_LENGTH 0x00120009
+#define SN730_GET_EXTENDED_LOG_LENGTH 0x00420009
+
+#define SN730_GET_FULL_LOG_SUBOPCODE 0x00010009
+#define SN730_GET_KEY_LOG_SUBOPCODE 0x00020009
+#define SN730_GET_CORE_LOG_SUBOPCODE 0x00030009
+#define SN730_GET_EXTEND_LOG_SUBOPCODE 0x00040009
+#define SN730_LOG_CHUNK_SIZE 0x1000
+
+/* Customer ID's */
+#define WDC_CUSTOMER_ID_GN 0x0001
+#define WDC_CUSTOMER_ID_GD 0x0101
+#define WDC_CUSTOMER_ID_BD 0x1009
+
+#define WDC_CUSTOMER_ID_0x1005 0x1005
+
+#define WDC_CUSTOMER_ID_0x1004 0x1004
+#define WDC_CUSTOMER_ID_0x1008 0x1008
+#define WDC_CUSTOMER_ID_0x1304 0x1304
+#define WDC_INVALID_CUSTOMER_ID -1
+
+#define WDC_ALL_PAGE_MASK 0xFFFF
+#define WDC_C0_PAGE_MASK 0x0001
+#define WDC_C1_PAGE_MASK 0x0002
+#define WDC_CA_PAGE_MASK 0x0004
+#define WDC_D0_PAGE_MASK 0x0008
+
+/* Drive Resize */
+#define WDC_NVME_DRIVE_RESIZE_OPCODE 0xCC
+#define WDC_NVME_DRIVE_RESIZE_CMD 0x03
+#define WDC_NVME_DRIVE_RESIZE_SUBCMD 0x01
+
+/* Namespace Resize */
+#define WDC_NVME_NAMESPACE_RESIZE_OPCODE 0xFB
+
+/* Drive Info */
+#define WDC_NVME_DRIVE_INFO_OPCODE 0xC6
+#define WDC_NVME_DRIVE_INFO_CMD 0x22
+#define WDC_NVME_DRIVE_INFO_SUBCMD 0x06
+
+/* VS PCIE Stats */
+#define WDC_NVME_PCIE_STATS_OPCODE 0xD1
+
+/* Capture Diagnostics */
+#define WDC_NVME_CAP_DIAG_HEADER_TOC_SIZE WDC_NVME_LOG_SIZE_DATA_LEN
+#define WDC_NVME_CAP_DIAG_OPCODE 0xE6
+#define WDC_NVME_CAP_DIAG_CMD_OPCODE 0xC6
+#define WDC_NVME_CAP_DIAG_SUBCMD 0x00
+#define WDC_NVME_CAP_DIAG_CMD 0x00
+
+#define WDC_NVME_CRASH_DUMP_TYPE 1
+#define WDC_NVME_PFAIL_DUMP_TYPE 2
+
+/* Capture Device Unit Info */
+#define WDC_NVME_CAP_DUI_HEADER_SIZE 0x400
+#define WDC_NVME_CAP_DUI_OPCODE 0xFA
+#define WDC_NVME_CAP_DUI_DISABLE_IO 0x01
+#define WDC_NVME_DUI_MAX_SECTION 0x3A
+#define WDC_NVME_DUI_MAX_SECTION_V2 0x26
+#define WDC_NVME_DUI_MAX_SECTION_V3 0x23
+#define WDC_NVME_DUI_MAX_DATA_AREA 0x05
+#define WDC_NVME_SN730_SECTOR_SIZE 512
+
+/* Telemtery types for vs-internal-log command */
+#define WDC_TELEMETRY_TYPE_NONE 0x0
+#define WDC_TELEMETRY_TYPE_HOST 0x1
+#define WDC_TELEMETRY_TYPE_CONTROLLER 0x2
+#define WDC_TELEMETRY_HEADER_LENGTH 512
+#define WDC_TELEMETRY_BLOCK_SIZE 512
+
+/* Crash dump */
+#define WDC_NVME_CRASH_DUMP_SIZE_DATA_LEN WDC_NVME_LOG_SIZE_DATA_LEN
+#define WDC_NVME_CRASH_DUMP_SIZE_NDT 0x02
+#define WDC_NVME_CRASH_DUMP_SIZE_CMD 0x20
+#define WDC_NVME_CRASH_DUMP_SIZE_SUBCMD 0x03
+
+#define WDC_NVME_CRASH_DUMP_OPCODE WDC_NVME_CAP_DIAG_CMD_OPCODE
+#define WDC_NVME_CRASH_DUMP_CMD 0x20
+#define WDC_NVME_CRASH_DUMP_SUBCMD 0x04
+
+/* PFail Crash dump */
+#define WDC_NVME_PF_CRASH_DUMP_SIZE_DATA_LEN WDC_NVME_LOG_SIZE_HDR_LEN
+#define WDC_NVME_PF_CRASH_DUMP_SIZE_NDT 0x02
+#define WDC_NVME_PF_CRASH_DUMP_SIZE_CMD 0x20
+#define WDC_NVME_PF_CRASH_DUMP_SIZE_SUBCMD 0x05
+
+#define WDC_NVME_PF_CRASH_DUMP_OPCODE WDC_NVME_CAP_DIAG_CMD_OPCODE
+#define WDC_NVME_PF_CRASH_DUMP_CMD 0x20
+#define WDC_NVME_PF_CRASH_DUMP_SUBCMD 0x06
+
+/* Drive Log */
+#define WDC_NVME_DRIVE_LOG_SIZE_OPCODE WDC_NVME_CAP_DIAG_CMD_OPCODE
+#define WDC_NVME_DRIVE_LOG_SIZE_DATA_LEN WDC_NVME_LOG_SIZE_DATA_LEN
+#define WDC_NVME_DRIVE_LOG_SIZE_NDT 0x02
+#define WDC_NVME_DRIVE_LOG_SIZE_CMD 0x20
+#define WDC_NVME_DRIVE_LOG_SIZE_SUBCMD 0x01
+
+#define WDC_NVME_DRIVE_LOG_OPCODE WDC_NVME_CAP_DIAG_CMD_OPCODE
+#define WDC_NVME_DRIVE_LOG_CMD 0x20
+#define WDC_NVME_DRIVE_LOG_SUBCMD 0x00
+
+/* Purge and Purge Monitor */
+#define WDC_NVME_PURGE_CMD_OPCODE 0xDD
+#define WDC_NVME_PURGE_MONITOR_OPCODE 0xDE
+#define WDC_NVME_PURGE_MONITOR_DATA_LEN 0x2F
+#define WDC_NVME_PURGE_MONITOR_CMD_CDW10 0x0000000C
+#define WDC_NVME_PURGE_MONITOR_TIMEOUT 0x7530
+#define WDC_NVME_PURGE_CMD_SEQ_ERR 0x0C
+#define WDC_NVME_PURGE_INT_DEV_ERR 0x06
+
+#define WDC_NVME_PURGE_STATE_IDLE 0x00
+#define WDC_NVME_PURGE_STATE_DONE 0x01
+#define WDC_NVME_PURGE_STATE_BUSY 0x02
+#define WDC_NVME_PURGE_STATE_REQ_PWR_CYC 0x03
+#define WDC_NVME_PURGE_STATE_PWR_CYC_PURGE 0x04
+
+/* Clear dumps */
+#define WDC_NVME_CLEAR_DUMP_OPCODE 0xFF
+#define WDC_NVME_CLEAR_CRASH_DUMP_CMD 0x03
+#define WDC_NVME_CLEAR_CRASH_DUMP_SUBCMD 0x05
+#define WDC_NVME_CLEAR_PF_CRASH_DUMP_SUBCMD 0x06
+
+/* Clear FW Activate History */
+#define WDC_NVME_CLEAR_FW_ACT_HIST_OPCODE 0xC6
+#define WDC_NVME_CLEAR_FW_ACT_HIST_CMD 0x23
+#define WDC_NVME_CLEAR_FW_ACT_HIST_SUBCMD 0x05
+#define WDC_NVME_CLEAR_FW_ACT_HIST_VU_FID 0xC1
+
+/* Additional Smart Log */
+#define WDC_ADD_LOG_BUF_LEN 0x4000
+#define WDC_NVME_ADD_LOG_OPCODE 0xC1
+#define WDC_GET_LOG_PAGE_SSD_PERFORMANCE 0x37
+#define WDC_NVME_GET_STAT_PERF_INTERVAL_LIFETIME 0x0F
+
+/* C2 Log Page */
+#define WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_ID 0xC2
+#define WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_ID_C8 0xC8
+#define WDC_C2_LOG_BUF_LEN 0x1000
+#define WDC_C2_LOG_PAGES_SUPPORTED_ID 0x08
+#define WDC_C2_CUSTOMER_ID_ID 0x15
+#define WDC_C2_THERMAL_THROTTLE_STATUS_ID 0x18
+#define WDC_C2_ASSERT_DUMP_PRESENT_ID 0x19
+#define WDC_C2_USER_EOL_STATUS_ID 0x1A
+#define WDC_C2_USER_EOL_STATE_ID 0x1C
+#define WDC_C2_SYSTEM_EOL_STATE_ID 0x1D
+#define WDC_C2_FORMAT_CORRUPT_REASON_ID 0x1E
+#define WDC_EOL_STATUS_NORMAL cpu_to_le32(0x00000000)
+#define WDC_EOL_STATUS_END_OF_LIFE cpu_to_le32(0x00000001)
+#define WDC_EOL_STATUS_READ_ONLY cpu_to_le32(0x00000002)
+#define WDC_ASSERT_DUMP_NOT_PRESENT cpu_to_le32(0x00000000)
+#define WDC_ASSERT_DUMP_PRESENT cpu_to_le32(0x00000001)
+#define WDC_THERMAL_THROTTLING_OFF cpu_to_le32(0x00000000)
+#define WDC_THERMAL_THROTTLING_ON cpu_to_le32(0x00000001)
+#define WDC_THERMAL_THROTTLING_UNAVAILABLE cpu_to_le32(0x00000002)
+#define WDC_FORMAT_NOT_CORRUPT cpu_to_le32(0x00000000)
+#define WDC_FORMAT_CORRUPT_FW_ASSERT cpu_to_le32(0x00000001)
+#define WDC_FORMAT_CORRUPT_UNKNOWN cpu_to_le32(0x000000FF)
+
+/* CA Log Page */
+#define WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE 0xCA
+#define WDC_FB_CA_LOG_BUF_LEN 0x80
+/* Added 4 padding bytes to resolve build warning messages */
+#define WDC_BD_CA_LOG_BUF_LEN 0xA0
+
+/* C0 EOL Status Log Page */
+#define WDC_NVME_GET_EOL_STATUS_LOG_OPCODE 0xC0
+#define WDC_NVME_EOL_STATUS_LOG_LEN 0x200
+#define WDC_NVME_SMART_CLOUD_ATTR_LEN 0x200
+
+/* C0 SMART Cloud Attributes Log Page*/
+#define WDC_NVME_GET_SMART_CLOUD_ATTR_LOG_ID 0xC0
+
+/* CB - FW Activate History Log Page */
+#define WDC_NVME_GET_FW_ACT_HISTORY_LOG_ID 0xCB
+#define WDC_FW_ACT_HISTORY_LOG_BUF_LEN 0x3d0
+
+/* C2 - FW Activation History Log Page */
+#define WDC_NVME_GET_FW_ACT_HISTORY_C2_LOG_ID 0xC2
+#define WDC_FW_ACT_HISTORY_C2_LOG_BUF_LEN 0x1000
+#define WDC_MAX_NUM_ACT_HIST_ENTRIES 20
+#define WDC_C2_GUID_LENGTH 16
+
+/* C3 Latency Monitor Log Page */
+#define WDC_LATENCY_MON_LOG_BUF_LEN 0x200
+#define WDC_LATENCY_MON_LOG_ID 0xC3
+#define WDC_LATENCY_MON_VERSION 0x0001
+
+#define WDC_C3_GUID_LENGTH 16
+static __u8 wdc_lat_mon_guid[WDC_C3_GUID_LENGTH] = {
+ 0x92, 0x7a, 0xc0, 0x8c, 0xd0, 0x84, 0x6c, 0x9c,
+ 0x70, 0x43, 0xe6, 0xd4, 0x58, 0x5e, 0xd4, 0x85
+};
+
+/* D0 Smart Log Page */
+#define WDC_NVME_GET_VU_SMART_LOG_OPCODE 0xD0
+#define WDC_NVME_VU_SMART_LOG_LEN 0x200
+
+/* Log Page Directory defines */
+#define NVME_LOG_PERSISTENT_EVENT 0x0D
+#define WDC_LOG_ID_C0 0xC0
+#define WDC_LOG_ID_C1 0xC1
+#define WDC_LOG_ID_C2 WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_ID
+#define WDC_LOG_ID_C3 0xC3
+#define WDC_LOG_ID_C4 0xC4
+#define WDC_LOG_ID_C5 0xC5
+#define WDC_LOG_ID_C6 0xC6
+#define WDC_LOG_ID_C8 WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_ID_C8
+#define WDC_LOG_ID_CA WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE
+#define WDC_LOG_ID_CB WDC_NVME_GET_FW_ACT_HISTORY_LOG_ID
+#define WDC_LOG_ID_D0 WDC_NVME_GET_VU_SMART_LOG_OPCODE
+#define WDC_LOG_ID_D1 0xD1
+#define WDC_LOG_ID_D6 0xD6
+#define WDC_LOG_ID_D7 0xD7
+#define WDC_LOG_ID_D8 0xD8
+#define WDC_LOG_ID_DE 0xDE
+#define WDC_LOG_ID_F0 0xF0
+#define WDC_LOG_ID_F1 0xF1
+#define WDC_LOG_ID_F2 0xF2
+#define WDC_LOG_ID_FA 0xFA
+
+/* Clear PCIe Correctable Errors */
+#define WDC_NVME_CLEAR_PCIE_CORR_OPCODE WDC_NVME_CAP_DIAG_CMD_OPCODE
+#define WDC_NVME_CLEAR_PCIE_CORR_CMD 0x22
+#define WDC_NVME_CLEAR_PCIE_CORR_SUBCMD 0x04
+#define WDC_NVME_CLEAR_PCIE_CORR_OPCODE_VUC 0xD2
+#define WDC_NVME_CLEAR_PCIE_CORR_FEATURE_ID 0xC3
+/* Clear Assert Dump Status */
+#define WDC_NVME_CLEAR_ASSERT_DUMP_OPCODE 0xD8
+#define WDC_NVME_CLEAR_ASSERT_DUMP_CMD 0x03
+#define WDC_NVME_CLEAR_ASSERT_DUMP_SUBCMD 0x05
+
+#define WDC_VU_DISABLE_CNTLR_TELEMETRY_OPTION_FEATURE_ID 0xD2
+
+/* Drive Essentials */
+#define WDC_DE_DEFAULT_NUMBER_OF_ERROR_ENTRIES 64
+#define WDC_DE_GENERIC_BUFFER_SIZE 80
+#define WDC_DE_GLOBAL_NSID 0xFFFFFFFF
+#define WDC_DE_DEFAULT_NAMESPACE_ID 0x01
+#define WDC_DE_PATH_SEPARATOR "/"
+#define WDC_DE_TAR_FILES "*.bin"
+#define WDC_DE_TAR_FILE_EXTN ".tar.gz"
+#define WDC_DE_TAR_CMD "tar -czf"
+
+/* VS NAND Stats */
+#define WDC_NVME_NAND_STATS_LOG_ID 0xFB
+#define WDC_NVME_NAND_STATS_SIZE 0x200
+
+/* VU Opcodes */
+#define WDC_DE_VU_READ_SIZE_OPCODE 0xC0
+#define WDC_DE_VU_READ_BUFFER_OPCODE 0xC2
+#define WDC_NVME_ADMIN_ENC_MGMT_SND 0xC9
+#define WDC_NVME_ADMIN_ENC_MGMT_RCV 0xCA
+
+#define WDC_DE_FILE_HEADER_SIZE 4
+#define WDC_DE_FILE_OFFSET_SIZE 2
+#define WDC_DE_FILE_NAME_SIZE 32
+#define WDC_DE_VU_READ_BUFFER_STANDARD_OFFSET 0x8000
+#define WDC_DE_READ_MAX_TRANSFER_SIZE 0x8000
+
+#define WDC_DE_MANUFACTURING_INFO_PAGE_FILE_NAME "manufacturing_info" /* Unique log entry page name. */
+#define WDC_DE_CORE_DUMP_FILE_NAME "core_dump"
+#define WDC_DE_EVENT_LOG_FILE_NAME "event_log"
+#define WDC_DE_DESTN_SPI 1
+#define WDC_DE_DUMPTRACE_DESTINATION 6
+
+#define NVME_ID_CTRL_MODEL_NUMBER_SIZE 40
+#define NVME_ID_CTRL_SERIAL_NUMBER_SIZE 20
+
+/* Enclosure log */
+#define WDC_NVME_ENC_LOG_SIZE_CHUNK 0x1000
+#define WDC_NVME_ENC_NIC_LOG_SIZE 0x400000
+
+/* Enclosure nic crash dump get-log id */
+#define WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_1 0xD1
+#define WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_2 0xD2
+#define WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_3 0xD3
+#define WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_4 0xD4
+#define WDC_ENC_CRASH_DUMP_ID 0xE4
+#define WDC_ENC_LOG_DUMP_ID 0xE2
+
+/* OCP Log Page Directory Data Structure */
+#define BYTE_TO_BIT(byte) ((byte) * 8)
+
+/* Set latency monitor feature */
+#define NVME_FEAT_OCP_LATENCY_MONITOR 0xC5
+
+enum _NVME_FEATURES_SELECT {
+ FS_CURRENT = 0,
+ FS_DEFAULT = 1,
+ FS_SAVED = 2,
+ FS_SUPPORTED_CAPBILITIES = 3
+};
+
+enum NVME_FEATURE_IDENTIFIERS {
+ FID_ARBITRATION = 0x01,
+ FID_POWER_MANAGEMENT = 0x02,
+ FID_LBA_RANGE_TYPE = 0x03,
+ FID_TEMPERATURE_THRESHOLD = 0x04,
+ FID_ERROR_RECOVERY = 0x05,
+ FID_VOLATILE_WRITE_CACHE = 0x06,
+ FID_NUMBER_OF_QUEUES = 0x07,
+ FID_INTERRUPT_COALESCING = 0x08,
+ FID_INTERRUPT_VECTOR_CONFIGURATION = 0x09,
+ FID_WRITE_ATOMICITY = 0x0A,
+ FID_ASYNCHRONOUS_EVENT_CONFIGURATION = 0x0B,
+ FID_AUTONOMOUS_POWER_STATE_TRANSITION = 0x0C,
+ /*Below FID's are NVM Command Set Specific*/
+ FID_SOFTWARE_PROGRESS_MARKER = 0x80,
+ FID_HOST_IDENTIFIER = 0x81,
+ FID_RESERVATION_NOTIFICATION_MASK = 0x82,
+ FID_RESERVATION_PERSISTENCE = 0x83
+};
+
+/* WDC UUID value */
+const uint8_t WDC_UUID[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0xb9, 0x8c, 0x52, 0x0c, 0x4c,
+ 0x5a, 0x15, 0xab, 0xe6, 0x33, 0x29, 0x9a, 0x70, 0xdf, 0xd0
+};
+
+/* WDC_UUID value for SN640_3 devices */
+const uint8_t WDC_UUID_SN640_3[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+ 0x11, 0x11, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22
+};
+
+/* UUID field with value of 0 indicates end of UUID List*/
+const uint8_t UUID_END[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+enum WDC_DRIVE_ESSENTIAL_TYPE {
+ WDC_DE_TYPE_IDENTIFY = 0x1,
+ WDC_DE_TYPE_SMARTATTRIBUTEDUMP = 0x2,
+ WDC_DE_TYPE_EVENTLOG = 0x4,
+ WDC_DE_TYPE_DUMPTRACE = 0x8,
+ WDC_DE_TYPE_DUMPSNAPSHOT = 0x10,
+ WDC_DE_TYPE_ATA_LOGS = 0x20,
+ WDC_DE_TYPE_SMART_LOGS = 0x40,
+ WDC_DE_TYPE_SCSI_LOGS = 0x80,
+ WDC_DE_TYPE_SCSI_MODE_PAGES = 0x100,
+ WDC_DE_TYPE_NVMe_FEATURES = 0x200,
+ WDC_DE_TYPE_DUMPSMARTERRORLOG3 = 0x400,
+ WDC_DE_TYPE_DUMPLOG3E = 0x800,
+ WDC_DE_TYPE_DUMPSCRAM = 0x1000,
+ WDC_DE_TYPE_PCU_LOG = 0x2000,
+ WDC_DE_TYPE_DUMP_ERROR_LOGS = 0x4000,
+ WDC_DE_TYPE_FW_SLOT_LOGS = 0x8000,
+ WDC_DE_TYPE_MEDIA_SETTINGS = 0x10000,
+ WDC_DE_TYPE_SMART_DATA = 0x20000,
+ WDC_DE_TYPE_NVME_SETTINGS = 0x40000,
+ WDC_DE_TYPE_NVME_ERROR_LOGS = 0x80000,
+ WDC_DE_TYPE_NVME_LOGS = 0x100000,
+ WDC_DE_TYPE_UART_LOGS = 0x200000,
+ WDC_DE_TYPE_DLOGS_SPI = 0x400000,
+ WDC_DE_TYPE_DLOGS_RAM = 0x800000,
+ WDC_DE_TYPE_NVME_MANF_INFO = 0x2000000,
+ WDC_DE_TYPE_NONE = 0x1000000,
+ WDC_DE_TYPE_ALL = 0xFFFFFFF,
+};
+
+#define WDC_C0_GUID_LENGTH 16
+#define WDC_SCA_V1_NAND_STATS 0x1
+#define WDC_SCA_V1_ALL 0xF
+enum {
+ SCAO_V1_PMUWT = 0, /* Physical media units written TLC */
+ SCAO_V1_PMUWS = 16, /* Physical media units written SLC */
+ SCAO_V1_BUNBN = 32, /* Bad user nand blocks normalized */
+ SCAO_V1_BUNBR = 34, /* Bad user nand blocks raw */
+ SCAO_V1_XRC = 40, /* XOR recovery count */
+ SCAO_V1_UREC = 48, /* Uncorrectable read error count */
+ SCAO_V1_EECE = 56, /* End to end corrected errors */
+ SCAO_V1_EEDE = 64, /* End to end detected errors */
+ SCAO_V1_EEUE = 72, /* End to end uncorrected errors */
+ SCAO_V1_SDPU = 80, /* System data percent used */
+ SCAO_V1_MNUDEC = 84, /* Min User data erase counts (TLC) */
+ SCAO_V1_MXUDEC = 92, /* Max User data erase counts (TLC) */
+ SCAO_V1_AVUDEC = 100, /* Average User data erase counts (TLC) */
+ SCAO_V1_MNEC = 108, /* Min Erase counts (SLC) */
+ SCAO_V1_MXEC = 116, /* Max Erase counts (SLC) */
+ SCAO_V1_AVEC = 124, /* Average Erase counts (SLC) */
+ SCAO_V1_PFCN = 132, /* Program fail count normalized */
+ SCAO_V1_PFCR = 134, /* Program fail count raw */
+ SCAO_V1_EFCN = 140, /* Erase fail count normalized */
+ SCAO_V1_EFCR = 142, /* Erase fail count raw */
+ SCAO_V1_PCEC = 148, /* PCIe correctable error count */
+ SCAO_V1_PFBU = 156, /* Percent free blocks (User) */
+ SCAO_V1_SVN = 160, /* Security Version Number */
+ SCAO_V1_PFBS = 168, /* Percent free blocks (System) */
+ SCAO_V1_DCC = 172, /* Deallocate Commands Completed */
+ SCAO_V1_TNU = 188, /* Total Namespace Utilization */
+ SCAO_V1_FCC = 196, /* Format NVM Commands Completed */
+ SCAO_V1_BBPG = 198, /* Background Back-Pressure Gauge */
+ SCAO_V1_SEEC = 202, /* Soft ECC error count */
+ SCAO_V1_RFSC = 210, /* Refresh count */
+ SCAO_V1_BSNBN = 218, /* Bad system nand blocks normalized */
+ SCAO_V1_BSNBR = 220, /* Bad system nand blocks raw */
+ SCAO_V1_EEST = 226, /* Endurance estimate */
+ SCAO_V1_TTC = 242, /* Thermal throttling count */
+ SCAO_V1_UIO = 244, /* Unaligned I/O */
+ SCAO_V1_PMUR = 252, /* Physical media units read */
+ SCAO_V1_RTOC = 268, /* Read command timeout count */
+ SCAO_V1_WTOC = 272, /* Write command timeout count */
+ SCAO_V1_TTOC = 276, /* Trim command timeout count */
+ SCAO_V1_PLRC = 284, /* PCIe Link Retraining Count */
+ SCAO_V1_PSCC = 292, /* Power State Change Count */
+ SCAO_V1_MAVF = 300, /* Boot SSD major version field */
+ SCAO_V1_MIVF = 302, /* Boot SSD minor version field */
+ SCAO_V1_PVF = 304, /* Boot SSD point version field */
+ SCAO_V1_EVF = 306, /* Boot SSD errata version field */
+ SCAO_V1_FTLUS = 308, /* FTL Unit Size */
+ SCAO_V1_TCGOS = 312, /* TCG Ownership Status */
+
+ SCAO_V1_LPV = 494, /* Log page version - 0x0001 */
+ SCAO_V1_LPG = 496, /* Log page GUID */
+};
+
+static __u8 ext_smart_guid[WDC_C0_GUID_LENGTH] = {
+ 0x65, 0x43, 0x88, 0x78, 0xAC, 0xD8, 0x78, 0xA1,
+ 0x66, 0x42, 0x1E, 0x0F, 0x92, 0xD7, 0x6D, 0xC4
+};
+
+struct __packed wdc_nvme_ext_smart_log {
+ __u8 ext_smart_pmuwt[16]; /* 000 Physical media units written TLC */
+ __u8 ext_smart_pmuws[16]; /* 016 Physical media units written SLC */
+ __u8 ext_smart_bunbc[8]; /* 032 Bad user nand block count */
+ __u64 ext_smart_xrc; /* 040 XOR recovery count */
+ __u64 ext_smart_urec; /* 048 Uncorrectable read error count */
+ __u64 ext_smart_eece; /* 056 End to end corrected errors */
+ __u64 ext_smart_eede; /* 064 End to end detected errors */
+ __u64 ext_smart_eeue; /* 072 End to end uncorrected errors */
+ __u8 ext_smart_sdpu; /* 080 System data percent used */
+ __u8 ext_smart_rsvd1[3]; /* 081 reserved */
+ __u64 ext_smart_mnudec; /* 084 Min User data erase counts (TLC) */
+ __u64 ext_smart_mxudec; /* 092 Max User data erase counts (TLC) */
+ __u64 ext_smart_avudec; /* 100 Average User data erase counts (TLC) */
+ __u64 ext_smart_mnec; /* 108 Min Erase counts (SLC) */
+ __u64 ext_smart_mxec; /* 116 Max Erase counts (SLC) */
+ __u64 ext_smart_avec; /* 124 Average Erase counts (SLC) */
+ __u8 ext_smart_pfc[8]; /* 132 Program fail count */
+ __u8 ext_smart_efc[8]; /* 140 Erase fail count */
+ __u64 ext_smart_pcec; /* 148 PCIe correctable error count */
+ __u8 ext_smart_pfbu; /* 156 Percent free blocks (User) */
+ __u8 ext_smart_rsvd2[3]; /* 157 reserved */
+ __u64 ext_smart_svn; /* 160 Security Version Number */
+ __u8 ext_smart_pfbs; /* 168 Percent free blocks (System) */
+ __u8 ext_smart_rsvd3[3]; /* 169 reserved */
+ __u8 ext_smart_dcc[16]; /* 172 Deallocate Commands Completed */
+ __u64 ext_smart_tnu; /* 188 Total Namespace Utilization */
+ __u16 ext_smart_fcc; /* 196 Format NVM Commands Completed */
+ __u8 ext_smart_bbpg; /* 198 Background Back-Pressure Gauge */
+ __u8 ext_smart_rsvd4[3]; /* 199 reserved */
+ __u64 ext_smart_seec; /* 202 Soft ECC error count */
+ __u64 ext_smart_rfsc; /* 210 Refresh count */
+ __u8 ext_smart_bsnbc[8]; /* 218 Bad system nand block count */
+ __u8 ext_smart_eest[16]; /* 226 Endurance estimate */
+ __u16 ext_smart_ttc; /* 242 Thermal throttling count */
+ __u64 ext_smart_uio; /* 244 Unaligned I/O */
+ __u8 ext_smart_pmur[16]; /* 252 Physical media units read */
+ __u32 ext_smart_rtoc; /* 268 Read command timeout count */
+ __u32 ext_smart_wtoc; /* 272 Write command timeout count */
+ __u32 ext_smart_ttoc; /* 276 Trim command timeout count */
+ __u8 ext_smart_rsvd5[4]; /* 280 reserved */
+ __u64 ext_smart_plrc; /* 284 PCIe Link Retraining Count */
+ __u64 ext_smart_pscc; /* 292 Power State Change Count */
+ __u16 ext_smart_maj; /* 300 Boot SSD major version field */
+ __u16 ext_smart_min; /* 302 Boot SSD minor version field */
+ __u16 ext_smart_pt; /* 304 Boot SSD point version field */
+ __u16 ext_smart_err; /* 306 Boot SSD errata version field */
+ __u32 ext_smart_ftlus; /* 308 FTL Unit Size */
+ __u32 ext_smart_tcgos; /* 312 TCG Ownership Status */
+ __u8 ext_smart_rsvd6[178]; /* 316 reserved */
+ __u16 ext_smart_lpv; /* 494 Log page version - 0x0001 */
+ __u8 ext_smart_lpg[16]; /* 496 Log page GUID */
+};
+
+enum {
+ SCAO_PMUW = 0, /* Physical media units written */
+ SCAO_PMUR = 16, /* Physical media units read */
+ SCAO_BUNBR = 32, /* Bad user nand blocks raw */
+ SCAO_BUNBN = 38, /* Bad user nand blocks normalized */
+ SCAO_BSNBR = 40, /* Bad system nand blocks raw */
+ SCAO_BSNBN = 46, /* Bad system nand blocks normalized */
+ SCAO_XRC = 48, /* XOR recovery count */
+ SCAO_UREC = 56, /* Uncorrectable read error count */
+ SCAO_SEEC = 64, /* Soft ecc error count */
+ SCAO_EECE = 72, /* End to end corrected errors */
+ SCAO_EEDC = 76, /* End to end detected errors */
+ SCAO_SDPU = 80, /* System data percent used */
+ SCAO_RFSC = 81, /* Refresh counts */
+ SCAO_MXUDEC = 88, /* Max User data erase counts */
+ SCAO_MNUDEC = 92, /* Min User data erase counts */
+ SCAO_NTTE = 96, /* Number of Thermal throttling events */
+ SCAO_CTS = 97, /* Current throttling status */
+ SCAO_EVF = 98, /* Errata Version Field */
+ SCAO_PVF = 99, /* Point Version Field */
+ SCAO_MIVF = 101, /* Minor Version Field */
+ SCAO_MAVF = 103, /* Major Version Field */
+ SCAO_PCEC = 104, /* PCIe correctable error count */
+ SCAO_ICS = 112, /* Incomplete shutdowns */
+ SCAO_PFB = 120, /* Percent free blocks */
+ SCAO_CPH = 128, /* Capacitor health */
+ SCAO_NEV = 130, /* NVMe Errata Version */
+ SCAO_UIO = 136, /* Unaligned I/O */
+ SCAO_SVN = 144, /* Security Version Number */
+ SCAO_NUSE = 152, /* NUSE - Namespace utilization */
+ SCAO_PSC = 160, /* PLP start count */
+ SCAO_EEST = 176, /* Endurance estimate */
+ SCAO_PLRC = 192, /* PCIe Link Retraining Count */
+ SCAO_PSCC = 200, /* Power State Change Count */
+ SCAO_LPV = 494, /* Log page version */
+ SCAO_LPG = 496, /* Log page GUID */
+};
+
+struct ocp_bad_nand_block_count {
+ __u64 raw : 48;
+ __u16 normalized : 16;
+};
+
+struct ocp_e2e_correction_count {
+ __u32 detected;
+ __u32 corrected;
+};
+
+struct ocp_user_data_erase_count {
+ __u32 maximum;
+ __u32 minimum;
+};
+
+struct ocp_thermal_status {
+ __u8 num_events;
+ __u8 current_status;
+};
+
+struct __packed ocp_dssd_specific_ver {
+ __u8 errata_ver;
+ __u16 point_ver;
+ __u16 minor_ver;
+ __u8 major_ver;
+};
+
+struct ocp_cloud_smart_log {
+ __u8 physical_media_units_written[16];
+ __u8 physical_media_units_read[16];
+ struct ocp_bad_nand_block_count bad_user_nand_blocks;
+ struct ocp_bad_nand_block_count bad_system_nand_blocks;
+ __u64 xor_recovery_count;
+ __u64 uncorrectable_read_error_count;
+ __u64 soft_ecc_error_count;
+ struct ocp_e2e_correction_count e2e_correction_counts;
+ __u8 system_data_percent_used;
+ __u64 refresh_counts : 56;
+ struct ocp_user_data_erase_count user_data_erase_counts;
+ struct ocp_thermal_status thermal_status;
+ struct ocp_dssd_specific_ver dssd_specific_ver;
+ __u64 pcie_correctable_error_count;
+ __u32 incomplete_shutdowns;
+ __u8 rsvd116[4];
+ __u8 percent_free_blocks;
+ __u8 rsvd121[7];
+ __u16 capacitor_health;
+ __u8 nvme_errata_ver;
+ __u8 rsvd131[5];
+ __u64 unaligned_io;
+ __u64 security_version_number;
+ __u64 total_nuse;
+ __u8 plp_start_count[16];
+ __u8 endurance_estimate[16];
+ __u64 pcie_link_retraining_cnt;
+ __u64 power_state_change_cnt;
+ __u8 rsvd208[286];
+ __u16 log_page_version;
+ __u8 log_page_guid[16];
+};
+
+static __u8 scao_guid[WDC_C0_GUID_LENGTH] = {
+ 0xC5, 0xAF, 0x10, 0x28, 0xEA, 0xBF, 0xF2, 0xA4,
+ 0x9C, 0x4F, 0x6F, 0x7C, 0xC9, 0x14, 0xD5, 0xAF
+};
+
+enum {
+ EOL_RBC = 76, /* Realloc Block Count */
+ EOL_ECCR = 80, /* ECC Rate */
+ EOL_WRA = 84, /* Write Amp */
+ EOL_PLR = 88, /* Percent Life Remaining */
+ EOL_RSVBC = 92, /* Reserved Block Count */
+ EOL_PFC = 96, /* Program Fail Count */
+ EOL_EFC = 100, /* Erase Fail Count */
+ EOL_RRER = 108, /* Raw Read Error Rate */
+};
+
+#define WDC_NVME_C6_GUID_LENGTH 16
+#define WDC_NVME_GET_HW_REV_LOG_OPCODE 0xc6
+#define WDC_NVME_HW_REV_LOG_PAGE_LEN 512
+
+struct __packed wdc_nvme_hw_rev_log {
+ __u8 hw_rev_gdr; /* 0 Global Device HW Revision */
+ __u8 hw_rev_ar; /* 1 ASIC HW Revision */
+ __u8 hw_rev_pbc_mc; /* 2 PCB Manufacturer Code */
+ __u8 hw_rev_dram_mc; /* 3 DRAM Manufacturer Code */
+ __u8 hw_rev_nand_mc; /* 4 NAND Manufacturer Code */
+ __u8 hw_rev_pmic1_mc; /* 5 PMIC 1 Manufacturer Code */
+ __u8 hw_rev_pmic2_mc; /* 6 PMIC 2 Manufacturer Code */
+ __u8 hw_rev_c1_mc; /* 7 Other Component 1 Manf Code */
+ __u8 hw_rev_c2_mc; /* 8 Other Component 2 Manf Code */
+ __u8 hw_rev_c3_mc; /* 9 Other Component 3 Manf Code */
+ __u8 hw_rev_c4_mc; /* 10 Other Component 4 Manf Code */
+ __u8 hw_rev_c5_mc; /* 11 Other Component 5 Manf Code */
+ __u8 hw_rev_c6_mc; /* 12 Other Component 6 Manf Code */
+ __u8 hw_rev_c7_mc; /* 13 Other Component 7 Manf Code */
+ __u8 hw_rev_c8_mc; /* 14 Other Component 8 Manf Code */
+ __u8 hw_rev_c9_mc; /* 15 Other Component 9 Manf Code */
+ __u8 hw_rev_rsrvd1[48]; /* 16 Reserved 48 bytes */
+ __u8 hw_rev_dev_mdi[16]; /* 64 Device Manf Detailed Info */
+ __u8 hw_rev_asic_di[16]; /* 80 ASIC Detailed Info */
+ __u8 hw_rev_pcb_di[16]; /* 96 PCB Detailed Info */
+ __u8 hw_rev_dram_di[16]; /* 112 DRAM Detailed Info */
+ __u8 hw_rev_nand_di[16]; /* 128 NAND Detailed Info */
+ __u8 hw_rev_pmic1_di[16]; /* 144 PMIC1 Detailed Info */
+ __u8 hw_rev_pmic2_di[16]; /* 160 PMIC2 Detailed Info */
+ __u8 hw_rev_c1_di[16]; /* 176 Component 1 Detailed Info */
+ __u8 hw_rev_c2_di[16]; /* 192 Component 2 Detailed Info */
+ __u8 hw_rev_c3_di[16]; /* 208 Component 3 Detailed Info */
+ __u8 hw_rev_c4_di[16]; /* 224 Component 4 Detailed Info */
+ __u8 hw_rev_c5_di[16]; /* 240 Component 5 Detailed Info */
+ __u8 hw_rev_c6_di[16]; /* 256 Component 6 Detailed Info */
+ __u8 hw_rev_c7_di[16]; /* 272 Component 7 Detailed Info */
+ __u8 hw_rev_c8_di[16]; /* 288 Component 8 Detailed Info */
+ __u8 hw_rev_c9_di[16]; /* 304 Component 9 Detailed Info */
+ __u8 hw_rev_sn[32]; /* 320 Serial Number */
+ __u8 hw_rev_rsrvd2[142]; /* 352 Reserved 143 bytes */
+ __u16 hw_rev_version; /* 494 Log Page Version */
+ __u8 hw_rev_guid[16]; /* 496 Log Page GUID */
+};
+
+static __u8 hw_rev_log_guid[WDC_NVME_C6_GUID_LENGTH] = {
+ 0xAA, 0xB0, 0x05, 0xF5, 0x13, 0x5E, 0x48, 0x15,
+ 0xAB, 0x89, 0x05, 0xBA, 0x8B, 0xE2, 0xBF, 0x3C
+};
+
+struct __packed WDC_DE_VU_FILE_META_DATA {
+ __u8 fileName[WDC_DE_FILE_NAME_SIZE];
+ __u16 fileID;
+ __u64 fileSize;
+};
+
+struct WDC_DRIVE_ESSENTIALS {
+ struct __packed WDC_DE_VU_FILE_META_DATA metaData;
+ enum WDC_DRIVE_ESSENTIAL_TYPE essentialType;
+};
+
+struct WDC_DE_VU_LOG_DIRECTORY {
+ struct WDC_DRIVE_ESSENTIALS *logEntry; /* Caller to allocate memory */
+ __u32 maxNumLogEntries; /* Caller to input memory allocated */
+ __u32 numOfValidLogEntries; /* API will output this value */
+};
+
+struct WDC_DE_CSA_FEATURE_ID_LIST {
+ enum NVME_FEATURE_IDENTIFIERS featureId;
+ __u8 featureName[WDC_DE_GENERIC_BUFFER_SIZE];
+};
+
+struct tarfile_metadata {
+ char fileName[MAX_PATH_LEN];
+ int8_t bufferFolderPath[MAX_PATH_LEN];
+ char bufferFolderName[MAX_PATH_LEN];
+ char tarFileName[MAX_PATH_LEN];
+ char tarFiles[MAX_PATH_LEN];
+ char tarCmd[MAX_PATH_LEN+MAX_PATH_LEN];
+ char currDir[MAX_PATH_LEN];
+ UtilsTimeInfo timeInfo;
+ uint8_t *timeString[MAX_PATH_LEN];
+};
+
+static struct WDC_DE_CSA_FEATURE_ID_LIST deFeatureIdList[] = {
+ {0x00, "Dummy Placeholder"},
+ {FID_ARBITRATION, "Arbitration"},
+ {FID_POWER_MANAGEMENT, "PowerMgmnt"},
+ {FID_LBA_RANGE_TYPE, "LbaRangeType"},
+ {FID_TEMPERATURE_THRESHOLD, "TempThreshold"},
+ {FID_ERROR_RECOVERY, "ErrorRecovery"},
+ {FID_VOLATILE_WRITE_CACHE, "VolatileWriteCache"},
+ {FID_NUMBER_OF_QUEUES, "NumOfQueues"},
+ {FID_INTERRUPT_COALESCING, "InterruptCoalesing"},
+ {FID_INTERRUPT_VECTOR_CONFIGURATION, "InterruptVectorConfig"},
+ {FID_WRITE_ATOMICITY, "WriteAtomicity"},
+ {FID_ASYNCHRONOUS_EVENT_CONFIGURATION, "AsynEventConfig"},
+ {FID_AUTONOMOUS_POWER_STATE_TRANSITION, "AutonomousPowerState"},
+};
+
+enum NVME_VU_DE_LOGPAGE_NAMES {
+ NVME_DE_LOGPAGE_E3 = 0x01,
+ NVME_DE_LOGPAGE_C0 = 0x02
+};
+
+struct NVME_VU_DE_LOGPAGE_LIST {
+ enum NVME_VU_DE_LOGPAGE_NAMES logPageName;
+ __u32 logPageId;
+ __u32 logPageLen;
+ char logPageIdStr[5];
+};
+
+struct WDC_NVME_DE_VU_LOGPAGES {
+ enum NVME_VU_DE_LOGPAGE_NAMES vuLogPageReqd;
+ __u32 numOfVULogPages;
+};
+
+static struct NVME_VU_DE_LOGPAGE_LIST deVULogPagesList[] = {
+ { NVME_DE_LOGPAGE_E3, 0xE3, 1072, "0xe3"},
+ { NVME_DE_LOGPAGE_C0, 0xC0, 512, "0xc0"}
+};
+
+enum {
+ WDC_NVME_ADMIN_VUC_OPCODE_D2 = 0xD2,
+ WDC_VUC_SUBOPCODE_VS_DRIVE_INFO_D2 = 0x0000010A,
+ WDC_VUC_SUBOPCODE_LOG_PAGE_DIR_D2 = 0x00000105,
+};
+
+enum {
+ NVME_LOG_NS_BASE = 0x80,
+ NVME_LOG_VS_BASE = 0xC0,
+};
+
+/*drive_info struct*/
+struct ocp_drive_info {
+ __u32 hw_revision;
+ __u32 ftl_unit_size;
+};
+
+/*get log page directory struct*/
+struct log_page_directory {
+ __u64 supported_lid_bitmap;
+ __u64 rsvd;
+ __u64 supported_ns_lid_bitmap;
+ __u64 supported_vs_lid_bitmap;
+};
+
+/*set latency monitor feature */
+struct __packed feature_latency_monitor {
+ __u16 active_bucket_timer_threshold;
+ __u8 active_threshold_a;
+ __u8 active_threshold_b;
+ __u8 active_threshold_c;
+ __u8 active_threshold_d;
+ __u16 active_latency_config;
+ __u8 active_latency_minimum_window;
+ __u16 debug_log_trigger_enable;
+ __u8 discard_debug_log;
+ __u8 latency_monitor_feature_enable;
+ __u8 reserved[4083];
+};
+
+static int wdc_get_serial_name(struct nvme_dev *dev, char *file, size_t len, const char *suffix);
+static int wdc_create_log_file(char *file, __u8 *drive_log_data, __u32 drive_log_length);
+static int wdc_do_clear_dump(struct nvme_dev *dev, __u8 opcode, __u32 cdw12);
+static int wdc_do_dump(struct nvme_dev *dev, __u32 opcode, __u32 data_len, __u32 cdw12, char *file,
+ __u32 xfer_size);
+static int wdc_do_crash_dump(struct nvme_dev *dev, char *file, int type);
+static int wdc_crash_dump(struct nvme_dev *dev, char *file, int type);
+static int wdc_get_crash_dump(int argc, char **argv, struct command *command,
+ struct plugin *plugin);
+static int wdc_do_drive_log(struct nvme_dev *dev, char *file);
+static int wdc_drive_log(int argc, char **argv, struct command *command, struct plugin *plugin);
+static const char *wdc_purge_mon_status_to_string(__u32 status);
+static int wdc_purge(int argc, char **argv, struct command *command, struct plugin *plugin);
+static int wdc_purge_monitor(int argc, char **argv, struct command *command, struct plugin *plugin);
+static bool wdc_nvme_check_supported_log_page(nvme_root_t r, struct nvme_dev *dev, __u8 log_id);
+static int wdc_clear_pcie_correctable_errors(int argc, char **argv, struct command *command,
+ struct plugin *plugin);
+static int wdc_do_drive_essentials(nvme_root_t r, struct nvme_dev *dev, char *dir, char *key);
+static int wdc_drive_essentials(int argc, char **argv, struct command *command,
+ struct plugin *plugin);
+static int wdc_drive_status(int argc, char **argv, struct command *command, struct plugin *plugin);
+static int wdc_clear_assert_dump(int argc, char **argv, struct command *command,
+ struct plugin *plugin);
+static int wdc_drive_resize(int argc, char **argv, struct command *command, struct plugin *plugin);
+static int wdc_do_drive_resize(struct nvme_dev *dev, uint64_t new_size);
+static int wdc_namespace_resize(int argc, char **argv, struct command *command,
+ struct plugin *plugin);
+static int wdc_do_namespace_resize(struct nvme_dev *dev, __u32 nsid, __u32 op_option);
+static int wdc_reason_identifier(int argc, char **argv, struct command *command,
+ struct plugin *plugin);
+static int wdc_do_get_reason_id(struct nvme_dev *dev, char *file, int log_id);
+static int wdc_save_reason_id(struct nvme_dev *dev, __u8 *rsn_ident, int size);
+static int wdc_clear_reason_id(struct nvme_dev *dev);
+static int wdc_log_page_directory(int argc, char **argv, struct command *command,
+ struct plugin *plugin);
+static int wdc_do_drive_info(struct nvme_dev *dev, __u32 *result);
+static int wdc_vs_drive_info(int argc, char **argv, struct command *command, struct plugin *plugin);
+static int wdc_vs_temperature_stats(int argc, char **argv, struct command *command,
+ struct plugin *plugin);
+static __u64 wdc_get_enc_drive_capabilities(nvme_root_t r, struct nvme_dev *dev);
+static int wdc_enc_get_nic_log(struct nvme_dev *dev, __u8 log_id, __u32 xfer_size, __u32 data_len,
+ FILE *out);
+static int wdc_enc_submit_move_data(struct nvme_dev *dev, char *cmd, int len, int xfer_size,
+ FILE *out, int data_id, int cdw14, int cdw15);
+static bool get_dev_mgment_cbs_data(nvme_root_t r, struct nvme_dev *dev, __u8 log_id,
+ void **cbs_data);
+static __u32 wdc_get_fw_cust_id(nvme_root_t r, struct nvme_dev *dev);
+
+/* Drive log data size */
+struct wdc_log_size {
+ __le32 log_size;
+};
+
+/* E6 log header */
+struct wdc_e6_log_hdr {
+ __le32 eye_catcher;
+ __u8 log_size[4];
+};
+
+/* DUI log header */
+struct wdc_dui_log_section {
+ __le16 section_type;
+ __le16 reserved;
+ __le32 section_size;
+};
+
+/* DUI log header V2 */
+struct __packed wdc_dui_log_section_v2 {
+ __le16 section_type;
+ __le16 data_area_id;
+ __le64 section_size;
+};
+
+/* DUI log header V4 */
+struct wdc_dui_log_section_v4 {
+ __le16 section_type;
+ __u8 data_area_id;
+ __u8 reserved;
+ __le32 section_size_sectors;
+};
+
+struct wdc_dui_log_hdr {
+ __u8 telemetry_hdr[512];
+ __le16 hdr_version;
+ __le16 section_count;
+ __le32 log_size;
+ struct wdc_dui_log_section log_section[WDC_NVME_DUI_MAX_SECTION];
+ __u8 log_data[40];
+};
+
+struct __packed wdc_dui_log_hdr_v2 {
+ __u8 telemetry_hdr[512];
+ __u8 hdr_version;
+ __u8 product_id;
+ __le16 section_count;
+ __le64 log_size;
+ struct wdc_dui_log_section_v2 log_section[WDC_NVME_DUI_MAX_SECTION_V2];
+ __u8 log_data[40];
+};
+
+struct __packed wdc_dui_log_hdr_v3 {
+ __u8 telemetry_hdr[512];
+ __u8 hdr_version;
+ __u8 product_id;
+ __le16 section_count;
+ __le64 log_size;
+ struct wdc_dui_log_section_v2 log_section[WDC_NVME_DUI_MAX_SECTION_V3];
+ __u8 securityNonce[36];
+ __u8 log_data[40];
+};
+
+struct __packed wdc_dui_log_hdr_v4 {
+ __u8 telemetry_hdr[512];
+ __u8 hdr_version;
+ __u8 product_id;
+ __le16 section_count;
+ __le32 log_size_sectors;
+ struct wdc_dui_log_section_v4 log_section[WDC_NVME_DUI_MAX_SECTION];
+ __u8 log_data[40];
+};
+
+/* Purge monitor response */
+struct wdc_nvme_purge_monitor_data {
+ __le16 rsvd1;
+ __le16 rsvd2;
+ __le16 first_erase_failure_cnt;
+ __le16 second_erase_failure_cnt;
+ __le16 rsvd3;
+ __le16 programm_failure_cnt;
+ __le32 rsvd4;
+ __le32 rsvd5;
+ __le32 entire_progress_total;
+ __le32 entire_progress_current;
+ __u8 rsvd6[14];
+};
+
+/* Additional Smart Log */
+struct wdc_log_page_header {
+ uint8_t num_subpages;
+ uint8_t reserved;
+ __le16 total_log_size;
+};
+
+struct wdc_log_page_subpage_header {
+ uint8_t spcode;
+ uint8_t pcset;
+ __le16 subpage_length;
+};
+
+struct wdc_ssd_perf_stats {
+ __le64 hr_cmds; /* Host Read Commands */
+ __le64 hr_blks; /* Host Read Blocks */
+ __le64 hr_ch_cmds; /* Host Read Cache Hit Commands */
+ __le64 hr_ch_blks; /* Host Read Cache Hit Blocks */
+ __le64 hr_st_cmds; /* Host Read Stalled Commands */
+ __le64 hw_cmds; /* Host Write Commands */
+ __le64 hw_blks; /* Host Write Blocks */
+ __le64 hw_os_cmds; /* Host Write Odd Start Commands */
+ __le64 hw_oe_cmds; /* Host Write Odd End Commands */
+ __le64 hw_st_cmds; /* Host Write Commands Stalled */
+ __le64 nr_cmds; /* NAND Read Commands */
+ __le64 nr_blks; /* NAND Read Blocks */
+ __le64 nw_cmds; /* NAND Write Commands */
+ __le64 nw_blks; /* NAND Write Blocks */
+ __le64 nrbw; /* NAND Read Before Write */
+};
+
+/* Additional C2 Log Page */
+struct wdc_c2_log_page_header {
+ __le32 length;
+ __le32 version;
+};
+
+struct wdc_c2_log_subpage_header {
+ __le32 length;
+ __le32 entry_id;
+ __le32 data;
+};
+
+struct wdc_c2_cbs_data {
+ __le32 length;
+ __u8 data[];
+};
+
+struct __packed wdc_bd_ca_log_format {
+ __u8 field_id;
+ __u8 reserved1[2];
+ __u8 normalized_value;
+ __u8 raw_value[8];
+};
+
+#define LATENCY_LOG_BUCKET_READ 3
+#define LATENCY_LOG_BUCKET_WRITE 2
+#define LATENCY_LOG_BUCKET_TRIM 1
+#define LATENCY_LOG_BUCKET_RESERVED 0
+
+#define LATENCY_LOG_MEASURED_LAT_READ 2
+#define LATENCY_LOG_MEASURED_LAT_WRITE 1
+#define LATENCY_LOG_MEASURED_LAT_TRIM 0
+
+struct __packed wdc_ssd_latency_monitor_log {
+ __u8 feature_status; /* 0x00 */
+ __u8 rsvd1; /* 0x01 */
+ __le16 active_bucket_timer; /* 0x02 */
+ __le16 active_bucket_timer_threshold; /* 0x04 */
+ __u8 active_threshold_a; /* 0x06 */
+ __u8 active_threshold_b; /* 0x07 */
+ __u8 active_threshold_c; /* 0x08 */
+ __u8 active_threshold_d; /* 0x09 */
+ __le16 active_latency_config; /* 0x0A */
+ __u8 active_latency_min_window; /* 0x0C */
+ __u8 rsvd2[0x13]; /* 0x0D */
+
+ __le32 active_bucket_counter[4][4]; /* 0x20 - 0x5F */
+ __le64 active_latency_timestamp[4][3]; /* 0x60 - 0xBF */
+ __le16 active_measured_latency[4][3]; /* 0xC0 - 0xD7 */
+ __le16 active_latency_stamp_units; /* 0xD8 */
+ __u8 rsvd3[0x16]; /* 0xDA */
+
+ __le32 static_bucket_counter[4][4] ; /* 0xF0 - 0x12F */
+ __le64 static_latency_timestamp[4][3]; /* 0x130 - 0x18F */
+ __le16 static_measured_latency[4][3]; /* 0x190 - 0x1A7 */
+ __le16 static_latency_stamp_units; /* 0x1A8 */
+ __u8 rsvd4[0x16]; /* 0x1AA */
+
+ __le16 debug_log_trigger_enable; /* 0x1C0 */
+ __le16 debug_log_measured_latency; /* 0x1C2 */
+ __le64 debug_log_latency_stamp; /* 0x1C4 */
+ __le16 debug_log_ptr; /* 0x1CC */
+ __le16 debug_log_counter_trigger; /* 0x1CE */
+ __u8 debug_log_stamp_units; /* 0x1D0 */
+ __u8 rsvd5[0x1D]; /* 0x1D1 */
+
+ __le16 log_page_version; /* 0x1EE */
+ __u8 log_page_guid[0x10]; /* 0x1F0 */
+};
+
+struct __packed wdc_ssd_ca_perf_stats {
+ __le64 nand_bytes_wr_lo; /* 0x00 - NAND Bytes Written lo */
+ __le64 nand_bytes_wr_hi; /* 0x08 - NAND Bytes Written hi */
+ __le64 nand_bytes_rd_lo; /* 0x10 - NAND Bytes Read lo */
+ __le64 nand_bytes_rd_hi; /* 0x18 - NAND Bytes Read hi */
+ __le64 nand_bad_block; /* 0x20 - NAND Bad Block Count */
+ __le64 uncorr_read_count; /* 0x28 - Uncorrectable Read Count */
+ __le64 ecc_error_count; /* 0x30 - Soft ECC Error Count */
+ __le32 ssd_detect_count; /* 0x38 - SSD End to End Detection Count */
+ __le32 ssd_correct_count; /* 0x3C - SSD End to End Correction Count */
+ __u8 data_percent_used; /* 0x40 - System Data Percent Used */
+ __le32 data_erase_max; /* 0x41 - User Data Erase Counts */
+ __le32 data_erase_min; /* 0x45 - User Data Erase Counts */
+ __le64 refresh_count; /* 0x49 - Refresh Count */
+ __le64 program_fail; /* 0x51 - Program Fail Count */
+ __le64 user_erase_fail; /* 0x59 - User Data Erase Fail Count */
+ __le64 system_erase_fail; /* 0x61 - System Area Erase Fail Count */
+ __u8 thermal_throttle_status; /* 0x69 - Thermal Throttling Status */
+ __u8 thermal_throttle_count; /* 0x6A - Thermal Throttling Count */
+ __le64 pcie_corr_error; /* 0x6B - pcie Correctable Error Count */
+ __le32 incomplete_shutdown_count; /* 0x73 - Incomplete Shutdown Count */
+ __u8 percent_free_blocks; /* 0x77 - Percent Free Blocks */
+ __u8 rsvd[392]; /* 0x78 - Reserved bytes 120-511 */
+};
+
+struct __packed wdc_ssd_d0_smart_log {
+ __le32 smart_log_page_header; /* 0x00 - Smart Log Page Header */
+ __le32 lifetime_realloc_erase_block_count; /* 0x04 - Lifetime reallocated erase block count */
+ __le32 lifetime_power_on_hours; /* 0x08 - Lifetime power on hours */
+ __le32 lifetime_uecc_count; /* 0x0C - Lifetime UECC count */
+ __le32 lifetime_wrt_amp_factor; /* 0x10 - Lifetime write amplification factor */
+ __le32 trailing_hr_wrt_amp_factor; /* 0x14 - Trailing hour write amplification factor */
+ __le32 reserve_erase_block_count; /* 0x18 - Reserve erase block count */
+ __le32 lifetime_program_fail_count; /* 0x1C - Lifetime program fail count */
+ __le32 lifetime_block_erase_fail_count; /* 0x20 - Lifetime block erase fail count */
+ __le32 lifetime_die_failure_count; /* 0x24 - Lifetime die failure count */
+ __le32 lifetime_link_rate_downgrade_count; /* 0x28 - Lifetime link rate downgrade count */
+ __le32 lifetime_clean_shutdown_count; /* 0x2C - Lifetime clean shutdown count on power loss */
+ __le32 lifetime_unclean_shutdown_count; /* 0x30 - Lifetime unclean shutdowns on power loss */
+ __le32 current_temp; /* 0x34 - Current temperature */
+ __le32 max_recorded_temp; /* 0x38 - Max recorded temperature */
+ __le32 lifetime_retired_block_count; /* 0x3C - Lifetime retired block count */
+ __le32 lifetime_read_disturb_realloc_events; /* 0x40 - Lifetime read disturb reallocation events */
+ __le64 lifetime_nand_writes; /* 0x44 - Lifetime NAND write Lpages */
+ __le32 capacitor_health; /* 0x4C - Capacitor health */
+ __le64 lifetime_user_writes; /* 0x50 - Lifetime user writes */
+ __le64 lifetime_user_reads; /* 0x58 - Lifetime user reads */
+ __le32 lifetime_thermal_throttle_act; /* 0x60 - Lifetime thermal throttle activations */
+ __le32 percentage_pe_cycles_remaining; /* 0x64 - Percentage of P/E cycles remaining */
+ __u8 rsvd[408]; /* 0x68 - 408 Reserved bytes */
+};
+
+#define WDC_OCP_C1_GUID_LENGTH 16
+#define WDC_ERROR_REC_LOG_BUF_LEN 512
+#define WDC_ERROR_REC_LOG_ID 0xC1
+#define WDC_ERROR_REC_LOG_VERSION1 0001
+#define WDC_ERROR_REC_LOG_VERSION2 0002
+
+struct __packed wdc_ocp_c1_error_recovery_log {
+ __le16 panic_reset_wait_time; /* 000 - Panic Reset Wait Time */
+ __u8 panic_reset_action; /* 002 - Panic Reset Action */
+ __u8 dev_recovery_action1; /* 003 - Device Recovery Action 1 */
+ __le64 panic_id; /* 004 - Panic ID */
+ __le32 dev_capabilities; /* 012 - Device Capabilities */
+ __u8 vs_recovery_opc; /* 016 - Vendor Specific Recovery Opcode */
+ __u8 rsvd1[3]; /* 017 - 3 Reserved Bytes */
+ __le32 vs_cmd_cdw12; /* 020 - Vendor Specific Command CDW12 */
+ __le32 vs_cmd_cdw13; /* 024 - Vendor Specific Command CDW13 */
+ __u8 vs_cmd_to; /* 028 - Vendor Specific Command Timeout V2 */
+ __u8 dev_recovery_action2; /* 029 - Device Recovery Action 2 V2 */
+ __u8 dev_recovery_action2_to; /* 030 - Device Recovery Action 2 Timeout V2 */
+ __u8 rsvd2[463]; /* 031 - 463 Reserved Bytes */
+ __le16 log_page_version; /* 494 - Log Page Version */
+ __u8 log_page_guid[WDC_OCP_C1_GUID_LENGTH]; /* 496 - Log Page GUID */
+};
+
+static __u8 wdc_ocp_c1_guid[WDC_OCP_C1_GUID_LENGTH] = { 0x44, 0xD9, 0x31, 0x21, 0xFE, 0x30, 0x34, 0xAE,
+ 0xAB, 0x4D, 0xFD, 0x3D, 0xBA, 0x83, 0x19, 0x5A };
+
+/* NAND Stats */
+struct __packed wdc_nand_stats {
+ __u8 nand_write_tlc[16];
+ __u8 nand_write_slc[16];
+ __le32 nand_prog_failure;
+ __le32 nand_erase_failure;
+ __le32 bad_block_count;
+ __le64 nand_rec_trigger_event;
+ __le64 e2e_error_counter;
+ __le64 successful_ns_resize_event;
+ __u8 rsvd[442];
+ __u16 log_page_version;
+};
+
+struct __packed wdc_nand_stats_V3 {
+ __u8 nand_write_tlc[16];
+ __u8 nand_write_slc[16];
+ __u8 bad_nand_block_count[8];
+ __le64 xor_recovery_count;
+ __le64 uecc_read_error_count;
+ __u8 ssd_correction_counts[16];
+ __u8 percent_life_used;
+ __le64 user_data_erase_counts[4];
+ __u8 program_fail_count[8];
+ __u8 erase_fail_count[8];
+ __le64 correctable_error_count;
+ __u8 percent_free_blocks_user;
+ __le64 security_version_number;
+ __u8 percent_free_blocks_system;
+ __u8 trim_completions[25];
+ __u8 back_pressure_guage;
+ __le64 soft_ecc_error_count;
+ __le64 refresh_count;
+ __u8 bad_sys_nand_block_count[8];
+ __u8 endurance_estimate[16];
+ __u8 thermal_throttling_st_ct[2];
+ __le64 unaligned_IO;
+ __u8 physical_media_units[16];
+ __u8 reserved[279];
+ __u16 log_page_version;
+};
+
+struct wdc_vs_pcie_stats {
+ __le64 unsupportedRequestErrorCount;
+ __le64 ecrcErrorStatusCount;
+ __le64 malformedTlpStatusCount;
+ __le64 receiverOverflowStatusCount;
+ __le64 unexpectedCmpltnStatusCount;
+ __le64 completeAbortStatusCount;
+ __le64 cmpltnTimoutStatusCount;
+ __le64 flowControlErrorStatusCount;
+ __le64 poisonedTlpStatusCount;
+ __le64 dLinkPrtclErrorStatusCount;
+ __le64 advsryNFatalErrStatusCount;
+ __le64 replayTimerToStatusCount;
+ __le64 replayNumRolloverStCount;
+ __le64 badDllpStatusCount;
+ __le64 badTlpStatusCount;
+ __le64 receiverErrStatusCount;
+ __u8 reserved1[384];
+};
+
+struct wdc_fw_act_history_log_hdr {
+ __le32 eye_catcher;
+ __u8 version;
+ __u8 reserved1;
+ __u8 num_entries;
+ __u8 reserved2;
+ __le32 entry_size;
+ __le32 reserved3;
+};
+
+struct wdc_fw_act_history_log_entry {
+ __le32 entry_num;
+ __le32 power_cycle_count;
+ __le64 power_on_seconds;
+ __le64 previous_fw_version;
+ __le64 new_fw_version;
+ __u8 slot_number;
+ __u8 commit_action_type;
+ __le16 result;
+ __u8 reserved[12];
+};
+
+struct __packed wdc_fw_act_history_log_entry_c2 {
+ __u8 entry_version_num;
+ __u8 entry_len;
+ __le16 reserved;
+ __le16 fw_act_hist_entries;
+ __le64 timestamp;
+ __u8 reserved2[8];
+ __le64 power_cycle_count;
+ __le64 previous_fw_version;
+ __le64 current_fw_version;
+ __u8 slot_number;
+ __u8 commit_action_type;
+ __le16 result;
+ __u8 reserved3[14];
+};
+
+struct __packed wdc_fw_act_history_log_format_c2 {
+ __u8 log_identifier;
+ __u8 reserved[3];
+ __le32 num_entries;
+ struct wdc_fw_act_history_log_entry_c2 entry[WDC_MAX_NUM_ACT_HIST_ENTRIES];
+ __u8 reserved2[2790];
+ __le16 log_page_version;
+ __u8 log_page_guid[WDC_C2_GUID_LENGTH];
+};
+
+#define WDC_OCP_C4_GUID_LENGTH 16
+#define WDC_DEV_CAP_LOG_BUF_LEN 4096
+#define WDC_DEV_CAP_LOG_ID 0xC4
+#define WDC_DEV_CAP_LOG_VERSION 0001
+#define WDC_OCP_C4_NUM_PS_DESCR 127
+
+struct __packed wdc_ocp_C4_dev_cap_log {
+ __le16 num_pcie_ports; /* 0000 - Number of PCI Express Ports */
+ __le16 oob_mgmt_support; /* 0002 - OOB Management Interfaces Supported */
+ __le16 wrt_zeros_support; /* 0004 - Write Zeros Command Support */
+ __le16 sanitize_support; /* 0006 - Sanitize Command Support */
+ __le16 dsm_support; /* 0008 - Dataset Management Command Support */
+ __le16 wrt_uncor_support; /* 0010 - Write Uncorrectable Command Support */
+ __le16 fused_support; /* 0012 - Fused Operation Support */
+ __le16 min_dssd_ps; /* 0014 - Minimum Valid DSSD Power State */
+ __u8 rsvd1; /* 0016 - Reserved must be cleared to zero */
+ __u8 dssd_ps_descr[WDC_OCP_C4_NUM_PS_DESCR];/* 0017 - DSSD Power State Descriptors */
+ __u8 rsvd2[3934]; /* 0144 - Reserved must be cleared to zero */
+ __le16 log_page_version; /* 4078 - Log Page Version */
+ __u8 log_page_guid[WDC_OCP_C4_GUID_LENGTH]; /* 4080 - Log Page GUID */
+};
+
+static __u8 wdc_ocp_c4_guid[WDC_OCP_C4_GUID_LENGTH] = {
+ 0x97, 0x42, 0x05, 0x0D, 0xD1, 0xE1, 0xC9, 0x98,
+ 0x5D, 0x49, 0x58, 0x4B, 0x91, 0x3C, 0x05, 0xB7
+};
+
+#define WDC_OCP_C5_GUID_LENGTH 16
+#define WDC_UNSUPPORTED_REQS_LOG_BUF_LEN 4096
+#define WDC_UNSUPPORTED_REQS_LOG_ID 0xC5
+#define WDC_UNSUPPORTED_REQS_LOG_VERSION 0001
+#define WDC_NUM_UNSUPPORTED_REQ_ENTRIES 253
+
+struct __packed wdc_ocp_C5_unsupported_reqs {
+ __le16 unsupported_count; /* 0000 - Number of Unsupported Requirement IDs */
+ __u8 rsvd1[14]; /* 0002 - Reserved must be cleared to zero */
+ __u8 unsupported_req_list[WDC_NUM_UNSUPPORTED_REQ_ENTRIES][16]; /* 0016 - Unsupported Requirements List */
+ __u8 rsvd2[14]; /* 4064 - Reserved must be cleared to zero */
+ __le16 log_page_version; /* 4078 - Log Page Version */
+ __u8 log_page_guid[WDC_OCP_C5_GUID_LENGTH]; /* 4080 - Log Page GUID */
+};
+
+static __u8 wdc_ocp_c5_guid[WDC_OCP_C5_GUID_LENGTH] = { 0x2F, 0x72, 0x9C, 0x0E, 0x99, 0x23, 0x2C, 0xBB,
+ 0x63, 0x48, 0x32, 0xD0, 0xB7, 0x98, 0xBB, 0xC7 };
+
+#define WDC_REASON_INDEX_MAX 16
+#define WDC_REASON_ID_ENTRY_LEN 128
+#define WDC_REASON_ID_PATH_NAME "/usr/local/nvmecli"
+
+const char *log_page_name[256] = {
+ [NVME_LOG_LID_ERROR] = "Error Information",
+ [NVME_LOG_LID_SMART] = "SMART / Health Information",
+ [NVME_LOG_LID_FW_SLOT] = "Firmware Slot Information",
+ [NVME_LOG_LID_CHANGED_NS] = "Changed Namespace List",
+ [NVME_LOG_LID_CMD_EFFECTS] = "Command Supported and Effects",
+ [NVME_LOG_LID_TELEMETRY_HOST] = "Telemetry Host-Initiated",
+ [NVME_LOG_LID_TELEMETRY_CTRL] = "Telemetry Controller-Initiated",
+ [NVME_LOG_LID_SANITIZE] = "Sanitize Status",
+ [WDC_LOG_ID_C0] = "Extended SMART Information",
+ [WDC_LOG_ID_C2] = "Firmware Activation History",
+ [WDC_LOG_ID_C3] = "Latency Monitor",
+ [WDC_LOG_ID_C4] = "Device Capabilities",
+ [WDC_LOG_ID_C5] = "Unsupported Requirements",
+};
+
+static double safe_div_fp(double numerator, double denominator)
+{
+ return denominator ? numerator / denominator : 0;
+}
+
+static double calc_percent(uint64_t numerator, uint64_t denominator)
+{
+ return denominator ?
+ (uint64_t)(((double)numerator / (double)denominator) * 100) : 0;
+}
+
+static int wdc_get_pci_ids(nvme_root_t r, struct nvme_dev *dev,
+ uint32_t *device_id, uint32_t *vendor_id)
+{
+ char vid[256], did[256], id[32];
+ nvme_ctrl_t c = NULL;
+ nvme_ns_t n = NULL;
+ int fd, ret;
+
+ c = nvme_scan_ctrl(r, dev->name);
+ if (c) {
+ snprintf(vid, sizeof(vid), "%s/device/vendor",
+ nvme_ctrl_get_sysfs_dir(c));
+ snprintf(did, sizeof(did), "%s/device/device",
+ nvme_ctrl_get_sysfs_dir(c));
+ nvme_free_ctrl(c);
+ } else {
+ n = nvme_scan_namespace(dev->name);
+ if (!n) {
+ fprintf(stderr, "Unable to find %s\n", dev->name);
+ return -1;
+ }
+
+ snprintf(vid, sizeof(vid), "%s/device/device/vendor",
+ nvme_ns_get_sysfs_dir(n));
+ snprintf(did, sizeof(did), "%s/device/device/device",
+ nvme_ns_get_sysfs_dir(n));
+ nvme_free_ns(n);
+ }
+
+ fd = open(vid, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "ERROR: WDC: %s : Open vendor file failed\n", __func__);
+ return -1;
+ }
+
+ ret = read(fd, id, 32);
+ close(fd);
+
+ if (ret < 0) {
+ fprintf(stderr, "%s: Read of pci vendor id failed\n", __func__);
+ return -1;
+ }
+ id[ret < 32 ? ret : 31] = '\0';
+ if (id[strlen(id) - 1] == '\n')
+ id[strlen(id) - 1] = '\0';
+
+ *vendor_id = strtol(id, NULL, 0);
+ ret = 0;
+
+ fd = open(did, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "ERROR: WDC: %s : Open device file failed\n", __func__);
+ return -1;
+ }
+
+ ret = read(fd, id, 32);
+ close(fd);
+
+ if (ret < 0) {
+ fprintf(stderr, "%s: Read of pci device id failed\n", __func__);
+ return -1;
+ }
+ id[ret < 32 ? ret : 31] = '\0';
+ if (id[strlen(id) - 1] == '\n')
+ id[strlen(id) - 1] = '\0';
+
+ *device_id = strtol(id, NULL, 0);
+ return 0;
+}
+
+static int wdc_get_vendor_id(struct nvme_dev *dev, uint32_t *vendor_id)
+{
+ int ret;
+ struct nvme_id_ctrl ctrl;
+
+ memset(&ctrl, 0, sizeof(struct nvme_id_ctrl));
+ ret = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: nvme_identify_ctrl() failed 0x%x\n", ret);
+ return -1;
+ }
+
+ *vendor_id = (uint32_t) ctrl.vid;
+
+ return ret;
+}
+
+static bool wdc_is_sn861(__u32 device_id)
+{
+ if ((device_id == WDC_NVME_SN861_DEV_ID) ||
+ (device_id == WDC_NVME_SN861_DEV_ID_1))
+ return true;
+ else
+ return false;
+}
+
+
+static bool wdc_is_sn640(__u32 device_id)
+{
+ if ((device_id == WDC_NVME_SN640_DEV_ID) ||
+ (device_id == WDC_NVME_SN640_DEV_ID_1) ||
+ (device_id == WDC_NVME_SN640_DEV_ID_2))
+ return true;
+ else
+ return false;
+}
+
+static bool wdc_is_sn640_3(__u32 device_id)
+{
+ if (device_id == WDC_NVME_SN640_DEV_ID_3)
+ return true;
+ else
+ return false;
+}
+
+static bool wdc_is_sn650_u2(__u32 device_id)
+{
+ if (device_id == WDC_NVME_SN650_DEV_ID_3)
+ return true;
+ else
+ return false;
+}
+
+static bool wdc_is_sn650_e1l(__u32 device_id)
+{
+ if (device_id == WDC_NVME_SN650_DEV_ID_4)
+ return true;
+ else
+ return false;
+}
+
+static bool needs_c2_log_page_check(__u32 device_id)
+{
+ if ((wdc_is_sn640(device_id)) ||
+ (wdc_is_sn650_u2(device_id)) ||
+ (wdc_is_sn650_e1l(device_id)))
+ return true;
+ else
+ return false;
+}
+
+static bool wdc_check_power_of_2(int num)
+{
+ return num && (!(num & (num-1)));
+}
+
+static int wdc_get_model_number(struct nvme_dev *dev, char *model)
+{
+ int ret, i;
+ struct nvme_id_ctrl ctrl;
+
+ memset(&ctrl, 0, sizeof(struct nvme_id_ctrl));
+ ret = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: nvme_identify_ctrl() failed 0x%x\n", ret);
+ return -1;
+ }
+
+ memcpy(model, ctrl.mn, NVME_ID_CTRL_MODEL_NUMBER_SIZE);
+ /* get rid of the padded spaces */
+ i = NVME_ID_CTRL_MODEL_NUMBER_SIZE-1;
+ while (model[i] == ' ')
+ i--;
+ model[i+1] = 0;
+
+ return ret;
+}
+
+static bool wdc_check_device(nvme_root_t r, struct nvme_dev *dev)
+{
+ int ret;
+ bool supported;
+ uint32_t read_device_id = -1, read_vendor_id = -1;
+
+ ret = wdc_get_pci_ids(r, dev, &read_device_id, &read_vendor_id);
+ if (ret < 0) {
+ /* Use the identify nvme command to get vendor id due to NVMeOF device. */
+ if (wdc_get_vendor_id(dev, &read_vendor_id) < 0)
+ return false;
+ }
+
+ supported = false;
+
+ if (read_vendor_id == WDC_NVME_VID ||
+ read_vendor_id == WDC_NVME_VID_2 ||
+ read_vendor_id == WDC_NVME_SNDK_VID)
+ supported = true;
+ else
+ fprintf(stderr,
+ "ERROR: WDC: unsupported WDC device, Vendor ID = 0x%x, Device ID = 0x%x\n",
+ read_vendor_id, read_device_id);
+
+ return supported;
+}
+
+static bool wdc_enc_check_model(struct nvme_dev *dev)
+{
+ int ret;
+ bool supported;
+ char model[NVME_ID_CTRL_MODEL_NUMBER_SIZE+1];
+
+ ret = wdc_get_model_number(dev, model);
+ if (ret < 0)
+ return false;
+
+ supported = false;
+ model[NVME_ID_CTRL_MODEL_NUMBER_SIZE] = 0; /* forced termination */
+ if (strstr(model, WDC_OPENFLEX_MI_DEVICE_MODEL))
+ supported = true;
+ else
+ fprintf(stderr, "ERROR: WDC: unsupported WDC enclosure, Model = %s\n", model);
+
+ return supported;
+}
+
+static __u64 wdc_get_drive_capabilities(nvme_root_t r, struct nvme_dev *dev)
+{
+ int ret;
+ uint32_t read_device_id = -1, read_vendor_id = -1;
+ __u64 capabilities = 0;
+ __u32 cust_id;
+
+ ret = wdc_get_pci_ids(r, dev, &read_device_id, &read_vendor_id);
+ if (ret < 0) {
+ if (wdc_get_vendor_id(dev, &read_vendor_id) < 0)
+ return capabilities;
+ }
+
+ /* below check condition is added due in NVMeOF device we dont have device_id so we need to use only vendor_id*/
+ if (read_device_id == -1 && read_vendor_id != -1) {
+ capabilities = wdc_get_enc_drive_capabilities(r, dev);
+ return capabilities;
+ }
+
+ switch (read_vendor_id) {
+ case WDC_NVME_VID:
+ switch (read_device_id) {
+ case WDC_NVME_SN100_DEV_ID:
+ capabilities = (WDC_DRIVE_CAP_CAP_DIAG | WDC_DRIVE_CAP_INTERNAL_LOG | WDC_DRIVE_CAP_C1_LOG_PAGE |
+ WDC_DRIVE_CAP_DRIVE_LOG | WDC_DRIVE_CAP_CRASH_DUMP | WDC_DRIVE_CAP_PFAIL_DUMP |
+ WDC_DRIVE_CAP_PURGE);
+ break;
+
+ case WDC_NVME_SN200_DEV_ID:
+ capabilities = (WDC_DRIVE_CAP_CAP_DIAG | WDC_DRIVE_CAP_INTERNAL_LOG | WDC_DRIVE_CAP_CLEAR_PCIE |
+ WDC_DRIVE_CAP_DRIVE_LOG | WDC_DRIVE_CAP_CRASH_DUMP | WDC_DRIVE_CAP_PFAIL_DUMP |
+ WDC_DRIVE_CAP_PURGE);
+
+ /* verify the 0xCA log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev,
+ WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE))
+ capabilities |= WDC_DRIVE_CAP_CA_LOG_PAGE;
+
+ /* verify the 0xC1 log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev,
+ WDC_NVME_ADD_LOG_OPCODE))
+ capabilities |= WDC_DRIVE_CAP_C1_LOG_PAGE;
+ break;
+
+ default:
+ capabilities = 0;
+ }
+ break;
+
+ case WDC_NVME_VID_2:
+ switch (read_device_id) {
+ case WDC_NVME_SN630_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN630_DEV_ID_1:
+ capabilities = (WDC_DRIVE_CAP_CAP_DIAG | WDC_DRIVE_CAP_INTERNAL_LOG |
+ WDC_DRIVE_CAP_DRIVE_STATUS | WDC_DRIVE_CAP_CLEAR_ASSERT |
+ WDC_DRIVE_CAP_RESIZE | WDC_DRIVE_CAP_CLEAR_PCIE);
+ /* verify the 0xCA log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev,
+ WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE))
+ capabilities |= WDC_DRIVE_CAP_CA_LOG_PAGE;
+
+ /* verify the 0xD0 log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev,
+ WDC_NVME_GET_VU_SMART_LOG_OPCODE))
+ capabilities |= WDC_DRIVE_CAP_D0_LOG_PAGE;
+ break;
+
+ case WDC_NVME_SN640_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN640_DEV_ID_1:
+ fallthrough;
+ case WDC_NVME_SN640_DEV_ID_2:
+ fallthrough;
+ case WDC_NVME_SN640_DEV_ID_3:
+ fallthrough;
+ case WDC_NVME_SN560_DEV_ID_1:
+ fallthrough;
+ case WDC_NVME_SN560_DEV_ID_2:
+ fallthrough;
+ case WDC_NVME_SN560_DEV_ID_3:
+ fallthrough;
+ case WDC_NVME_SN660_DEV_ID:
+ /* verify the 0xC0 log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev,
+ WDC_NVME_GET_SMART_CLOUD_ATTR_LOG_ID)
+ == true) {
+ capabilities |= WDC_DRIVE_CAP_C0_LOG_PAGE;
+ }
+
+ capabilities |= (WDC_DRIVE_CAP_CAP_DIAG | WDC_DRIVE_CAP_INTERNAL_LOG |
+ WDC_DRIVE_CAP_DRIVE_STATUS | WDC_DRIVE_CAP_CLEAR_ASSERT |
+ WDC_DRIVE_CAP_RESIZE | WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY |
+ WDC_DRVIE_CAP_DISABLE_CTLR_TELE_LOG | WDC_DRIVE_CAP_REASON_ID |
+ WDC_DRIVE_CAP_LOG_PAGE_DIR);
+
+ /* verify the 0xC1 (OCP Error Recovery) log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev,
+ WDC_ERROR_REC_LOG_ID))
+ capabilities |= WDC_DRIVE_CAP_OCP_C1_LOG_PAGE;
+
+ /* verify the 0xC3 (OCP Latency Monitor) log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev,
+ WDC_LATENCY_MON_LOG_ID))
+ capabilities |= WDC_DRIVE_CAP_C3_LOG_PAGE;
+
+ /* verify the 0xC4 (OCP Device Capabilities) log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev,
+ WDC_DEV_CAP_LOG_ID))
+ capabilities |= WDC_DRIVE_CAP_OCP_C4_LOG_PAGE;
+
+ /* verify the 0xC5 (OCP Unsupported Requirements) log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev,
+ WDC_UNSUPPORTED_REQS_LOG_ID))
+ capabilities |= WDC_DRIVE_CAP_OCP_C5_LOG_PAGE;
+
+ /* verify the 0xCA log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev,
+ WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE))
+ capabilities |= WDC_DRIVE_CAP_CA_LOG_PAGE;
+
+ /* verify the 0xD0 log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev,
+ WDC_NVME_GET_VU_SMART_LOG_OPCODE))
+ capabilities |= WDC_DRIVE_CAP_D0_LOG_PAGE;
+
+ cust_id = wdc_get_fw_cust_id(r, dev);
+ if (cust_id == WDC_INVALID_CUSTOMER_ID) {
+ fprintf(stderr, "%s: ERROR: WDC: invalid customer id\n", __func__);
+ return -1;
+ }
+
+ if ((cust_id == WDC_CUSTOMER_ID_0x1004) || (cust_id == WDC_CUSTOMER_ID_0x1008) ||
+ (cust_id == WDC_CUSTOMER_ID_0x1005) || (cust_id == WDC_CUSTOMER_ID_0x1304))
+ capabilities |= (WDC_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY | WDC_DRIVE_CAP_VU_FID_CLEAR_PCIE |
+ WDC_DRIVE_CAP_INFO | WDC_DRIVE_CAP_CLOUD_SSD_VERSION);
+ else
+ capabilities |= (WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY | WDC_DRIVE_CAP_CLEAR_PCIE);
+
+ break;
+
+ case WDC_NVME_SN840_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN840_DEV_ID_1:
+ fallthrough;
+ case WDC_NVME_SN860_DEV_ID:
+ /* verify the 0xC0 log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev,
+ WDC_NVME_GET_EOL_STATUS_LOG_OPCODE))
+ capabilities |= WDC_DRIVE_CAP_C0_LOG_PAGE;
+ fallthrough;
+ case WDC_NVME_ZN540_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN540_DEV_ID:
+ capabilities |= (WDC_DRIVE_CAP_CAP_DIAG | WDC_DRIVE_CAP_INTERNAL_LOG |
+ WDC_DRIVE_CAP_DRIVE_STATUS | WDC_DRIVE_CAP_CLEAR_ASSERT |
+ WDC_DRIVE_CAP_RESIZE | WDC_DRIVE_CAP_CLEAR_PCIE |
+ WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY | WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY |
+ WDC_DRVIE_CAP_DISABLE_CTLR_TELE_LOG | WDC_DRIVE_CAP_REASON_ID |
+ WDC_DRIVE_CAP_LOG_PAGE_DIR);
+
+ /* verify the 0xCA log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev,
+ WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE))
+ capabilities |= WDC_DRIVE_CAP_CA_LOG_PAGE;
+
+ /* verify the 0xD0 log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev,
+ WDC_NVME_GET_VU_SMART_LOG_OPCODE))
+ capabilities |= WDC_DRIVE_CAP_D0_LOG_PAGE;
+ break;
+
+ case WDC_NVME_SN650_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN650_DEV_ID_1:
+ fallthrough;
+ case WDC_NVME_SN650_DEV_ID_2:
+ fallthrough;
+ case WDC_NVME_SN650_DEV_ID_3:
+ fallthrough;
+ case WDC_NVME_SN650_DEV_ID_4:
+ fallthrough;
+ case WDC_NVME_SN655_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN550_DEV_ID:
+ /* verify the 0xC0 log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev,
+ WDC_NVME_GET_SMART_CLOUD_ATTR_LOG_ID))
+ capabilities |= WDC_DRIVE_CAP_C0_LOG_PAGE;
+
+ /* verify the 0xC1 (OCP Error Recovery) log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev, WDC_ERROR_REC_LOG_ID))
+ capabilities |= WDC_DRIVE_CAP_OCP_C1_LOG_PAGE;
+
+ /* verify the 0xC3 (OCP Latency Monitor) log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev, WDC_LATENCY_MON_LOG_ID))
+ capabilities |= WDC_DRIVE_CAP_C3_LOG_PAGE;
+
+ /* verify the 0xC4 (OCP Device Capabilities) log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev, WDC_DEV_CAP_LOG_ID))
+ capabilities |= WDC_DRIVE_CAP_OCP_C4_LOG_PAGE;
+
+ /* verify the 0xC5 (OCP Unsupported Requirements) log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev, WDC_UNSUPPORTED_REQS_LOG_ID))
+ capabilities |= WDC_DRIVE_CAP_OCP_C5_LOG_PAGE;
+
+ capabilities |= (WDC_DRIVE_CAP_CAP_DIAG | WDC_DRIVE_CAP_INTERNAL_LOG |
+ WDC_DRIVE_CAP_DRIVE_STATUS | WDC_DRIVE_CAP_CLEAR_ASSERT |
+ WDC_DRIVE_CAP_RESIZE | WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY |
+ WDC_DRVIE_CAP_DISABLE_CTLR_TELE_LOG |
+ WDC_DRIVE_CAP_REASON_ID | WDC_DRIVE_CAP_LOG_PAGE_DIR);
+
+ cust_id = wdc_get_fw_cust_id(r, dev);
+ if (cust_id == WDC_INVALID_CUSTOMER_ID) {
+ fprintf(stderr, "%s: ERROR: WDC: invalid customer id\n", __func__);
+ return -1;
+ }
+
+ if ((cust_id == WDC_CUSTOMER_ID_0x1004) ||
+ (cust_id == WDC_CUSTOMER_ID_0x1008) ||
+ (cust_id == WDC_CUSTOMER_ID_0x1005) ||
+ (cust_id == WDC_CUSTOMER_ID_0x1304))
+ capabilities |= (WDC_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY |
+ WDC_DRIVE_CAP_VU_FID_CLEAR_PCIE |
+ WDC_DRIVE_CAP_INFO |
+ WDC_DRIVE_CAP_CLOUD_SSD_VERSION);
+ else
+ capabilities |= (WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY |
+ WDC_DRIVE_CAP_CLEAR_PCIE);
+
+ break;
+
+ case WDC_NVME_SN861_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN861_DEV_ID_1:
+ capabilities |= (WDC_DRIVE_CAP_C0_LOG_PAGE |
+ WDC_DRIVE_CAP_C3_LOG_PAGE |
+ WDC_DRIVE_CAP_CA_LOG_PAGE |
+ WDC_DRIVE_CAP_OCP_C4_LOG_PAGE |
+ WDC_DRIVE_CAP_OCP_C5_LOG_PAGE |
+ WDC_DRIVE_CAP_INTERNAL_LOG |
+ WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY_C2 |
+ WDC_DRIVE_CAP_VU_FID_CLEAR_PCIE |
+ WDC_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY |
+ WDC_DRIVE_CAP_INFO |
+ WDC_DRIVE_CAP_CLOUD_SSD_VERSION |
+ WDC_DRIVE_CAP_LOG_PAGE_DIR |
+ WDC_DRIVE_CAP_SET_LATENCY_MONITOR);
+ break;
+
+ default:
+ capabilities = 0;
+ }
+ break;
+
+ case WDC_NVME_SNDK_VID:
+ switch (read_device_id) {
+ case WDC_NVME_SXSLCL_DEV_ID:
+ capabilities = WDC_DRIVE_CAP_DRIVE_ESSENTIALS;
+ break;
+
+ case WDC_NVME_SN520_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN520_DEV_ID_1:
+ fallthrough;
+ case WDC_NVME_SN520_DEV_ID_2:
+ fallthrough;
+ case WDC_NVME_SN810_DEV_ID:
+ capabilities = WDC_DRIVE_CAP_DUI_DATA;
+ break;
+
+ case WDC_NVME_SN820CL_DEV_ID:
+ capabilities = WDC_DRIVE_CAP_DUI_DATA |
+ WDC_DRIVE_CAP_CLOUD_BOOT_SSD_VERSION |
+ WDC_DRIVE_CAP_CLOUD_LOG_PAGE | WDC_DRIVE_CAP_C0_LOG_PAGE |
+ WDC_DRIVE_CAP_HW_REV_LOG_PAGE | WDC_DRIVE_CAP_INFO |
+ WDC_DRIVE_CAP_VU_FID_CLEAR_PCIE | WDC_DRIVE_CAP_NAND_STATS |
+ WDC_DRIVE_CAP_DEVICE_WAF | WDC_DRIVE_CAP_TEMP_STATS;
+ break;
+
+ case WDC_NVME_SN720_DEV_ID:
+ capabilities = WDC_DRIVE_CAP_DUI_DATA | WDC_DRIVE_CAP_NAND_STATS |
+ WDC_DRIVE_CAP_NS_RESIZE;
+ break;
+
+ case WDC_NVME_SN730_DEV_ID:
+ capabilities = WDC_DRIVE_CAP_DUI | WDC_DRIVE_CAP_NAND_STATS |
+ WDC_DRIVE_CAP_INFO | WDC_DRIVE_CAP_TEMP_STATS |
+ WDC_DRIVE_CAP_VUC_CLEAR_PCIE | WDC_DRIVE_CAP_PCIE_STATS;
+ break;
+
+ case WDC_NVME_SN530_DEV_ID_1:
+ fallthrough;
+ case WDC_NVME_SN530_DEV_ID_2:
+ fallthrough;
+ case WDC_NVME_SN530_DEV_ID_3:
+ fallthrough;
+ case WDC_NVME_SN530_DEV_ID_4:
+ fallthrough;
+ case WDC_NVME_SN530_DEV_ID_5:
+ fallthrough;
+ case WDC_NVME_SN350_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN570_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN850X_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN5000_DEV_ID_1:
+ fallthrough;
+ case WDC_NVME_SN5000_DEV_ID_2:
+ fallthrough;
+ case WDC_NVME_SN5000_DEV_ID_3:
+ fallthrough;
+ case WDC_NVME_SN5000_DEV_ID_4:
+ fallthrough;
+ case WDC_NVME_SN7000S_DEV_ID_1:
+ fallthrough;
+ case WDC_NVME_SN7150_DEV_ID_1:
+ fallthrough;
+ case WDC_NVME_SN7150_DEV_ID_2:
+ fallthrough;
+ case WDC_NVME_SN7150_DEV_ID_3:
+ fallthrough;
+ case WDC_NVME_SN7150_DEV_ID_4:
+ fallthrough;
+ case WDC_NVME_SN7150_DEV_ID_5:
+ fallthrough;
+ case WDC_NVME_SN7100_DEV_ID_1:
+ fallthrough;
+ case WDC_NVME_SN7100_DEV_ID_2:
+ fallthrough;
+ case WDC_NVME_SN7100_DEV_ID_3:
+ fallthrough;
+ case WDC_NVME_SN8000S_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN740_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN740_DEV_ID_1:
+ fallthrough;
+ case WDC_NVME_SN740_DEV_ID_2:
+ fallthrough;
+ case WDC_NVME_SN740_DEV_ID_3:
+ fallthrough;
+ case WDC_NVME_SN340_DEV_ID:
+ capabilities = WDC_DRIVE_CAP_DUI;
+ break;
+
+ case WDC_NVME_ZN350_DEV_ID:
+ fallthrough;
+ case WDC_NVME_ZN350_DEV_ID_1:
+ capabilities = WDC_DRIVE_CAP_DUI_DATA | WDC_DRIVE_CAP_VU_FID_CLEAR_PCIE |
+ WDC_DRIVE_CAP_C0_LOG_PAGE |
+ WDC_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY |
+ WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY_C2 | WDC_DRIVE_CAP_INFO |
+ WDC_DRIVE_CAP_CLOUD_SSD_VERSION | WDC_DRIVE_CAP_LOG_PAGE_DIR;
+ break;
+
+ default:
+ capabilities = 0;
+ }
+ break;
+ default:
+ capabilities = 0;
+ }
+
+ return capabilities;
+}
+
+static __u64 wdc_get_enc_drive_capabilities(nvme_root_t r,
+ struct nvme_dev *dev)
+{
+ int ret;
+ uint32_t read_vendor_id;
+ __u64 capabilities = 0;
+ __u32 cust_id;
+
+ ret = wdc_get_vendor_id(dev, &read_vendor_id);
+ if (ret < 0)
+ return capabilities;
+
+ switch (read_vendor_id) {
+ case WDC_NVME_VID:
+ capabilities = (WDC_DRIVE_CAP_CAP_DIAG | WDC_DRIVE_CAP_INTERNAL_LOG | WDC_DRIVE_CAP_CLEAR_PCIE |
+ WDC_DRIVE_CAP_DRIVE_LOG | WDC_DRIVE_CAP_CRASH_DUMP | WDC_DRIVE_CAP_PFAIL_DUMP);
+
+ /* verify the 0xCA log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev, WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE) == true)
+ capabilities |= WDC_DRIVE_CAP_CA_LOG_PAGE;
+
+ /* verify the 0xC1 log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev, WDC_NVME_ADD_LOG_OPCODE) == true)
+ capabilities |= WDC_DRIVE_CAP_C1_LOG_PAGE;
+ break;
+ case WDC_NVME_VID_2:
+ capabilities = (WDC_DRIVE_CAP_CAP_DIAG | WDC_DRIVE_CAP_INTERNAL_LOG |
+ WDC_DRIVE_CAP_DRIVE_STATUS | WDC_DRIVE_CAP_CLEAR_ASSERT |
+ WDC_DRIVE_CAP_RESIZE);
+
+ /* verify the 0xC3 log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev, WDC_LATENCY_MON_LOG_ID) == true)
+ capabilities |= WDC_DRIVE_CAP_C3_LOG_PAGE;
+
+ /* verify the 0xCB log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev, WDC_NVME_GET_FW_ACT_HISTORY_LOG_ID) == true)
+ capabilities |= WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY;
+
+ /* verify the 0xCA log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev, WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE) == true)
+ capabilities |= WDC_DRIVE_CAP_CA_LOG_PAGE;
+
+ /* verify the 0xD0 log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev, WDC_NVME_GET_VU_SMART_LOG_OPCODE) == true)
+ capabilities |= WDC_DRIVE_CAP_D0_LOG_PAGE;
+
+ cust_id = wdc_get_fw_cust_id(r, dev);
+ if (cust_id == WDC_INVALID_CUSTOMER_ID) {
+ fprintf(stderr, "%s: ERROR: WDC: invalid customer id\n", __func__);
+ return -1;
+ }
+
+ if ((cust_id == WDC_CUSTOMER_ID_0x1004) || (cust_id == WDC_CUSTOMER_ID_0x1008) ||
+ (cust_id == WDC_CUSTOMER_ID_0x1005) || (cust_id == WDC_CUSTOMER_ID_0x1304))
+ capabilities |= (WDC_DRIVE_CAP_VU_FID_CLEAR_FW_ACT_HISTORY | WDC_DRIVE_CAP_VU_FID_CLEAR_PCIE);
+ else
+ capabilities |= (WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY | WDC_DRIVE_CAP_CLEAR_PCIE);
+
+ break;
+ case WDC_NVME_SNDK_VID:
+ capabilities = WDC_DRIVE_CAP_DRIVE_ESSENTIALS;
+ break;
+ default:
+ capabilities = 0;
+ }
+
+ return capabilities;
+}
+
+static int wdc_get_serial_name(struct nvme_dev *dev, char *file, size_t len,
+ const char *suffix)
+{
+ int i;
+ int ret;
+ int res_len = 0;
+ char orig[PATH_MAX] = {0};
+ struct nvme_id_ctrl ctrl;
+ int ctrl_sn_len = sizeof(ctrl.sn);
+
+ i = sizeof(ctrl.sn) - 1;
+ strncpy(orig, file, PATH_MAX - 1);
+ memset(file, 0, len);
+ memset(&ctrl, 0, sizeof(struct nvme_id_ctrl));
+ ret = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: nvme_identify_ctrl() failed 0x%x\n", ret);
+ return -1;
+ }
+ /* Remove trailing spaces from the name */
+ while (i && ctrl.sn[i] == ' ') {
+ ctrl.sn[i] = '\0';
+ i--;
+ }
+ if (ctrl.sn[sizeof(ctrl.sn) - 1] == '\0')
+ ctrl_sn_len = strlen(ctrl.sn);
+
+ res_len = snprintf(file, len, "%s%.*s%s", orig, ctrl_sn_len, ctrl.sn, suffix);
+ if (len <= res_len) {
+ fprintf(stderr,
+ "ERROR: WDC: cannot format serial number due to data of unexpected length\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int wdc_create_log_file(char *file, __u8 *drive_log_data,
+ __u32 drive_log_length)
+{
+ int fd;
+ int ret;
+
+ if (!drive_log_length) {
+ fprintf(stderr, "ERROR: WDC: invalid log file length\n");
+ return -1;
+ }
+
+ fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (fd < 0) {
+ fprintf(stderr, "ERROR: WDC: open: %s\n", strerror(errno));
+ return -1;
+ }
+
+ while (drive_log_length > WRITE_SIZE) {
+ ret = write(fd, drive_log_data, WRITE_SIZE);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC: write: %s\n", strerror(errno));
+ close(fd);
+ return -1;
+ }
+ drive_log_data += WRITE_SIZE;
+ drive_log_length -= WRITE_SIZE;
+ }
+
+ ret = write(fd, drive_log_data, drive_log_length);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC: write: %s\n", strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ if (fsync(fd) < 0) {
+ fprintf(stderr, "ERROR: WDC: fsync: %s\n", strerror(errno));
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ return 0;
+}
+
+bool wdc_get_dev_mng_log_entry(__u32 log_length, __u32 entry_id,
+ struct wdc_c2_log_page_header *p_log_hdr,
+ struct wdc_c2_log_subpage_header **p_p_found_log_entry)
+{
+ __u32 remaining_len = 0;
+ __u32 log_entry_hdr_size = sizeof(struct wdc_c2_log_subpage_header) - 1;
+ __u32 log_entry_size = 0;
+ __u32 size = 0;
+ bool valid_log;
+ __u32 current_data_offset = 0;
+ struct wdc_c2_log_subpage_header *p_next_log_entry = NULL;
+
+ if (!*p_p_found_log_entry) {
+ fprintf(stderr, "ERROR: WDC - %s: No ppLogEntry pointer.\n", __func__);
+ return false;
+ }
+
+ *p_p_found_log_entry = NULL;
+
+ /* Ensure log data is large enough for common header */
+ if (log_length < sizeof(struct wdc_c2_log_page_header)) {
+ fprintf(stderr,
+ "ERROR: WDC - %s: Buffer is not large enough for the common header. BufSize: 0x%x HdrSize: %"PRIxPTR"\n",
+ __func__, log_length, sizeof(struct wdc_c2_log_page_header));
+ return false;
+ }
+
+ /* Get pointer to first log Entry */
+ size = sizeof(struct wdc_c2_log_page_header);
+ current_data_offset = size;
+ p_next_log_entry = (struct wdc_c2_log_subpage_header *)((__u8 *)p_log_hdr + current_data_offset);
+ remaining_len = log_length - size;
+ valid_log = false;
+
+ /*
+ * Walk the entire structure. Perform a sanity check to make sure this is a
+ * standard version of the structure. This means making sure each entry looks
+ * valid. But allow for the data to overflow the allocated
+ * buffer (we don't want a false negative because of a FW formatting error)
+ */
+
+ /* Proceed only if there is at least enough data to read an entry header */
+ while (remaining_len >= log_entry_hdr_size) {
+ /* Get size of the next entry */
+ log_entry_size = p_next_log_entry->length;
+
+ /*
+ * If log entry size is 0 or the log entry goes past the end
+ * of the data, we must be at the end of the data
+ */
+ if (!log_entry_size || log_entry_size > remaining_len) {
+ fprintf(stderr, "ERROR: WDC: %s: Detected unaligned end of the data. ",
+ __func__);
+ fprintf(stderr, "Data Offset: 0x%x Entry Size: 0x%x, ",
+ current_data_offset, log_entry_size);
+ fprintf(stderr, "Remaining Log Length: 0x%x Entry Id: 0x%x\n",
+ remaining_len, p_next_log_entry->entry_id);
+
+ /* Force the loop to end */
+ remaining_len = 0;
+ } else if (!p_next_log_entry->entry_id || p_next_log_entry->entry_id > 200) {
+ /* Invalid entry - fail the search */
+ fprintf(stderr, "ERROR: WDC: %s: Invalid entry found at offset: 0x%x ",
+ __func__, current_data_offset);
+ fprintf(stderr, "Entry Size: 0x%x, Remaining Log Length: 0x%x ",
+ log_entry_size, remaining_len);
+ fprintf(stderr, "Entry Id: 0x%x\n", p_next_log_entry->entry_id);
+
+ /* Force the loop to end */
+ remaining_len = 0;
+ valid_log = false;
+
+ /* The structure is invalid, so any match that was found is invalid. */
+ *p_p_found_log_entry = NULL;
+ } else {
+ /* Structure must have at least one valid entry to be considered valid */
+ valid_log = true;
+ if (p_next_log_entry->entry_id == entry_id)
+ /* A potential match. */
+ *p_p_found_log_entry = p_next_log_entry;
+
+ remaining_len -= log_entry_size;
+
+ if (remaining_len > 0) {
+ /* Increment the offset counter */
+ current_data_offset += log_entry_size;
+
+ /* Get the next entry */
+ p_next_log_entry = (struct wdc_c2_log_subpage_header *)(((__u8 *)p_log_hdr) + current_data_offset);
+ }
+ }
+ }
+
+ return valid_log;
+}
+
+static bool get_dev_mgmt_log_page_lid_data(struct nvme_dev *dev,
+ void **cbs_data,
+ __u8 lid,
+ __u8 log_id,
+ __u8 uuid_ix)
+{
+ void *data;
+ struct wdc_c2_log_page_header *hdr_ptr;
+ struct wdc_c2_log_subpage_header *sph;
+ __u32 length = 0;
+ int ret = 0;
+ bool found = false;
+
+ data = (__u8 *)malloc(sizeof(__u8) * WDC_C2_LOG_BUF_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return false;
+ }
+
+ memset(data, 0, sizeof(__u8) * WDC_C2_LOG_BUF_LEN);
+
+ /* get the log page length */
+ struct nvme_get_log_args args_len = {
+ .args_size = sizeof(args_len),
+ .fd = dev_fd(dev),
+ .lid = lid,
+ .nsid = 0xFFFFFFFF,
+ .lpo = 0,
+ .lsp = NVME_LOG_LSP_NONE,
+ .lsi = 0,
+ .rae = false,
+ .uuidx = uuid_ix,
+ .csi = NVME_CSI_NVM,
+ .ot = false,
+ .len = WDC_C2_LOG_BUF_LEN,
+ .log = data,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ };
+ ret = nvme_get_log(&args_len);
+ if (ret) {
+ fprintf(stderr,
+ "ERROR: WDC: Unable to get 0x%x Log Page length with uuid %d, ret = 0x%x\n",
+ lid, uuid_ix, ret);
+ goto end;
+ }
+
+ hdr_ptr = (struct wdc_c2_log_page_header *)data;
+ length = le32_to_cpu(hdr_ptr->length);
+
+ if (length > WDC_C2_LOG_BUF_LEN) {
+ /* Log Page buffer too small, free and reallocate the necessary size */
+ free(data);
+ data = calloc(length, sizeof(__u8));
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ goto end;
+ }
+
+ /* get the log page data with the increased length */
+ struct nvme_get_log_args args_data = {
+ .args_size = sizeof(args_data),
+ .fd = dev_fd(dev),
+ .lid = lid,
+ .nsid = 0xFFFFFFFF,
+ .lpo = 0,
+ .lsp = NVME_LOG_LSP_NONE,
+ .lsi = 0,
+ .rae = false,
+ .uuidx = uuid_ix,
+ .csi = NVME_CSI_NVM,
+ .ot = false,
+ .len = length,
+ .log = data,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ };
+ ret = nvme_get_log(&args_data);
+
+ if (ret) {
+ fprintf(stderr,
+ "ERROR: WDC: Unable to read 0x%x Log Page data with uuid %d, ret = 0x%x\n",
+ lid, uuid_ix, ret);
+ goto end;
+ }
+ }
+
+ /* Check the log data to see if the WD version of log page ID's is found */
+ length = sizeof(struct wdc_c2_log_page_header);
+ hdr_ptr = (struct wdc_c2_log_page_header *)data;
+ sph = (struct wdc_c2_log_subpage_header *)(data + length);
+ found = wdc_get_dev_mng_log_entry(le32_to_cpu(hdr_ptr->length), log_id, hdr_ptr, &sph);
+ if (found) {
+ *cbs_data = calloc(le32_to_cpu(sph->length), sizeof(__u8));
+ if (!*cbs_data) {
+ fprintf(stderr, "ERROR: WDC: calloc: %s\n", strerror(errno));
+ found = false;
+ goto end;
+ }
+ memcpy((void *)*cbs_data, (void *)&sph->data, le32_to_cpu(sph->length));
+ } else {
+ fprintf(stderr, "ERROR: WDC: C2 log id 0x%x not found with uuid index %d\n",
+ log_id, uuid_ix);
+ }
+
+end:
+ free(data);
+ return found;
+}
+
+static bool get_dev_mgment_cbs_data(nvme_root_t r, struct nvme_dev *dev,
+ __u8 log_id, void **cbs_data)
+{
+ int ret = -1;
+ bool found = false;
+ __u8 uuid_ix = 0;
+ __u8 lid = 0;
+ *cbs_data = NULL;
+ __u32 device_id, read_vendor_id;
+ bool uuid_present = false;
+ int index = 0, uuid_index = 0;
+ struct nvme_id_uuid_list uuid_list;
+
+ ret = wdc_get_pci_ids(r, dev, &device_id, &read_vendor_id);
+ if (ret == 0) {
+ if (device_id == WDC_NVME_ZN350_DEV_ID || device_id == WDC_NVME_ZN350_DEV_ID_1) {
+ lid = WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_ID_C8;
+ uuid_ix = 0;
+ } else {
+ lid = WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_ID;
+ }
+ } else {
+ fprintf(stderr, "ERROR: WDC: get pci ids: %d\n", ret);
+ return false;
+ }
+
+ typedef struct nvme_id_uuid_list_entry *uuid_list_entry;
+
+ memset(&uuid_list, 0, sizeof(struct nvme_id_uuid_list));
+ if (wdc_CheckUuidListSupport(dev, &uuid_list)) {
+ uuid_list_entry uuid_list_entry_ptr = (uuid_list_entry)&uuid_list.entry[0];
+
+ while (index <= NVME_ID_UUID_LIST_MAX &&
+ !wdc_UuidEqual(uuid_list_entry_ptr, (uuid_list_entry)UUID_END)) {
+
+ if (wdc_UuidEqual(uuid_list_entry_ptr,
+ (uuid_list_entry)WDC_UUID)) {
+ uuid_present = true;
+ break;
+ } else if (wdc_UuidEqual(uuid_list_entry_ptr,
+ (uuid_list_entry)WDC_UUID_SN640_3) &&
+ wdc_is_sn640_3(device_id)) {
+ uuid_present = true;
+ break;
+ }
+ index++;
+ uuid_list_entry_ptr = (uuid_list_entry)&uuid_list.entry[index];
+ }
+ if (uuid_present)
+ uuid_index = index + 1;
+ }
+
+ if (!uuid_index && needs_c2_log_page_check(device_id)) {
+ /* In certain devices that don't support UUID lists, there are multiple
+ * definitions of the C2 logpage. In those cases, the code
+ * needs to try two UUID indexes and use an identification algorithm
+ * to determine which is returning the correct log page data.
+ */
+ uuid_ix = 1;
+ }
+
+ found = get_dev_mgmt_log_page_lid_data(dev, cbs_data, lid, log_id, uuid_ix);
+
+ if (!found) {
+ /* not found with uuid = 1 try with uuid = 0 */
+ uuid_ix = 0;
+ fprintf(stderr, "Not found, requesting log page with uuid_index %d\n", uuid_index);
+
+ found = get_dev_mgmt_log_page_lid_data(dev, cbs_data, lid, log_id, uuid_ix);
+ }
+
+ return found;
+}
+
+static bool wdc_nvme_check_supported_log_page(nvme_root_t r, struct nvme_dev *dev, __u8 log_id)
+{
+ int i;
+ bool found = false;
+ struct wdc_c2_cbs_data *cbs_data = NULL;
+
+ if (get_dev_mgment_cbs_data(r, dev, WDC_C2_LOG_PAGES_SUPPORTED_ID, (void *)&cbs_data)) {
+ if (cbs_data) {
+ for (i = 0; i < le32_to_cpu(cbs_data->length); i++) {
+ if (log_id == cbs_data->data[i]) {
+ found = true;
+ break;
+ }
+ }
+
+#ifdef WDC_NVME_CLI_DEBUG
+ if (!found) {
+ fprintf(stderr, "ERROR: WDC: Log Page 0x%x not supported\n", log_id);
+ fprintf(stderr, "WDC: Supported Log Pages:\n");
+ /* print the supported pages */
+ d((__u8 *)cbs_data->data, le32_to_cpu(cbs_data->length), 16, 1);
+ }
+#endif
+ free(cbs_data);
+ } else {
+ fprintf(stderr, "ERROR: WDC: cbs_data ptr = NULL\n");
+ }
+ } else {
+ fprintf(stderr, "ERROR: WDC: 0xC2 Log Page entry ID 0x%x not found\n",
+ WDC_C2_LOG_PAGES_SUPPORTED_ID);
+ }
+
+ return found;
+}
+
+static bool wdc_nvme_get_dev_status_log_data(nvme_root_t r, struct nvme_dev *dev, __le32 *ret_data,
+ __u8 log_id)
+{
+ __u32 *cbs_data = NULL;
+
+ if (get_dev_mgment_cbs_data(r, dev, log_id, (void *)&cbs_data)) {
+ if (cbs_data) {
+ memcpy((void *)ret_data, (void *)cbs_data, 4);
+ free(cbs_data);
+
+ return true;
+ }
+ }
+
+ *ret_data = 0;
+ return false;
+}
+
+static int wdc_do_clear_dump(struct nvme_dev *dev, __u8 opcode, __u32 cdw12)
+{
+ int ret;
+ struct nvme_passthru_cmd admin_cmd;
+
+ memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd));
+ admin_cmd.opcode = opcode;
+ admin_cmd.cdw12 = cdw12;
+ ret = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd, NULL);
+ if (ret)
+ fprintf(stdout, "ERROR: WDC: Crash dump erase failed\n");
+ nvme_show_status(ret);
+ return ret;
+}
+
+static __u32 wdc_dump_length(int fd, __u32 opcode, __u32 cdw10, __u32 cdw12, __u32 *dump_length)
+{
+ int ret;
+ __u8 buf[WDC_NVME_LOG_SIZE_DATA_LEN] = {0};
+ struct wdc_log_size *l;
+ struct nvme_passthru_cmd admin_cmd;
+
+ l = (struct wdc_log_size *) buf;
+ memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd));
+ admin_cmd.opcode = opcode;
+ admin_cmd.addr = (__u64)(uintptr_t)buf;
+ admin_cmd.data_len = WDC_NVME_LOG_SIZE_DATA_LEN;
+ admin_cmd.cdw10 = cdw10;
+ admin_cmd.cdw12 = cdw12;
+
+ ret = nvme_submit_admin_passthru(fd, &admin_cmd, NULL);
+ if (ret) {
+ l->log_size = 0;
+ ret = -1;
+ fprintf(stderr, "ERROR: WDC: reading dump length failed\n");
+ nvme_show_status(ret);
+ return ret;
+ }
+
+ if (opcode == WDC_NVME_CAP_DIAG_OPCODE)
+ *dump_length = buf[0x04] << 24 | buf[0x05] << 16 | buf[0x06] << 8 | buf[0x07];
+ else
+ *dump_length = le32_to_cpu(l->log_size);
+ return ret;
+}
+
+static __u32 wdc_dump_length_e6(int fd, __u32 opcode, __u32 cdw10, __u32 cdw12, struct wdc_e6_log_hdr *dump_hdr)
+{
+ int ret;
+ struct nvme_passthru_cmd admin_cmd;
+
+ memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd));
+ admin_cmd.opcode = opcode;
+ admin_cmd.addr = (__u64)(uintptr_t)dump_hdr;
+ admin_cmd.data_len = WDC_NVME_LOG_SIZE_HDR_LEN;
+ admin_cmd.cdw10 = cdw10;
+ admin_cmd.cdw12 = cdw12;
+
+ ret = nvme_submit_admin_passthru(fd, &admin_cmd, NULL);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: reading dump length failed\n");
+ nvme_show_status(ret);
+ }
+
+ return ret;
+}
+
+static __u32 wdc_dump_dui_data(int fd, __u32 dataLen, __u32 offset, __u8 *dump_data, bool last_xfer)
+{
+ int ret;
+ struct nvme_passthru_cmd admin_cmd;
+
+ memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd));
+ admin_cmd.opcode = WDC_NVME_CAP_DUI_OPCODE;
+ admin_cmd.nsid = 0xFFFFFFFF;
+ admin_cmd.addr = (__u64)(uintptr_t)dump_data;
+ admin_cmd.data_len = dataLen;
+ admin_cmd.cdw10 = ((dataLen >> 2) - 1);
+ admin_cmd.cdw12 = offset;
+ if (last_xfer)
+ admin_cmd.cdw14 = 0;
+ else
+ admin_cmd.cdw14 = WDC_NVME_CAP_DUI_DISABLE_IO;
+
+
+ ret = nvme_submit_admin_passthru(fd, &admin_cmd, NULL);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: reading DUI data failed\n");
+ nvme_show_status(ret);
+ }
+
+ return ret;
+}
+
+static __u32 wdc_dump_dui_data_v2(int fd, __u32 dataLen, __u64 offset, __u8 *dump_data, bool last_xfer)
+{
+ int ret;
+ struct nvme_passthru_cmd admin_cmd;
+ __u64 offset_lo, offset_hi;
+
+ memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd));
+ admin_cmd.opcode = WDC_NVME_CAP_DUI_OPCODE;
+ admin_cmd.nsid = 0xFFFFFFFF;
+ admin_cmd.addr = (__u64)(uintptr_t)dump_data;
+ admin_cmd.data_len = dataLen;
+ admin_cmd.cdw10 = ((dataLen >> 2) - 1);
+ offset_lo = offset & 0x00000000FFFFFFFF;
+ offset_hi = ((offset & 0xFFFFFFFF00000000) >> 32);
+ admin_cmd.cdw12 = (__u32)offset_lo;
+ admin_cmd.cdw13 = (__u32)offset_hi;
+
+ if (last_xfer)
+ admin_cmd.cdw14 = 0;
+ else
+ admin_cmd.cdw14 = WDC_NVME_CAP_DUI_DISABLE_IO;
+
+ ret = nvme_submit_admin_passthru(fd, &admin_cmd, NULL);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: reading DUI data V2 failed\n");
+ nvme_show_status(ret);
+ }
+
+ return ret;
+}
+
+static int wdc_do_dump(struct nvme_dev *dev, __u32 opcode, __u32 data_len,
+ __u32 cdw12, char *file, __u32 xfer_size)
+{
+ int ret = 0;
+ __u8 *dump_data;
+ __u32 curr_data_offset, curr_data_len;
+ int i;
+ struct nvme_passthru_cmd admin_cmd;
+ __u32 dump_length = data_len;
+
+ dump_data = (__u8 *)malloc(sizeof(__u8) * dump_length);
+ if (!dump_data) {
+ fprintf(stderr, "%s: ERROR: malloc: %s\n", __func__, strerror(errno));
+ return -1;
+ }
+ memset(dump_data, 0, sizeof(__u8) * dump_length);
+ memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd));
+ curr_data_offset = 0;
+ curr_data_len = xfer_size;
+ i = 0;
+
+ admin_cmd.opcode = opcode;
+ admin_cmd.addr = (__u64)(uintptr_t)dump_data;
+ admin_cmd.data_len = curr_data_len;
+ admin_cmd.cdw10 = curr_data_len >> 2;
+ admin_cmd.cdw12 = cdw12;
+ admin_cmd.cdw13 = curr_data_offset;
+
+ while (curr_data_offset < data_len) {
+ ret = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd,
+ NULL);
+ if (ret) {
+ nvme_show_status(ret);
+ fprintf(stderr, "%s: ERROR: WDC: Get chunk %d, size = 0x%x, offset = 0x%x, addr = 0x%lx\n",
+ __func__, i, admin_cmd.data_len, curr_data_offset, (unsigned long)admin_cmd.addr);
+ break;
+ }
+
+ if ((curr_data_offset + xfer_size) <= data_len)
+ curr_data_len = xfer_size;
+ else
+ curr_data_len = data_len - curr_data_offset; /* last transfer */
+
+ curr_data_offset += curr_data_len;
+ admin_cmd.addr = (__u64)(uintptr_t)dump_data + (__u64)curr_data_offset;
+ admin_cmd.data_len = curr_data_len;
+ admin_cmd.cdw10 = curr_data_len >> 2;
+ admin_cmd.cdw13 = curr_data_offset >> 2;
+ i++;
+ }
+
+ if (!ret) {
+ nvme_show_status(ret);
+ ret = wdc_create_log_file(file, dump_data, dump_length);
+ }
+ free(dump_data);
+ return ret;
+}
+
+static int wdc_do_dump_e6(int fd, __u32 opcode, __u32 data_len,
+ __u32 cdw12, char *file, __u32 xfer_size, __u8 *log_hdr)
+{
+ int ret = 0;
+ __u8 *dump_data;
+ __u32 curr_data_offset, log_size;
+ int i;
+ struct nvme_passthru_cmd admin_cmd;
+
+ /* if data_len is not 4 byte aligned */
+ if (data_len & 0x00000003) {
+ /* Round down to the next 4 byte aligned value */
+ fprintf(stderr, "%s: INFO: data_len 0x%x not 4 byte aligned.\n",
+ __func__, data_len);
+ fprintf(stderr, "%s: INFO: Round down to 0x%x.\n",
+ __func__, (data_len &= 0xFFFFFFFC));
+ data_len &= 0xFFFFFFFC;
+ }
+
+ dump_data = (__u8 *)malloc(sizeof(__u8) * data_len);
+
+ if (!dump_data) {
+ fprintf(stderr, "%s: ERROR: malloc: %s\n", __func__, strerror(errno));
+ return -1;
+ }
+ memset(dump_data, 0, sizeof(__u8) * data_len);
+ memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd));
+ curr_data_offset = WDC_NVME_LOG_SIZE_HDR_LEN;
+ i = 0;
+
+ /* copy the 8 byte header into the dump_data buffer */
+ memcpy(dump_data, log_hdr, WDC_NVME_LOG_SIZE_HDR_LEN);
+
+ admin_cmd.opcode = opcode;
+ admin_cmd.cdw12 = cdw12;
+
+ /* subtract off the header size since that was already copied into the buffer */
+ log_size = (data_len - curr_data_offset);
+ while (log_size > 0) {
+ xfer_size = min(xfer_size, log_size);
+
+ admin_cmd.addr = (__u64)(uintptr_t)dump_data + (__u64)curr_data_offset;
+ admin_cmd.data_len = xfer_size;
+ admin_cmd.cdw10 = xfer_size >> 2;
+ admin_cmd.cdw13 = curr_data_offset >> 2;
+
+ ret = nvme_submit_admin_passthru(fd, &admin_cmd, NULL);
+ if (ret) {
+ nvme_show_status(ret);
+ fprintf(stderr, "%s: ERROR: WDC: Get chunk %d, size = 0x%x, offset = 0x%x, addr = 0x%lx\n",
+ __func__, i, admin_cmd.data_len, curr_data_offset, (unsigned long)admin_cmd.addr);
+ break;
+ }
+
+ log_size -= xfer_size;
+ curr_data_offset += xfer_size;
+ i++;
+ }
+
+ if (!ret) {
+ fprintf(stderr, "%s: INFO: ", __func__);
+ nvme_show_status(ret);
+ } else {
+ fprintf(stderr, "%s: FAILURE: ", __func__);
+ nvme_show_status(ret);
+ fprintf(stderr, "%s: Partial data may have been captured\n", __func__);
+ snprintf(file + strlen(file), PATH_MAX, "%s", "-PARTIAL");
+ }
+
+ ret = wdc_create_log_file(file, dump_data, data_len);
+
+ free(dump_data);
+ return ret;
+}
+
+static int wdc_do_cap_telemetry_log(struct nvme_dev *dev, char *file,
+ __u32 bs, int type, int data_area)
+{
+ struct nvme_telemetry_log *log;
+ size_t full_size = 0;
+ int err = 0, output;
+ __u32 host_gen = 1;
+ int ctrl_init = 0;
+ __u32 result;
+ void *buf = NULL;
+ __u8 *data_ptr = NULL;
+ int data_written = 0, data_remaining = 0;
+ struct nvme_id_ctrl ctrl;
+ __u64 capabilities = 0;
+ nvme_root_t r;
+
+ memset(&ctrl, 0, sizeof(struct nvme_id_ctrl));
+ err = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (err) {
+ fprintf(stderr, "ERROR: WDC: nvme_identify_ctrl() failed 0x%x\n", err);
+ return err;
+ }
+
+ if (!(ctrl.lpa & 0x8)) {
+ fprintf(stderr, "Telemetry Host-Initiated and Telemetry Controller-Initiated log pages not supported\n");
+ return -EINVAL;
+ }
+
+ r = nvme_scan(NULL);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+
+ if (type == WDC_TELEMETRY_TYPE_HOST) {
+ host_gen = 1;
+ ctrl_init = 0;
+ } else if (type == WDC_TELEMETRY_TYPE_CONTROLLER) {
+ if ((capabilities & WDC_DRIVE_CAP_INTERNAL_LOG) == WDC_DRIVE_CAP_INTERNAL_LOG) {
+ /* Verify the Controller Initiated Option is enabled */
+ err = nvme_get_features_data(dev_fd(dev),
+ WDC_VU_DISABLE_CNTLR_TELEMETRY_OPTION_FEATURE_ID,
+ 0, 4, buf, &result);
+ if (!err) {
+ if (!result) {
+ /* enabled */
+ host_gen = 0;
+ ctrl_init = 1;
+ } else {
+ fprintf(stderr, "%s: Controller initiated option telemetry log page disabled\n", __func__);
+ return -EINVAL;
+ }
+ } else {
+ fprintf(stderr, "ERROR: WDC: Get telemetry option feature failed.");
+ nvme_show_status(err);
+ return -EPERM;
+ }
+ } else {
+ host_gen = 0;
+ ctrl_init = 1;
+ }
+ } else {
+ fprintf(stderr, "%s: Invalid type parameter; type = %d\n", __func__, type);
+ return -EINVAL;
+ }
+
+ if (!file) {
+ fprintf(stderr, "%s: Please provide an output file!\n", __func__);
+ return -EINVAL;
+ }
+
+ output = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (output < 0) {
+ fprintf(stderr, "%s: Failed to open output file %s: %s!\n",
+ __func__, file, strerror(errno));
+ return output;
+ }
+
+ if (ctrl_init)
+ err = nvme_get_ctrl_telemetry(dev_fd(dev), true, &log,
+ data_area, &full_size);
+ else if (host_gen)
+ err = nvme_get_new_host_telemetry(dev_fd(dev), &log,
+ data_area, &full_size);
+ else
+ err = nvme_get_host_telemetry(dev_fd(dev), &log, data_area,
+ &full_size);
+
+ if (err < 0) {
+ perror("get-telemetry-log");
+ goto close_output;
+ } else if (err > 0) {
+ nvme_show_status(err);
+ fprintf(stderr, "%s: Failed to acquire telemetry header!\n", __func__);
+ goto close_output;
+ }
+
+ /*
+ *Continuously pull data until the offset hits the end of the last
+ *block.
+ */
+ data_written = 0;
+ data_remaining = full_size;
+ data_ptr = (__u8 *)log;
+
+ while (data_remaining) {
+ data_written = write(output, data_ptr, data_remaining);
+
+ if (data_written < 0) {
+ data_remaining = data_written;
+ break;
+ } else if (data_written <= data_remaining) {
+ data_remaining -= data_written;
+ data_ptr += data_written;
+ } else {
+ /* Unexpected overwrite */
+ fprintf(stderr, "Failure: Unexpected telemetry log overwrite - data_remaining = 0x%x, data_written = 0x%x\n",
+ data_remaining, data_written);
+ break;
+ }
+ }
+
+ if (fsync(output) < 0) {
+ fprintf(stderr, "ERROR: %s: fsync: %s\n", __func__, strerror(errno));
+ err = -1;
+ }
+
+ free(log);
+close_output:
+ close(output);
+ return err;
+}
+
+static int wdc_do_cap_diag(nvme_root_t r, struct nvme_dev *dev, char *file,
+ __u32 xfer_size, int type, int data_area)
+{
+ int ret = -1;
+ __u32 e6_log_hdr_size = WDC_NVME_CAP_DIAG_HEADER_TOC_SIZE;
+ struct wdc_e6_log_hdr *log_hdr;
+ __u32 cap_diag_length;
+
+ log_hdr = (struct wdc_e6_log_hdr *)malloc(e6_log_hdr_size);
+ if (!log_hdr) {
+ fprintf(stderr, "%s: ERROR: malloc: %s\n", __func__, strerror(errno));
+ ret = -1;
+ goto out;
+ }
+ memset(log_hdr, 0, e6_log_hdr_size);
+
+ if (type == WDC_TELEMETRY_TYPE_NONE) {
+ ret = wdc_dump_length_e6(dev_fd(dev),
+ WDC_NVME_CAP_DIAG_OPCODE,
+ WDC_NVME_CAP_DIAG_HEADER_TOC_SIZE>>2,
+ 0x00,
+ log_hdr);
+ if (ret == -1) {
+ ret = -1;
+ goto out;
+ }
+
+ cap_diag_length = (log_hdr->log_size[0] << 24 | log_hdr->log_size[1] << 16 |
+ log_hdr->log_size[2] << 8 | log_hdr->log_size[3]);
+
+ if (!cap_diag_length) {
+ fprintf(stderr, "INFO: WDC: Capture Diagnostics log is empty\n");
+ } else {
+ ret = wdc_do_dump_e6(dev_fd(dev),
+ WDC_NVME_CAP_DIAG_OPCODE,
+ cap_diag_length,
+ (WDC_NVME_CAP_DIAG_SUBCMD << WDC_NVME_SUBCMD_SHIFT) | WDC_NVME_CAP_DIAG_CMD,
+ file, xfer_size, (__u8 *)log_hdr);
+
+ fprintf(stderr, "INFO: WDC: Capture Diagnostics log, length = 0x%x\n", cap_diag_length);
+ }
+ } else if ((type == WDC_TELEMETRY_TYPE_HOST) ||
+ (type == WDC_TELEMETRY_TYPE_CONTROLLER)) {
+ /* Get the desired telemetry log page */
+ ret = wdc_do_cap_telemetry_log(dev, file, xfer_size, type, data_area);
+ } else {
+ fprintf(stderr, "%s: ERROR: Invalid type : %d\n", __func__, type);
+ }
+
+out:
+ free(log_hdr);
+ return ret;
+}
+
+static int wdc_do_cap_dui_v1(int fd, char *file, __u32 xfer_size, int data_area, int verbose,
+ struct wdc_dui_log_hdr *log_hdr, __s64 *total_size)
+{
+ __s32 log_size = 0;
+ __u32 cap_dui_length = le32_to_cpu(log_hdr->log_size);
+ __u32 curr_data_offset = 0;
+ __u8 *buffer_addr;
+ __u8 *dump_data = NULL;
+ bool last_xfer = false;
+ int err;
+ int i;
+ int j;
+ int output;
+ int ret = 0;
+
+ if (verbose) {
+ fprintf(stderr, "INFO: WDC: Capture V1 Device Unit Info log, data area = %d\n",
+ data_area);
+ fprintf(stderr, "INFO: WDC: DUI Header Version = 0x%x\n", log_hdr->hdr_version);
+ fprintf(stderr, "INFO: WDC: DUI section count = 0x%x\n", log_hdr->section_count);
+ fprintf(stderr, "INFO: WDC: DUI log size = 0x%x\n", log_hdr->log_size);
+ }
+
+ if (!cap_dui_length) {
+ fprintf(stderr, "INFO: WDC: Capture V1 Device Unit Info log is empty\n");
+ return 0;
+ }
+
+ /* parse log header for all sections up to specified data area inclusively */
+ if (data_area != WDC_NVME_DUI_MAX_DATA_AREA) {
+ for (j = 0; j < log_hdr->section_count; j++) {
+ log_size += log_hdr->log_section[j].section_size;
+ if (verbose)
+ fprintf(stderr,
+ "%s: section size 0x%x, total size = 0x%x\n",
+ __func__,
+ (unsigned int)log_hdr->log_section[j].section_size,
+ (unsigned int)log_size);
+
+ }
+ } else {
+ log_size = cap_dui_length;
+ }
+
+ *total_size = log_size;
+
+ dump_data = (__u8 *)malloc(sizeof(__u8) * xfer_size);
+ if (!dump_data) {
+ fprintf(stderr, "%s: ERROR: dump data V1 malloc failed : status %s, size = 0x%x\n",
+ __func__, strerror(errno), (unsigned int)xfer_size);
+ return -1;
+ }
+ memset(dump_data, 0, sizeof(__u8) * xfer_size);
+
+ output = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (output < 0) {
+ fprintf(stderr, "%s: Failed to open output file %s: %s!\n", __func__, file,
+ strerror(errno));
+ free(dump_data);
+ return output;
+ }
+
+ /* write the telemetry and log headers into the dump_file */
+ err = write(output, (void *)log_hdr, WDC_NVME_CAP_DUI_HEADER_SIZE);
+ if (err != WDC_NVME_CAP_DUI_HEADER_SIZE) {
+ fprintf(stderr, "%s: Failed to flush header data to file!\n", __func__);
+ goto free_mem;
+ }
+
+ log_size -= WDC_NVME_CAP_DUI_HEADER_SIZE;
+ curr_data_offset = WDC_NVME_CAP_DUI_HEADER_SIZE;
+ i = 0;
+ buffer_addr = dump_data;
+
+ for (; log_size > 0; log_size -= xfer_size) {
+ xfer_size = min(xfer_size, log_size);
+
+ if (log_size <= xfer_size)
+ last_xfer = true;
+
+ ret = wdc_dump_dui_data(fd, xfer_size, curr_data_offset, buffer_addr, last_xfer);
+ if (ret) {
+ fprintf(stderr,
+ "%s: ERROR: WDC: Get chunk %d, size = 0x%"PRIx64", offset = 0x%x, addr = %p\n",
+ __func__, i, (uint64_t)log_size, curr_data_offset, buffer_addr);
+ fprintf(stderr, "%s: ERROR: WDC: ", __func__);
+ nvme_show_status(ret);
+ break;
+ }
+
+ /* write the dump data into the file */
+ err = write(output, (void *)buffer_addr, xfer_size);
+ if (err != xfer_size) {
+ fprintf(stderr,
+ "%s: ERROR: WDC: Failed to flush DUI data to file! chunk %d, err = 0x%x, xfer_size = 0x%x\n",
+ __func__, i, err, xfer_size);
+ ret = -1;
+ goto free_mem;
+ }
+
+ curr_data_offset += xfer_size;
+ i++;
+ }
+
+free_mem:
+ close(output);
+ free(dump_data);
+ return ret;
+}
+
+static int wdc_do_cap_dui_v2_v3(int fd, char *file, __u32 xfer_size, int data_area, int verbose,
+ struct wdc_dui_log_hdr *log_hdr, __s64 *total_size, __u64 file_size,
+ __u64 offset)
+{
+ __u64 cap_dui_length_v3;
+ __u64 curr_data_offset = 0;
+ __s64 log_size = 0;
+ __u64 xfer_size_long = (__u64)xfer_size;
+ __u8 *buffer_addr;
+ __u8 *dump_data = NULL;
+ bool last_xfer = false;
+ int err;
+ int i;
+ int j;
+ int output;
+ int ret = 0;
+ struct wdc_dui_log_hdr_v3 *log_hdr_v3 = (struct wdc_dui_log_hdr_v3 *)log_hdr;
+
+ cap_dui_length_v3 = le64_to_cpu(log_hdr_v3->log_size);
+
+ if (verbose) {
+ fprintf(stderr,
+ "INFO: WDC: Capture V2 or V3 Device Unit Info log, data area = %d\n",
+ data_area);
+
+ fprintf(stderr, "INFO: WDC: DUI Header Version = 0x%x\n",
+ log_hdr_v3->hdr_version);
+ if ((log_hdr->hdr_version & 0xFF) == 0x03)
+ fprintf(stderr, "INFO: WDC: DUI Product ID = 0x%x/%c\n",
+ log_hdr_v3->product_id, log_hdr_v3->product_id);
+ }
+
+ if (!cap_dui_length_v3) {
+ fprintf(stderr, "INFO: WDC: Capture V2 or V3 Device Unit Info log is empty\n");
+ return 0;
+ }
+
+ /* parse log header for all sections up to specified data area inclusively */
+ if (data_area != WDC_NVME_DUI_MAX_DATA_AREA) {
+ for (j = 0; j < WDC_NVME_DUI_MAX_SECTION_V3; j++) {
+ if (log_hdr_v3->log_section[j].data_area_id <= data_area &&
+ log_hdr_v3->log_section[j].data_area_id) {
+ log_size += log_hdr_v3->log_section[j].section_size;
+ if (verbose)
+ fprintf(stderr,
+ "%s: Data area ID %d : section size 0x%x, total size = 0x%"PRIx64"\n",
+ __func__, log_hdr_v3->log_section[j].data_area_id,
+ (unsigned int)log_hdr_v3->log_section[j].section_size,
+ (uint64_t)log_size);
+ } else {
+ if (verbose)
+ fprintf(stderr, "%s: break, total size = 0x%"PRIx64"\n",
+ __func__, (uint64_t)log_size);
+ break;
+ }
+ }
+ } else {
+ log_size = cap_dui_length_v3;
+ }
+
+ *total_size = log_size;
+
+ if (offset >= *total_size) {
+ fprintf(stderr,
+ "%s: INFO: WDC: Offset 0x%"PRIx64" exceeds total size 0x%"PRIx64", no data retrieved\n",
+ __func__, (uint64_t)offset, (uint64_t)*total_size);
+ return -1;
+ }
+
+ dump_data = (__u8 *)malloc(sizeof(__u8) * xfer_size_long);
+ if (!dump_data) {
+ fprintf(stderr,
+ "%s: ERROR: dump data v3 malloc failed : status %s, size = 0x%"PRIx64"\n",
+ __func__, strerror(errno), (uint64_t)xfer_size_long);
+ return -1;
+ }
+ memset(dump_data, 0, sizeof(__u8) * xfer_size_long);
+
+ output = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (output < 0) {
+ fprintf(stderr, "%s: Failed to open output file %s: %s!\n",
+ __func__, file, strerror(errno));
+ free(dump_data);
+ return output;
+ }
+
+ curr_data_offset = 0;
+
+ if (file_size) {
+ /* Write the DUI data based on the passed in file size */
+ if ((offset + file_size) > *total_size)
+ log_size = min((*total_size - offset), file_size);
+ else
+ log_size = min(*total_size, file_size);
+
+ if (verbose)
+ fprintf(stderr,
+ "%s: INFO: WDC: Offset 0x%"PRIx64", file size 0x%"PRIx64", total size 0x%"PRIx64", log size 0x%"PRIx64"\n",
+ __func__, (uint64_t)offset,
+ (uint64_t)file_size, (uint64_t)*total_size, (uint64_t)log_size);
+
+ curr_data_offset = offset;
+ }
+
+ i = 0;
+ buffer_addr = dump_data;
+
+ for (; log_size > 0; log_size -= xfer_size_long) {
+ xfer_size_long = min(xfer_size_long, log_size);
+
+ if (log_size <= xfer_size_long)
+ last_xfer = true;
+
+ ret = wdc_dump_dui_data_v2(fd, (__u32)xfer_size_long, curr_data_offset, buffer_addr,
+ last_xfer);
+ if (ret) {
+ fprintf(stderr,
+ "%s: ERROR: WDC: Get chunk %d, size = 0x%"PRIx64", offset = 0x%"PRIx64", addr = %p\n",
+ __func__, i, (uint64_t)*total_size, (uint64_t)curr_data_offset,
+ buffer_addr);
+ fprintf(stderr, "%s: ERROR: WDC: ", __func__);
+ nvme_show_status(ret);
+ break;
+ }
+
+ /* write the dump data into the file */
+ err = write(output, (void *)buffer_addr, xfer_size_long);
+ if (err != xfer_size_long) {
+ fprintf(stderr,
+ "%s: ERROR: WDC: Failed to flush DUI data to file! chunk %d, err = 0x%x, xfer_size = 0x%"PRIx64"\n",
+ __func__, i, err, (uint64_t)xfer_size_long);
+ ret = -1;
+ goto free_mem;
+ }
+
+ curr_data_offset += xfer_size_long;
+ i++;
+ }
+
+free_mem:
+ close(output);
+ free(dump_data);
+ return ret;
+}
+
+static int wdc_do_cap_dui_v4(int fd, char *file, __u32 xfer_size, int data_area, int verbose,
+ struct wdc_dui_log_hdr *log_hdr, __s64 *total_size, __u64 file_size,
+ __u64 offset)
+{
+ __s64 log_size = 0;
+ __s64 section_size_bytes = 0;
+ __s64 xfer_size_long = (__s64)xfer_size;
+ __u64 cap_dui_length_v4;
+ __u64 curr_data_offset = 0;
+ __u8 *buffer_addr;
+ __u8 *dump_data = NULL;
+ int err;
+ int i;
+ int j;
+ int output;
+ int ret = 0;
+ bool last_xfer = false;
+ struct wdc_dui_log_hdr_v4 *log_hdr_v4 = (struct wdc_dui_log_hdr_v4 *)log_hdr;
+
+ cap_dui_length_v4 = le64_to_cpu(log_hdr_v4->log_size_sectors) * WDC_NVME_SN730_SECTOR_SIZE;
+
+ if (verbose) {
+ fprintf(stderr, "INFO: WDC: Capture V4 Device Unit Info log, data area = %d\n", data_area);
+ fprintf(stderr, "INFO: WDC: DUI Header Version = 0x%x\n", log_hdr_v4->hdr_version);
+ fprintf(stderr, "INFO: WDC: DUI Product ID = 0x%x/%c\n", log_hdr_v4->product_id, log_hdr_v4->product_id);
+ fprintf(stderr, "INFO: WDC: DUI log size sectors = 0x%x\n", log_hdr_v4->log_size_sectors);
+ fprintf(stderr, "INFO: WDC: DUI cap_dui_length = 0x%"PRIx64"\n", (uint64_t)cap_dui_length_v4);
+ }
+
+ if (!cap_dui_length_v4) {
+ fprintf(stderr, "INFO: WDC: Capture V4 Device Unit Info log is empty\n");
+ return 0;
+ }
+
+ /* parse log header for all sections up to specified data area inclusively */
+ if (data_area != WDC_NVME_DUI_MAX_DATA_AREA) {
+ for (j = 0; j < WDC_NVME_DUI_MAX_SECTION; j++) {
+ if (log_hdr_v4->log_section[j].data_area_id <= data_area &&
+ log_hdr_v4->log_section[j].data_area_id) {
+ section_size_bytes = ((__s64)log_hdr_v4->log_section[j].section_size_sectors * WDC_NVME_SN730_SECTOR_SIZE);
+ log_size += section_size_bytes;
+ if (verbose)
+ fprintf(stderr,
+ "%s: Data area ID %d : section size 0x%x sectors, section size 0x%"PRIx64" bytes, total size = 0x%"PRIx64"\n",
+ __func__, log_hdr_v4->log_section[j].data_area_id,
+ log_hdr_v4->log_section[j].section_size_sectors,
+ (uint64_t)section_size_bytes, (uint64_t)log_size);
+ } else {
+ if (verbose)
+ fprintf(stderr, "%s: break, total size = 0x%"PRIx64"\n", __func__, (uint64_t)log_size);
+ break;
+ }
+ }
+ } else {
+ log_size = cap_dui_length_v4;
+ }
+
+ *total_size = log_size;
+
+ if (offset >= *total_size) {
+ fprintf(stderr,
+ "%s: INFO: WDC: Offset 0x%"PRIx64" exceeds total size 0x%"PRIx64", no data retrieved\n",
+ __func__, (uint64_t)offset, (uint64_t)*total_size);
+ return -1;
+ }
+
+ dump_data = (__u8 *)malloc(sizeof(__u8) * xfer_size_long);
+ if (!dump_data) {
+ fprintf(stderr, "%s: ERROR: dump data V4 malloc failed : status %s, size = 0x%x\n",
+ __func__, strerror(errno), (unsigned int)xfer_size_long);
+ return -1;
+ }
+ memset(dump_data, 0, sizeof(__u8) * xfer_size_long);
+
+ output = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (output < 0) {
+ fprintf(stderr, "%s: Failed to open output file %s: %s!\n", __func__, file,
+ strerror(errno));
+ free(dump_data);
+ return output;
+ }
+
+ curr_data_offset = 0;
+
+ if (file_size) {
+ /* Write the DUI data based on the passed in file size */
+ if ((offset + file_size) > *total_size)
+ log_size = min((*total_size - offset), file_size);
+ else
+ log_size = min(*total_size, file_size);
+
+ if (verbose)
+ fprintf(stderr,
+ "%s: INFO: WDC: Offset 0x%"PRIx64", file size 0x%"PRIx64", total size 0x%"PRIx64", log size 0x%"PRIx64"\n",
+ __func__, (uint64_t)offset, (uint64_t)file_size,
+ (uint64_t)*total_size, (uint64_t)log_size);
+
+ curr_data_offset = offset;
+ }
+
+ i = 0;
+ buffer_addr = dump_data;
+
+ for (; log_size > 0; log_size -= xfer_size_long) {
+ xfer_size_long = min(xfer_size_long, log_size);
+
+ if (log_size <= xfer_size_long)
+ last_xfer = true;
+
+ ret = wdc_dump_dui_data_v2(fd, (__u32)xfer_size_long, curr_data_offset, buffer_addr, last_xfer);
+ if (ret) {
+ fprintf(stderr,
+ "%s: ERROR: WDC: Get chunk %d, size = 0x%"PRIx64", offset = 0x%"PRIx64", addr = %p\n",
+ __func__, i, (uint64_t)log_size, (uint64_t)curr_data_offset,
+ buffer_addr);
+ fprintf(stderr, "%s: ERROR: WDC:", __func__);
+ nvme_show_status(ret);
+ break;
+ }
+
+ /* write the dump data into the file */
+ err = write(output, (void *)buffer_addr, xfer_size_long);
+ if (err != xfer_size_long) {
+ fprintf(stderr,
+ "%s: ERROR: WDC: Failed to flush DUI data to file! chunk %d, err = 0x%x, xfer_size_long = 0x%"PRIx64"\n",
+ __func__, i, err, (uint64_t)xfer_size_long);
+ ret = -1;
+ goto free_mem;
+ }
+
+ curr_data_offset += xfer_size_long;
+ i++;
+ }
+
+free_mem:
+ close(output);
+ free(dump_data);
+ return ret;
+}
+
+static int wdc_do_cap_dui(int fd, char *file, __u32 xfer_size, int data_area, int verbose,
+ __u64 file_size, __u64 offset)
+{
+ int ret = 0;
+ __u32 dui_log_hdr_size = WDC_NVME_CAP_DUI_HEADER_SIZE;
+ struct wdc_dui_log_hdr *log_hdr;
+ __s64 total_size = 0;
+ bool last_xfer = false;
+
+ log_hdr = (struct wdc_dui_log_hdr *)malloc(dui_log_hdr_size);
+ if (!log_hdr) {
+ fprintf(stderr, "%s: ERROR: log header malloc failed : status %s, size 0x%x\n",
+ __func__, strerror(errno), dui_log_hdr_size);
+ return -1;
+ }
+ memset(log_hdr, 0, dui_log_hdr_size);
+
+ /* get the dui telemetry and log headers */
+ ret = wdc_dump_dui_data(fd, WDC_NVME_CAP_DUI_HEADER_SIZE, 0x00, (__u8 *)log_hdr, last_xfer);
+ if (ret) {
+ fprintf(stderr, "%s: ERROR: WDC: Get DUI headers failed\n", __func__);
+ fprintf(stderr, "%s: ERROR: WDC: ", __func__);
+ nvme_show_status(ret);
+ goto out;
+ }
+
+ /* Check the Log Header version */
+ if ((log_hdr->hdr_version & 0xFF) == 0x00 || (log_hdr->hdr_version & 0xFF) == 0x01) {
+ ret = wdc_do_cap_dui_v1(fd, file, xfer_size, data_area, verbose, log_hdr,
+ &total_size);
+ if (ret)
+ goto out;
+ } else if ((log_hdr->hdr_version & 0xFF) == 0x02 ||
+ (log_hdr->hdr_version & 0xFF) == 0x03) {
+ /* Process Version 2 or 3 header */
+ ret = wdc_do_cap_dui_v2_v3(fd, file, xfer_size, data_area, verbose, log_hdr,
+ &total_size, file_size, offset);
+ if (ret)
+ goto out;
+ } else if ((log_hdr->hdr_version & 0xFF) == 0x04) {
+ ret = wdc_do_cap_dui_v4(fd, file, xfer_size, data_area, verbose, log_hdr,
+ &total_size, file_size, offset);
+ if (ret)
+ goto out;
+ } else {
+ fprintf(stderr, "INFO: WDC: Unsupported header version = 0x%x\n",
+ log_hdr->hdr_version);
+ goto out;
+ }
+
+ nvme_show_status(ret);
+ if (verbose)
+ fprintf(stderr, "INFO: WDC: Capture Device Unit Info log, length = 0x%"PRIx64"\n",
+ (uint64_t)total_size);
+
+out:
+ free(log_hdr);
+ return ret;
+}
+
+static int wdc_cap_diag(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ nvme_root_t r;
+ char *desc = "Capture Diagnostics Log.";
+ char *file = "Output file pathname.";
+ char *size = "Data retrieval transfer size.";
+ __u64 capabilities = 0;
+ char f[PATH_MAX] = {0};
+ struct nvme_dev *dev;
+ __u32 xfer_size = 0;
+ int ret = 0;
+
+ struct config {
+ char *file;
+ __u32 xfer_size;
+ };
+
+ struct config cfg = {
+ .file = NULL,
+ .xfer_size = 0x10000
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FILE("output-file", 'o', &cfg.file, file),
+ OPT_UINT("transfer-size", 's', &cfg.xfer_size, size),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+
+ if (cfg.file)
+ strncpy(f, cfg.file, PATH_MAX - 1);
+ if (cfg.xfer_size)
+ xfer_size = cfg.xfer_size;
+ ret = wdc_get_serial_name(dev, f, PATH_MAX, "cap_diag");
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: failed to generate file name\n");
+ goto out;
+ }
+ if (!cfg.file) {
+ if (strlen(f) > PATH_MAX - 5) {
+ fprintf(stderr, "ERROR: WDC: file name overflow\n");
+ ret = -1;
+ goto out;
+ }
+ strcat(f, ".bin");
+ }
+
+ capabilities = wdc_get_drive_capabilities(r, dev);
+ if ((capabilities & WDC_DRIVE_CAP_CAP_DIAG) == WDC_DRIVE_CAP_CAP_DIAG)
+ ret = wdc_do_cap_diag(r, dev, f, xfer_size, 0, 0);
+ else
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+out:
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_do_get_sn730_log_len(int fd, uint32_t *len_buf, uint32_t subopcode)
+{
+ int ret;
+ uint32_t *output = NULL;
+ struct nvme_passthru_cmd admin_cmd;
+
+ output = (uint32_t *)malloc(sizeof(uint32_t));
+ if (!output) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+ memset(output, 0, sizeof(uint32_t));
+ memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd));
+
+ admin_cmd.data_len = 8;
+ admin_cmd.opcode = SN730_NVME_GET_LOG_OPCODE;
+ admin_cmd.addr = (uintptr_t)output;
+ admin_cmd.cdw12 = subopcode;
+ admin_cmd.cdw10 = SN730_LOG_CHUNK_SIZE / 4;
+
+ ret = nvme_submit_admin_passthru(fd, &admin_cmd, NULL);
+ if (!ret)
+ *len_buf = *output;
+ free(output);
+ return ret;
+}
+
+static int wdc_do_get_sn730_log(int fd, void *log_buf, uint32_t offset, uint32_t subopcode)
+{
+ int ret;
+ uint8_t *output = NULL;
+ struct nvme_passthru_cmd admin_cmd;
+
+ output = (uint8_t *)calloc(SN730_LOG_CHUNK_SIZE, sizeof(uint8_t));
+ if (!output) {
+ fprintf(stderr, "ERROR: WDC: calloc: %s\n", strerror(errno));
+ return -1;
+ }
+ memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd));
+ admin_cmd.data_len = SN730_LOG_CHUNK_SIZE;
+ admin_cmd.opcode = SN730_NVME_GET_LOG_OPCODE;
+ admin_cmd.addr = (uintptr_t)output;
+ admin_cmd.cdw12 = subopcode;
+ admin_cmd.cdw13 = offset;
+ admin_cmd.cdw10 = SN730_LOG_CHUNK_SIZE / 4;
+
+ ret = nvme_submit_admin_passthru(fd, &admin_cmd, NULL);
+ if (!ret)
+ memcpy(log_buf, output, SN730_LOG_CHUNK_SIZE);
+ return ret;
+}
+
+static int get_sn730_log_chunks(int fd, uint8_t *log_buf, uint32_t log_len, uint32_t subopcode)
+{
+ int ret = 0;
+ uint8_t *chunk_buf = NULL;
+ int remaining = log_len;
+ int curr_offset = 0;
+
+ chunk_buf = (uint8_t *)malloc(sizeof(uint8_t) * SN730_LOG_CHUNK_SIZE);
+ if (!chunk_buf) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ ret = -1;
+ goto out;
+ }
+
+ while (remaining > 0) {
+ memset(chunk_buf, 0, SN730_LOG_CHUNK_SIZE);
+ ret = wdc_do_get_sn730_log(fd, chunk_buf, curr_offset, subopcode);
+ if (!ret) {
+ if (remaining >= SN730_LOG_CHUNK_SIZE) {
+ memcpy(log_buf + (curr_offset * SN730_LOG_CHUNK_SIZE),
+ chunk_buf, SN730_LOG_CHUNK_SIZE);
+ } else {
+ memcpy(log_buf + (curr_offset * SN730_LOG_CHUNK_SIZE),
+ chunk_buf, remaining);
+ }
+ remaining -= SN730_LOG_CHUNK_SIZE;
+ curr_offset += 1;
+ } else {
+ goto out;
+ }
+ }
+out:
+ free(chunk_buf);
+ return ret;
+}
+
+static int wdc_do_sn730_get_and_tar(int fd, char *outputName)
+{
+ int ret = 0;
+ void *retPtr;
+ uint8_t *full_log_buf = NULL;
+ uint8_t *key_log_buf = NULL;
+ uint8_t *core_dump_log_buf = NULL;
+ uint8_t *extended_log_buf = NULL;
+ uint32_t full_log_len = 0;
+ uint32_t key_log_len = 0;
+ uint32_t core_dump_log_len = 0;
+ uint32_t extended_log_len = 0;
+ struct tarfile_metadata *tarInfo = NULL;
+
+ tarInfo = (struct tarfile_metadata *)malloc(sizeof(struct tarfile_metadata));
+ if (!tarInfo) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ ret = -1;
+ goto free_buf;
+ }
+ memset(tarInfo, 0, sizeof(struct tarfile_metadata));
+
+ /* Create Logs directory */
+ wdc_UtilsGetTime(&tarInfo->timeInfo);
+ memset(tarInfo->timeString, 0, sizeof(tarInfo->timeString));
+ wdc_UtilsSnprintf((char *)tarInfo->timeString, MAX_PATH_LEN, "%02u%02u%02u_%02u%02u%02u",
+ tarInfo->timeInfo.year, tarInfo->timeInfo.month, tarInfo->timeInfo.dayOfMonth,
+ tarInfo->timeInfo.hour, tarInfo->timeInfo.minute, tarInfo->timeInfo.second);
+
+ wdc_UtilsSnprintf((char *)tarInfo->bufferFolderName, MAX_PATH_LEN, "%s",
+ (char *)outputName);
+
+ retPtr = getcwd((char *)tarInfo->currDir, MAX_PATH_LEN);
+ if (retPtr) {
+ wdc_UtilsSnprintf((char *)tarInfo->bufferFolderPath, MAX_PATH_LEN, "%s%s%s",
+ (char *)tarInfo->currDir, WDC_DE_PATH_SEPARATOR, (char *)tarInfo->bufferFolderName);
+ } else {
+ fprintf(stderr, "ERROR: WDC: get current working directory failed\n");
+ goto free_buf;
+ }
+
+ ret = wdc_UtilsCreateDir((char *)tarInfo->bufferFolderPath);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: create directory failed, ret = %d, dir = %s\n", ret, tarInfo->bufferFolderPath);
+ goto free_buf;
+ } else {
+ fprintf(stderr, "Stored log files in directory: %s\n", tarInfo->bufferFolderPath);
+ }
+
+ ret = wdc_do_get_sn730_log_len(fd, &full_log_len, SN730_GET_FULL_LOG_LENGTH);
+ if (ret) {
+ nvme_show_status(ret);
+ goto free_buf;
+ }
+ ret = wdc_do_get_sn730_log_len(fd, &key_log_len, SN730_GET_KEY_LOG_LENGTH);
+ if (ret) {
+ nvme_show_status(ret);
+ goto free_buf;
+ }
+ ret = wdc_do_get_sn730_log_len(fd, &core_dump_log_len, SN730_GET_COREDUMP_LOG_LENGTH);
+ if (ret) {
+ nvme_show_status(ret);
+ goto free_buf;
+ }
+ ret = wdc_do_get_sn730_log_len(fd, &extended_log_len, SN730_GET_EXTENDED_LOG_LENGTH);
+ if (ret) {
+ nvme_show_status(ret);
+ goto free_buf;
+ }
+
+ full_log_buf = (uint8_t *) calloc(full_log_len, sizeof(uint8_t));
+ key_log_buf = (uint8_t *) calloc(key_log_len, sizeof(uint8_t));
+ core_dump_log_buf = (uint8_t *) calloc(core_dump_log_len, sizeof(uint8_t));
+ extended_log_buf = (uint8_t *) calloc(extended_log_len, sizeof(uint8_t));
+
+ if (!full_log_buf || !key_log_buf || !core_dump_log_buf || !extended_log_buf) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ ret = -1;
+ goto free_buf;
+ }
+
+ /* Get the full log */
+ ret = get_sn730_log_chunks(fd, full_log_buf, full_log_len, SN730_GET_FULL_LOG_SUBOPCODE);
+ if (ret) {
+ nvme_show_status(ret);
+ goto free_buf;
+ }
+
+ /* Get the key log */
+ ret = get_sn730_log_chunks(fd, key_log_buf, key_log_len, SN730_GET_KEY_LOG_SUBOPCODE);
+ if (ret) {
+ nvme_show_status(ret);
+ goto free_buf;
+ }
+
+ /* Get the core dump log */
+ ret = get_sn730_log_chunks(fd, core_dump_log_buf, core_dump_log_len, SN730_GET_CORE_LOG_SUBOPCODE);
+ if (ret) {
+ nvme_show_status(ret);
+ goto free_buf;
+ }
+
+ /* Get the extended log */
+ ret = get_sn730_log_chunks(fd, extended_log_buf, extended_log_len, SN730_GET_EXTEND_LOG_SUBOPCODE);
+ if (ret) {
+ nvme_show_status(ret);
+ goto free_buf;
+ }
+
+ /* Write log files */
+ wdc_UtilsSnprintf(tarInfo->fileName, MAX_PATH_LEN, "%s%s%s_%s.bin", (char *)tarInfo->bufferFolderPath, WDC_DE_PATH_SEPARATOR,
+ "full_log", (char *)tarInfo->timeString);
+ wdc_WriteToFile(tarInfo->fileName, (char *)full_log_buf, full_log_len);
+
+ wdc_UtilsSnprintf(tarInfo->fileName, MAX_PATH_LEN, "%s%s%s_%s.bin", (char *)tarInfo->bufferFolderPath, WDC_DE_PATH_SEPARATOR,
+ "key_log", (char *)tarInfo->timeString);
+ wdc_WriteToFile(tarInfo->fileName, (char *)key_log_buf, key_log_len);
+
+ wdc_UtilsSnprintf(tarInfo->fileName, MAX_PATH_LEN, "%s%s%s_%s.bin", (char *)tarInfo->bufferFolderPath, WDC_DE_PATH_SEPARATOR,
+ "core_dump_log", (char *)tarInfo->timeString);
+ wdc_WriteToFile(tarInfo->fileName, (char *)core_dump_log_buf, core_dump_log_len);
+
+ wdc_UtilsSnprintf(tarInfo->fileName, MAX_PATH_LEN, "%s%s%s_%s.bin", (char *)tarInfo->bufferFolderPath, WDC_DE_PATH_SEPARATOR,
+ "extended_log", (char *)tarInfo->timeString);
+ wdc_WriteToFile(tarInfo->fileName, (char *)extended_log_buf, extended_log_len);
+
+ /* Tar the log directory */
+ wdc_UtilsSnprintf(tarInfo->tarFileName, sizeof(tarInfo->tarFileName), "%s%s", (char *)tarInfo->bufferFolderPath, WDC_DE_TAR_FILE_EXTN);
+ wdc_UtilsSnprintf(tarInfo->tarFiles, sizeof(tarInfo->tarFiles), "%s%s%s", (char *)tarInfo->bufferFolderName, WDC_DE_PATH_SEPARATOR, WDC_DE_TAR_FILES);
+ wdc_UtilsSnprintf(tarInfo->tarCmd, sizeof(tarInfo->tarCmd), "%s %s %s", WDC_DE_TAR_CMD, (char *)tarInfo->tarFileName, (char *)tarInfo->tarFiles);
+
+ ret = system(tarInfo->tarCmd);
+
+ if (ret)
+ fprintf(stderr, "ERROR: WDC: Tar of log data failed, ret = %d\n", ret);
+
+free_buf:
+ free(tarInfo);
+ free(full_log_buf);
+ free(core_dump_log_buf);
+ free(key_log_buf);
+ free(extended_log_buf);
+ return ret;
+}
+
+static int dump_internal_logs(struct nvme_dev *dev, char *dir_name, int verbose)
+{
+ char file_path[128];
+ void *telemetry_log;
+ const size_t bs = 512;
+ struct nvme_telemetry_log *hdr;
+ size_t full_size, offset = bs;
+ int err, output;
+
+ if (verbose)
+ printf("NVMe Telemetry log...\n");
+
+ hdr = malloc(bs);
+ telemetry_log = malloc(bs);
+ if (!hdr || !telemetry_log) {
+ fprintf(stderr, "Failed to allocate %zu bytes for log: %s\n", bs, strerror(errno));
+ err = -ENOMEM;
+ goto free_mem;
+ }
+ memset(hdr, 0, bs);
+
+ sprintf(file_path, "%s/telemetry.bin", dir_name);
+ output = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (output < 0) {
+ fprintf(stderr, "Failed to open output file %s: %s!\n", file_path, strerror(errno));
+ err = output;
+ goto free_mem;
+ }
+
+ struct nvme_get_log_args args = {
+ .lpo = 0,
+ .result = NULL,
+ .log = hdr,
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .lid = NVME_LOG_LID_TELEMETRY_HOST,
+ .len = bs,
+ .nsid = NVME_NSID_ALL,
+ .csi = NVME_CSI_NVM,
+ .lsi = NVME_LOG_LSI_NONE,
+ .lsp = NVME_LOG_TELEM_HOST_LSP_CREATE,
+ .uuidx = NVME_UUID_NONE,
+ .rae = true,
+ .ot = false,
+ };
+
+ err = nvme_get_log(&args);
+ if (err < 0)
+ perror("get-telemetry-log");
+ else if (err > 0) {
+ nvme_show_status(err);
+ fprintf(stderr, "Failed to acquire telemetry header %d!\n", err);
+ goto close_output;
+ }
+
+ err = write(output, (void *)hdr, bs);
+ if (err != bs) {
+ fprintf(stderr, "Failed to flush all data to file!\n");
+ goto close_output;
+ }
+
+ full_size = (le16_to_cpu(hdr->dalb3) * bs) + offset;
+
+ while (offset != full_size) {
+ args.log = telemetry_log;
+ args.lpo = offset;
+ args.lsp = NVME_LOG_LSP_NONE;
+ err = nvme_get_log(&args);
+ if (err < 0) {
+ perror("get-telemetry-log");
+ break;
+ } else if (err > 0) {
+ fprintf(stderr, "Failed to acquire full telemetry log!\n");
+ nvme_show_status(err);
+ break;
+ }
+
+ err = write(output, (void *)telemetry_log, bs);
+ if (err != bs) {
+ fprintf(stderr, "Failed to flush all data to file!\n");
+ break;
+ }
+ err = 0;
+ offset += bs;
+ }
+
+close_output:
+ close(output);
+free_mem:
+ free(hdr);
+ free(telemetry_log);
+
+ return err;
+}
+
+static int wdc_vs_internal_fw_log(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ char *desc = "Internal Firmware Log.";
+ char *file = "Output file pathname.";
+ char *size = "Data retrieval transfer size.";
+ char *data_area = "Data area to retrieve up to. Currently only supported on the SN340, SN640, SN730, and SN840 devices.";
+ char *file_size = "Output file size. Currently only supported on the SN340 device.";
+ char *offset = "Output file data offset. Currently only supported on the SN340 device.";
+ char *type = "Telemetry type - NONE, HOST, or CONTROLLER. Currently only supported on the SN530, SN640, SN730, SN740, SN810, SN840 and ZN350 devices.";
+ char *verbose = "Display more debug messages.";
+ char f[PATH_MAX] = {0};
+ char fb[PATH_MAX/2] = {0};
+ char fileSuffix[PATH_MAX] = {0};
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ __u32 xfer_size = 0;
+ int telemetry_type = 0, telemetry_data_area = 0;
+ UtilsTimeInfo timeInfo;
+ __u8 timeStamp[MAX_PATH_LEN];
+ __u64 capabilities = 0;
+ __u32 device_id, read_vendor_id;
+ char file_path[PATH_MAX/2] = {0};
+ char cmd_buf[PATH_MAX] = {0};
+ int ret = -1;
+
+ struct config {
+ char *file;
+ __u32 xfer_size;
+ int data_area;
+ __u64 file_size;
+ __u64 offset;
+ char *type;
+ bool verbose;
+ };
+
+ struct config cfg = {
+ .file = NULL,
+ .xfer_size = 0x10000,
+ .data_area = 0,
+ .file_size = 0,
+ .offset = 0,
+ .type = NULL,
+ .verbose = false,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FILE("output-file", 'o', &cfg.file, file),
+ OPT_UINT("transfer-size", 's', &cfg.xfer_size, size),
+ OPT_UINT("data-area", 'd', &cfg.data_area, data_area),
+ OPT_LONG("file-size", 'f', &cfg.file_size, file_size),
+ OPT_LONG("offset", 'e', &cfg.offset, offset),
+ OPT_FILE("type", 't', &cfg.type, type),
+ OPT_FLAG("verbose", 'v', &cfg.verbose, verbose),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+ if (!wdc_check_device(r, dev))
+ goto out;
+
+ if (cfg.xfer_size) {
+ xfer_size = cfg.xfer_size;
+ } else {
+ fprintf(stderr, "ERROR: WDC: Invalid length\n");
+ goto out;
+ }
+
+ ret = wdc_get_pci_ids(r, dev, &device_id, &read_vendor_id);
+
+ if (!wdc_is_sn861(device_id)) {
+ if (cfg.file) {
+ int verify_file;
+
+ /* verify file name and path is valid before getting dump data */
+ verify_file = open(cfg.file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (verify_file < 0) {
+ fprintf(stderr, "ERROR: WDC: open: %s\n", strerror(errno));
+ goto out;
+ }
+ close(verify_file);
+ strncpy(f, cfg.file, PATH_MAX - 1);
+ } else {
+ wdc_UtilsGetTime(&timeInfo);
+ memset(timeStamp, 0, sizeof(timeStamp));
+ wdc_UtilsSnprintf((char *)timeStamp, MAX_PATH_LEN,
+ "%02u%02u%02u_%02u%02u%02u", timeInfo.year,
+ timeInfo.month, timeInfo.dayOfMonth,
+ timeInfo.hour, timeInfo.minute,
+ timeInfo.second);
+ snprintf(fileSuffix, PATH_MAX, "_internal_fw_log_%s", (char *)timeStamp);
+
+ ret = wdc_get_serial_name(dev, f, PATH_MAX, fileSuffix);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: failed to generate file name\n");
+ goto out;
+ }
+ }
+
+ if (!cfg.file) {
+ if (strlen(f) > PATH_MAX - 5) {
+ fprintf(stderr, "ERROR: WDC: file name overflow\n");
+ ret = -1;
+ goto out;
+ }
+ strcat(f, ".bin");
+ }
+ fprintf(stderr, "%s: filename = %s\n", __func__, f);
+
+ if (cfg.data_area) {
+ if (cfg.data_area > 5 || cfg.data_area < 1) {
+ fprintf(stderr, "ERROR: WDC: Data area must be 1-5\n");
+ ret = -1;
+ goto out;
+ }
+ }
+
+ if (!cfg.type || !strcmp(cfg.type, "NONE") || !strcmp(cfg.type, "none")) {
+ telemetry_type = WDC_TELEMETRY_TYPE_NONE;
+ data_area = 0;
+ } else if (!strcmp(cfg.type, "HOST") || !strcmp(cfg.type, "host")) {
+ telemetry_type = WDC_TELEMETRY_TYPE_HOST;
+ telemetry_data_area = cfg.data_area;
+ } else if (!strcmp(cfg.type, "CONTROLLER") || !strcmp(cfg.type, "controller")) {
+ telemetry_type = WDC_TELEMETRY_TYPE_CONTROLLER;
+ telemetry_data_area = cfg.data_area;
+ } else {
+ fprintf(stderr,
+ "ERROR: WDC: Invalid type - Must be NONE, HOST or CONTROLLER\n");
+ ret = -1;
+ goto out;
+ }
+ } else {
+ if (cfg.file) {
+ strncpy(fb, cfg.file, PATH_MAX/2 - 8);
+ } else {
+ wdc_UtilsGetTime(&timeInfo);
+ memset(timeStamp, 0, sizeof(timeStamp));
+ wdc_UtilsSnprintf((char *)timeStamp, MAX_PATH_LEN,
+ "%02u%02u%02u_%02u%02u%02u", timeInfo.year,
+ timeInfo.month, timeInfo.dayOfMonth,
+ timeInfo.hour, timeInfo.minute,
+ timeInfo.second);
+ snprintf(fileSuffix, PATH_MAX, "_internal_fw_log_%s", (char *)timeStamp);
+
+ ret = wdc_get_serial_name(dev, fb, PATH_MAX/2 - 7, fileSuffix);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: failed to generate file name\n");
+ goto out;
+ }
+
+ if (strlen(fb) > PATH_MAX/2 - 7) {
+ fprintf(stderr, "ERROR: WDC: file name overflow\n");
+ ret = -1;
+ goto out;
+ }
+ }
+ fprintf(stderr, "%s: filename = %s.tar.gz\n", __func__, fb);
+
+
+ memset(file_path, 0, sizeof(file_path));
+ if (snprintf(file_path, PATH_MAX/2 - 8, "%s.tar.gz", fb) >= PATH_MAX/2 - 8) {
+ fprintf(stderr, "File path is too long!\n");
+ ret = -1;
+ goto out;
+ }
+ if (access(file_path, F_OK) != -1) {
+ fprintf(stderr, "Output file already exists!\n");
+ ret = -EEXIST;
+ goto out;
+ }
+ }
+
+ capabilities = wdc_get_drive_capabilities(r, dev);
+ if ((capabilities & WDC_DRIVE_CAP_INTERNAL_LOG) == WDC_DRIVE_CAP_INTERNAL_LOG) {
+ if (!wdc_is_sn861(device_id)) {
+ /* Set the default DA to 3 if not specified */
+ if (!telemetry_data_area)
+ telemetry_data_area = 3;
+
+ ret = wdc_do_cap_diag(r, dev, f, xfer_size,
+ telemetry_type, telemetry_data_area);
+ } else {
+ if (cfg.verbose)
+ printf("Creating temp directory...\n");
+
+ ret = mkdir(fb, 0666);
+ if (ret) {
+ fprintf(stderr, "Failed to create directory!\n");
+ goto out;
+ }
+
+ ret = dump_internal_logs(dev, fb, cfg.verbose);
+ if (ret < 0)
+ perror("vs-internal-log");
+
+ if (cfg.verbose)
+ printf("Archiving...\n");
+
+ if (snprintf(cmd_buf, PATH_MAX,
+ "tar --remove-files -czf %s %s",
+ file_path, fb) >= PATH_MAX) {
+ fprintf(stderr, "Command buffer is too long!\n");
+ ret = -1;
+ goto out;
+ }
+
+ ret = system(cmd_buf);
+ if (ret)
+ fprintf(stderr, "Failed to create an archive file!\n");
+ }
+ goto out;
+ }
+ if ((capabilities & WDC_DRIVE_CAP_DUI) == WDC_DRIVE_CAP_DUI) {
+ if ((telemetry_type == WDC_TELEMETRY_TYPE_HOST) ||
+ (telemetry_type == WDC_TELEMETRY_TYPE_CONTROLLER)) {
+ if (!telemetry_data_area)
+ telemetry_data_area = 3; /* Set the default DA to 3 if not specified */
+ /* Get the desired telemetry log page */
+ ret = wdc_do_cap_telemetry_log(dev, f, xfer_size,
+ telemetry_type, telemetry_data_area);
+ goto out;
+ } else {
+ if (!cfg.data_area)
+ cfg.data_area = 1;
+
+ /* FW requirement - xfer size must be 256k for data area 4 */
+ if (cfg.data_area >= 4)
+ xfer_size = 0x40000;
+ ret = wdc_do_cap_dui(dev_fd(dev), f, xfer_size,
+ cfg.data_area,
+ cfg.verbose, cfg.file_size,
+ cfg.offset);
+ goto out;
+ }
+ }
+ if ((capabilities & WDC_DRIVE_CAP_DUI_DATA) == WDC_DRIVE_CAP_DUI_DATA) {
+ if ((telemetry_type == WDC_TELEMETRY_TYPE_HOST) ||
+ (telemetry_type == WDC_TELEMETRY_TYPE_CONTROLLER)) {
+ if (!telemetry_data_area)
+ telemetry_data_area = 3; /* Set the default DA to 3 if not specified */
+ /* Get the desired telemetry log page */
+ ret = wdc_do_cap_telemetry_log(dev, f, xfer_size,
+ telemetry_type, telemetry_data_area);
+ goto out;
+ } else {
+ ret = wdc_do_cap_dui(dev_fd(dev), f, xfer_size,
+ WDC_NVME_DUI_MAX_DATA_AREA,
+ cfg.verbose, 0, 0);
+ goto out;
+ }
+ }
+ if ((capabilities & WDC_SN730B_CAP_VUC_LOG) == WDC_SN730B_CAP_VUC_LOG) {
+ ret = wdc_do_sn730_get_and_tar(dev_fd(dev), f);
+ } else {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ }
+out:
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_do_crash_dump(struct nvme_dev *dev, char *file, int type)
+{
+ int ret;
+ __u32 crash_dump_length;
+ __u32 opcode;
+ __u32 cdw12;
+ __u32 cdw10_size;
+ __u32 cdw12_size;
+ __u32 cdw12_clear;
+
+ if (type == WDC_NVME_PFAIL_DUMP_TYPE) {
+ /* set parms to get the PFAIL Crash Dump */
+ opcode = WDC_NVME_PF_CRASH_DUMP_OPCODE;
+ cdw10_size = WDC_NVME_PF_CRASH_DUMP_SIZE_NDT;
+ cdw12_size = ((WDC_NVME_PF_CRASH_DUMP_SIZE_SUBCMD << WDC_NVME_SUBCMD_SHIFT) |
+ WDC_NVME_PF_CRASH_DUMP_SIZE_CMD);
+
+ cdw12 = (WDC_NVME_PF_CRASH_DUMP_SUBCMD << WDC_NVME_SUBCMD_SHIFT) |
+ WDC_NVME_PF_CRASH_DUMP_CMD;
+
+ cdw12_clear = ((WDC_NVME_CLEAR_PF_CRASH_DUMP_SUBCMD << WDC_NVME_SUBCMD_SHIFT) |
+ WDC_NVME_CLEAR_CRASH_DUMP_CMD);
+
+ } else {
+ /* set parms to get the Crash Dump */
+ opcode = WDC_NVME_CRASH_DUMP_OPCODE;
+ cdw10_size = WDC_NVME_CRASH_DUMP_SIZE_NDT;
+ cdw12_size = ((WDC_NVME_CRASH_DUMP_SIZE_SUBCMD << WDC_NVME_SUBCMD_SHIFT) |
+ WDC_NVME_CRASH_DUMP_SIZE_CMD);
+
+ cdw12 = (WDC_NVME_CRASH_DUMP_SUBCMD << WDC_NVME_SUBCMD_SHIFT) |
+ WDC_NVME_CRASH_DUMP_CMD;
+
+ cdw12_clear = ((WDC_NVME_CLEAR_CRASH_DUMP_SUBCMD << WDC_NVME_SUBCMD_SHIFT) |
+ WDC_NVME_CLEAR_CRASH_DUMP_CMD);
+ }
+
+ ret = wdc_dump_length(dev_fd(dev),
+ opcode,
+ cdw10_size,
+ cdw12_size,
+ &crash_dump_length);
+
+ if (ret == -1) {
+ if (type == WDC_NVME_PFAIL_DUMP_TYPE)
+ fprintf(stderr, "INFO: WDC: Pfail dump get size failed\n");
+ else
+ fprintf(stderr, "INFO: WDC: Crash dump get size failed\n");
+
+ return -1;
+ }
+
+ if (!crash_dump_length) {
+ if (type == WDC_NVME_PFAIL_DUMP_TYPE)
+ fprintf(stderr, "INFO: WDC: Pfail dump is empty\n");
+ else
+ fprintf(stderr, "INFO: WDC: Crash dump is empty\n");
+ } else {
+ ret = wdc_do_dump(dev,
+ opcode,
+ crash_dump_length,
+ cdw12,
+ file,
+ crash_dump_length);
+
+ if (!ret)
+ ret = wdc_do_clear_dump(dev, WDC_NVME_CLEAR_DUMP_OPCODE,
+ cdw12_clear);
+ }
+ return ret;
+}
+
+static int wdc_crash_dump(struct nvme_dev *dev, char *file, int type)
+{
+ char f[PATH_MAX] = {0};
+ const char *dump_type;
+ int ret;
+
+ if (file)
+ strncpy(f, file, PATH_MAX - 1);
+
+ if (type == WDC_NVME_PFAIL_DUMP_TYPE)
+ dump_type = "_pfail_dump";
+ else
+ dump_type = "_crash_dump";
+
+ ret = wdc_get_serial_name(dev, f, PATH_MAX, dump_type);
+ if (ret)
+ fprintf(stderr, "ERROR: WDC: failed to generate file name\n");
+ else
+ ret = wdc_do_crash_dump(dev, f, type);
+ return ret;
+}
+
+static int wdc_do_drive_log(struct nvme_dev *dev, char *file)
+{
+ int ret;
+ __u8 *drive_log_data;
+ __u32 drive_log_length;
+ struct nvme_passthru_cmd admin_cmd;
+
+ ret = wdc_dump_length(dev_fd(dev), WDC_NVME_DRIVE_LOG_SIZE_OPCODE,
+ WDC_NVME_DRIVE_LOG_SIZE_NDT,
+ (WDC_NVME_DRIVE_LOG_SIZE_SUBCMD <<
+ WDC_NVME_SUBCMD_SHIFT | WDC_NVME_DRIVE_LOG_SIZE_CMD),
+ &drive_log_length);
+ if (ret == -1)
+ return -1;
+
+ drive_log_data = (__u8 *)malloc(sizeof(__u8) * drive_log_length);
+ if (!drive_log_data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+
+ memset(drive_log_data, 0, sizeof(__u8) * drive_log_length);
+ memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd));
+ admin_cmd.opcode = WDC_NVME_DRIVE_LOG_OPCODE;
+ admin_cmd.addr = (__u64)(uintptr_t)drive_log_data;
+ admin_cmd.data_len = drive_log_length;
+ admin_cmd.cdw10 = drive_log_length;
+ admin_cmd.cdw12 = ((WDC_NVME_DRIVE_LOG_SUBCMD <<
+ WDC_NVME_SUBCMD_SHIFT) | WDC_NVME_DRIVE_LOG_SIZE_CMD);
+
+ ret = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd, NULL);
+ nvme_show_status(ret);
+ if (!ret)
+ ret = wdc_create_log_file(file, drive_log_data, drive_log_length);
+ free(drive_log_data);
+ return ret;
+}
+
+static int wdc_drive_log(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Capture Drive Log.";
+ const char *file = "Output file pathname.";
+ char f[PATH_MAX] = {0};
+ struct nvme_dev *dev;
+ int ret;
+ nvme_root_t r;
+ __u64 capabilities = 0;
+ struct config {
+ char *file;
+ };
+
+ struct config cfg = {
+ .file = NULL
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FILE("output-file", 'o', &cfg.file, file),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+
+ if (!wdc_check_device(r, dev)) {
+ nvme_free_tree(r);
+ dev_close(dev);
+ return -1;
+ }
+ capabilities = wdc_get_drive_capabilities(r, dev);
+
+ if (!(capabilities & WDC_DRIVE_CAP_DRIVE_LOG)) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ } else {
+ if (cfg.file)
+ strncpy(f, cfg.file, PATH_MAX - 1);
+ ret = wdc_get_serial_name(dev, f, PATH_MAX, "drive_log");
+ if (ret)
+ fprintf(stderr, "ERROR: WDC: failed to generate file name\n");
+ else
+ ret = wdc_do_drive_log(dev, f);
+ }
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_get_crash_dump(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Get Crash Dump.";
+ const char *file = "Output file pathname.";
+ __u64 capabilities = 0;
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ int ret;
+
+ struct config {
+ char *file;
+ };
+
+ struct config cfg = {
+ .file = NULL,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FILE("output-file", 'o', &cfg.file, file),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+
+ if (!wdc_check_device(r, dev)) {
+ nvme_free_tree(r);
+ dev_close(dev);
+ return -1;
+
+ }
+
+ capabilities = wdc_get_drive_capabilities(r, dev);
+
+ if (!(capabilities & WDC_DRIVE_CAP_CRASH_DUMP)) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ } else {
+ ret = wdc_crash_dump(dev, cfg.file, WDC_NVME_CRASH_DUMP_TYPE);
+ if (ret)
+ fprintf(stderr, "ERROR: WDC: failed to read crash dump\n");
+ }
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_get_pfail_dump(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ char *desc = "Get Pfail Crash Dump.";
+ char *file = "Output file pathname.";
+ __u64 capabilities = 0;
+ struct nvme_dev *dev;
+ struct config {
+ char *file;
+ };
+ nvme_root_t r;
+ int ret;
+
+ struct config cfg = {
+ .file = NULL,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FILE("output-file", 'o', &cfg.file, file),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+
+ if (!wdc_check_device(r, dev)) {
+ nvme_free_tree(r);
+ dev_close(dev);
+ return -1;
+ }
+
+ capabilities = wdc_get_drive_capabilities(r, dev);
+ if (!(capabilities & WDC_DRIVE_CAP_PFAIL_DUMP)) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ } else {
+ ret = wdc_crash_dump(dev, cfg.file, WDC_NVME_PFAIL_DUMP_TYPE);
+ if (ret)
+ fprintf(stderr, "ERROR: WDC: failed to read pfail crash dump\n");
+ }
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static void wdc_do_id_ctrl(__u8 *vs, struct json_object *root)
+{
+ char vsn[24] = {0};
+ int base = 3072;
+ int vsn_start = 3081;
+
+ memcpy(vsn, &vs[vsn_start - base], sizeof(vsn));
+ if (root)
+ json_object_add_value_string(root, "wdc vsn", strlen(vsn) > 1 ? vsn : "NULL");
+ else
+ printf("wdc vsn: %s\n", strlen(vsn) > 1 ? vsn : "NULL");
+}
+
+static int wdc_id_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return __id_ctrl(argc, argv, cmd, plugin, wdc_do_id_ctrl);
+}
+
+static const char *wdc_purge_mon_status_to_string(__u32 status)
+{
+ const char *str;
+
+ switch (status) {
+ case WDC_NVME_PURGE_STATE_IDLE:
+ str = "Purge State Idle.";
+ break;
+ case WDC_NVME_PURGE_STATE_DONE:
+ str = "Purge State Done.";
+ break;
+ case WDC_NVME_PURGE_STATE_BUSY:
+ str = "Purge State Busy.";
+ break;
+ case WDC_NVME_PURGE_STATE_REQ_PWR_CYC:
+ str = "Purge Operation resulted in an error that requires power cycle.";
+ break;
+ case WDC_NVME_PURGE_STATE_PWR_CYC_PURGE:
+ str = "The previous purge operation was interrupted by a power cycle\n"
+ "or reset interruption. Other commands may be rejected until\n"
+ "Purge Execute is issued and completed.";
+ break;
+ default:
+ str = "Unknown.";
+ }
+ return str;
+}
+
+static int wdc_purge(int argc, char **argv,
+ struct command *command, struct plugin *plugin)
+{
+ const char *desc = "Send a Purge command.";
+ struct nvme_passthru_cmd admin_cmd;
+ __u64 capabilities = 0;
+ struct nvme_dev *dev;
+ char *err_str;
+ nvme_root_t r;
+ int ret;
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+
+ if (!wdc_check_device(r, dev)) {
+ nvme_free_tree(r);
+ dev_close(dev);
+ return -1;
+ }
+
+ capabilities = wdc_get_drive_capabilities(r, dev);
+ if (!(capabilities & WDC_DRIVE_CAP_PURGE)) {
+ ret = -1;
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ } else {
+ err_str = "";
+ memset(&admin_cmd, 0, sizeof(admin_cmd));
+ admin_cmd.opcode = WDC_NVME_PURGE_CMD_OPCODE;
+
+ ret = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd,
+ NULL);
+ if (ret > 0) {
+ switch (ret) {
+ case WDC_NVME_PURGE_CMD_SEQ_ERR:
+ err_str = "ERROR: WDC: Cannot execute purge, Purge operation is in progress.\n";
+ break;
+ case WDC_NVME_PURGE_INT_DEV_ERR:
+ err_str = "ERROR: WDC: Internal Device Error.\n";
+ break;
+ default:
+ err_str = "ERROR: WDC\n";
+ }
+ }
+
+ fprintf(stderr, "%s", err_str);
+ nvme_show_status(ret);
+ }
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_purge_monitor(int argc, char **argv,
+ struct command *command, struct plugin *plugin)
+{
+ const char *desc = "Send a Purge Monitor command.";
+ __u8 output[WDC_NVME_PURGE_MONITOR_DATA_LEN];
+ double progress_percent;
+ struct nvme_passthru_cmd admin_cmd;
+ struct wdc_nvme_purge_monitor_data *mon;
+ struct nvme_dev *dev;
+ __u64 capabilities;
+ nvme_root_t r;
+ int ret;
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+ if (!wdc_check_device(r, dev)) {
+ nvme_free_tree(r);
+ dev_close(dev);
+ return -1;
+ }
+
+ capabilities = wdc_get_drive_capabilities(r, dev);
+ if (!(capabilities & WDC_DRIVE_CAP_PURGE)) {
+ ret = -1;
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ } else {
+ memset(output, 0, sizeof(output));
+ memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd));
+ admin_cmd.opcode = WDC_NVME_PURGE_MONITOR_OPCODE;
+ admin_cmd.addr = (__u64)(uintptr_t)output;
+ admin_cmd.data_len = WDC_NVME_PURGE_MONITOR_DATA_LEN;
+ admin_cmd.cdw10 = WDC_NVME_PURGE_MONITOR_CMD_CDW10;
+ admin_cmd.timeout_ms = WDC_NVME_PURGE_MONITOR_TIMEOUT;
+
+ ret = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd,
+ NULL);
+ if (!ret) {
+ mon = (struct wdc_nvme_purge_monitor_data *) output;
+ printf("Purge state = 0x%0x\n", admin_cmd.result);
+ printf("%s\n", wdc_purge_mon_status_to_string(admin_cmd.result));
+ if (admin_cmd.result == WDC_NVME_PURGE_STATE_BUSY) {
+ progress_percent =
+ ((double)le32_to_cpu(mon->entire_progress_current) * 100) /
+ le32_to_cpu(mon->entire_progress_total);
+ printf("Purge Progress = %f%%\n", progress_percent);
+ }
+ }
+
+ nvme_show_status(ret);
+ }
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static void wdc_print_log_normal(struct wdc_ssd_perf_stats *perf)
+{
+ printf(" C1 Log Page Performance Statistics :-\n");
+ printf(" Host Read Commands %20"PRIu64"\n",
+ le64_to_cpu(perf->hr_cmds));
+ printf(" Host Read Blocks %20"PRIu64"\n",
+ le64_to_cpu(perf->hr_blks));
+ printf(" Average Read Size %20lf\n",
+ safe_div_fp((le64_to_cpu(perf->hr_blks)), (le64_to_cpu(perf->hr_cmds))));
+ printf(" Host Read Cache Hit Commands %20"PRIu64"\n",
+ le64_to_cpu(perf->hr_ch_cmds));
+ printf(" Host Read Cache Hit_Percentage %20"PRIu64"%%\n",
+ (uint64_t) calc_percent(le64_to_cpu(perf->hr_ch_cmds), le64_to_cpu(perf->hr_cmds)));
+ printf(" Host Read Cache Hit Blocks %20"PRIu64"\n",
+ le64_to_cpu(perf->hr_ch_blks));
+ printf(" Average Read Cache Hit Size %20f\n",
+ safe_div_fp((le64_to_cpu(perf->hr_ch_blks)), (le64_to_cpu(perf->hr_ch_cmds))));
+ printf(" Host Read Commands Stalled %20"PRIu64"\n",
+ le64_to_cpu(perf->hr_st_cmds));
+ printf(" Host Read Commands Stalled Percentage %20"PRIu64"%%\n",
+ (uint64_t)calc_percent((le64_to_cpu(perf->hr_st_cmds)), le64_to_cpu(perf->hr_cmds)));
+ printf(" Host Write Commands %20"PRIu64"\n",
+ le64_to_cpu(perf->hw_cmds));
+ printf(" Host Write Blocks %20"PRIu64"\n",
+ le64_to_cpu(perf->hw_blks));
+ printf(" Average Write Size %20f\n",
+ safe_div_fp((le64_to_cpu(perf->hw_blks)), (le64_to_cpu(perf->hw_cmds))));
+ printf(" Host Write Odd Start Commands %20"PRIu64"\n",
+ le64_to_cpu(perf->hw_os_cmds));
+ printf(" Host Write Odd Start Commands Percentage %20"PRIu64"%%\n",
+ (uint64_t)calc_percent((le64_to_cpu(perf->hw_os_cmds)), (le64_to_cpu(perf->hw_cmds))));
+ printf(" Host Write Odd End Commands %20"PRIu64"\n",
+ le64_to_cpu(perf->hw_oe_cmds));
+ printf(" Host Write Odd End Commands Percentage %20"PRIu64"%%\n",
+ (uint64_t)calc_percent((le64_to_cpu(perf->hw_oe_cmds)), (le64_to_cpu((perf->hw_cmds)))));
+ printf(" Host Write Commands Stalled %20"PRIu64"\n",
+ le64_to_cpu(perf->hw_st_cmds));
+ printf(" Host Write Commands Stalled Percentage %20"PRIu64"%%\n",
+ (uint64_t)calc_percent((le64_to_cpu(perf->hw_st_cmds)), (le64_to_cpu(perf->hw_cmds))));
+ printf(" NAND Read Commands %20"PRIu64"\n",
+ le64_to_cpu(perf->nr_cmds));
+ printf(" NAND Read Blocks Commands %20"PRIu64"\n",
+ le64_to_cpu(perf->nr_blks));
+ printf(" Average NAND Read Size %20f\n",
+ safe_div_fp((le64_to_cpu(perf->nr_blks)), (le64_to_cpu((perf->nr_cmds)))));
+ printf(" Nand Write Commands %20"PRIu64"\n",
+ le64_to_cpu(perf->nw_cmds));
+ printf(" NAND Write Blocks %20"PRIu64"\n",
+ le64_to_cpu(perf->nw_blks));
+ printf(" Average NAND Write Size %20f\n",
+ safe_div_fp((le64_to_cpu(perf->nw_blks)), (le64_to_cpu(perf->nw_cmds))));
+ printf(" NAND Read Before Write %20"PRIu64"\n",
+ le64_to_cpu(perf->nrbw));
+}
+
+static void wdc_print_log_json(struct wdc_ssd_perf_stats *perf)
+{
+ struct json_object *root = json_create_object();
+
+ json_object_add_value_int(root, "Host Read Commands", le64_to_cpu(perf->hr_cmds));
+ json_object_add_value_int(root, "Host Read Blocks", le64_to_cpu(perf->hr_blks));
+ json_object_add_value_int(root, "Average Read Size",
+ safe_div_fp((le64_to_cpu(perf->hr_blks)), (le64_to_cpu(perf->hr_cmds))));
+ json_object_add_value_int(root, "Host Read Cache Hit Commands",
+ le64_to_cpu(perf->hr_ch_cmds));
+ json_object_add_value_int(root, "Host Read Cache Hit Percentage",
+ (uint64_t) calc_percent(le64_to_cpu(perf->hr_ch_cmds), le64_to_cpu(perf->hr_cmds)));
+ json_object_add_value_int(root, "Host Read Cache Hit Blocks",
+ le64_to_cpu(perf->hr_ch_blks));
+ json_object_add_value_int(root, "Average Read Cache Hit Size",
+ safe_div_fp((le64_to_cpu(perf->hr_ch_blks)), (le64_to_cpu(perf->hr_ch_cmds))));
+ json_object_add_value_int(root, "Host Read Commands Stalled",
+ le64_to_cpu(perf->hr_st_cmds));
+ json_object_add_value_int(root, "Host Read Commands Stalled Percentage",
+ (uint64_t)calc_percent((le64_to_cpu(perf->hr_st_cmds)), le64_to_cpu(perf->hr_cmds)));
+ json_object_add_value_int(root, "Host Write Commands",
+ le64_to_cpu(perf->hw_cmds));
+ json_object_add_value_int(root, "Host Write Blocks",
+ le64_to_cpu(perf->hw_blks));
+ json_object_add_value_int(root, "Average Write Size",
+ safe_div_fp((le64_to_cpu(perf->hw_blks)), (le64_to_cpu(perf->hw_cmds))));
+ json_object_add_value_int(root, "Host Write Odd Start Commands",
+ le64_to_cpu(perf->hw_os_cmds));
+ json_object_add_value_int(root, "Host Write Odd Start Commands Percentage",
+ (uint64_t)calc_percent((le64_to_cpu(perf->hw_os_cmds)), (le64_to_cpu(perf->hw_cmds))));
+ json_object_add_value_int(root, "Host Write Odd End Commands",
+ le64_to_cpu(perf->hw_oe_cmds));
+ json_object_add_value_int(root, "Host Write Odd End Commands Percentage",
+ (uint64_t)calc_percent((le64_to_cpu(perf->hw_oe_cmds)), (le64_to_cpu((perf->hw_cmds)))));
+ json_object_add_value_int(root, "Host Write Commands Stalled",
+ le64_to_cpu(perf->hw_st_cmds));
+ json_object_add_value_int(root, "Host Write Commands Stalled Percentage",
+ (uint64_t)calc_percent((le64_to_cpu(perf->hw_st_cmds)), (le64_to_cpu(perf->hw_cmds))));
+ json_object_add_value_int(root, "NAND Read Commands",
+ le64_to_cpu(perf->nr_cmds));
+ json_object_add_value_int(root, "NAND Read Blocks Commands",
+ le64_to_cpu(perf->nr_blks));
+ json_object_add_value_int(root, "Average NAND Read Size",
+ safe_div_fp((le64_to_cpu(perf->nr_blks)), (le64_to_cpu((perf->nr_cmds)))));
+ json_object_add_value_int(root, "Nand Write Commands",
+ le64_to_cpu(perf->nw_cmds));
+ json_object_add_value_int(root, "NAND Write Blocks",
+ le64_to_cpu(perf->nw_blks));
+ json_object_add_value_int(root, "Average NAND Write Size",
+ safe_div_fp((le64_to_cpu(perf->nw_blks)), (le64_to_cpu(perf->nw_cmds))));
+ json_object_add_value_int(root, "NAND Read Before Written",
+ le64_to_cpu(perf->nrbw));
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+}
+
+static int wdc_print_log(struct wdc_ssd_perf_stats *perf, int fmt)
+{
+ if (!perf) {
+ fprintf(stderr, "ERROR: WDC: Invalid buffer to read perf stats\n");
+ return -1;
+ }
+ switch (fmt) {
+ case NORMAL:
+ wdc_print_log_normal(perf);
+ break;
+ case JSON:
+ wdc_print_log_json(perf);
+ break;
+ }
+ return 0;
+}
+
+static int wdc_print_latency_monitor_log_normal(struct nvme_dev *dev,
+ struct wdc_ssd_latency_monitor_log *log_data)
+{
+ printf("Latency Monitor/C3 Log Page Data\n");
+ printf(" Controller : %s\n", dev->name);
+ int err = -1, i, j;
+ struct nvme_id_ctrl ctrl;
+ char ts_buf[128];
+
+ err = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (!err) {
+ printf(" Serial Number: %-.*s\n", (int)sizeof(ctrl.sn), ctrl.sn);
+ } else {
+ fprintf(stderr, "ERROR: WDC: latency monitor read id ctrl failure, err = %d\n", err);
+ return err;
+ }
+
+ printf(" Feature Status 0x%x\n", log_data->feature_status);
+ printf(" Active Bucket Timer %d min\n", 5*le16_to_cpu(log_data->active_bucket_timer));
+ printf(" Active Bucket Timer Threshold %d min\n", 5*le16_to_cpu(log_data->active_bucket_timer_threshold));
+ printf(" Active Threshold A %d ms\n", 5*(le16_to_cpu(log_data->active_threshold_a+1)));
+ printf(" Active Threshold B %d ms\n", 5*(le16_to_cpu(log_data->active_threshold_b+1)));
+ printf(" Active Threshold C %d ms\n", 5*(le16_to_cpu(log_data->active_threshold_c+1)));
+ printf(" Active Threshold D %d ms\n", 5*(le16_to_cpu(log_data->active_threshold_d+1)));
+ printf(" Active Latency Config 0x%x\n", le16_to_cpu(log_data->active_latency_config));
+ printf(" Active Latency Minimum Window %d ms\n", 100*log_data->active_latency_min_window);
+ printf(" Active Latency Stamp Units %d\n", le16_to_cpu(log_data->active_latency_stamp_units));
+ printf(" Static Latency Stamp Units %d\n", le16_to_cpu(log_data->static_latency_stamp_units));
+ printf(" Debug Log Trigger Enable %d\n", le16_to_cpu(log_data->debug_log_trigger_enable));
+
+ printf(" Read Write Deallocate/Trim\n");
+ for (i = 0; i <= 3; i++)
+ printf(" Active Bucket Counter: Bucket %d %27d %27d %27d\n",
+ i, le32_to_cpu(log_data->active_bucket_counter[i][LATENCY_LOG_BUCKET_READ]),
+ le32_to_cpu(log_data->active_bucket_counter[i][LATENCY_LOG_BUCKET_WRITE]),
+ le32_to_cpu(log_data->active_bucket_counter[i][LATENCY_LOG_BUCKET_TRIM]));
+
+ for (i = 3; i >= 0; i--)
+ printf(" Active Measured Latency: Bucket %d %27d ms %27d ms %27d ms\n",
+ 3-i, le16_to_cpu(log_data->active_measured_latency[i][LATENCY_LOG_MEASURED_LAT_READ]),
+ le16_to_cpu(log_data->active_measured_latency[i][LATENCY_LOG_MEASURED_LAT_WRITE]),
+ le16_to_cpu(log_data->active_measured_latency[i][LATENCY_LOG_MEASURED_LAT_TRIM]));
+
+ for (i = 3; i >= 0; i--) {
+ printf(" Active Latency Time Stamp: Bucket %d ", 3-i);
+ for (j = 2; j >= 0; j--) {
+ if (le64_to_cpu(log_data->active_latency_timestamp[i][j]) == -1) {
+ printf(" N/A ");
+ } else {
+ convert_ts(le64_to_cpu(log_data->active_latency_timestamp[i][j]), ts_buf);
+ printf("%s ", ts_buf);
+ }
+ }
+ printf("\n");
+ }
+
+ for (i = 0; i <= 3; i++)
+ printf(" Static Bucket Counter: Bucket %d %27d %27d %27d\n",
+ i, le32_to_cpu(log_data->static_bucket_counter[i][LATENCY_LOG_BUCKET_READ]),
+ le32_to_cpu(log_data->static_bucket_counter[i][LATENCY_LOG_BUCKET_WRITE]),
+ le32_to_cpu(log_data->static_bucket_counter[i][LATENCY_LOG_BUCKET_TRIM]));
+
+ for (i = 3; i >= 0; i--)
+ printf(" Static Measured Latency: Bucket %d %27d ms %27d ms %27d ms\n",
+ 3-i, le16_to_cpu(log_data->static_measured_latency[i][LATENCY_LOG_MEASURED_LAT_READ]),
+ le16_to_cpu(log_data->static_measured_latency[i][LATENCY_LOG_MEASURED_LAT_WRITE]),
+ le16_to_cpu(log_data->static_measured_latency[i][LATENCY_LOG_MEASURED_LAT_TRIM]));
+
+ for (i = 3; i >= 0; i--) {
+ printf(" Static Latency Time Stamp: Bucket %d ", 3-i);
+ for (j = 2; j >= 0; j--) {
+ if (le64_to_cpu(log_data->static_latency_timestamp[i][j]) == -1) {
+ printf(" N/A ");
+ } else {
+ convert_ts(le64_to_cpu(log_data->static_latency_timestamp[i][j]), ts_buf);
+ printf("%s ", ts_buf);
+ }
+ }
+ printf("\n");
+ }
+
+ return 0;
+}
+
+static void wdc_print_latency_monitor_log_json(struct wdc_ssd_latency_monitor_log *log_data)
+{
+ int i, j;
+ char buf[128];
+ char *operation[3] = {"Read", "Write", "Trim"};
+ struct json_object *root = json_create_object();
+
+ json_object_add_value_int(root, "Feature Status", log_data->feature_status);
+ json_object_add_value_int(root, "Active Bucket Timer", 5*le16_to_cpu(log_data->active_bucket_timer));
+ json_object_add_value_int(root, "Active Bucket Timer Threshold", 5*le16_to_cpu(log_data->active_bucket_timer_threshold));
+ json_object_add_value_int(root, "Active Threshold A", 5*le16_to_cpu(log_data->active_threshold_a+1));
+ json_object_add_value_int(root, "Active Threshold B", 5*le16_to_cpu(log_data->active_threshold_b+1));
+ json_object_add_value_int(root, "Active Threshold C", 5*le16_to_cpu(log_data->active_threshold_c+1));
+ json_object_add_value_int(root, "Active Threshold D", 5*le16_to_cpu(log_data->active_threshold_d+1));
+ json_object_add_value_int(root, "Active Latency Config", le16_to_cpu(log_data->active_latency_config));
+ json_object_add_value_int(root, "Active Lantency Minimum Window", 100*log_data->active_latency_min_window);
+ json_object_add_value_int(root, "Active Latency Stamp Units", le16_to_cpu(log_data->active_latency_stamp_units));
+ json_object_add_value_int(root, "Static Latency Stamp Units", le16_to_cpu(log_data->static_latency_stamp_units));
+ json_object_add_value_int(root, "Debug Log Trigger Enable", le16_to_cpu(log_data->debug_log_trigger_enable));
+
+ for (i = 0; i <= 3; i++) {
+ for (j = 2; j >= 0; j--) {
+ sprintf(buf, "Active Bucket Counter: Bucket %d %s", i, operation[2-j]);
+ json_object_add_value_int(root, buf, le32_to_cpu(log_data->active_bucket_counter[i][j+1]));
+ }
+ }
+ for (i = 3; i >= 0; i--) {
+ for (j = 2; j >= 0; j--) {
+ sprintf(buf, "Active Measured Latency: Bucket %d %s", 3-i, operation[2-j]);
+ json_object_add_value_int(root, buf, le16_to_cpu(log_data->active_measured_latency[i][j]));
+ }
+ }
+ for (i = 3; i >= 0; i--) {
+ for (j = 2; j >= 0; j--) {
+ sprintf(buf, "Active Latency Time Stamp: Bucket %d %s", 3-i, operation[2-j]);
+ json_object_add_value_int(root, buf, le64_to_cpu(log_data->active_latency_timestamp[i][j]));
+ }
+ }
+ for (i = 0; i <= 3; i++) {
+ for (j = 2; j >= 0; j--) {
+ sprintf(buf, "Static Bucket Counter: Bucket %d %s", i, operation[2-j]);
+ json_object_add_value_int(root, buf, le32_to_cpu(log_data->static_bucket_counter[i][j+1]));
+ }
+ }
+ for (i = 3; i >= 0; i--) {
+ for (j = 2; j >= 0; j--) {
+ sprintf(buf, "Static Measured Latency: Bucket %d %s", 3-i, operation[2-j]);
+ json_object_add_value_int(root, buf, le16_to_cpu(log_data->static_measured_latency[i][j]));
+ }
+ }
+ for (i = 3; i >= 0; i--) {
+ for (j = 2; j >= 0; j--) {
+ sprintf(buf, "Static Latency Time Stamp: Bucket %d %s", 3-i, operation[2-j]);
+ json_object_add_value_int(root, buf, le64_to_cpu(log_data->static_latency_timestamp[i][j]));
+ }
+ }
+
+ json_print_object(root, NULL);
+ printf("\n");
+
+ json_free_object(root);
+}
+
+static void wdc_print_error_rec_log_normal(struct wdc_ocp_c1_error_recovery_log *log_data)
+{
+ int j;
+
+ printf("Error Recovery/C1 Log Page Data\n");
+
+ printf(" Panic Reset Wait Time : 0x%x\n", le16_to_cpu(log_data->panic_reset_wait_time));
+ printf(" Panic Reset Action : 0x%x\n", log_data->panic_reset_action);
+ printf(" Device Recovery Action 1 : 0x%x\n", log_data->dev_recovery_action1);
+ printf(" Panic ID : 0x%" PRIu64 "\n", le64_to_cpu(log_data->panic_id));
+ printf(" Device Capabilities : 0x%x\n", le32_to_cpu(log_data->dev_capabilities));
+ printf(" Vendor Specific Recovery Opcode : 0x%x\n", log_data->vs_recovery_opc);
+ printf(" Vendor Specific Command CDW12 : 0x%x\n", le32_to_cpu(log_data->vs_cmd_cdw12));
+ printf(" Vendor Specific Command CDW13 : 0x%x\n", le32_to_cpu(log_data->vs_cmd_cdw13));
+ if (le16_to_cpu(log_data->log_page_version) == WDC_ERROR_REC_LOG_VERSION2) {
+ printf(" Vendor Specific Command Timeout : 0x%x\n", log_data->vs_cmd_to);
+ printf(" Device Recovery Action 2 : 0x%x\n", log_data->dev_recovery_action2);
+ printf(" Device Recovery Action 2 Timeout : 0x%x\n", log_data->dev_recovery_action2_to);
+ }
+ printf(" Log Page Version : 0x%x\n", le16_to_cpu(log_data->log_page_version));
+ printf(" Log page GUID : 0x");
+ for (j = 0; j < WDC_OCP_C1_GUID_LENGTH; j++)
+ printf("%x", log_data->log_page_guid[j]);
+ printf("\n");
+}
+
+static void wdc_print_error_rec_log_json(struct wdc_ocp_c1_error_recovery_log *log_data)
+{
+ struct json_object *root = json_create_object();
+
+ json_object_add_value_int(root, "Panic Reset Wait Time", le16_to_cpu(log_data->panic_reset_wait_time));
+ json_object_add_value_int(root, "Panic Reset Action", log_data->panic_reset_wait_time);
+ json_object_add_value_int(root, "Device Recovery Action 1", log_data->dev_recovery_action1);
+ json_object_add_value_int(root, "Panic ID", le64_to_cpu(log_data->panic_id));
+ json_object_add_value_int(root, "Device Capabilities", le32_to_cpu(log_data->dev_capabilities));
+ json_object_add_value_int(root, "Vendor Specific Recovery Opcode", log_data->vs_recovery_opc);
+ json_object_add_value_int(root, "Vendor Specific Command CDW12", le32_to_cpu(log_data->vs_cmd_cdw12));
+ json_object_add_value_int(root, "Vendor Specific Command CDW13", le32_to_cpu(log_data->vs_cmd_cdw13));
+ if (le16_to_cpu(log_data->log_page_version) == WDC_ERROR_REC_LOG_VERSION2) {
+ json_object_add_value_int(root, "Vendor Specific Command Timeout", log_data->vs_cmd_to);
+ json_object_add_value_int(root, "Device Recovery Action 2", log_data->dev_recovery_action2);
+ json_object_add_value_int(root, "Device Recovery Action 2 Timeout", log_data->dev_recovery_action2_to);
+ }
+ json_object_add_value_int(root, "Log Page Version", le16_to_cpu(log_data->log_page_version));
+
+ char guid[40];
+
+ memset((void *)guid, 0, 40);
+ sprintf((char *)guid, "0x%"PRIx64"%"PRIx64"",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[8]),
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[0]));
+ json_object_add_value_string(root, "Log page GUID", guid);
+
+ json_print_object(root, NULL);
+ printf("\n");
+
+ json_free_object(root);
+}
+
+static void wdc_print_dev_cap_log_normal(struct wdc_ocp_C4_dev_cap_log *log_data)
+{
+ int j;
+
+ printf("Device Capabilities/C4 Log Page Data\n");
+
+ printf(" Number PCIE Ports : 0x%x\n", le16_to_cpu(log_data->num_pcie_ports));
+ printf(" Number OOB Management Interfaces : 0x%x\n", le16_to_cpu(log_data->oob_mgmt_support));
+ printf(" Write Zeros Command Support : 0x%x\n", le16_to_cpu(log_data->wrt_zeros_support));
+ printf(" Sanitize Command Support : 0x%x\n", le16_to_cpu(log_data->sanitize_support));
+ printf(" DSM Command Support : 0x%x\n", le16_to_cpu(log_data->dsm_support));
+ printf(" Write Uncorr Command Support : 0x%x\n", le16_to_cpu(log_data->wrt_uncor_support));
+ printf(" Fused Command Support : 0x%x\n", le16_to_cpu(log_data->fused_support));
+ printf(" Minimum DSSD Power State : 0x%x\n", le16_to_cpu(log_data->min_dssd_ps));
+
+ for (j = 0; j < WDC_OCP_C4_NUM_PS_DESCR; j++)
+ printf(" DSSD Power State %d Descriptor : 0x%x\n", j, log_data->dssd_ps_descr[j]);
+
+ printf(" Log Page Version : 0x%x\n", le16_to_cpu(log_data->log_page_version));
+ printf(" Log page GUID : 0x");
+ for (j = 0; j < WDC_OCP_C4_GUID_LENGTH; j++)
+ printf("%x", log_data->log_page_guid[j]);
+ printf("\n");
+}
+
+static void wdc_print_dev_cap_log_json(struct wdc_ocp_C4_dev_cap_log *log_data)
+{
+ int j;
+ struct json_object *root = json_create_object();
+
+ json_object_add_value_int(root, "Number PCIE Ports", le16_to_cpu(log_data->num_pcie_ports));
+ json_object_add_value_int(root, "Number OOB Management Interfaces", le16_to_cpu(log_data->num_pcie_ports));
+ json_object_add_value_int(root, "Write Zeros Command Support", le16_to_cpu(log_data->num_pcie_ports));
+ json_object_add_value_int(root, "Sanitize Command Support", le16_to_cpu(log_data->num_pcie_ports));
+ json_object_add_value_int(root, "DSM Command Support", le16_to_cpu(log_data->num_pcie_ports));
+ json_object_add_value_int(root, "Write Uncorr Command Support", le16_to_cpu(log_data->num_pcie_ports));
+ json_object_add_value_int(root, "Fused Command Support", le16_to_cpu(log_data->num_pcie_ports));
+ json_object_add_value_int(root, "Minimum DSSD Power State", le16_to_cpu(log_data->num_pcie_ports));
+
+ char dssd_descr_str[40];
+
+ memset((void *)dssd_descr_str, 0, 40);
+ for (j = 0; j < WDC_OCP_C4_NUM_PS_DESCR; j++) {
+ sprintf((char *)dssd_descr_str, "DSSD Power State %d Descriptor", j);
+ json_object_add_value_int(root, dssd_descr_str, log_data->dssd_ps_descr[j]);
+ }
+
+ json_object_add_value_int(root, "Log Page Version", le16_to_cpu(log_data->log_page_version));
+ char guid[40];
+
+ memset((void *)guid, 0, 40);
+ sprintf((char *)guid, "0x%"PRIx64"%"PRIx64"",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[8]),
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[0]));
+ json_object_add_value_string(root, "Log page GUID", guid);
+
+ json_print_object(root, NULL);
+ printf("\n");
+
+ json_free_object(root);
+}
+
+static void wdc_print_unsupported_reqs_log_normal(struct wdc_ocp_C5_unsupported_reqs *log_data)
+{
+ int j;
+
+ printf("Unsupported Requirements/C5 Log Page Data\n");
+
+ printf(" Number Unsupported Req IDs : 0x%x\n",
+ le16_to_cpu(log_data->unsupported_count));
+
+ for (j = 0; j < le16_to_cpu(log_data->unsupported_count); j++)
+ printf(" Unsupported Requirement List %d : %s\n", j,
+ log_data->unsupported_req_list[j]);
+
+ printf(" Log Page Version : 0x%x\n", le16_to_cpu(log_data->log_page_version));
+ printf(" Log page GUID : 0x");
+ for (j = 0; j < WDC_OCP_C5_GUID_LENGTH; j++)
+ printf("%x", log_data->log_page_guid[j]);
+ printf("\n");
+}
+
+static void wdc_print_unsupported_reqs_log_json(struct wdc_ocp_C5_unsupported_reqs *log_data)
+{
+ int j;
+ struct json_object *root = json_create_object();
+
+ json_object_add_value_int(root, "Number Unsupported Req IDs", le16_to_cpu(log_data->unsupported_count));
+
+ char unsup_req_list_str[40];
+
+ memset((void *)unsup_req_list_str, 0, 40);
+ for (j = 0; j < le16_to_cpu(log_data->unsupported_count); j++) {
+ sprintf((char *)unsup_req_list_str, "Unsupported Requirement List %d", j);
+ json_object_add_value_string(root, unsup_req_list_str, (char *)log_data->unsupported_req_list[j]);
+ }
+
+ json_object_add_value_int(root, "Log Page Version",
+ le16_to_cpu(log_data->log_page_version));
+ char guid[40];
+
+ memset((void *)guid, 0, 40);
+ sprintf((char *)guid, "0x%"PRIx64"%"PRIx64"",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[8]),
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data->log_page_guid[0]));
+ json_object_add_value_string(root, "Log page GUID", guid);
+
+ json_print_object(root, NULL);
+ printf("\n");
+
+ json_free_object(root);
+}
+
+static void wdc_print_fb_ca_log_normal(struct wdc_ssd_ca_perf_stats *perf)
+{
+ uint64_t converted = 0;
+
+ printf(" CA Log Page Performance Statistics :-\n");
+ printf(" NAND Bytes Written %20"PRIu64 "%20"PRIu64"\n",
+ le64_to_cpu(perf->nand_bytes_wr_hi), le64_to_cpu(perf->nand_bytes_wr_lo));
+ printf(" NAND Bytes Read %20"PRIu64 "%20"PRIu64"\n",
+ le64_to_cpu(perf->nand_bytes_rd_hi), le64_to_cpu(perf->nand_bytes_rd_lo));
+
+ converted = le64_to_cpu(perf->nand_bad_block);
+ printf(" NAND Bad Block Count (Normalized) %20"PRIu64"\n",
+ converted & 0xFFFF);
+ printf(" NAND Bad Block Count (Raw) %20"PRIu64"\n",
+ converted >> 16);
+
+ printf(" Uncorrectable Read Count %20"PRIu64"\n",
+ le64_to_cpu(perf->uncorr_read_count));
+ printf(" Soft ECC Error Count %20"PRIu64"\n",
+ le64_to_cpu(perf->ecc_error_count));
+ printf(" SSD End to End Detected Correction Count %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->ssd_detect_count));
+ printf(" SSD End to End Corrected Correction Count %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->ssd_correct_count));
+ printf(" System Data Percent Used %20"PRIu32"%%\n",
+ perf->data_percent_used);
+ printf(" User Data Erase Counts Max %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->data_erase_max));
+ printf(" User Data Erase Counts Min %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->data_erase_min));
+ printf(" Refresh Count %20"PRIu64"\n",
+ le64_to_cpu(perf->refresh_count));
+
+ converted = le64_to_cpu(perf->program_fail);
+ printf(" Program Fail Count (Normalized) %20"PRIu64"\n",
+ converted & 0xFFFF);
+ printf(" Program Fail Count (Raw) %20"PRIu64"\n",
+ converted >> 16);
+
+ converted = le64_to_cpu(perf->user_erase_fail);
+ printf(" User Data Erase Fail Count (Normalized) %20"PRIu64"\n",
+ converted & 0xFFFF);
+ printf(" User Data Erase Fail Count (Raw) %20"PRIu64"\n",
+ converted >> 16);
+
+ converted = le64_to_cpu(perf->system_erase_fail);
+ printf(" System Area Erase Fail Count (Normalized) %20"PRIu64"\n",
+ converted & 0xFFFF);
+ printf(" System Area Erase Fail Count (Raw) %20"PRIu64"\n",
+ converted >> 16);
+
+ printf(" Thermal Throttling Status %20"PRIu8"\n",
+ perf->thermal_throttle_status);
+ printf(" Thermal Throttling Count %20"PRIu8"\n",
+ perf->thermal_throttle_count);
+ printf(" PCIe Correctable Error Count %20"PRIu64"\n",
+ le64_to_cpu(perf->pcie_corr_error));
+ printf(" Incomplete Shutdown Count %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->incomplete_shutdown_count));
+ printf(" Percent Free Blocks %20"PRIu32"%%\n",
+ perf->percent_free_blocks);
+}
+
+static void wdc_print_fb_ca_log_json(struct wdc_ssd_ca_perf_stats *perf)
+{
+ struct json_object *root = json_create_object();
+ uint64_t converted = 0;
+
+ json_object_add_value_int(root, "NAND Bytes Written Hi", le64_to_cpu(perf->nand_bytes_wr_hi));
+ json_object_add_value_int(root, "NAND Bytes Written Lo", le64_to_cpu(perf->nand_bytes_wr_lo));
+ json_object_add_value_int(root, "NAND Bytes Read Hi", le64_to_cpu(perf->nand_bytes_rd_hi));
+ json_object_add_value_int(root, "NAND Bytes Read Lo", le64_to_cpu(perf->nand_bytes_rd_lo));
+
+ converted = le64_to_cpu(perf->nand_bad_block);
+ json_object_add_value_int(root, "NAND Bad Block Count (Normalized)",
+ converted & 0xFFFF);
+ json_object_add_value_int(root, "NAND Bad Block Count (Raw)",
+ converted >> 16);
+
+ json_object_add_value_int(root, "Uncorrectable Read Count", le64_to_cpu(perf->uncorr_read_count));
+ json_object_add_value_int(root, "Soft ECC Error Count", le64_to_cpu(perf->ecc_error_count));
+ json_object_add_value_int(root, "SSD End to End Detected Correction Count",
+ le32_to_cpu(perf->ssd_detect_count));
+ json_object_add_value_int(root, "SSD End to End Corrected Correction Count",
+ le32_to_cpu(perf->ssd_correct_count));
+ json_object_add_value_int(root, "System Data Percent Used",
+ perf->data_percent_used);
+ json_object_add_value_int(root, "User Data Erase Counts Max",
+ le32_to_cpu(perf->data_erase_max));
+ json_object_add_value_int(root, "User Data Erase Counts Min",
+ le32_to_cpu(perf->data_erase_min));
+ json_object_add_value_int(root, "Refresh Count", le64_to_cpu(perf->refresh_count));
+
+ converted = le64_to_cpu(perf->program_fail);
+ json_object_add_value_int(root, "Program Fail Count (Normalized)",
+ converted & 0xFFFF);
+ json_object_add_value_int(root, "Program Fail Count (Raw)",
+ converted >> 16);
+
+ converted = le64_to_cpu(perf->user_erase_fail);
+ json_object_add_value_int(root, "User Data Erase Fail Count (Normalized)",
+ converted & 0xFFFF);
+ json_object_add_value_int(root, "User Data Erase Fail Count (Raw)",
+ converted >> 16);
+
+ converted = le64_to_cpu(perf->system_erase_fail);
+ json_object_add_value_int(root, "System Area Erase Fail Count (Normalized)",
+ converted & 0xFFFF);
+ json_object_add_value_int(root, "System Area Erase Fail Count (Raw)",
+ converted >> 16);
+
+ json_object_add_value_int(root, "Thermal Throttling Status",
+ perf->thermal_throttle_status);
+ json_object_add_value_int(root, "Thermal Throttling Count",
+ perf->thermal_throttle_count);
+ json_object_add_value_int(root, "PCIe Correctable Error", le64_to_cpu(perf->pcie_corr_error));
+ json_object_add_value_int(root, "Incomplete Shutdown Counte", le32_to_cpu(perf->incomplete_shutdown_count));
+ json_object_add_value_int(root, "Percent Free Blocks", perf->percent_free_blocks);
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+}
+
+static void wdc_print_bd_ca_log_normal(struct nvme_dev *dev, void *data)
+{
+ struct wdc_bd_ca_log_format *bd_data = (struct wdc_bd_ca_log_format *)data;
+ __u64 *raw;
+ __u16 *word_raw1, *word_raw2, *word_raw3;
+ __u32 *dword_raw;
+ __u8 *byte_raw;
+
+ if (bd_data->field_id == 0x00) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ printf("Additional Smart Log for NVME device:%s namespace-id:%x\n", dev->name,
+ WDC_DE_GLOBAL_NSID);
+ printf("key normalized raw\n");
+ printf("program_fail_count : %3"PRIu8"%% %"PRIu64"\n",
+ bd_data->normalized_value, le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x01) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ printf("erase_fail_count : %3"PRIu8"%% %"PRIu64"\n",
+ bd_data->normalized_value, le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x02) {
+ word_raw1 = (__u16 *)&bd_data->raw_value[1];
+ word_raw2 = (__u16 *)&bd_data->raw_value[3];
+ word_raw3 = (__u16 *)&bd_data->raw_value[5];
+ printf("wear_leveling : %3"PRIu8"%% min: %"PRIu16", max: %"PRIu16", avg: %"PRIu16"\n",
+ bd_data->normalized_value,
+ le16_to_cpu(*word_raw1),
+ le16_to_cpu(*word_raw2),
+ le16_to_cpu(*word_raw3));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x03) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ printf("end_to_end_error_detection_count: %3"PRIu8"%% %"PRIu64"\n",
+ bd_data->normalized_value, le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x04) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ printf("crc_error_count : %3"PRIu8"%% %"PRIu64"\n",
+ bd_data->normalized_value, le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x05) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ printf("timed_workload_media_wear : %3"PRIu8"%% %-.3f%%\n",
+ bd_data->normalized_value, safe_div_fp((*raw & 0x00FFFFFFFFFFFFFF), 1024.0));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x06) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ printf("timed_workload_host_reads : %3"PRIu8"%% %"PRIu64"%%\n",
+ bd_data->normalized_value, le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x07) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ printf("timed_workload_timer : %3"PRIu8"%% %"PRIu64"\n",
+ bd_data->normalized_value, le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x08) {
+ byte_raw = (__u8 *)&bd_data->raw_value[1];
+ dword_raw = (__u32 *)&bd_data->raw_value[2];
+ printf("thermal_throttle_status : %3"PRIu8"%% %"PRIu16"%%, cnt: %"PRIu16"\n",
+ bd_data->normalized_value, *byte_raw, le32_to_cpu(*dword_raw));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x09) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ printf("retry_buffer_overflow_count : %3"PRIu8"%% %"PRIu64"\n",
+ bd_data->normalized_value, le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x0A) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ printf("pll_lock_loss_count : %3"PRIu8"%% %"PRIu64"\n",
+ bd_data->normalized_value, le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x0B) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ printf("nand_bytes_written : %3"PRIu8"%% sectors: %.f\n",
+ bd_data->normalized_value, safe_div_fp((*raw & 0x00FFFFFFFFFFFFFF), 0xFFFF));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x0C) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ printf("host_bytes_written : %3"PRIu8"%% sectors: %.f\n",
+ bd_data->normalized_value, safe_div_fp((*raw & 0x00FFFFFFFFFFFFFF), 0xFFFF));
+ } else {
+ goto invalid_id;
+ }
+
+ goto done;
+
+invalid_id:
+ printf(" Invalid Field ID = %d\n", bd_data->field_id);
+
+done:
+ return;
+
+}
+
+static void wdc_print_bd_ca_log_json(void *data)
+{
+ struct wdc_bd_ca_log_format *bd_data = (struct wdc_bd_ca_log_format *)data;
+ __u64 *raw;
+ __u16 *word_raw;
+ __u32 *dword_raw;
+ __u8 *byte_raw;
+ struct json_object *root = json_create_object();
+
+ if (bd_data->field_id == 0x00) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ json_object_add_value_int(root, "program_fail_count normalized",
+ bd_data->normalized_value);
+ json_object_add_value_int(root, "program_fail_count raw",
+ le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x01) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ json_object_add_value_int(root, "erase_fail_count normalized",
+ bd_data->normalized_value);
+ json_object_add_value_int(root, "erase_fail_count raw",
+ le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x02) {
+ word_raw = (__u16 *)&bd_data->raw_value[1];
+ json_object_add_value_int(root, "wear_leveling normalized", bd_data->normalized_value);
+ json_object_add_value_int(root, "wear_leveling min", le16_to_cpu(*word_raw));
+ word_raw = (__u16 *)&bd_data->raw_value[3];
+ json_object_add_value_int(root, "wear_leveling max", le16_to_cpu(*word_raw));
+ word_raw = (__u16 *)&bd_data->raw_value[5];
+ json_object_add_value_int(root, "wear_leveling avg", le16_to_cpu(*word_raw));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x03) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ json_object_add_value_int(root, "end_to_end_error_detection_count normalized",
+ bd_data->normalized_value);
+ json_object_add_value_int(root, "end_to_end_error_detection_count raw",
+ le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x04) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ json_object_add_value_int(root, "crc_error_count normalized",
+ bd_data->normalized_value);
+ json_object_add_value_int(root, "crc_error_count raw",
+ le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x05) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ json_object_add_value_int(root, "timed_workload_media_wear normalized",
+ bd_data->normalized_value);
+ json_object_add_value_double(root, "timed_workload_media_wear raw",
+ safe_div_fp((*raw & 0x00FFFFFFFFFFFFFF), 1024.0));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x06) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ json_object_add_value_int(root, "timed_workload_host_reads normalized",
+ bd_data->normalized_value);
+ json_object_add_value_int(root, "timed_workload_host_reads raw",
+ le64_to_cpu(*raw & 0x00000000000000FF));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x07) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ json_object_add_value_int(root, "timed_workload_timer normalized",
+ bd_data->normalized_value);
+ json_object_add_value_int(root, "timed_workload_timer",
+ le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x08) {
+ byte_raw = (__u8 *)&bd_data->raw_value[1];
+ json_object_add_value_int(root, "thermal_throttle_status normalized",
+ bd_data->normalized_value);
+ json_object_add_value_int(root, "thermal_throttle_status", *byte_raw);
+ dword_raw = (__u32 *)&bd_data->raw_value[2];
+ json_object_add_value_int(root, "thermal_throttle_cnt", le32_to_cpu(*dword_raw));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x09) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ json_object_add_value_int(root, "retry_buffer_overflow_count normalized",
+ bd_data->normalized_value);
+ json_object_add_value_int(root, "retry_buffer_overflow_count raw",
+ le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x0A) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ json_object_add_value_int(root, "pll_lock_loss_count normalized",
+ bd_data->normalized_value);
+ json_object_add_value_int(root, "pll_lock_loss_count raw",
+ le64_to_cpu(*raw & 0x00FFFFFFFFFFFFFF));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x0B) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ json_object_add_value_int(root, "nand_bytes_written normalized",
+ bd_data->normalized_value);
+ json_object_add_value_double(root, "nand_bytes_written raw",
+ safe_div_fp((*raw & 0x00FFFFFFFFFFFFFF), 0xFFFF));
+ } else {
+ goto invalid_id;
+ }
+ bd_data++;
+ if (bd_data->field_id == 0x0C) {
+ raw = (__u64 *)&bd_data->raw_value[0];
+ json_object_add_value_int(root, "host_bytes_written normalized",
+ bd_data->normalized_value);
+ json_object_add_value_double(root, "host_bytes_written raw",
+ safe_div_fp((*raw & 0x00FFFFFFFFFFFFFF), 0xFFFF));
+ } else {
+ goto invalid_id;
+ }
+
+ goto done;
+
+invalid_id:
+ printf(" Invalid Field ID = %d\n", bd_data->field_id);
+
+done:
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+
+ return;
+
+}
+
+static void wdc_print_d0_log_normal(struct wdc_ssd_d0_smart_log *perf)
+{
+ printf(" D0 Smart Log Page Statistics :-\n");
+ printf(" Lifetime Reallocated Erase Block Count %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->lifetime_realloc_erase_block_count));
+ printf(" Lifetime Power on Hours %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->lifetime_power_on_hours));
+ printf(" Lifetime UECC Count %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->lifetime_uecc_count));
+ printf(" Lifetime Write Amplification Factor %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->lifetime_wrt_amp_factor));
+ printf(" Trailing Hour Write Amplification Factor %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->trailing_hr_wrt_amp_factor));
+ printf(" Reserve Erase Block Count %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->reserve_erase_block_count));
+ printf(" Lifetime Program Fail Count %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->lifetime_program_fail_count));
+ printf(" Lifetime Block Erase Fail Count %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->lifetime_block_erase_fail_count));
+ printf(" Lifetime Die Failure Count %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->lifetime_die_failure_count));
+ printf(" Lifetime Link Rate Downgrade Count %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->lifetime_link_rate_downgrade_count));
+ printf(" Lifetime Clean Shutdown Count on Power Loss %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->lifetime_clean_shutdown_count));
+ printf(" Lifetime Unclean Shutdowns on Power Loss %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->lifetime_unclean_shutdown_count));
+ printf(" Current Temperature %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->current_temp));
+ printf(" Max Recorded Temperature %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->max_recorded_temp));
+ printf(" Lifetime Retired Block Count %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->lifetime_retired_block_count));
+ printf(" Lifetime Read Disturb Reallocation Events %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->lifetime_read_disturb_realloc_events));
+ printf(" Lifetime NAND Writes %20"PRIu64"\n",
+ le64_to_cpu(perf->lifetime_nand_writes));
+ printf(" Capacitor Health %20"PRIu32"%%\n",
+ (uint32_t)le32_to_cpu(perf->capacitor_health));
+ printf(" Lifetime User Writes %20"PRIu64"\n",
+ le64_to_cpu(perf->lifetime_user_writes));
+ printf(" Lifetime User Reads %20"PRIu64"\n",
+ le64_to_cpu(perf->lifetime_user_reads));
+ printf(" Lifetime Thermal Throttle Activations %20"PRIu32"\n",
+ (uint32_t)le32_to_cpu(perf->lifetime_thermal_throttle_act));
+ printf(" Percentage of P/E Cycles Remaining %20"PRIu32"%%\n",
+ (uint32_t)le32_to_cpu(perf->percentage_pe_cycles_remaining));
+}
+
+static void wdc_print_d0_log_json(struct wdc_ssd_d0_smart_log *perf)
+{
+ struct json_object *root = json_create_object();
+
+ json_object_add_value_int(root, "Lifetime Reallocated Erase Block Count",
+ le32_to_cpu(perf->lifetime_realloc_erase_block_count));
+ json_object_add_value_int(root, "Lifetime Power on Hours",
+ le32_to_cpu(perf->lifetime_power_on_hours));
+ json_object_add_value_int(root, "Lifetime UECC Count",
+ le32_to_cpu(perf->lifetime_uecc_count));
+ json_object_add_value_int(root, "Lifetime Write Amplification Factor",
+ le32_to_cpu(perf->lifetime_wrt_amp_factor));
+ json_object_add_value_int(root, "Trailing Hour Write Amplification Factor",
+ le32_to_cpu(perf->trailing_hr_wrt_amp_factor));
+ json_object_add_value_int(root, "Reserve Erase Block Count",
+ le32_to_cpu(perf->reserve_erase_block_count));
+ json_object_add_value_int(root, "Lifetime Program Fail Count",
+ le32_to_cpu(perf->lifetime_program_fail_count));
+ json_object_add_value_int(root, "Lifetime Block Erase Fail Count",
+ le32_to_cpu(perf->lifetime_block_erase_fail_count));
+ json_object_add_value_int(root, "Lifetime Die Failure Count",
+ le32_to_cpu(perf->lifetime_die_failure_count));
+ json_object_add_value_int(root, "Lifetime Link Rate Downgrade Count",
+ le32_to_cpu(perf->lifetime_link_rate_downgrade_count));
+ json_object_add_value_int(root, "Lifetime Clean Shutdown Count on Power Loss",
+ le32_to_cpu(perf->lifetime_clean_shutdown_count));
+ json_object_add_value_int(root, "Lifetime Unclean Shutdowns on Power Loss",
+ le32_to_cpu(perf->lifetime_unclean_shutdown_count));
+ json_object_add_value_int(root, "Current Temperature",
+ le32_to_cpu(perf->current_temp));
+ json_object_add_value_int(root, "Max Recorded Temperature",
+ le32_to_cpu(perf->max_recorded_temp));
+ json_object_add_value_int(root, "Lifetime Retired Block Count",
+ le32_to_cpu(perf->lifetime_retired_block_count));
+ json_object_add_value_int(root, "Lifetime Read Disturb Reallocation Events",
+ le32_to_cpu(perf->lifetime_read_disturb_realloc_events));
+ json_object_add_value_int(root, "Lifetime NAND Writes",
+ le64_to_cpu(perf->lifetime_nand_writes));
+ json_object_add_value_int(root, "Capacitor Health",
+ le32_to_cpu(perf->capacitor_health));
+ json_object_add_value_int(root, "Lifetime User Writes",
+ le64_to_cpu(perf->lifetime_user_writes));
+ json_object_add_value_int(root, "Lifetime User Reads",
+ le64_to_cpu(perf->lifetime_user_reads));
+ json_object_add_value_int(root, "Lifetime Thermal Throttle Activations",
+ le32_to_cpu(perf->lifetime_thermal_throttle_act));
+ json_object_add_value_int(root, "Percentage of P/E Cycles Remaining",
+ le32_to_cpu(perf->percentage_pe_cycles_remaining));
+
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+}
+
+static void wdc_get_commit_action_bin(__u8 commit_action_type, char *action_bin)
+{
+
+ switch (commit_action_type) {
+ case 0:
+ strcpy(action_bin, "000b");
+ break;
+ case 1:
+ strcpy(action_bin, "001b");
+ break;
+ case 2:
+ strcpy(action_bin, "010b");
+ break;
+ case 3:
+ strcpy(action_bin, "011b");
+ break;
+ case 4:
+ strcpy(action_bin, "100b");
+ break;
+ case 5:
+ strcpy(action_bin, "101b");
+ break;
+ case 6:
+ strcpy(action_bin, "110b");
+ break;
+ case 7:
+ strcpy(action_bin, "111b");
+ break;
+ default:
+ strcpy(action_bin, "INVALID");
+ }
+
+}
+
+static void wdc_print_fw_act_history_log_normal(__u8 *data, int num_entries,
+ __u32 cust_id, __u32 vendor_id,
+ __u32 device_id)
+{
+ int i, j;
+ char previous_fw[9];
+ char new_fw[9];
+ char commit_action_bin[8];
+ char time_str[11];
+ __u16 oldestEntryIdx = 0, entryIdx = 0;
+ char *null_fw = "--------";
+
+ memset((void *)time_str, 0, 11);
+
+ if (data[0] == WDC_NVME_GET_FW_ACT_HISTORY_C2_LOG_ID) {
+ printf(" Firmware Activate History Log\n");
+ if (cust_id == WDC_CUSTOMER_ID_0x1005 ||
+ vendor_id == WDC_NVME_SNDK_VID ||
+ wdc_is_sn861(device_id)) {
+ printf(" Power on Hour Power Cycle Previous New\n");
+ printf(" Entry hh:mm:ss Count Firmware Firmware Slot Action Result\n");
+ printf(" ----- ----------------- ----------------- --------- --------- ----- ------ -------\n");
+ } else {
+ printf(" Power Cycle Previous New\n");
+ printf(" Entry Timestamp Count Firmware Firmware Slot Action Result\n");
+ printf(" ----- ----------------- ----------------- --------- --------- ----- ------ -------\n");
+ }
+
+ struct wdc_fw_act_history_log_format_c2 *fw_act_history_entry = (struct wdc_fw_act_history_log_format_c2 *)(data);
+
+ oldestEntryIdx = WDC_MAX_NUM_ACT_HIST_ENTRIES;
+ if (num_entries == WDC_MAX_NUM_ACT_HIST_ENTRIES) {
+ /* find lowest/oldest entry */
+ for (i = 0; i < num_entries; i++) {
+ j = (i+1 == WDC_MAX_NUM_ACT_HIST_ENTRIES) ? 0 : i+1;
+ if (le16_to_cpu(fw_act_history_entry->entry[i].fw_act_hist_entries) >
+ le16_to_cpu(fw_act_history_entry->entry[j].fw_act_hist_entries)) {
+ oldestEntryIdx = j;
+ break;
+ }
+ }
+ }
+ if (oldestEntryIdx == WDC_MAX_NUM_ACT_HIST_ENTRIES)
+ entryIdx = 0;
+ else
+ entryIdx = oldestEntryIdx;
+
+ for (i = 0; i < num_entries; i++) {
+ memset((void *)previous_fw, 0, 9);
+ memset((void *)new_fw, 0, 9);
+ memset((void *)commit_action_bin, 0, 8);
+
+ memcpy(previous_fw, (char *)&(fw_act_history_entry->entry[entryIdx].previous_fw_version), 8);
+ if (strlen((char *)&(fw_act_history_entry->entry[entryIdx].current_fw_version)) > 1)
+ memcpy(new_fw, (char *)&(fw_act_history_entry->entry[entryIdx].current_fw_version), 8);
+ else
+ memcpy(new_fw, null_fw, 8);
+
+ printf("%5"PRIu16"", (uint16_t)le16_to_cpu(fw_act_history_entry->entry[entryIdx].fw_act_hist_entries));
+ if (cust_id == WDC_CUSTOMER_ID_0x1005) {
+ printf(" ");
+ memset((void *)time_str, 0, 9);
+ sprintf((char *)time_str, "%04d:%02d:%02d", (int)(le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp)/3600),
+ (int)((le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp%3600)/60)),
+ (int)(le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp%60)));
+
+ printf("%s", time_str);
+ printf(" ");
+ } else if (vendor_id == WDC_NVME_SNDK_VID) {
+ printf(" ");
+ uint64_t timestamp = (0x0000FFFFFFFFFFFF & le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp));
+
+ memset((void *)time_str, 0, 9);
+ sprintf((char *)time_str, "%04d:%02d:%02d", (int)((timestamp/(3600*1000))%24), (int)((timestamp/(1000*60))%60),
+ (int)((timestamp/1000)%60));
+ printf("%s", time_str);
+ printf(" ");
+ } else if (wdc_is_sn861(device_id)) {
+ printf(" ");
+ char timestamp[20];
+ __u64 hour;
+ __u8 min;
+ __u8 sec;
+ __u64 timestamp_sec;
+
+ timestamp_sec =
+ le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp)
+ / 1000;
+ hour = timestamp_sec / 3600;
+ min = (timestamp_sec % 3600) / 60;
+ sec = timestamp_sec % 60;
+
+ sprintf(timestamp,
+ "%"PRIu64":%02"PRIu8":%02"PRIu8,
+ (uint64_t)hour, min, sec);
+ printf("%-11s", timestamp);
+ printf(" ");
+ } else {
+ printf(" ");
+ uint64_t timestamp = (0x0000FFFFFFFFFFFF & le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp));
+
+ printf("%16"PRIu64"", timestamp);
+ printf(" ");
+ }
+
+ printf("%16"PRIu64"", (uint64_t)le64_to_cpu(fw_act_history_entry->entry[entryIdx].power_cycle_count));
+ printf(" ");
+ printf("%s", (char *)previous_fw);
+ printf(" ");
+ printf("%s", (char *)new_fw);
+ printf(" ");
+ printf("%2"PRIu8"", (uint8_t)fw_act_history_entry->entry[entryIdx].slot_number);
+ printf(" ");
+ wdc_get_commit_action_bin(
+ fw_act_history_entry->entry[entryIdx].commit_action_type,
+ (char *)&commit_action_bin);
+ printf(" %s", (char *)commit_action_bin);
+ printf(" ");
+ if (!le16_to_cpu(fw_act_history_entry->entry[entryIdx].result))
+ printf("pass");
+ else
+ printf("fail #%d", (uint16_t)le16_to_cpu(fw_act_history_entry->entry[entryIdx].result));
+ printf("\n");
+
+ entryIdx++;
+ if (entryIdx >= WDC_MAX_NUM_ACT_HIST_ENTRIES)
+ entryIdx = 0;
+ }
+ } else {
+ printf(" Firmware Activate History Log\n");
+ printf(" Power on Hour Power Cycle Previous New\n");
+ printf(" Entry hh:mm:ss Count Firmware Firmware Slot Action Result\n");
+ printf(" ----- -------------- -------------------- ---------- ---------- ----- ------ -------\n");
+
+ struct wdc_fw_act_history_log_entry *fw_act_history_entry = (struct wdc_fw_act_history_log_entry *)(data + sizeof(struct wdc_fw_act_history_log_hdr));
+
+ oldestEntryIdx = WDC_MAX_NUM_ACT_HIST_ENTRIES;
+ if (num_entries == WDC_MAX_NUM_ACT_HIST_ENTRIES) {
+ /* find lowest/oldest entry */
+ for (i = 0; i < num_entries; i++) {
+ if (le32_to_cpu(fw_act_history_entry[i].entry_num) > le32_to_cpu(fw_act_history_entry[i+1].entry_num)) {
+ oldestEntryIdx = i+1;
+ break;
+ }
+ }
+ }
+
+ if (oldestEntryIdx == WDC_MAX_NUM_ACT_HIST_ENTRIES)
+ entryIdx = 0;
+ else
+ entryIdx = oldestEntryIdx;
+
+ for (i = 0; i < num_entries; i++) {
+ memset((void *)previous_fw, 0, 9);
+ memset((void *)new_fw, 0, 9);
+ memset((void *)commit_action_bin, 0, 8);
+
+ memcpy(previous_fw, (char *)&(fw_act_history_entry[entryIdx].previous_fw_version), 8);
+ if (strlen((char *)&(fw_act_history_entry[entryIdx].new_fw_version)) > 1)
+ memcpy(new_fw, (char *)&(fw_act_history_entry[entryIdx].new_fw_version), 8);
+ else
+ memcpy(new_fw, null_fw, 8);
+
+ printf("%5"PRIu32"", (uint32_t)le32_to_cpu(fw_act_history_entry[entryIdx].entry_num));
+ printf(" ");
+ printf("%04d:%02d:%02d", (int)(le64_to_cpu(fw_act_history_entry[entryIdx].power_on_seconds)/3600),
+ (int)((le64_to_cpu(fw_act_history_entry[entryIdx].power_on_seconds)%3600)/60),
+ (int)(le64_to_cpu(fw_act_history_entry[entryIdx].power_on_seconds)%60));
+ printf(" ");
+ printf("%16"PRIu32"", (uint32_t)le32_to_cpu(fw_act_history_entry[entryIdx].power_cycle_count));
+ printf(" ");
+ printf("%s", (char *)previous_fw);
+ printf(" ");
+ printf("%s", (char *)new_fw);
+ printf(" ");
+ printf("%2"PRIu8"", (uint8_t)fw_act_history_entry[entryIdx].slot_number);
+ printf(" ");
+ wdc_get_commit_action_bin(fw_act_history_entry[entryIdx].commit_action_type,
+ (char *)&commit_action_bin);
+ printf(" %s", (char *)commit_action_bin);
+ printf(" ");
+ if (!le16_to_cpu(fw_act_history_entry[entryIdx].result))
+ printf("pass");
+ else
+ printf("fail #%d", (uint16_t)le16_to_cpu(fw_act_history_entry[entryIdx].result));
+
+ printf("\n");
+
+ entryIdx++;
+ if (entryIdx >= WDC_MAX_NUM_ACT_HIST_ENTRIES)
+ entryIdx = 0;
+ }
+ }
+}
+
+static void wdc_print_fw_act_history_log_json(__u8 *data, int num_entries,
+ __u32 cust_id, __u32 vendor_id,
+ __u32 device_id)
+{
+ struct json_object *root = json_create_object();
+ int i, j;
+ char previous_fw[9];
+ char new_fw[9];
+ char commit_action_bin[8];
+ char fail_str[32];
+ char time_str[11];
+ char ext_time_str[20];
+
+ memset((void *)previous_fw, 0, 9);
+ memset((void *)new_fw, 0, 9);
+ memset((void *)commit_action_bin, 0, 8);
+ memset((void *)time_str, 0, 11);
+ memset((void *)ext_time_str, 0, 20);
+ memset((void *)fail_str, 0, 11);
+ char *null_fw = "--------";
+ __u16 oldestEntryIdx = 0, entryIdx = 0;
+
+ if (data[0] == WDC_NVME_GET_FW_ACT_HISTORY_C2_LOG_ID) {
+ struct wdc_fw_act_history_log_format_c2 *fw_act_history_entry = (struct wdc_fw_act_history_log_format_c2 *)(data);
+
+ oldestEntryIdx = WDC_MAX_NUM_ACT_HIST_ENTRIES;
+ if (num_entries == WDC_MAX_NUM_ACT_HIST_ENTRIES) {
+ /* find lowest/oldest entry */
+ for (i = 0; i < num_entries; i++) {
+ j = (i+1 == WDC_MAX_NUM_ACT_HIST_ENTRIES) ? 0 : i+1;
+ if (le16_to_cpu(fw_act_history_entry->entry[i].fw_act_hist_entries) >
+ le16_to_cpu(fw_act_history_entry->entry[j].fw_act_hist_entries)) {
+ oldestEntryIdx = j;
+ break;
+ }
+ }
+ }
+ if (oldestEntryIdx == WDC_MAX_NUM_ACT_HIST_ENTRIES)
+ entryIdx = 0;
+ else
+ entryIdx = oldestEntryIdx;
+
+ for (i = 0; i < num_entries; i++) {
+ memcpy(previous_fw,
+ (char *)&(fw_act_history_entry->entry[entryIdx].previous_fw_version),
+ 8);
+ if (strlen((char *)&(fw_act_history_entry->entry[entryIdx].current_fw_version)) > 1)
+ memcpy(new_fw,
+ (char *)&(fw_act_history_entry->entry[entryIdx].current_fw_version),
+ 8);
+ else
+ memcpy(new_fw, null_fw, 8);
+
+ json_object_add_value_int(root, "Entry",
+ le16_to_cpu(fw_act_history_entry->entry[entryIdx].fw_act_hist_entries));
+
+ if (cust_id == WDC_CUSTOMER_ID_0x1005) {
+ sprintf((char *)time_str, "%04d:%02d:%02d", (int)(le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp)/3600),
+ (int)((le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp%3600)/60)),
+ (int)(le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp%60)));
+
+ json_object_add_value_string(root, "Power on Hour", time_str);
+
+ } else if (vendor_id == WDC_NVME_SNDK_VID) {
+ uint64_t timestamp = (0x0000FFFFFFFFFFFF & le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp));
+
+ sprintf((char *)time_str, "%04d:%02d:%02d", (int)((timestamp/(3600*1000))%24), (int)((timestamp/(1000*60))%60),
+ (int)((timestamp/1000)%60));
+ json_object_add_value_string(root, "Power on Hour", time_str);
+ } else if (wdc_is_sn861(device_id)) {
+ __u64 timestamp_sec =
+ le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp)
+ / 1000;
+
+ sprintf((char *)ext_time_str,
+ "%"PRIu64":%02"PRIu8":%02"PRIu8,
+ (uint64_t)(__u64)(timestamp_sec/3600),
+ (__u8)((timestamp_sec%3600)/60),
+ (__u8)(timestamp_sec%60));
+ json_object_add_value_string(root, "Power on Hour", ext_time_str);
+ } else {
+ uint64_t timestamp = (0x0000FFFFFFFFFFFF & le64_to_cpu(fw_act_history_entry->entry[entryIdx].timestamp));
+
+ json_object_add_value_uint64(root, "Timestamp", timestamp);
+ }
+
+ json_object_add_value_int(root, "Power Cycle Count",
+ le64_to_cpu(fw_act_history_entry->entry[entryIdx].power_cycle_count));
+ json_object_add_value_string(root, "Previous Firmware",
+ previous_fw);
+ json_object_add_value_string(root, "New Firmware",
+ new_fw);
+ json_object_add_value_int(root, "Slot",
+ fw_act_history_entry->entry[entryIdx].slot_number);
+
+ wdc_get_commit_action_bin(
+ fw_act_history_entry->entry[entryIdx].commit_action_type,
+ (char *)&commit_action_bin);
+ json_object_add_value_string(root, "Action", commit_action_bin);
+
+ if (!le16_to_cpu(fw_act_history_entry->entry[entryIdx].result)) {
+ json_object_add_value_string(root, "Result", "pass");
+ } else {
+ sprintf((char *)fail_str, "fail #%d", (int)(le16_to_cpu(fw_act_history_entry->entry[entryIdx].result)));
+ json_object_add_value_string(root, "Result", fail_str);
+ }
+
+ json_print_object(root, NULL);
+ printf("\n");
+
+ entryIdx++;
+ if (entryIdx >= WDC_MAX_NUM_ACT_HIST_ENTRIES)
+ entryIdx = 0;
+ }
+ } else {
+ struct wdc_fw_act_history_log_entry *fw_act_history_entry = (struct wdc_fw_act_history_log_entry *)(data + sizeof(struct wdc_fw_act_history_log_hdr));
+
+ oldestEntryIdx = WDC_MAX_NUM_ACT_HIST_ENTRIES;
+ if (num_entries == WDC_MAX_NUM_ACT_HIST_ENTRIES) {
+ /* find lowest/oldest entry */
+ for (i = 0; i < num_entries; i++) {
+ if (le32_to_cpu(fw_act_history_entry[i].entry_num) > le32_to_cpu(fw_act_history_entry[i+1].entry_num)) {
+ oldestEntryIdx = i+1;
+ break;
+ }
+ }
+ }
+ if (oldestEntryIdx == WDC_MAX_NUM_ACT_HIST_ENTRIES)
+ entryIdx = 0;
+ else
+ entryIdx = oldestEntryIdx;
+
+ for (i = 0; i < num_entries; i++) {
+ memcpy(previous_fw,
+ (char *)&(fw_act_history_entry[entryIdx].previous_fw_version), 8);
+ if (strlen((char *)&(fw_act_history_entry[entryIdx].new_fw_version)) > 1)
+ memcpy(new_fw,
+ (char *)&(fw_act_history_entry[entryIdx].new_fw_version), 8);
+ else
+ memcpy(new_fw, null_fw, 8);
+
+ json_object_add_value_int(root, "Entry",
+ le32_to_cpu(fw_act_history_entry[entryIdx].entry_num));
+
+ sprintf((char *)time_str, "%04d:%02d:%02d", (int)(le64_to_cpu(fw_act_history_entry[entryIdx].power_on_seconds)/3600),
+ (int)((le64_to_cpu(fw_act_history_entry[entryIdx].power_on_seconds)%3600)/60),
+ (int)(le64_to_cpu(fw_act_history_entry[entryIdx].power_on_seconds)%60));
+ json_object_add_value_string(root, "Power on Hour", time_str);
+
+ json_object_add_value_int(root, "Power Cycle Count",
+ le32_to_cpu(fw_act_history_entry[entryIdx].power_cycle_count));
+ json_object_add_value_string(root, "Previous Firmware",
+ previous_fw);
+ json_object_add_value_string(root, "New Firmware",
+ new_fw);
+ json_object_add_value_int(root, "Slot",
+ fw_act_history_entry[entryIdx].slot_number);
+
+ wdc_get_commit_action_bin(fw_act_history_entry[entryIdx].commit_action_type,
+ (char *)&commit_action_bin);
+ json_object_add_value_string(root, "Action", commit_action_bin);
+
+ if (!le16_to_cpu(fw_act_history_entry[entryIdx].result)) {
+ json_object_add_value_string(root, "Result", "pass");
+ } else {
+ sprintf((char *)fail_str, "fail #%d", (int)(le16_to_cpu(fw_act_history_entry[entryIdx].result)));
+ json_object_add_value_string(root, "Result", fail_str);
+ }
+
+ json_print_object(root, NULL);
+ printf("\n");
+
+ entryIdx++;
+ if (entryIdx >= WDC_MAX_NUM_ACT_HIST_ENTRIES)
+ entryIdx = 0;
+ }
+ }
+
+ json_free_object(root);
+}
+
+static int nvme_get_ext_smart_cloud_log(int fd, __u8 **data, int uuid_index, __u32 namespace_id)
+{
+ int ret, i;
+ __u8 *log_ptr = NULL;
+
+ log_ptr = (__u8 *)malloc(sizeof(__u8) * WDC_NVME_SMART_CLOUD_ATTR_LEN);
+ if (!log_ptr) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+
+ /* Get the 0xC0 log data */
+ struct nvme_get_log_args args = {
+ .args_size = sizeof(args),
+ .fd = fd,
+ .lid = WDC_NVME_GET_SMART_CLOUD_ATTR_LOG_ID,
+ .nsid = namespace_id,
+ .lpo = 0,
+ .lsp = NVME_LOG_LSP_NONE,
+ .lsi = 0,
+ .rae = false,
+ .uuidx = uuid_index,
+ .csi = NVME_CSI_NVM,
+ .ot = false,
+ .len = WDC_NVME_SMART_CLOUD_ATTR_LEN,
+ .log = log_ptr,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ };
+ ret = nvme_get_log(&args);
+
+ if (!ret) {
+ /* Verify GUID matches */
+ for (i = 0; i < WDC_C0_GUID_LENGTH; i++) {
+ if (ext_smart_guid[i] != *&log_ptr[SCAO_V1_LPG + i]) {
+ fprintf(stderr, "ERROR: WDC: Unknown GUID in C0 Log Page V1 data\n");
+ int j;
+
+ fprintf(stderr, "ERROR: WDC: Expected GUID: 0x");
+ for (j = 0; j < WDC_C0_GUID_LENGTH; j++)
+ fprintf(stderr, "%x", ext_smart_guid[j]);
+ fprintf(stderr, "\nERROR: WDC: Actual GUID: 0x");
+ for (j = 0; j < WDC_C0_GUID_LENGTH; j++)
+ fprintf(stderr, "%x", *&log_ptr[SCAO_V1_LPG + j]);
+ fprintf(stderr, "\n");
+
+ ret = -1;
+ break;
+ }
+ }
+ }
+
+ *data = log_ptr;
+
+ return ret;
+}
+
+
+static int nvme_get_hw_rev_log(int fd, __u8 **data, int uuid_index, __u32 namespace_id)
+{
+ int ret, i;
+ struct wdc_nvme_hw_rev_log *log_ptr = NULL;
+
+ log_ptr = (struct wdc_nvme_hw_rev_log *)malloc(sizeof(__u8) * WDC_NVME_HW_REV_LOG_PAGE_LEN);
+ if (!log_ptr) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+
+ /* Get the 0xC0 log data */
+ struct nvme_get_log_args args = {
+ .args_size = sizeof(args),
+ .fd = fd,
+ .lid = WDC_NVME_GET_HW_REV_LOG_OPCODE,
+ .nsid = namespace_id,
+ .lpo = 0,
+ .lsp = NVME_LOG_LSP_NONE,
+ .lsi = 0,
+ .rae = false,
+ .uuidx = uuid_index,
+ .csi = NVME_CSI_NVM,
+ .ot = false,
+ .len = WDC_NVME_HW_REV_LOG_PAGE_LEN,
+ .log = log_ptr,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ };
+ ret = nvme_get_log(&args);
+
+ if (!ret) {
+ /* Verify GUID matches */
+ for (i = 0; i < WDC_NVME_C6_GUID_LENGTH; i++) {
+ if (hw_rev_log_guid[i] != log_ptr->hw_rev_guid[i]) {
+ fprintf(stderr, "ERROR: WDC: Unknown GUID in HW Revision Log Page data\n");
+ int j;
+
+ fprintf(stderr, "ERROR: WDC: Expected GUID: 0x");
+ for (j = 0; j < WDC_NVME_C6_GUID_LENGTH; j++)
+ fprintf(stderr, "%x", hw_rev_log_guid[j]);
+ fprintf(stderr, "\nERROR: WDC: Actual GUID: 0x");
+ for (j = 0; j < WDC_NVME_C6_GUID_LENGTH; j++)
+ fprintf(stderr, "%x", log_ptr->hw_rev_guid[j]);
+ fprintf(stderr, "\n");
+
+ ret = -1;
+ break;
+ }
+ }
+ }
+
+ *data = (__u8 *)log_ptr;
+
+ return ret;
+}
+
+
+static void wdc_print_hw_rev_log_normal(void *data)
+{
+ int i;
+ struct wdc_nvme_hw_rev_log *log_data = (struct wdc_nvme_hw_rev_log *)data;
+
+ printf(" Hardware Revision Log:-\n");
+
+ printf(" Global Device HW Revision : %d\n",
+ log_data->hw_rev_gdr);
+ printf(" ASIC HW Revision : %d\n",
+ log_data->hw_rev_ar);
+ printf(" PCB Manufacturer Code : %d\n",
+ log_data->hw_rev_pbc_mc);
+ printf(" DRAM Manufacturer Code : %d\n",
+ log_data->hw_rev_dram_mc);
+ printf(" NAND Manufacturer Code : %d\n",
+ log_data->hw_rev_nand_mc);
+ printf(" PMIC 1 Manufacturer Code : %d\n",
+ log_data->hw_rev_pmic1_mc);
+ printf(" PMIC 2 Manufacturer Code : %d\n",
+ log_data->hw_rev_pmic2_mc);
+ printf(" Other Component 1 Manf Code : %d\n",
+ log_data->hw_rev_c1_mc);
+ printf(" Other Component 2 Manf Code : %d\n",
+ log_data->hw_rev_c2_mc);
+ printf(" Other Component 3 Manf Code : %d\n",
+ log_data->hw_rev_c3_mc);
+ printf(" Other Component 4 Manf Code : %d\n",
+ log_data->hw_rev_c4_mc);
+ printf(" Other Component 5 Manf Code : %d\n",
+ log_data->hw_rev_c5_mc);
+ printf(" Other Component 6 Manf Code : %d\n",
+ log_data->hw_rev_c6_mc);
+ printf(" Other Component 7 Manf Code : %d\n",
+ log_data->hw_rev_c7_mc);
+ printf(" Other Component 8 Manf Code : %d\n",
+ log_data->hw_rev_c8_mc);
+ printf(" Other Component 9 Manf Code : %d\n",
+ log_data->hw_rev_c9_mc);
+
+ printf(" Device Manf Detailed Info : 0x");
+ for (i = 0; i < 16; i++) {
+ printf("%02x", log_data->hw_rev_dev_mdi[i]);
+ if (i == 7)
+ printf(" 0x");
+ }
+ printf("\n");
+ printf(" ASIC Detailed Info : 0x");
+ for (i = 0; i < 16; i++) {
+ printf("%02x", log_data->hw_rev_asic_di[i]);
+ if (i == 7)
+ printf(" 0x");
+ }
+ printf("\n");
+ printf(" PCB Detailed Info : 0x");
+ for (i = 0; i < 16; i++) {
+ printf("%02x", log_data->hw_rev_pcb_di[i]);
+ if (i == 7)
+ printf(" 0x");
+ }
+ printf("\n");
+ printf(" DRAM Detailed Info : 0x");
+ for (i = 0; i < 16; i++) {
+ printf("%02x", log_data->hw_rev_dram_di[i]);
+ if (i == 7)
+ printf(" 0x");
+ }
+ printf("\n");
+ printf(" NAND Detailed Info : 0x");
+ for (i = 0; i < 16; i++) {
+ printf("%02x", log_data->hw_rev_nand_di[i]);
+ if (i == 7)
+ printf(" 0x");
+ }
+ printf("\n");
+ printf(" PMIC 1 Detailed Info : 0x");
+ for (i = 0; i < 16; i++) {
+ printf("%02x", log_data->hw_rev_pmic1_di[i]);
+ if (i == 7)
+ printf(" 0x");
+ }
+ printf("\n");
+ printf(" PMIC 2 Detailed Info : 0x");
+ for (i = 0; i < 16; i++) {
+ printf("%02x", log_data->hw_rev_pmic2_di[i]);
+ if (i == 7)
+ printf(" 0x");
+ }
+ printf("\n");
+ printf(" Component 1 Detailed Info : 0x");
+ for (i = 0; i < 16; i++) {
+ printf("%02x", log_data->hw_rev_c1_di[i]);
+ if (i == 7)
+ printf(" 0x");
+ }
+ printf("\n");
+ printf(" Component 2 Detailed Info : 0x");
+ for (i = 0; i < 16; i++) {
+ printf("%02x", log_data->hw_rev_c2_di[i]);
+ if (i == 7)
+ printf(" 0x");
+ }
+ printf("\n");
+ printf(" Component 3 Detailed Info : 0x");
+ for (i = 0; i < 16; i++) {
+ printf("%02x", log_data->hw_rev_c3_di[i]);
+ if (i == 7)
+ printf(" 0x");
+ }
+ printf("\n");
+ printf(" Component 4 Detailed Info : 0x");
+ for (i = 0; i < 16; i++) {
+ printf("%02x", log_data->hw_rev_c4_di[i]);
+ if (i == 7)
+ printf(" 0x");
+ }
+ printf("\n");
+ printf(" Component 5 Detailed Info : 0x");
+ for (i = 0; i < 16; i++) {
+ printf("%02x", log_data->hw_rev_c5_di[i]);
+ if (i == 7)
+ printf(" 0x");
+ }
+ printf("\n");
+ printf(" Component 6 Detailed Info : 0x");
+ for (i = 0; i < 16; i++) {
+ printf("%02x", log_data->hw_rev_c6_di[i]);
+ if (i == 7)
+ printf(" 0x");
+ }
+ printf("\n");
+ printf(" Component 7 Detailed Info : 0x");
+ for (i = 0; i < 16; i++) {
+ printf("%02x", log_data->hw_rev_c7_di[i]);
+ if (i == 7)
+ printf(" 0x");
+ }
+ printf("\n");
+ printf(" Component 8 Detailed Info : 0x");
+ for (i = 0; i < 16; i++) {
+ printf("%02x", log_data->hw_rev_c8_di[i]);
+ if (i == 7)
+ printf(" 0x");
+ }
+ printf("\n");
+ printf(" Component 9 Detailed Info : 0x");
+ for (i = 0; i < 16; i++) {
+ printf("%02x", log_data->hw_rev_c9_di[i]);
+ if (i == 7)
+ printf(" 0x");
+ }
+ printf("\n");
+ printf(" Serial Number : 0x");
+ for (i = 0; i < 32; i++) {
+ if ((i > 1) & !(i % 8))
+ printf(" 0x");
+ printf("%02x", log_data->hw_rev_sn[i]);
+ }
+ printf("\n");
+
+ printf(" Log Page Version : %d\n", log_data->hw_rev_version);
+ printf(" Log page GUID : 0x");
+ printf("%"PRIx64"%"PRIx64"\n", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_guid[8]),
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_guid[0]));
+ printf("\n");
+}
+
+static void wdc_print_hw_rev_log_json(void *data)
+{
+ struct wdc_nvme_hw_rev_log *log_data = (struct wdc_nvme_hw_rev_log *)data;
+ struct json_object *root = json_create_object();
+ char json_data[80];
+
+ json_object_add_value_uint(root, "Global Device HW Revision",
+ log_data->hw_rev_gdr);
+ json_object_add_value_uint(root, "ASIC HW Revision",
+ log_data->hw_rev_ar);
+ json_object_add_value_uint(root, "PCB Manufacturer Code",
+ log_data->hw_rev_pbc_mc);
+ json_object_add_value_uint(root, "DRAM Manufacturer Code",
+ log_data->hw_rev_dram_mc);
+ json_object_add_value_uint(root, "NAND Manufacturer Code",
+ log_data->hw_rev_nand_mc);
+ json_object_add_value_uint(root, "PMIC 1 Manufacturer Code",
+ log_data->hw_rev_pmic1_mc);
+ json_object_add_value_uint(root, "PMIC 2 Manufacturer Code",
+ log_data->hw_rev_pmic2_mc);
+ json_object_add_value_uint(root, "Other Component 1 Manf Code",
+ log_data->hw_rev_c1_mc);
+ json_object_add_value_uint(root, "Other Component 2 Manf Code",
+ log_data->hw_rev_c2_mc);
+ json_object_add_value_uint(root, "Other Component 3 Manf Code",
+ log_data->hw_rev_c3_mc);
+ json_object_add_value_uint(root, "Other Component 4 Manf Code",
+ log_data->hw_rev_c4_mc);
+ json_object_add_value_uint(root, "Other Component 5 Manf Code",
+ log_data->hw_rev_c5_mc);
+ json_object_add_value_uint(root, "Other Component 6 Manf Code",
+ log_data->hw_rev_c6_mc);
+ json_object_add_value_uint(root, "Other Component 7 Manf Code",
+ log_data->hw_rev_c7_mc);
+ json_object_add_value_uint(root, "Other Component 8 Manf Code",
+ log_data->hw_rev_c8_mc);
+ json_object_add_value_uint(root, "Other Component 9 Manf Code",
+ log_data->hw_rev_c9_mc);
+
+ memset((void *)json_data, 0, 40);
+ sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_dev_mdi[8]),
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_dev_mdi[0]));
+ json_object_add_value_string(root, "Device Manf Detailed Info", json_data);
+
+ memset((void *)json_data, 0, 40);
+ sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_asic_di[8]),
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_asic_di[0]));
+ json_object_add_value_string(root, "ASIC Detailed Info", json_data);
+
+ memset((void *)json_data, 0, 40);
+ sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_pcb_di[8]),
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_pcb_di[0]));
+ json_object_add_value_string(root, "PCB Detailed Info", json_data);
+
+ memset((void *)json_data, 0, 40);
+ sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_dram_di[8]),
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_dram_di[0]));
+ json_object_add_value_string(root, "DRAM Detailed Info", json_data);
+
+ memset((void *)json_data, 0, 40);
+ sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_nand_di[8]),
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_nand_di[0]));
+ json_object_add_value_string(root, "NAND Detailed Info", json_data);
+
+ memset((void *)json_data, 0, 40);
+ sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_pmic1_di[8]),
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_pmic1_di[0]));
+ json_object_add_value_string(root, "PMIC 1 Detailed Info", json_data);
+
+ memset((void *)json_data, 0, 40);
+ sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_pmic2_di[8]),
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_pmic2_di[0]));
+ json_object_add_value_string(root, "PMIC 2 Detailed Info", json_data);
+
+ memset((void *)json_data, 0, 40);
+ sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c1_di[8]),
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c1_di[0]));
+ json_object_add_value_string(root, "Component 1 Detailed Info", json_data);
+
+ memset((void *)json_data, 0, 40);
+ sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c2_di[8]),
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c2_di[0]));
+ json_object_add_value_string(root, "Component 2 Detailed Info", json_data);
+
+ memset((void *)json_data, 0, 40);
+ sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c3_di[8]),
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c3_di[0]));
+ json_object_add_value_string(root, "Component 3 Detailed Info", json_data);
+
+ memset((void *)json_data, 0, 40);
+ sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c4_di[8]),
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c4_di[0]));
+ json_object_add_value_string(root, "Component 4 Detailed Info", json_data);
+
+ memset((void *)json_data, 0, 40);
+ sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c5_di[8]),
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c5_di[0]));
+ json_object_add_value_string(root, "Component 5 Detailed Info", json_data);
+
+ memset((void *)json_data, 0, 40);
+ sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c6_di[8]),
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c6_di[0]));
+ json_object_add_value_string(root, "Component 6 Detailed Info", json_data);
+
+ memset((void *)json_data, 0, 40);
+ sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c7_di[8]),
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c7_di[0]));
+ json_object_add_value_string(root, "Component 7 Detailed Info", json_data);
+
+ memset((void *)json_data, 0, 40);
+ sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c8_di[8]),
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c8_di[0]));
+ json_object_add_value_string(root, "Component 8 Detailed Info", json_data);
+
+ memset((void *)json_data, 0, 40);
+ sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c9_di[8]),
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_c9_di[0]));
+ json_object_add_value_string(root, "Component 9 Detailed Info", json_data);
+
+ memset((void *)json_data, 0, 80);
+ sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"%"PRIx64"%"PRIx64"",
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_sn[0]), le64_to_cpu(*(uint64_t *)&log_data->hw_rev_sn[8]),
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_sn[16]), le64_to_cpu(*(uint64_t *)&log_data->hw_rev_sn[24]));
+ json_object_add_value_string(root, "Serial Number", json_data);
+
+ json_object_add_value_uint(root, "Log Page Version",
+ le16_to_cpu(log_data->hw_rev_version));
+
+ memset((void *)json_data, 0, 40);
+ sprintf((char *)json_data, "0x%"PRIx64"%"PRIx64"", le64_to_cpu(*(uint64_t *)&log_data->hw_rev_guid[8]),
+ le64_to_cpu(*(uint64_t *)&log_data->hw_rev_guid[0]));
+ json_object_add_value_string(root, "Log Page GUID", json_data);
+
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+}
+
+static void wdc_print_ext_smart_cloud_log_normal(void *data, int mask)
+{
+ int i;
+ struct __packed wdc_nvme_ext_smart_log * ext_smart_log_ptr = (struct __packed wdc_nvme_ext_smart_log *)data;
+
+ if (mask == WDC_SCA_V1_NAND_STATS)
+ printf(" NAND Statistics :-\n");
+ else
+ printf(" SMART Cloud Attributes :-\n");
+
+ printf(" Physical Media Units Written TLC (Bytes): %s\n",
+ uint128_t_to_string(le128_to_cpu(
+ ext_smart_log_ptr->ext_smart_pmuwt)));
+ printf(" Physical Media Units Written SLC (Bytes): %s\n",
+ uint128_t_to_string(le128_to_cpu(
+ ext_smart_log_ptr->ext_smart_pmuws)));
+ printf(" Bad User NAND Block Count (Normalized) (Int) : %d\n",
+ le16_to_cpu(*(uint16_t *)ext_smart_log_ptr->ext_smart_bunbc));
+ printf(" Bad User NAND Block Count (Raw) (Int) : %"PRIu64"\n",
+ le64_to_cpu(*(uint64_t *)ext_smart_log_ptr->ext_smart_bunbc & 0xFFFFFFFFFFFF0000));
+ printf(" XOR Recovery Count (Int) : %"PRIu64"\n",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_xrc));
+ printf(" Uncorrectable Read Error Count (Int) : %"PRIu64"\n",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_urec));
+ if (mask == WDC_SCA_V1_ALL) {
+ printf(" SSD End to End correction counts (Corrected Errors) (Int) : %"PRIu64"\n",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_eece));
+ printf(" SSD End to End correction counts (Detected Errors) (Int) : %"PRIu64"\n",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_eede));
+ printf(" SSD End to End correction counts (Uncorrected E2E Errors) (Int) : %"PRIu64"\n",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_eeue));
+ printf(" System Data %% life-used : %d %%\n",
+ ext_smart_log_ptr->ext_smart_sdpu);
+ }
+ printf(" User data erase counts (Minimum TLC) (Int) : %"PRIu64"\n",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_mnudec));
+ printf(" User data erase counts (Maximum TLC) (Int) : %"PRIu64"\n",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_mxudec));
+ printf(" User data erase counts (Minimum SLC) (Int) : %"PRIu64"\n",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_mnec));
+ printf(" User data erase counts (Maximum SLC) (Int) : %"PRIu64"\n",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_mxec));
+ printf(" User data erase counts (Average SLC) (Int) : %"PRIu64"\n",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_avec));
+ printf(" User data erase counts (Average TLC) (Int) : %"PRIu64"\n",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_avudec));
+ printf(" Program Fail Count (Normalized) (Int) : %d\n",
+ le16_to_cpu(*(uint16_t *)ext_smart_log_ptr->ext_smart_pfc));
+ printf(" Program Fail Count (Raw) (Int) : %"PRIu64"\n",
+ le64_to_cpu(*(uint64_t *)ext_smart_log_ptr->ext_smart_pfc & 0xFFFFFFFFFFFF0000));
+ printf(" Erase Fail Count (Normalized) (Int) : %d\n",
+ le16_to_cpu(*(uint16_t *)ext_smart_log_ptr->ext_smart_efc));
+ printf(" Erase Fail Count (Raw) (Int) : %"PRIu64"\n",
+ le64_to_cpu(*(uint64_t *)ext_smart_log_ptr->ext_smart_efc & 0xFFFFFFFFFFFF0000));
+ if (mask == WDC_SCA_V1_ALL) {
+ printf(" PCIe Correctable Error Count (Int) : %"PRIu64"\n",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_pcec));
+ printf(" %% Free Blocks (User) (Int) : %d %%\n",
+ ext_smart_log_ptr->ext_smart_pfbu);
+ printf(" Security Version Number (Int) : %"PRIu64"\n",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_svn));
+ printf(" %% Free Blocks (System) (Int) : %d %%\n",
+ ext_smart_log_ptr->ext_smart_pfbs);
+ printf(" NVMe Stats (# Data Set Management/TRIM Commands Completed) (Int): %s\n",
+ uint128_t_to_string(le128_to_cpu(
+ ext_smart_log_ptr->ext_smart_dcc)));
+ printf(" Total Namespace Utilization (nvme0n1 NUSE) (Bytes) : %"PRIu64"\n",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_tnu));
+ printf(" NVMe Stats (# NVMe Format Commands Completed) (Int) : %d\n",
+ le16_to_cpu(ext_smart_log_ptr->ext_smart_fcc));
+ printf(" Background Back-Pressure Gauge(%%) (Int) : %d\n",
+ ext_smart_log_ptr->ext_smart_bbpg);
+ }
+ printf(" Total # of Soft ECC Error Count (Int) : %"PRIu64"\n",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_seec));
+ if (mask == WDC_SCA_V1_ALL) {
+ printf(" Total # of Read Refresh Count (Int) : %"PRIu64"\n",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_rfsc));
+ }
+ printf(" Bad System NAND Block Count (Normalized) (Int) : %d\n",
+ le16_to_cpu(*(uint16_t *)ext_smart_log_ptr->ext_smart_bsnbc));
+ printf(" Bad System NAND Block Count (Raw) (Int) : %"PRIu64"\n",
+ le64_to_cpu(*(uint64_t *)ext_smart_log_ptr->ext_smart_bsnbc & 0xFFFFFFFFFFFF0000));
+ printf(" Endurance Estimate (Total Writable Lifetime Bytes) (Bytes) : %s\n",
+ uint128_t_to_string(
+ le128_to_cpu(ext_smart_log_ptr->ext_smart_eest)));
+ if (mask == WDC_SCA_V1_ALL) {
+ printf(" Thermal Throttling Status & Count (Number of thermal throttling events) (Int) : %d\n",
+ le16_to_cpu(ext_smart_log_ptr->ext_smart_ttc));
+ printf(" Total # Unaligned I/O (Int) : %"PRIu64"\n",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_uio));
+ }
+ printf(" Total Physical Media Units Read (Bytes) (Int) : %s\n",
+ uint128_t_to_string(
+ le128_to_cpu(ext_smart_log_ptr->ext_smart_pmur)));
+ if (mask == WDC_SCA_V1_ALL) {
+ printf(" Command Timeout (# of READ Commands > 5 Seconds) (Int) : %"PRIu32"\n",
+ le32_to_cpu(ext_smart_log_ptr->ext_smart_rtoc));
+ printf(" Command Timeout (# of WRITE Commands > 5 Seconds) (Int) : %"PRIu32"\n",
+ le32_to_cpu(ext_smart_log_ptr->ext_smart_wtoc));
+ printf(" Command Timeout (# of TRIM Commands > 5 Seconds) (Int) : %"PRIu32"\n",
+ le32_to_cpu(ext_smart_log_ptr->ext_smart_ttoc));
+ printf(" Total PCIe Link Retraining Count (Int) : %"PRIu64"\n",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_plrc));
+ printf(" Active Power State Change Count (Int) : %"PRIu64"\n",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_pscc));
+ }
+ printf(" Cloud Boot SSD Spec Version (Int) : %d.%d.%d.%d\n",
+ le16_to_cpu(ext_smart_log_ptr->ext_smart_maj),
+ le16_to_cpu(ext_smart_log_ptr->ext_smart_min),
+ le16_to_cpu(ext_smart_log_ptr->ext_smart_pt),
+ le16_to_cpu(ext_smart_log_ptr->ext_smart_err));
+ printf(" Cloud Boot SSD HW Revision (Int) : %d.%d.%d.%d\n",
+ 0, 0, 0, 0);
+ if (mask == WDC_SCA_V1_ALL) {
+ printf(" FTL Unit Size : %"PRIu32"\n",
+ le32_to_cpu(ext_smart_log_ptr->ext_smart_ftlus));
+ printf(" TCG Ownership Status : %"PRIu32"\n",
+ le32_to_cpu(ext_smart_log_ptr->ext_smart_tcgos));
+ printf(" Log Page Version (Int) : %d\n",
+ le16_to_cpu(ext_smart_log_ptr->ext_smart_lpv));
+ printf(" Log page GUID (Hex) : 0x");
+ for (i = WDC_C0_GUID_LENGTH; i > 0; i--)
+ printf("%02x", ext_smart_log_ptr->ext_smart_lpg[i-1]);
+ printf("\n");
+ }
+ printf("\n");
+}
+
+static void wdc_print_ext_smart_cloud_log_json(void *data, int mask)
+{
+ struct __packed wdc_nvme_ext_smart_log * ext_smart_log_ptr =
+ (struct __packed wdc_nvme_ext_smart_log *)data;
+ struct json_object *root = json_create_object();
+
+ json_object_add_value_uint128(root, "physical_media_units_bytes_tlc",
+ le128_to_cpu(ext_smart_log_ptr->ext_smart_pmuwt));
+ json_object_add_value_uint128(root, "physical_media_units_bytes_slc",
+ le128_to_cpu(ext_smart_log_ptr->ext_smart_pmuws));
+ json_object_add_value_uint(root, "bad_user_blocks_normalized",
+ le16_to_cpu(*(uint16_t *)ext_smart_log_ptr->ext_smart_bunbc));
+ json_object_add_value_uint64(root, "bad_user_blocks_raw",
+ le64_to_cpu(*(uint64_t *)ext_smart_log_ptr->ext_smart_bunbc & 0xFFFFFFFFFFFF0000));
+ json_object_add_value_uint64(root, "xor_recovery_count",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_xrc));
+ json_object_add_value_uint64(root, "uncorrectable_read_errors",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_urec));
+ if (mask == WDC_SCA_V1_ALL) {
+ json_object_add_value_uint64(root, "corrected_e2e_errors",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_eece));
+ json_object_add_value_uint64(root, "detected_e2e_errors",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_eede));
+ json_object_add_value_uint64(root, "uncorrected_e2e_errors",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_eeue));
+ json_object_add_value_uint(root, "system_data_life_used_pct",
+ (__u8)ext_smart_log_ptr->ext_smart_sdpu);
+ }
+ json_object_add_value_uint64(root, "min_slc_user_data_erase_count",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_mnec));
+ json_object_add_value_uint64(root, "min_tlc_user_data_erase_count",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_mnudec));
+ json_object_add_value_uint64(root, "max_slc_user_data_erase_count",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_mxec));
+ json_object_add_value_uint64(root, "max_tlc_user_data_erase_count",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_mxudec));
+ json_object_add_value_uint64(root, "avg_slc_user_data_erase_count",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_avec));
+ json_object_add_value_uint64(root, "avg_tlc_user_data_erase_count",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_avudec));
+ json_object_add_value_uint(root, "program_fail_count_normalized",
+ le16_to_cpu(*(uint16_t *)ext_smart_log_ptr->ext_smart_pfc));
+ json_object_add_value_uint64(root, "program_fail_count_raw",
+ le64_to_cpu(*(uint64_t *)ext_smart_log_ptr->ext_smart_pfc & 0xFFFFFFFFFFFF0000));
+ json_object_add_value_uint(root, "erase_fail_count_normalized",
+ le16_to_cpu(*(uint16_t *)ext_smart_log_ptr->ext_smart_efc));
+ json_object_add_value_uint64(root, "erase_fail_count_raw",
+ le64_to_cpu(*(uint64_t *)ext_smart_log_ptr->ext_smart_efc & 0xFFFFFFFFFFFF0000));
+ if (mask == WDC_SCA_V1_ALL) {
+ json_object_add_value_uint64(root, "pcie_correctable_errors",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_pcec));
+ json_object_add_value_uint(root, "pct_free_blocks_user",
+ (__u8)ext_smart_log_ptr->ext_smart_pfbu);
+ json_object_add_value_uint64(root, "security_version",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_svn));
+ json_object_add_value_uint(root, "pct_free_blocks_system",
+ (__u8)ext_smart_log_ptr->ext_smart_pfbs);
+ json_object_add_value_uint128(root, "num_of_trim_commands",
+ le128_to_cpu(ext_smart_log_ptr->ext_smart_dcc));
+ json_object_add_value_uint64(root, "total_nuse_bytes",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_tnu));
+ json_object_add_value_uint(root, "num_of_format_commands",
+ le16_to_cpu(ext_smart_log_ptr->ext_smart_fcc));
+ json_object_add_value_uint(root, "background_pressure_gauge",
+ (__u8)ext_smart_log_ptr->ext_smart_bbpg);
+ }
+ json_object_add_value_uint64(root, "soft_ecc_error_count",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_seec));
+ if (mask == WDC_SCA_V1_ALL)
+ json_object_add_value_uint64(root, "read_refresh_count",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_rfsc));
+ json_object_add_value_uint(root, "bad_system_block_normalized",
+ le16_to_cpu(*(uint16_t *)ext_smart_log_ptr->ext_smart_bsnbc));
+ json_object_add_value_uint64(root, "bad_system_block_raw",
+ le64_to_cpu(*(uint64_t *)ext_smart_log_ptr->ext_smart_bsnbc & 0xFFFFFFFFFFFF0000));
+ json_object_add_value_uint128(root, "endurance_est_bytes",
+ le128_to_cpu(ext_smart_log_ptr->ext_smart_eest));
+ if (mask == WDC_SCA_V1_ALL) {
+ json_object_add_value_uint(root, "num_throttling_events",
+ le16_to_cpu(ext_smart_log_ptr->ext_smart_ttc));
+ json_object_add_value_uint64(root, "total_unaligned_io",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_uio));
+ }
+ json_object_add_value_uint128(root, "physical_media_units_read_bytes",
+ le128_to_cpu(ext_smart_log_ptr->ext_smart_pmur));
+ if (mask == WDC_SCA_V1_ALL) {
+ json_object_add_value_uint(root, "num_read_timeouts",
+ le32_to_cpu(ext_smart_log_ptr->ext_smart_rtoc));
+ json_object_add_value_uint(root, "num_write_timeouts",
+ le32_to_cpu(ext_smart_log_ptr->ext_smart_wtoc));
+ json_object_add_value_uint(root, "num_trim_timeouts",
+ le32_to_cpu(ext_smart_log_ptr->ext_smart_ttoc));
+ json_object_add_value_uint64(root, "pcie_link_retrain_count",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_plrc));
+ json_object_add_value_uint64(root, "active_power_state_change_count",
+ le64_to_cpu(ext_smart_log_ptr->ext_smart_pscc));
+ }
+ char vers_str[40];
+
+ memset((void *)vers_str, 0, 40);
+ sprintf((char *)vers_str, "%d.%d.%d.%d",
+ le16_to_cpu(ext_smart_log_ptr->ext_smart_maj),
+ le16_to_cpu(ext_smart_log_ptr->ext_smart_min),
+ le16_to_cpu(ext_smart_log_ptr->ext_smart_pt),
+ le16_to_cpu(ext_smart_log_ptr->ext_smart_err));
+ json_object_add_value_string(root, "cloud_boot_ssd_spec_ver", vers_str);
+ memset((void *)vers_str, 0, 40);
+ sprintf((char *)vers_str, "%d.%d.%d.%d", 0, 0, 0, 0);
+ json_object_add_value_string(root, "cloud_boot_ssd_hw_ver", vers_str);
+
+ if (mask == WDC_SCA_V1_ALL) {
+ json_object_add_value_uint(root, "ftl_unit_size",
+ le32_to_cpu(ext_smart_log_ptr->ext_smart_ftlus));
+ json_object_add_value_uint(root, "tcg_ownership_status",
+ le32_to_cpu(ext_smart_log_ptr->ext_smart_tcgos));
+ json_object_add_value_uint(root, "log_page_ver",
+ le16_to_cpu(ext_smart_log_ptr->ext_smart_lpv));
+ char guid[40];
+
+ memset((void *)guid, 0, 40);
+ sprintf((char *)guid, "0x%"PRIx64"%"PRIx64"",
+ le64_to_cpu(*(uint64_t *)&ext_smart_log_ptr->ext_smart_lpg[8]),
+ le64_to_cpu(*(uint64_t *)&ext_smart_log_ptr->ext_smart_lpg[0]));
+ json_object_add_value_string(root, "log_page_guid", guid);
+ }
+
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+}
+
+static void wdc_print_smart_cloud_attr_C0_normal(void *data)
+{
+ __u8 *log_data = (__u8 *)data;
+ uint16_t smart_log_ver = 0;
+
+ printf(" SMART Cloud Attributes :-\n");
+
+ printf(" Physical media units written : %s\n",
+ uint128_t_to_string(le128_to_cpu(&log_data[SCAO_PMUW])));
+ printf(" Physical media units read : %s\n",
+ uint128_t_to_string(le128_to_cpu(&log_data[SCAO_PMUR])));
+ printf(" Bad user nand blocks Raw : %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_BUNBR] & 0x0000FFFFFFFFFFFF));
+ printf(" Bad user nand blocks Normalized : %d\n",
+ (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_BUNBN]));
+ printf(" Bad system nand blocks Raw : %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_BSNBR] & 0x0000FFFFFFFFFFFF));
+ printf(" Bad system nand blocks Normalized : %d\n",
+ (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_BSNBN]));
+ printf(" XOR recovery count : %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_XRC]));
+ printf(" Uncorrectable read error count : %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_UREC]));
+ printf(" Soft ecc error count : %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_SEEC]));
+ printf(" End to end corrected errors : %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_EECE]));
+ printf(" End to end detected errors : %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_EEDC]));
+ printf(" System data percent used : %d\n", (__u8)log_data[SCAO_SDPU]);
+ printf(" Refresh counts : %"PRIu64"\n",
+ (uint64_t)(le64_to_cpu(*(uint64_t *)&log_data[SCAO_RFSC]) & 0x00FFFFFFFFFFFFFF));
+ printf(" Max User data erase counts : %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_MXUDEC]));
+ printf(" Min User data erase counts : %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_MNUDEC]));
+ printf(" Number of Thermal throttling events : %d\n", (__u8)log_data[SCAO_NTTE]);
+ printf(" Current throttling status : 0x%x\n", (__u8)log_data[SCAO_CTS]);
+ printf(" PCIe correctable error count : %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PCEC]));
+ printf(" Incomplete shutdowns : %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_ICS]));
+ printf(" Percent free blocks : %d\n", (__u8)log_data[SCAO_PFB]);
+ printf(" Capacitor health : %"PRIu16"\n",
+ (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_CPH]));
+ printf(" Unaligned I/O : %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_UIO]));
+ printf(" Security Version Number : %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_SVN]));
+ printf(" NUSE Namespace utilization : %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_NUSE]));
+ printf(" PLP start count : %s\n",
+ uint128_t_to_string(le128_to_cpu(&log_data[SCAO_PSC])));
+ printf(" Endurance estimate : %s\n",
+ uint128_t_to_string(le128_to_cpu(&log_data[SCAO_EEST])));
+ smart_log_ver = (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_LPV]);
+ printf(" Log page version : %"PRIu16"\n", smart_log_ver);
+ printf(" Log page GUID : 0x");
+ printf("%"PRIx64"%"PRIx64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_LPG + 8]),
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_LPG]));
+ if (smart_log_ver > 2) {
+ printf(" Errata Version Field : %d\n",
+ (__u8)log_data[SCAO_EVF]);
+ printf(" Point Version Field : %"PRIu16"\n",
+ (uint16_t)log_data[SCAO_PVF]);
+ printf(" Minor Version Field : %"PRIu16"\n",
+ (uint16_t)log_data[SCAO_MIVF]);
+ printf(" Major Version Field : %d\n",
+ (__u8)log_data[SCAO_MAVF]);
+ printf(" NVMe Errata Version : %d\n",
+ (__u8)log_data[SCAO_NEV]);
+ printf(" PCIe Link Retraining Count : %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PLRC]));
+ }
+ if (smart_log_ver > 3) {
+ printf(" Power State Change Count : %"PRIu64"\n",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PSCC]));
+ }
+ printf("\n");
+}
+
+static void wdc_print_smart_cloud_attr_C0_json(void *data)
+{
+ __u8 *log_data = (__u8 *)data;
+ struct json_object *root = json_create_object();
+ uint16_t smart_log_ver = 0;
+
+ json_object_add_value_uint128(root, "Physical media units written",
+ le128_to_cpu(&log_data[SCAO_PMUW]));
+ json_object_add_value_uint128(root, "Physical media units read",
+ le128_to_cpu(&log_data[SCAO_PMUR]));
+ json_object_add_value_uint64(root, "Bad user nand blocks - Raw",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_BUNBR] & 0x0000FFFFFFFFFFFF));
+ json_object_add_value_uint(root, "Bad user nand blocks - Normalized",
+ (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_BUNBN]));
+ json_object_add_value_uint64(root, "Bad system nand blocks - Raw",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_BSNBR] & 0x0000FFFFFFFFFFFF));
+ json_object_add_value_uint(root, "Bad system nand blocks - Normalized",
+ (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_BSNBN]));
+ json_object_add_value_uint64(root, "XOR recovery count",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_XRC]));
+ json_object_add_value_uint64(root, "Uncorrectable read error count",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_UREC]));
+ json_object_add_value_uint64(root, "Soft ecc error count",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_SEEC]));
+ json_object_add_value_uint(root, "End to end corrected errors",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_EECE]));
+ json_object_add_value_uint(root, "End to end detected errors",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_EEDC]));
+ json_object_add_value_uint(root, "System data percent used",
+ (__u8)log_data[SCAO_SDPU]);
+ json_object_add_value_uint64(root, "Refresh counts",
+ (uint64_t)(le64_to_cpu(*(uint64_t *)&log_data[SCAO_RFSC]) & 0x00FFFFFFFFFFFFFF));
+ json_object_add_value_uint(root, "Max User data erase counts",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_MXUDEC]));
+ json_object_add_value_uint(root, "Min User data erase counts",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_MNUDEC]));
+ json_object_add_value_uint(root, "Number of Thermal throttling events",
+ (__u8)log_data[SCAO_NTTE]);
+ json_object_add_value_uint(root, "Current throttling status",
+ (__u8)log_data[SCAO_CTS]);
+ json_object_add_value_uint64(root, "PCIe correctable error count",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PCEC]));
+ json_object_add_value_uint(root, "Incomplete shutdowns",
+ (uint32_t)le32_to_cpu(*(uint32_t *)&log_data[SCAO_ICS]));
+ json_object_add_value_uint(root, "Percent free blocks",
+ (__u8)log_data[SCAO_PFB]);
+ json_object_add_value_uint(root, "Capacitor health",
+ (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_CPH]));
+ json_object_add_value_uint64(root, "Unaligned I/O",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_UIO]));
+ json_object_add_value_uint64(root, "Security Version Number",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_SVN]));
+ json_object_add_value_uint64(root, "NUSE - Namespace utilization",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_NUSE]));
+ json_object_add_value_uint128(root, "PLP start count",
+ le128_to_cpu(&log_data[SCAO_PSC]));
+ json_object_add_value_uint128(root, "Endurance estimate",
+ le128_to_cpu(&log_data[SCAO_EEST]));
+ smart_log_ver = (uint16_t)le16_to_cpu(*(uint16_t *)&log_data[SCAO_LPV]);
+ json_object_add_value_uint(root, "Log page version", smart_log_ver);
+ char guid[40];
+
+ memset((void *)guid, 0, 40);
+ sprintf((char *)guid, "0x%"PRIx64"%"PRIx64"",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_LPG + 8]),
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_LPG]));
+ json_object_add_value_string(root, "Log page GUID", guid);
+ if (smart_log_ver > 2) {
+ json_object_add_value_uint(root, "Errata Version Field",
+ (__u8)log_data[SCAO_EVF]);
+ json_object_add_value_uint(root, "Point Version Field",
+ (uint16_t)log_data[SCAO_PVF]);
+ json_object_add_value_uint(root, "Minor Version Field",
+ (uint16_t)log_data[SCAO_MIVF]);
+ json_object_add_value_uint(root, "Major Version Field",
+ (__u8)log_data[SCAO_MAVF]);
+ json_object_add_value_uint(root, "NVMe Errata Version",
+ (__u8)log_data[SCAO_NEV]);
+ json_object_add_value_uint64(root, "PCIe Link Retraining Count",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PLRC]));
+ }
+ if (smart_log_ver > 3) {
+ json_object_add_value_uint64(root, "Power State Change Count",
+ (uint64_t)le64_to_cpu(*(uint64_t *)&log_data[SCAO_PSCC]));
+ }
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+}
+
+static void wdc_print_eol_c0_normal(void *data)
+{
+
+ __u8 *log_data = (__u8 *)data;
+
+ printf(" End of Life Log Page 0xC0 :-\n");
+
+ printf(" Realloc Block Count %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(log_data[EOL_RBC]));
+ printf(" ECC Rate %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(log_data[EOL_ECCR]));
+ printf(" Write Amp %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(log_data[EOL_WRA]));
+ printf(" Percent Life Remaining %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(log_data[EOL_PLR]));
+ printf(" Program Fail Count %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(log_data[EOL_PFC]));
+ printf(" Erase Fail Count %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(log_data[EOL_EFC]));
+ printf(" Raw Read Error Rate %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(log_data[EOL_RRER]));
+
+}
+
+static void wdc_print_eol_c0_json(void *data)
+{
+ __u8 *log_data = (__u8 *)data;
+ struct json_object *root = json_create_object();
+
+ json_object_add_value_uint(root, "Realloc Block Count",
+ (uint32_t)le32_to_cpu(log_data[EOL_RBC]));
+ json_object_add_value_uint(root, "ECC Rate",
+ (uint32_t)le32_to_cpu(log_data[EOL_ECCR]));
+ json_object_add_value_uint(root, "Write Amp",
+ (uint32_t)le32_to_cpu(log_data[EOL_WRA]));
+ json_object_add_value_uint(root, "Percent Life Remaining",
+ (uint32_t)le32_to_cpu(log_data[EOL_PLR]));
+ json_object_add_value_uint(root, "Program Fail Count",
+ (uint32_t)le32_to_cpu(log_data[EOL_PFC]));
+ json_object_add_value_uint(root, "Erase Fail Count",
+ (uint32_t)le32_to_cpu(log_data[EOL_EFC]));
+ json_object_add_value_uint(root, "Raw Read Error Rate",
+ (uint32_t)le32_to_cpu(log_data[EOL_RRER]));
+
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+}
+
+static int wdc_print_ext_smart_cloud_log(void *data, int fmt)
+{
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: Invalid buffer to read 0xC0 V1 log\n");
+ return -1;
+ }
+ switch (fmt) {
+ case NORMAL:
+ wdc_print_ext_smart_cloud_log_normal(data, WDC_SCA_V1_ALL);
+ break;
+ case JSON:
+ wdc_print_ext_smart_cloud_log_json(data, WDC_SCA_V1_ALL);
+ break;
+ }
+ return 0;
+}
+
+static int wdc_print_c0_cloud_attr_log(void *data, int fmt)
+{
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: Invalid buffer to read 0xC0 log\n");
+ return -1;
+ }
+ switch (fmt) {
+ case NORMAL:
+ wdc_print_smart_cloud_attr_C0_normal(data);
+ break;
+ case JSON:
+ wdc_print_smart_cloud_attr_C0_json(data);
+ break;
+ }
+ return 0;
+}
+
+static int wdc_print_c0_eol_log(void *data, int fmt)
+{
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: Invalid buffer to read 0xC0 log\n");
+ return -1;
+ }
+ switch (fmt) {
+ case NORMAL:
+ wdc_print_eol_c0_normal(data);
+ break;
+ case JSON:
+ wdc_print_eol_c0_json(data);
+ break;
+ }
+ return 0;
+}
+
+static int wdc_get_c0_log_page_sn_customer_id_0x100X(struct nvme_dev *dev, int uuid_index,
+ char *format, __u32 namespace_id, int fmt)
+{
+ int ret;
+ __u8 *data;
+ int i;
+
+ if (!uuid_index) {
+ data = (__u8 *)malloc(sizeof(__u8) * WDC_NVME_SMART_CLOUD_ATTR_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (namespace_id == NVME_NSID_ALL) {
+ ret = nvme_get_nsid(dev_fd(dev), &namespace_id);
+ if (ret < 0)
+ namespace_id = NVME_NSID_ALL;
+ }
+
+ /* Get the 0xC0 log data */
+ struct nvme_get_log_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .lid = WDC_NVME_GET_SMART_CLOUD_ATTR_LOG_ID,
+ .nsid = namespace_id,
+ .lpo = 0,
+ .lsp = NVME_LOG_LSP_NONE,
+ .lsi = 0,
+ .rae = false,
+ .uuidx = uuid_index,
+ .csi = NVME_CSI_NVM,
+ .ot = false,
+ .len = WDC_NVME_SMART_CLOUD_ATTR_LEN,
+ .log = data,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ };
+ ret = nvme_get_log(&args);
+
+ if (strcmp(format, "json"))
+ nvme_show_status(ret);
+
+ if (!ret) {
+ /* Verify GUID matches */
+ for (i = 0; i < 16; i++) {
+ if (scao_guid[i] != data[SCAO_LPG + i]) {
+ fprintf(stderr, "ERROR: WDC: Unknown GUID in C0 Log Page data\n");
+ int j;
+
+ fprintf(stderr, "ERROR: WDC: Expected GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", scao_guid[j]);
+ fprintf(stderr, "\nERROR: WDC: Actual GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", data[SCAO_LPG + j]);
+ fprintf(stderr, "\n");
+
+ ret = -1;
+ break;
+ }
+ }
+
+ if (!ret)
+ /* parse the data */
+ wdc_print_c0_cloud_attr_log(data, fmt);
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unable to read C0 Log Page data\n");
+ ret = -1;
+ }
+
+ free(data);
+ } else if (uuid_index == 1) {
+ data = (__u8 *)malloc(sizeof(__u8) * WDC_NVME_EOL_STATUS_LOG_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+
+ /* Get the 0xC0 log data */
+ struct nvme_get_log_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .lid = WDC_NVME_GET_EOL_STATUS_LOG_OPCODE,
+ .nsid = NVME_NSID_ALL,
+ .lpo = 0,
+ .lsp = NVME_LOG_LSP_NONE,
+ .lsi = 0,
+ .rae = false,
+ .uuidx = uuid_index,
+ .csi = NVME_CSI_NVM,
+ .ot = false,
+ .len = WDC_NVME_EOL_STATUS_LOG_LEN,
+ .log = data,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ };
+ ret = nvme_get_log(&args);
+
+ if (strcmp(format, "json"))
+ nvme_show_status(ret);
+
+ if (!ret) {
+ /* parse the data */
+ wdc_print_c0_eol_log(data, fmt);
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unable to read C0 Log Page data\n");
+ ret = -1;
+ }
+
+ free(data);
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unknown uuid index\n");
+ ret = -1;
+ }
+
+ return ret;
+}
+
+static int wdc_get_c0_log_page_sn(nvme_root_t r, struct nvme_dev *dev, int uuid_index, char *format,
+ __u32 namespace_id, int fmt)
+{
+ int ret = 0;
+ __u32 cust_id;
+ __u8 *data;
+
+ cust_id = wdc_get_fw_cust_id(r, dev);
+ if (cust_id == WDC_INVALID_CUSTOMER_ID) {
+ fprintf(stderr, "%s: ERROR: WDC: invalid customer id\n", __func__);
+ return -1;
+ }
+
+ if ((cust_id == WDC_CUSTOMER_ID_0x1004) || (cust_id == WDC_CUSTOMER_ID_0x1008) ||
+ (cust_id == WDC_CUSTOMER_ID_0x1005)) {
+ ret = wdc_get_c0_log_page_sn_customer_id_0x100X(dev, uuid_index, format,
+ namespace_id, fmt);
+ } else {
+ data = (__u8 *)malloc(sizeof(__u8) * WDC_NVME_EOL_STATUS_LOG_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+
+ /* Get the 0xC0 log data */
+ ret = nvme_get_log_simple(dev_fd(dev),
+ WDC_NVME_GET_EOL_STATUS_LOG_OPCODE,
+ WDC_NVME_EOL_STATUS_LOG_LEN,
+ data);
+
+ if (strcmp(format, "json"))
+ nvme_show_status(ret);
+
+ if (!ret) {
+ /* parse the data */
+ wdc_print_c0_eol_log(data, fmt);
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unable to read C0 Log Page data\n");
+ ret = -1;
+ }
+
+ free(data);
+ }
+
+ return ret;
+}
+
+static int wdc_get_c0_log_page(nvme_root_t r, struct nvme_dev *dev, char *format, int uuid_index,
+ __u32 namespace_id)
+{
+ uint32_t device_id, read_vendor_id;
+ enum nvme_print_flags fmt;
+ int ret;
+ __u8 *data;
+ __u8 log_id;
+ __u32 length;
+
+ if (!wdc_check_device(r, dev))
+ return -1;
+ ret = validate_output_format(format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC: invalid output format\n");
+ return ret;
+ }
+
+ ret = wdc_get_pci_ids(r, dev, &device_id, &read_vendor_id);
+
+ switch (device_id) {
+ case WDC_NVME_SN640_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN640_DEV_ID_1:
+ fallthrough;
+ case WDC_NVME_SN640_DEV_ID_2:
+ fallthrough;
+ case WDC_NVME_SN640_DEV_ID_3:
+ fallthrough;
+ case WDC_NVME_SN840_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN840_DEV_ID_1:
+ fallthrough;
+ case WDC_NVME_SN860_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN560_DEV_ID_1:
+ fallthrough;
+ case WDC_NVME_SN560_DEV_ID_2:
+ fallthrough;
+ case WDC_NVME_SN560_DEV_ID_3:
+ fallthrough;
+ case WDC_NVME_SN550_DEV_ID:
+ ret = wdc_get_c0_log_page_sn(r, dev, uuid_index, format, namespace_id, fmt);
+ break;
+
+ case WDC_NVME_SN650_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN650_DEV_ID_1:
+ fallthrough;
+ case WDC_NVME_SN650_DEV_ID_2:
+ fallthrough;
+ case WDC_NVME_SN650_DEV_ID_3:
+ fallthrough;
+ case WDC_NVME_SN650_DEV_ID_4:
+ fallthrough;
+ case WDC_NVME_SN655_DEV_ID:
+ if (uuid_index == 0) {
+ log_id = WDC_NVME_GET_SMART_CLOUD_ATTR_LOG_ID;
+ length = WDC_NVME_SMART_CLOUD_ATTR_LEN;
+ } else {
+ log_id = WDC_NVME_GET_EOL_STATUS_LOG_OPCODE;
+ length = WDC_NVME_EOL_STATUS_LOG_LEN;
+ }
+
+ data = (__u8 *)malloc(sizeof(__u8) * length);
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (namespace_id == NVME_NSID_ALL) {
+ ret = nvme_get_nsid(dev_fd(dev), &namespace_id);
+ if (ret < 0)
+ namespace_id = NVME_NSID_ALL;
+ }
+
+ /* Get the 0xC0 log data */
+ struct nvme_get_log_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .lid = log_id,
+ .nsid = namespace_id,
+ .lpo = 0,
+ .lsp = NVME_LOG_LSP_NONE,
+ .lsi = 0,
+ .rae = false,
+ .uuidx = uuid_index,
+ .csi = NVME_CSI_NVM,
+ .ot = false,
+ .len = length,
+ .log = data,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ };
+ ret = nvme_get_log(&args);
+
+ if (strcmp(format, "json"))
+ nvme_show_status(ret);
+
+ if (!ret) {
+ /* parse the data */
+ if (uuid_index == 0)
+ wdc_print_c0_cloud_attr_log(data, fmt);
+ else
+ wdc_print_c0_eol_log(data, fmt);
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unable to read C0 Log Page data ");
+ fprintf(stderr, "with uuid index %d\n", uuid_index);
+ ret = -1;
+ }
+ free(data);
+ break;
+
+ case WDC_NVME_ZN350_DEV_ID:
+ fallthrough;
+ case WDC_NVME_ZN350_DEV_ID_1:
+ data = (__u8 *)malloc(sizeof(__u8) * WDC_NVME_SMART_CLOUD_ATTR_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+
+ /* Get the 0xC0 log data */
+ ret = nvme_get_log_simple(dev_fd(dev),
+ WDC_NVME_GET_SMART_CLOUD_ATTR_LOG_ID,
+ WDC_NVME_SMART_CLOUD_ATTR_LEN, data);
+
+ if (strcmp(format, "json"))
+ nvme_show_status(ret);
+
+ if (!ret) {
+ /* parse the data */
+ wdc_print_c0_cloud_attr_log(data, fmt);
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unable to read C0 Log Page data\n");
+ ret = -1;
+ }
+
+ free(data);
+ break;
+ case WDC_NVME_SN820CL_DEV_ID:
+ /* Get the 0xC0 Extended Smart Cloud Attribute log data */
+ data = NULL;
+ ret = nvme_get_ext_smart_cloud_log(dev_fd(dev), &data,
+ uuid_index, namespace_id);
+
+ if (strcmp(format, "json"))
+ nvme_show_status(ret);
+
+ if (!ret) {
+ /* parse the data */
+ wdc_print_ext_smart_cloud_log(data, fmt);
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unable to read C0 Log Page V1 data\n");
+ ret = -1;
+ }
+
+ if (data)
+ free(data);
+ break;
+ default:
+ fprintf(stderr, "ERROR: WDC: Unknown device id - 0x%x\n", device_id);
+ ret = -1;
+ break;
+
+ }
+
+ return ret;
+}
+
+static int wdc_print_latency_monitor_log(struct nvme_dev *dev,
+ struct wdc_ssd_latency_monitor_log *log_data,
+ int fmt)
+{
+ if (!log_data) {
+ fprintf(stderr, "ERROR: WDC: Invalid C3 log data buffer\n");
+ return -1;
+ }
+ switch (fmt) {
+ case NORMAL:
+ wdc_print_latency_monitor_log_normal(dev, log_data);
+ break;
+ case JSON:
+ wdc_print_latency_monitor_log_json(log_data);
+ break;
+ }
+ return 0;
+}
+
+static int wdc_print_error_rec_log(struct wdc_ocp_c1_error_recovery_log *log_data, int fmt)
+{
+ if (!log_data) {
+ fprintf(stderr, "ERROR: WDC: Invalid C1 log data buffer\n");
+ return -1;
+ }
+ switch (fmt) {
+ case NORMAL:
+ wdc_print_error_rec_log_normal(log_data);
+ break;
+ case JSON:
+ wdc_print_error_rec_log_json(log_data);
+ break;
+ }
+ return 0;
+}
+
+static int wdc_print_dev_cap_log(struct wdc_ocp_C4_dev_cap_log *log_data, int fmt)
+{
+ if (!log_data) {
+ fprintf(stderr, "ERROR: WDC: Invalid C4 log data buffer\n");
+ return -1;
+ }
+ switch (fmt) {
+ case NORMAL:
+ wdc_print_dev_cap_log_normal(log_data);
+ break;
+ case JSON:
+ wdc_print_dev_cap_log_json(log_data);
+ break;
+ }
+ return 0;
+}
+
+static int wdc_print_unsupported_reqs_log(struct wdc_ocp_C5_unsupported_reqs *log_data, int fmt)
+{
+ if (!log_data) {
+ fprintf(stderr, "ERROR: WDC: Invalid C5 log data buffer\n");
+ return -1;
+ }
+ switch (fmt) {
+ case NORMAL:
+ wdc_print_unsupported_reqs_log_normal(log_data);
+ break;
+ case JSON:
+ wdc_print_unsupported_reqs_log_json(log_data);
+ break;
+ }
+ return 0;
+}
+
+static int wdc_print_fb_ca_log(struct wdc_ssd_ca_perf_stats *perf, int fmt)
+{
+ if (!perf) {
+ fprintf(stderr, "ERROR: WDC: Invalid buffer to read perf stats\n");
+ return -1;
+ }
+ switch (fmt) {
+ case NORMAL:
+ wdc_print_fb_ca_log_normal(perf);
+ break;
+ case JSON:
+ wdc_print_fb_ca_log_json(perf);
+ break;
+ }
+ return 0;
+}
+
+static int wdc_print_bd_ca_log(struct nvme_dev *dev, void *bd_data, int fmt)
+{
+ if (!bd_data) {
+ fprintf(stderr, "ERROR: WDC: Invalid buffer to read data\n");
+ return -1;
+ }
+ switch (fmt) {
+ case NORMAL:
+ wdc_print_bd_ca_log_normal(dev, bd_data);
+ break;
+ case JSON:
+ wdc_print_bd_ca_log_json(bd_data);
+ break;
+ default:
+ fprintf(stderr, "ERROR: WDC: Unknown format - %d\n", fmt);
+ return -1;
+ }
+ return 0;
+}
+
+static int wdc_print_d0_log(struct wdc_ssd_d0_smart_log *perf, int fmt)
+{
+ if (!perf) {
+ fprintf(stderr, "ERROR: WDC: Invalid buffer to read perf stats\n");
+ return -1;
+ }
+ switch (fmt) {
+ case NORMAL:
+ wdc_print_d0_log_normal(perf);
+ break;
+ case JSON:
+ wdc_print_d0_log_json(perf);
+ break;
+ }
+ return 0;
+}
+
+static int wdc_print_fw_act_history_log(__u8 *data, int num_entries, int fmt,
+ __u32 cust_id, __u32 vendor_id,
+ __u32 device_id)
+{
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: Invalid buffer to read fw activate history entries\n");
+ return -1;
+ }
+
+ switch (fmt) {
+ case NORMAL:
+ wdc_print_fw_act_history_log_normal(data, num_entries, cust_id,
+ vendor_id, device_id);
+ break;
+ case JSON:
+ wdc_print_fw_act_history_log_json(data, num_entries, cust_id,
+ vendor_id, device_id);
+ break;
+ }
+ return 0;
+}
+
+static int wdc_get_ca_log_page(nvme_root_t r, struct nvme_dev *dev, char *format)
+{
+ uint32_t read_device_id, read_vendor_id;
+ struct wdc_ssd_ca_perf_stats *perf;
+ enum nvme_print_flags fmt;
+ __u32 cust_id;
+ __u8 *data;
+ int ret;
+
+ if (!wdc_check_device(r, dev))
+ return -1;
+ ret = validate_output_format(format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC: invalid output format\n");
+ return ret;
+ }
+
+ /* verify the 0xCA log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev, WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE) == false) {
+ fprintf(stderr, "ERROR: WDC: 0xCA Log Page not supported\n");
+ return -1;
+ }
+
+ /* get the FW customer id */
+ cust_id = wdc_get_fw_cust_id(r, dev);
+ if (cust_id == WDC_INVALID_CUSTOMER_ID) {
+ fprintf(stderr, "%s: ERROR: WDC: invalid customer id\n", __func__);
+ return -1;
+ }
+
+ ret = wdc_get_pci_ids(r, dev, &read_device_id, &read_vendor_id);
+
+ switch (read_device_id) {
+ case WDC_NVME_SN200_DEV_ID:
+ if (cust_id == WDC_CUSTOMER_ID_0x1005) {
+ data = (__u8 *)malloc(sizeof(__u8) * WDC_FB_CA_LOG_BUF_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+
+ memset(data, 0, sizeof(__u8) * WDC_FB_CA_LOG_BUF_LEN);
+
+ ret = nvme_get_log_simple(dev_fd(dev),
+ WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE,
+ WDC_FB_CA_LOG_BUF_LEN, data);
+ if (strcmp(format, "json"))
+ nvme_show_status(ret);
+
+ if (!ret) {
+ /* parse the data */
+ perf = (struct wdc_ssd_ca_perf_stats *)(data);
+ ret = wdc_print_fb_ca_log(perf, fmt);
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unable to read CA Log Page data\n");
+ ret = -1;
+ }
+ } else {
+
+ fprintf(stderr, "ERROR: WDC: Unsupported Customer id, id = 0x%x\n", cust_id);
+ return -1;
+ }
+ break;
+ case WDC_NVME_SN640_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN640_DEV_ID_1:
+ fallthrough;
+ case WDC_NVME_SN640_DEV_ID_2:
+ fallthrough;
+ case WDC_NVME_SN640_DEV_ID_3:
+ fallthrough;
+ case WDC_NVME_SN840_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN840_DEV_ID_1:
+ fallthrough;
+ case WDC_NVME_SN860_DEV_ID:
+ if (cust_id == WDC_CUSTOMER_ID_0x1005) {
+ data = (__u8 *)malloc(sizeof(__u8) * WDC_FB_CA_LOG_BUF_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+
+ memset(data, 0, sizeof(__u8) * WDC_FB_CA_LOG_BUF_LEN);
+
+ ret = nvme_get_log_simple(dev_fd(dev),
+ WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE,
+ WDC_FB_CA_LOG_BUF_LEN, data);
+ if (strcmp(format, "json"))
+ nvme_show_status(ret);
+
+ if (!ret) {
+ /* parse the data */
+ perf = (struct wdc_ssd_ca_perf_stats *)(data);
+ ret = wdc_print_fb_ca_log(perf, fmt);
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unable to read CA Log Page data\n");
+ ret = -1;
+ }
+ } else if ((cust_id == WDC_CUSTOMER_ID_GN) || (cust_id == WDC_CUSTOMER_ID_GD) ||
+ (cust_id == WDC_CUSTOMER_ID_BD)) {
+ data = (__u8 *)malloc(sizeof(__u8) * WDC_BD_CA_LOG_BUF_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+
+ memset(data, 0, sizeof(__u8) * WDC_BD_CA_LOG_BUF_LEN);
+ ret = nvme_get_log_simple(dev_fd(dev),
+ WDC_NVME_GET_DEVICE_INFO_LOG_OPCODE,
+ WDC_BD_CA_LOG_BUF_LEN, data);
+ if (strcmp(format, "json"))
+ nvme_show_status(ret);
+
+ if (!ret) {
+ /* parse the data */
+ ret = wdc_print_bd_ca_log(dev, data, fmt);
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unable to read CA Log Page data\n");
+ ret = -1;
+ }
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unsupported Customer id, id = 0x%x\n", cust_id);
+ return -1;
+ }
+ break;
+ default:
+ fprintf(stderr, "ERROR: WDC: Log page 0xCA not supported for this device\n");
+ return -1;
+ }
+
+ free(data);
+ return ret;
+}
+
+static int wdc_get_c1_log_page(nvme_root_t r, struct nvme_dev *dev,
+ char *format, uint8_t interval)
+{
+ struct wdc_log_page_subpage_header *sph;
+ struct wdc_ssd_perf_stats *perf;
+ struct wdc_log_page_header *l;
+ enum nvme_print_flags fmt;
+ int total_subpages;
+ int skip_cnt = 4;
+ __u8 *data;
+ __u8 *p;
+ int i;
+ int ret;
+
+ if (!wdc_check_device(r, dev))
+ return -1;
+
+ ret = validate_output_format(format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC: invalid output format\n");
+ return ret;
+ }
+
+ if (interval < 1 || interval > 15) {
+ fprintf(stderr, "ERROR: WDC: interval out of range [1-15]\n");
+ return -1;
+ }
+
+ data = (__u8 *)malloc(sizeof(__u8) * WDC_ADD_LOG_BUF_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+ memset(data, 0, sizeof(__u8) * WDC_ADD_LOG_BUF_LEN);
+
+ ret = nvme_get_log_simple(dev_fd(dev), WDC_NVME_ADD_LOG_OPCODE,
+ WDC_ADD_LOG_BUF_LEN, data);
+ if (strcmp(format, "json"))
+ nvme_show_status(ret);
+ if (!ret) {
+ l = (struct wdc_log_page_header *)data;
+ total_subpages = l->num_subpages + WDC_NVME_GET_STAT_PERF_INTERVAL_LIFETIME - 1;
+ for (i = 0, p = data + skip_cnt; i < total_subpages; i++, p += skip_cnt) {
+ sph = (struct wdc_log_page_subpage_header *)p;
+ if (sph->spcode == WDC_GET_LOG_PAGE_SSD_PERFORMANCE) {
+ if (sph->pcset == interval) {
+ perf = (struct wdc_ssd_perf_stats *)(p + 4);
+ ret = wdc_print_log(perf, fmt);
+ break;
+ }
+ }
+ skip_cnt = le16_to_cpu(sph->subpage_length) + 4;
+ }
+ if (ret)
+ fprintf(stderr, "ERROR: WDC: Unable to read data from buffer\n");
+ }
+ free(data);
+ return ret;
+}
+
+static int wdc_get_c3_log_page(nvme_root_t r, struct nvme_dev *dev, char *format)
+{
+ struct wdc_ssd_latency_monitor_log *log_data;
+ enum nvme_print_flags fmt;
+ __u8 *data;
+ int ret;
+ int i;
+
+ if (!wdc_check_device(r, dev))
+ return -1;
+
+ ret = validate_output_format(format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC: invalid output format\n");
+ return ret;
+ }
+
+ data = (__u8 *)malloc(sizeof(__u8) * WDC_LATENCY_MON_LOG_BUF_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+ memset(data, 0, sizeof(__u8) * WDC_LATENCY_MON_LOG_BUF_LEN);
+
+ ret = nvme_get_log_simple(dev_fd(dev), WDC_LATENCY_MON_LOG_ID,
+ WDC_LATENCY_MON_LOG_BUF_LEN, data);
+
+ if (strcmp(format, "json"))
+ fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret, false), ret);
+
+ if (!ret) {
+ log_data = (struct wdc_ssd_latency_monitor_log *)data;
+
+ /* check log page version */
+ if (log_data->log_page_version != WDC_LATENCY_MON_VERSION) {
+ fprintf(stderr, "ERROR: WDC: invalid latency monitor version\n");
+ ret = -1;
+ goto out;
+ }
+
+ /* check log page guid */
+ /* Verify GUID matches */
+ for (i = 0; i < 16; i++) {
+ if (wdc_lat_mon_guid[i] != log_data->log_page_guid[i]) {
+ fprintf(stderr, "ERROR: WDC: Unknown GUID in C3 Log Page data\n");
+ int j;
+
+ fprintf(stderr, "ERROR: WDC: Expected GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", wdc_lat_mon_guid[j]);
+ fprintf(stderr, "\nERROR: WDC: Actual GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", log_data->log_page_guid[j]);
+ fprintf(stderr, "\n");
+
+ ret = -1;
+ goto out;
+ }
+ }
+
+ /* parse the data */
+ wdc_print_latency_monitor_log(dev, log_data, fmt);
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unable to read C3 data from buffer\n");
+ }
+
+out:
+ free(data);
+ return ret;
+
+}
+
+static int wdc_get_ocp_c1_log_page(nvme_root_t r, struct nvme_dev *dev, char *format)
+{
+ struct wdc_ocp_c1_error_recovery_log *log_data;
+ enum nvme_print_flags fmt;
+ __u8 *data;
+ int ret;
+ int i;
+
+ if (!wdc_check_device(r, dev))
+ return -1;
+
+ ret = validate_output_format(format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC: invalid output format\n");
+ return ret;
+ }
+
+ data = (__u8 *)malloc(sizeof(__u8) * WDC_ERROR_REC_LOG_BUF_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+ memset(data, 0, sizeof(__u8) * WDC_ERROR_REC_LOG_BUF_LEN);
+
+ ret = nvme_get_log_simple(dev_fd(dev), WDC_ERROR_REC_LOG_ID,
+ WDC_ERROR_REC_LOG_BUF_LEN, data);
+
+ if (strcmp(format, "json"))
+ fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret, false), ret);
+
+ if (!ret) {
+ log_data = (struct wdc_ocp_c1_error_recovery_log *)data;
+
+ /* check log page version */
+ if ((log_data->log_page_version != WDC_ERROR_REC_LOG_VERSION1) &&
+ (log_data->log_page_version != WDC_ERROR_REC_LOG_VERSION2)) {
+ fprintf(stderr, "ERROR: WDC: invalid error recovery log version - %d\n", log_data->log_page_version);
+ ret = -1;
+ goto out;
+ }
+
+ /* Verify GUID matches */
+ for (i = 0; i < WDC_OCP_C1_GUID_LENGTH; i++) {
+ if (wdc_ocp_c1_guid[i] != log_data->log_page_guid[i]) {
+ fprintf(stderr, "ERROR: WDC: Unknown GUID in C1 Log Page data\n");
+ int j;
+
+ fprintf(stderr, "ERROR: WDC: Expected GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", wdc_ocp_c1_guid[j]);
+ fprintf(stderr, "\nERROR: WDC: Actual GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", log_data->log_page_guid[j]);
+ fprintf(stderr, "\n");
+
+ ret = -1;
+ goto out;
+ }
+ }
+
+ /* parse the data */
+ wdc_print_error_rec_log(log_data, fmt);
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unable to read error recovery (C1) data from buffer\n");
+ }
+
+out:
+ free(data);
+ return ret;
+}
+
+static int wdc_get_ocp_c4_log_page(nvme_root_t r, struct nvme_dev *dev, char *format)
+{
+ struct wdc_ocp_C4_dev_cap_log *log_data;
+ enum nvme_print_flags fmt;
+ __u8 *data;
+ int ret;
+ int i;
+
+ if (!wdc_check_device(r, dev))
+ return -1;
+
+ ret = validate_output_format(format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC: invalid output format\n");
+ return ret;
+ }
+
+ data = (__u8 *)malloc(sizeof(__u8) * WDC_DEV_CAP_LOG_BUF_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+ memset(data, 0, sizeof(__u8) * WDC_DEV_CAP_LOG_BUF_LEN);
+
+ ret = nvme_get_log_simple(dev_fd(dev), WDC_DEV_CAP_LOG_ID,
+ WDC_DEV_CAP_LOG_BUF_LEN, data);
+
+ if (strcmp(format, "json"))
+ fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret, false), ret);
+
+ if (!ret) {
+ log_data = (struct wdc_ocp_C4_dev_cap_log *)data;
+
+ /* check log page version */
+ if (log_data->log_page_version != WDC_DEV_CAP_LOG_VERSION) {
+ fprintf(stderr, "ERROR: WDC: invalid device capabilities log version - %d\n", log_data->log_page_version);
+ ret = -1;
+ goto out;
+ }
+
+ /* Verify GUID matches */
+ for (i = 0; i < WDC_OCP_C4_GUID_LENGTH; i++) {
+ if (wdc_ocp_c4_guid[i] != log_data->log_page_guid[i]) {
+ fprintf(stderr, "ERROR: WDC: Unknown GUID in C4 Log Page data\n");
+ int j;
+
+ fprintf(stderr, "ERROR: WDC: Expected GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", wdc_ocp_c4_guid[j]);
+ fprintf(stderr, "\nERROR: WDC: Actual GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", log_data->log_page_guid[j]);
+ fprintf(stderr, "\n");
+
+ ret = -1;
+ goto out;
+ }
+ }
+
+ /* parse the data */
+ wdc_print_dev_cap_log(log_data, fmt);
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unable to read device capabilities (C4) data from buffer\n");
+ }
+
+out:
+ free(data);
+ return ret;
+}
+
+static int wdc_get_ocp_c5_log_page(nvme_root_t r, struct nvme_dev *dev, char *format)
+{
+ struct wdc_ocp_C5_unsupported_reqs *log_data;
+ enum nvme_print_flags fmt;
+ int ret;
+ __u8 *data;
+ int i;
+
+ if (!wdc_check_device(r, dev))
+ return -1;
+
+ ret = validate_output_format(format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC: invalid output format\n");
+ return ret;
+ }
+
+ data = (__u8 *)malloc(sizeof(__u8) * WDC_UNSUPPORTED_REQS_LOG_BUF_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+ memset(data, 0, sizeof(__u8) * WDC_UNSUPPORTED_REQS_LOG_BUF_LEN);
+
+ ret = nvme_get_log_simple(dev_fd(dev), WDC_UNSUPPORTED_REQS_LOG_ID,
+ WDC_UNSUPPORTED_REQS_LOG_BUF_LEN, data);
+
+ if (strcmp(format, "json"))
+ fprintf(stderr, "NVMe Status:%s(%x)\n", nvme_status_to_string(ret, false), ret);
+
+ if (!ret) {
+ log_data = (struct wdc_ocp_C5_unsupported_reqs *)data;
+
+ /* check log page version */
+ if (log_data->log_page_version != WDC_UNSUPPORTED_REQS_LOG_VERSION) {
+ fprintf(stderr, "ERROR: WDC: invalid unsupported requirements log version - %d\n", log_data->log_page_version);
+ ret = -1;
+ goto out;
+ }
+
+ /* Verify GUID matches */
+ for (i = 0; i < WDC_OCP_C5_GUID_LENGTH; i++) {
+ if (wdc_ocp_c5_guid[i] != log_data->log_page_guid[i]) {
+ fprintf(stderr, "ERROR: WDC: Unknown GUID in C5 Log Page data\n");
+ int j;
+
+ fprintf(stderr, "ERROR: WDC: Expected GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", wdc_ocp_c5_guid[j]);
+ fprintf(stderr, "\nERROR: WDC: Actual GUID: 0x");
+ for (j = 0; j < 16; j++)
+ fprintf(stderr, "%x", log_data->log_page_guid[j]);
+ fprintf(stderr, "\n");
+
+ ret = -1;
+ goto out;
+ }
+ }
+
+ /* parse the data */
+ wdc_print_unsupported_reqs_log(log_data, fmt);
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unable to read unsupported requirements (C5) data from buffer\n");
+ }
+
+out:
+ free(data);
+ return ret;
+}
+
+static int wdc_get_d0_log_page(nvme_root_t r, struct nvme_dev *dev, char *format)
+{
+ struct wdc_ssd_d0_smart_log *perf;
+ enum nvme_print_flags fmt;
+ int ret = 0;
+ __u8 *data;
+
+ if (!wdc_check_device(r, dev))
+ return -1;
+
+ ret = validate_output_format(format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC: invalid output format\n");
+ return ret;
+ }
+
+ /* verify the 0xD0 log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev, WDC_NVME_GET_VU_SMART_LOG_OPCODE) == false) {
+ fprintf(stderr, "ERROR: WDC: 0xD0 Log Page not supported\n");
+ return -1;
+ }
+
+ data = (__u8 *)malloc(sizeof(__u8) * WDC_NVME_VU_SMART_LOG_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+ memset(data, 0, sizeof(__u8) * WDC_NVME_VU_SMART_LOG_LEN);
+
+ ret = nvme_get_log_simple(dev_fd(dev),
+ WDC_NVME_GET_VU_SMART_LOG_OPCODE,
+ WDC_NVME_VU_SMART_LOG_LEN, data);
+ if (strcmp(format, "json"))
+ nvme_show_status(ret);
+
+ if (!ret) {
+ /* parse the data */
+ perf = (struct wdc_ssd_d0_smart_log *)(data);
+ ret = wdc_print_d0_log(perf, fmt);
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unable to read D0 Log Page data\n");
+ ret = -1;
+ }
+
+ free(data);
+ return ret;
+}
+
+static long double le_to_float(__u8 *data, int byte_len)
+{
+ long double result = 0;
+ int i;
+
+ for (i = 0; i < byte_len; i++) {
+ result *= 256;
+ result += data[15 - i];
+ }
+
+ return result;
+}
+
+static void stringify_log_page_guid(__u8 *guid, char *buf)
+{
+ char *ptr = buf;
+ int i;
+
+ memset(buf, 0, sizeof(char) * 19);
+
+ ptr += sprintf(ptr, "0x");
+ for (i = 0; i < 16; i++)
+ ptr += sprintf(ptr, "%x", guid[15 - i]);
+}
+
+static const char *const cloud_smart_log_thermal_status[] = {
+ [0x00] = "unthrottled",
+ [0x01] = "first_level",
+ [0x02] = "second_level",
+ [0x03] = "third_level",
+};
+
+static const char *stringify_cloud_smart_log_thermal_status(__u8 status)
+{
+ if (status < ARRAY_SIZE(cloud_smart_log_thermal_status) &&
+ cloud_smart_log_thermal_status[status])
+ return cloud_smart_log_thermal_status[status];
+ return "unrecognized";
+}
+
+static void show_cloud_smart_log_json(struct ocp_cloud_smart_log *log)
+{
+ struct json_object *root;
+ struct json_object *bad_user_nand_blocks;
+ struct json_object *bad_system_nand_blocks;
+ struct json_object *e2e_correction_counts;
+ struct json_object *user_data_erase_counts;
+ struct json_object *thermal_status;
+ struct json_object *dssd_specific_ver;
+ char buf[2 * sizeof(log->log_page_guid) + 3];
+
+ bad_user_nand_blocks = json_create_object();
+ json_object_add_value_uint(bad_user_nand_blocks, "normalized",
+ le16_to_cpu(log->bad_user_nand_blocks.normalized));
+ json_object_add_value_uint(bad_user_nand_blocks, "raw",
+ le64_to_cpu(log->bad_user_nand_blocks.raw));
+
+ bad_system_nand_blocks = json_create_object();
+ json_object_add_value_uint(bad_system_nand_blocks, "normalized",
+ le16_to_cpu(log->bad_system_nand_blocks.normalized));
+ json_object_add_value_uint(bad_system_nand_blocks, "raw",
+ le64_to_cpu(log->bad_system_nand_blocks.raw));
+
+ e2e_correction_counts = json_create_object();
+ json_object_add_value_uint(e2e_correction_counts, "corrected",
+ le32_to_cpu(log->e2e_correction_counts.corrected));
+ json_object_add_value_uint(e2e_correction_counts, "detected",
+ le32_to_cpu(log->e2e_correction_counts.detected));
+
+ user_data_erase_counts = json_create_object();
+ json_object_add_value_uint(user_data_erase_counts, "minimum",
+ le32_to_cpu(log->user_data_erase_counts.minimum));
+ json_object_add_value_uint(user_data_erase_counts, "maximum",
+ le32_to_cpu(log->user_data_erase_counts.maximum));
+
+ thermal_status = json_create_object();
+ json_object_add_value_string(thermal_status, "current_status",
+ stringify_cloud_smart_log_thermal_status(log->thermal_status.current_status));
+ json_object_add_value_uint(thermal_status, "num_events",
+ log->thermal_status.num_events);
+
+ dssd_specific_ver = json_create_object();
+ json_object_add_value_uint(dssd_specific_ver, "major_ver",
+ log->dssd_specific_ver.major_ver);
+ json_object_add_value_uint(dssd_specific_ver, "minor_ver",
+ le16_to_cpu(log->dssd_specific_ver.minor_ver));
+ json_object_add_value_uint(dssd_specific_ver, "point_ver",
+ le16_to_cpu(log->dssd_specific_ver.point_ver));
+ json_object_add_value_uint(dssd_specific_ver, "errata_ver",
+ log->dssd_specific_ver.errata_ver);
+
+ root = json_create_object();
+ json_object_add_value_uint64(root, "physical_media_units_written",
+ le_to_float(log->physical_media_units_written, 16));
+ json_object_add_value_uint64(root, "physical_media_units_read",
+ le_to_float(log->physical_media_units_read, 16));
+ json_object_add_value_object(root, "bad_user_nand_blocks",
+ bad_user_nand_blocks);
+ json_object_add_value_object(root, "bad_system_nand_blocks",
+ bad_system_nand_blocks);
+ json_object_add_value_uint(root, "xor_recovery_count",
+ le64_to_cpu(log->xor_recovery_count));
+ json_object_add_value_uint(root, "uncorrectable_read_error_count",
+ le64_to_cpu(log->uncorrectable_read_error_count));
+ json_object_add_value_uint(root, "soft_ecc_error_count",
+ le64_to_cpu(log->soft_ecc_error_count));
+ json_object_add_value_object(root, "e2e_correction_counts",
+ e2e_correction_counts);
+ json_object_add_value_uint(root, "system_data_percent_used",
+ log->system_data_percent_used);
+ json_object_add_value_uint(root, "refresh_counts",
+ le64_to_cpu(log->refresh_counts));
+ json_object_add_value_object(root, "user_data_erase_counts",
+ user_data_erase_counts);
+ json_object_add_value_object(root, "thermal_status", thermal_status);
+ json_object_add_value_object(root, "dssd_specific_ver",
+ dssd_specific_ver);
+ json_object_add_value_uint(root, "pcie_correctable_error_count",
+ le64_to_cpu(log->pcie_correctable_error_count));
+ json_object_add_value_uint(root, "incomplete_shutdowns",
+ le32_to_cpu(log->incomplete_shutdowns));
+ json_object_add_value_uint(root, "percent_free_blocks",
+ log->percent_free_blocks);
+ json_object_add_value_uint(root, "capacitor_health",
+ le16_to_cpu(log->capacitor_health));
+ sprintf(buf, "%c", log->nvme_errata_ver);
+ json_object_add_value_string(root, "nvme_errata_version", buf);
+ json_object_add_value_uint(root, "unaligned_io",
+ le64_to_cpu(log->unaligned_io));
+ json_object_add_value_uint(root, "security_version_number",
+ le64_to_cpu(log->security_version_number));
+ json_object_add_value_uint(root, "total_nuse",
+ le64_to_cpu(log->total_nuse));
+ json_object_add_value_uint64(root, "plp_start_count",
+ le_to_float(log->plp_start_count, 16));
+ json_object_add_value_uint64(root, "endurance_estimate",
+ le_to_float(log->endurance_estimate, 16));
+ json_object_add_value_uint(root, "pcie_link_retraining_count",
+ le64_to_cpu(log->pcie_link_retraining_cnt));
+ json_object_add_value_uint(root, "power_state_change_count",
+ le64_to_cpu(log->power_state_change_cnt));
+ json_object_add_value_uint(root, "log_page_version",
+ le16_to_cpu(log->log_page_version));
+ stringify_log_page_guid(log->log_page_guid, buf);
+ json_object_add_value_string(root, "log_page_guid", buf);
+
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+}
+
+static void show_cloud_smart_log_normal(struct ocp_cloud_smart_log *log, struct nvme_dev *dev)
+{
+ char buf[2 * sizeof(log->log_page_guid) + 3];
+
+ printf("Smart Extended Log for NVME device:%s\n", dev->name);
+ printf("Physical Media Units Written : %'.0Lf\n",
+ le_to_float(log->physical_media_units_written, 16));
+ printf("Physical Media Units Read : %'.0Lf\n",
+ le_to_float(log->physical_media_units_read, 16));
+ printf("Bad User NAND Blocks (Normalized) : %" PRIu16 "%%\n",
+ le16_to_cpu(log->bad_user_nand_blocks.normalized));
+ printf("Bad User NAND Blocks (Raw) : %" PRIu64 "\n",
+ le64_to_cpu(log->bad_user_nand_blocks.raw));
+ printf("Bad System NAND Blocks (Normalized) : %" PRIu16 "%%\n",
+ le16_to_cpu(log->bad_system_nand_blocks.normalized));
+ printf("Bad System NAND Blocks (Raw) : %" PRIu64 "\n",
+ le64_to_cpu(log->bad_system_nand_blocks.raw));
+ printf("XOR Recovery Count : %" PRIu64 "\n",
+ le64_to_cpu(log->xor_recovery_count));
+ printf("Uncorrectable Read Error Count : %" PRIu64 "\n",
+ le64_to_cpu(log->uncorrectable_read_error_count));
+ printf("Soft ECC Error Count : %" PRIu64 "\n",
+ le64_to_cpu(log->soft_ecc_error_count));
+ printf("End to End Correction Counts (Corrected) : %" PRIu32 "\n",
+ le32_to_cpu(log->e2e_correction_counts.corrected));
+ printf("End to End Correction Counts (Detected) : %" PRIu32 "\n",
+ le32_to_cpu(log->e2e_correction_counts.detected));
+ printf("System Data %% Used : %" PRIu8 "%%\n",
+ log->system_data_percent_used);
+ printf("Refresh Counts : %" PRIu64 "\n",
+ le64_to_cpu(log->refresh_counts));
+ printf("User Data Erase Counts (Minimum) : %" PRIu32 "\n",
+ le32_to_cpu(log->user_data_erase_counts.minimum));
+ printf("User Data Erase Counts (Maximum) : %" PRIu32 "\n",
+ le32_to_cpu(log->user_data_erase_counts.maximum));
+ printf("Thermal Throttling Status (Current Status) : %s\n",
+ stringify_cloud_smart_log_thermal_status(log->thermal_status.current_status));
+ printf("Thermal Throttling Status (Number of Events) : %" PRIu8 "\n",
+ log->thermal_status.num_events);
+ printf("NVMe Major Version : %" PRIu8 "\n",
+ log->dssd_specific_ver.major_ver);
+ printf(" Minor Version : %" PRIu16 "\n",
+ le16_to_cpu(log->dssd_specific_ver.minor_ver));
+ printf(" Point Version : %" PRIu16 "\n",
+ le16_to_cpu(log->dssd_specific_ver.point_ver));
+ printf(" Errata Version : %" PRIu8 "\n",
+ log->dssd_specific_ver.errata_ver);
+ printf("PCIe Correctable Error Count : %" PRIu64 "\n",
+ le64_to_cpu(log->pcie_correctable_error_count));
+ printf("Incomplete Shutdowns : %" PRIu32 "\n",
+ le32_to_cpu(log->incomplete_shutdowns));
+ printf("%% Free Blocks : %" PRIu8 "%%\n",
+ log->percent_free_blocks);
+ printf("Capacitor Health : %" PRIu16 "%%\n",
+ le16_to_cpu(log->capacitor_health));
+ printf("NVMe Errata Version : %c\n",
+ log->nvme_errata_ver);
+ printf("Unaligned IO : %" PRIu64 "\n",
+ le64_to_cpu(log->unaligned_io));
+ printf("Security Version Number : %" PRIu64 "\n",
+ le64_to_cpu(log->security_version_number));
+ printf("Total NUSE : %" PRIu64 "\n",
+ le64_to_cpu(log->total_nuse));
+ printf("PLP Start Count : %'.0Lf\n",
+ le_to_float(log->plp_start_count, 16));
+ printf("Endurance Estimate : %'.0Lf\n",
+ le_to_float(log->endurance_estimate, 16));
+ printf("PCIe Link Retraining Count : %" PRIu64 "\n",
+ le64_to_cpu(log->pcie_link_retraining_cnt));
+ printf("Power State Change Count : %" PRIu64 "\n",
+ le64_to_cpu(log->power_state_change_cnt));
+ printf("Log Page Version : %" PRIu16 "\n",
+ le16_to_cpu(log->log_page_version));
+ stringify_log_page_guid(log->log_page_guid, buf);
+ printf("Log Page GUID : %s\n", buf);
+ printf("\n\n");
+}
+
+static int wdc_vs_smart_add_log(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Retrieve additional performance statistics.";
+ const char *interval = "Interval to read the statistics from [1, 15].";
+ const char *log_page_version = "Log Page Version: 0 = vendor, 1 = WDC";
+ const char *log_page_mask = "Log Page Mask, comma separated list: 0xC0, 0xC1, 0xCA, 0xD0";
+ const char *namespace_id = "desired namespace id";
+ enum nvme_print_flags fmt;
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ int ret = 0;
+ int uuid_index = 0;
+ int page_mask = 0, num, i;
+ int log_page_list[16];
+ __u64 capabilities = 0;
+ __u32 device_id, read_vendor_id;
+
+ struct config {
+ uint8_t interval;
+ char *output_format;
+ __u8 log_page_version;
+ char *log_page_mask;
+ __u32 namespace_id;
+ };
+
+ struct config cfg = {
+ .interval = 14,
+ .output_format = "normal",
+ .log_page_version = 0,
+ .log_page_mask = "",
+ .namespace_id = NVME_NSID_ALL,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("interval", 'i', &cfg.interval, interval),
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_BYTE("log-page-version", 'l', &cfg.log_page_version, log_page_version),
+ OPT_LIST("log-page-mask", 'p', &cfg.log_page_mask, log_page_mask),
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+ if (!cfg.log_page_version) {
+ uuid_index = 0;
+ } else if (cfg.log_page_version == 1) {
+ uuid_index = 1;
+ } else {
+ fprintf(stderr, "ERROR: WDC: unsupported log page version for this command\n");
+ ret = -1;
+ goto out;
+ }
+
+ num = argconfig_parse_comma_sep_array(cfg.log_page_mask, log_page_list, 16);
+
+ if (num == -1) {
+ fprintf(stderr, "ERROR: WDC: log page list is malformed\n");
+ ret = -1;
+ goto out;
+ }
+
+ if (!num) {
+ page_mask |= WDC_ALL_PAGE_MASK;
+ } else {
+ for (i = 0; i < num; i++) {
+ if (log_page_list[i] == 0xc0)
+ page_mask |= WDC_C0_PAGE_MASK;
+ if (log_page_list[i] == 0xc1)
+ page_mask |= WDC_C1_PAGE_MASK;
+ if (log_page_list[i] == 0xca)
+ page_mask |= WDC_CA_PAGE_MASK;
+ if (log_page_list[i] == 0xd0)
+ page_mask |= WDC_D0_PAGE_MASK;
+ }
+ }
+
+ if (!page_mask)
+ fprintf(stderr, "ERROR: WDC: Unknown log page mask - %s\n", cfg.log_page_mask);
+
+ ret = wdc_get_pci_ids(r, dev, &device_id, &read_vendor_id);
+
+ capabilities = wdc_get_drive_capabilities(r, dev);
+ if (!(capabilities & WDC_DRIVE_CAP_SMART_LOG_MASK)) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ goto out;
+ }
+
+ if (((capabilities & WDC_DRIVE_CAP_C0_LOG_PAGE) == WDC_DRIVE_CAP_C0_LOG_PAGE) &&
+ (page_mask & WDC_C0_PAGE_MASK)) {
+ /* Get 0xC0 log page if possible. */
+ if (!wdc_is_sn861(device_id)) {
+ ret = wdc_get_c0_log_page(r, dev, cfg.output_format,
+ uuid_index, cfg.namespace_id);
+ if (ret)
+ fprintf(stderr,
+ "ERROR: WDC: Failure reading the C0 Log Page, ret = %d\n",
+ ret);
+ } else {
+ struct ocp_cloud_smart_log log;
+ char buf[2 * sizeof(log.log_page_guid) + 3];
+
+ ret = validate_output_format(cfg.output_format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "Invalid output format: %s\n", cfg.output_format);
+ goto out;
+ }
+
+ ret = nvme_get_log_simple(dev_fd(dev),
+ WDC_NVME_GET_SMART_CLOUD_ATTR_LOG_ID,
+ sizeof(log), &log);
+ if (!ret) {
+ char *ptr = buf;
+ int i;
+ __u8 *guid = log.log_page_guid;
+
+ memset(buf, 0, sizeof(char) * 19);
+
+ ptr += sprintf(ptr, "0x");
+ for (i = 0; i < 16; i++)
+ ptr += sprintf(ptr, "%x", guid[15 - i]);
+ if (strcmp(buf, "0xafd514c97c6f4f9ca4f2bfea2810afc5"))
+ fprintf(stderr, "Invalid GUID: %s\n", buf);
+ else {
+ if (fmt == BINARY)
+ d_raw((unsigned char *)&log, sizeof(log));
+ else if (fmt == JSON)
+ show_cloud_smart_log_json(&log);
+ else
+ show_cloud_smart_log_normal(&log, dev);
+ }
+ } else if (ret > 0) {
+ nvme_show_status(ret);
+ } else {
+ perror("vs-smart-add-log");
+ }
+ }
+ }
+ if (((capabilities & (WDC_DRIVE_CAP_CA_LOG_PAGE)) == (WDC_DRIVE_CAP_CA_LOG_PAGE)) &&
+ (page_mask & WDC_CA_PAGE_MASK) &&
+ (!wdc_is_sn861(device_id))) {
+ /* Get the CA Log Page */
+ ret = wdc_get_ca_log_page(r, dev, cfg.output_format);
+ if (ret)
+ fprintf(stderr, "ERROR: WDC: Failure reading the CA Log Page, ret = %d\n", ret);
+ }
+ if (((capabilities & WDC_DRIVE_CAP_C1_LOG_PAGE) == WDC_DRIVE_CAP_C1_LOG_PAGE) &&
+ (page_mask & WDC_C1_PAGE_MASK)) {
+ /* Get the C1 Log Page */
+ ret = wdc_get_c1_log_page(r, dev, cfg.output_format,
+ cfg.interval);
+ if (ret)
+ fprintf(stderr, "ERROR: WDC: Failure reading the C1 Log Page, ret = %d\n", ret);
+ }
+ if (((capabilities & WDC_DRIVE_CAP_D0_LOG_PAGE) == WDC_DRIVE_CAP_D0_LOG_PAGE) &&
+ (page_mask & WDC_D0_PAGE_MASK)) {
+ /* Get the D0 Log Page */
+ ret = wdc_get_d0_log_page(r, dev, cfg.output_format);
+ if (ret)
+ fprintf(stderr, "ERROR: WDC: Failure reading the D0 Log Page, ret = %d\n", ret);
+ }
+
+out:
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_vs_cloud_log(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Retrieve Cloud Log Smart/Health Information";
+ const char *namespace_id = "desired namespace id";
+ enum nvme_print_flags fmt;
+ __u64 capabilities = 0;
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ int ret;
+ __u8 *data;
+
+ struct config {
+ char *output_format;
+ __u32 namespace_id;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ .namespace_id = NVME_NSID_ALL,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"),
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+
+ capabilities = wdc_get_drive_capabilities(r, dev);
+
+ if (!(capabilities & WDC_DRIVE_CAP_CLOUD_LOG_PAGE)) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ goto out;
+ }
+
+ data = NULL;
+ ret = nvme_get_ext_smart_cloud_log(dev_fd(dev), &data, 0,
+ cfg.namespace_id);
+
+ if (strcmp(cfg.output_format, "json"))
+ nvme_show_status(ret);
+
+ if (!ret) {
+ ret = validate_output_format(cfg.output_format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC %s: invalid output format\n", __func__);
+ } else {
+ /* parse the data */
+ wdc_print_ext_smart_cloud_log(data, fmt);
+ }
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unable to read C0 Log Page V1 data\n");
+ ret = -1;
+ }
+
+ if (data)
+ free(data);
+
+out:
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_vs_hw_rev_log(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Retrieve Hardware Revision Log Information";
+ const char *namespace_id = "desired namespace id";
+ enum nvme_print_flags fmt;
+ __u64 capabilities = 0;
+ struct nvme_dev *dev;
+ int ret;
+ __u8 *data = NULL;
+ nvme_root_t r;
+
+ struct config {
+ char *output_format;
+ __u32 namespace_id;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ .namespace_id = NVME_NSID_ALL,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"),
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+
+ capabilities = wdc_get_drive_capabilities(r, dev);
+
+ if (!(capabilities & WDC_DRIVE_CAP_HW_REV_LOG_PAGE)) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ goto out;
+ }
+
+ ret = nvme_get_hw_rev_log(dev_fd(dev), &data, 0, cfg.namespace_id);
+
+ if (strcmp(cfg.output_format, "json"))
+ nvme_show_status(ret);
+
+ if (!ret) {
+ ret = validate_output_format(cfg.output_format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC %s: invalid output format\n", __func__);
+ goto free_buf;
+ }
+
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: Invalid buffer to read Hardware Revision log\n");
+ ret = -1;
+ goto out;
+ }
+ switch (fmt) {
+ case NORMAL:
+ wdc_print_hw_rev_log_normal(data);
+ break;
+ case JSON:
+ wdc_print_hw_rev_log_json(data);
+ break;
+ default:
+ break;
+ }
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unable to read Hardware Revision Log Page data\n");
+ ret = -1;
+ }
+
+free_buf:
+ if (data)
+ free(data);
+
+out:
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_vs_device_waf(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Retrieve Device Write Amplication Factor";
+ const char *namespace_id = "desired namespace id";
+ struct nvme_smart_log smart_log;
+ enum nvme_print_flags fmt;
+ struct nvme_dev *dev;
+ __u8 *data;
+ nvme_root_t r;
+ int ret = 0;
+ __u64 capabilities = 0;
+ struct __packed wdc_nvme_ext_smart_log * ext_smart_log_ptr;
+ long double data_units_written = 0,
+ phys_media_units_written_tlc = 0,
+ phys_media_units_written_slc = 0;
+ struct json_object *root = NULL;
+ char tlc_waf_str[32] = { 0 },
+ slc_waf_str[32] = { 0 };
+
+ struct config {
+ char *output_format;
+ __u32 namespace_id;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ .namespace_id = NVME_NSID_ALL,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"),
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+
+ capabilities = wdc_get_drive_capabilities(r, dev);
+
+ if (!(capabilities & WDC_DRIVE_CAP_DEVICE_WAF)) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ goto out;
+ }
+
+ /* get data units written from the smart log page */
+ ret = nvme_get_log_smart(dev_fd(dev), cfg.namespace_id, false,
+ &smart_log);
+ if (!ret) {
+ data_units_written = int128_to_double(smart_log.data_units_written);
+ } else if (ret > 0) {
+ nvme_show_status(ret);
+ ret = -1;
+ goto out;
+ } else {
+ fprintf(stderr, "smart log: %s\n", nvme_strerror(errno));
+ ret = -1;
+ goto out;
+ }
+
+ /* get Physical Media Units Written from extended smart/C0 log page */
+ data = NULL;
+ ret = nvme_get_ext_smart_cloud_log(dev_fd(dev), &data, 0,
+ cfg.namespace_id);
+
+ if (!ret) {
+ ext_smart_log_ptr = (struct __packed wdc_nvme_ext_smart_log *)data;
+ phys_media_units_written_tlc = int128_to_double(ext_smart_log_ptr->ext_smart_pmuwt);
+ phys_media_units_written_slc = int128_to_double(ext_smart_log_ptr->ext_smart_pmuws);
+
+ if (data)
+ free(data);
+ } else {
+ fprintf(stderr, "ERROR: WDC %s: get smart cloud log failure\n", __func__);
+ ret = -1;
+ goto out;
+ }
+
+ if (strcmp(cfg.output_format, "json"))
+ nvme_show_status(ret);
+
+ ret = validate_output_format(cfg.output_format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC %s: invalid output format\n", __func__);
+ goto out;
+ }
+
+ if (!data_units_written) {
+ fprintf(stderr, "ERROR: WDC %s: 0 data units written\n", __func__);
+ ret = -1;
+ goto out;
+ }
+
+ if (fmt == NORMAL) {
+ printf("Device Write Amplification Factor TLC : %4.2Lf\n",
+ (phys_media_units_written_tlc/data_units_written));
+ printf("Device Write Amplification Factor SLC : %4.2Lf\n",
+ (phys_media_units_written_slc/data_units_written));
+ } else if (fmt == JSON) {
+ root = json_create_object();
+ sprintf(tlc_waf_str, "%4.2Lf", (phys_media_units_written_tlc/data_units_written));
+ sprintf(slc_waf_str, "%4.2Lf", (phys_media_units_written_slc/data_units_written));
+
+ json_object_add_value_string(root, "Device Write Amplification Factor TLC", tlc_waf_str);
+ json_object_add_value_string(root, "Device Write Amplification Factor SLC", slc_waf_str);
+
+ json_print_object(root, NULL);
+ printf("\n");
+
+ json_free_object(root);
+ }
+
+out:
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_get_latency_monitor_log(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Retrieve latency monitor log data.";
+ __u64 capabilities = 0;
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ int ret = 0;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+
+ if (!(capabilities & WDC_DRIVE_CAP_C3_LOG_PAGE)) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ goto out;
+ }
+
+ ret = wdc_get_c3_log_page(r, dev, cfg.output_format);
+ if (ret)
+ fprintf(stderr, "ERROR: WDC: Failure reading the Latency Monitor (C3) Log Page, ret = %d\n", ret);
+
+out:
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_get_error_recovery_log(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Retrieve error recovery log data.";
+ __u64 capabilities = 0;
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ int ret = 0;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+
+ if (!(capabilities & WDC_DRIVE_CAP_OCP_C1_LOG_PAGE)) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ goto out;
+ }
+
+ ret = wdc_get_ocp_c1_log_page(r, dev, cfg.output_format);
+ if (ret)
+ fprintf(stderr, "ERROR: WDC: Failure reading the Error Recovery (C1) Log Page, ret = 0x%x\n", ret);
+
+out:
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_get_dev_capabilities_log(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Retrieve device capabilities log data.";
+ __u64 capabilities = 0;
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ int ret = 0;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+
+ if (!(capabilities & WDC_DRIVE_CAP_OCP_C4_LOG_PAGE)) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ goto out;
+ }
+
+ ret = wdc_get_ocp_c4_log_page(r, dev, cfg.output_format);
+ if (ret)
+ fprintf(stderr, "ERROR: WDC: Failure reading the Device Capabilities (C4) Log Page, ret = 0x%x\n", ret);
+
+out:
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_get_unsupported_reqs_log(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Retrieve unsupported requirements log data.";
+ __u64 capabilities = 0;
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ int ret = 0;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+
+ if (!(capabilities & WDC_DRIVE_CAP_OCP_C5_LOG_PAGE)) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ goto out;
+ }
+
+ ret = wdc_get_ocp_c5_log_page(r, dev, cfg.output_format);
+ if (ret)
+ fprintf(stderr, "ERROR: WDC: Failure reading the Unsupported Requirements (C5) Log Page, ret = 0x%x\n", ret);
+
+out:
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_do_clear_pcie_correctable_errors(int fd)
+{
+ int ret;
+ struct nvme_passthru_cmd admin_cmd;
+
+ memset(&admin_cmd, 0, sizeof(admin_cmd));
+ admin_cmd.opcode = WDC_NVME_CLEAR_PCIE_CORR_OPCODE;
+ admin_cmd.cdw12 = ((WDC_NVME_CLEAR_PCIE_CORR_SUBCMD << WDC_NVME_SUBCMD_SHIFT) |
+ WDC_NVME_CLEAR_PCIE_CORR_CMD);
+
+ ret = nvme_submit_admin_passthru(fd, &admin_cmd, NULL);
+ nvme_show_status(ret);
+ return ret;
+}
+
+static int wdc_do_clear_pcie_correctable_errors_vuc(int fd)
+{
+ int ret;
+ struct nvme_passthru_cmd admin_cmd;
+
+ memset(&admin_cmd, 0, sizeof(admin_cmd));
+ admin_cmd.opcode = WDC_NVME_CLEAR_PCIE_CORR_OPCODE_VUC;
+
+ ret = nvme_submit_admin_passthru(fd, &admin_cmd, NULL);
+ nvme_show_status(ret);
+ return ret;
+}
+
+static int wdc_do_clear_pcie_correctable_errors_fid(int fd)
+{
+ int ret;
+ __u32 result;
+ __u32 value = 1 << 31; /* Bit 31 - clear PCIe correctable count */
+
+ ret = nvme_set_features_simple(fd, WDC_NVME_CLEAR_PCIE_CORR_FEATURE_ID, 0, value,
+ false, &result);
+
+ nvme_show_status(ret);
+ return ret;
+}
+
+static int wdc_clear_pcie_correctable_errors(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ char *desc = "Clear PCIE Correctable Errors.";
+ __u64 capabilities = 0;
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ int ret;
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+ if (!wdc_check_device(r, dev)) {
+ ret = -1;
+ goto out;
+ }
+
+ capabilities = wdc_get_drive_capabilities(r, dev);
+ if (!(capabilities & WDC_DRIVE_CAP_CLEAR_PCIE_MASK)) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ goto out;
+ }
+
+ if (capabilities & WDC_DRIVE_CAP_CLEAR_PCIE)
+ ret = wdc_do_clear_pcie_correctable_errors(dev_fd(dev));
+ else if (capabilities & WDC_DRIVE_CAP_VUC_CLEAR_PCIE)
+ ret = wdc_do_clear_pcie_correctable_errors_vuc(dev_fd(dev));
+ else
+ ret = wdc_do_clear_pcie_correctable_errors_fid(dev_fd(dev));
+
+out:
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_drive_status(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ char *desc = "Get Drive Status.";
+ struct nvme_dev *dev;
+ int ret = 0;
+ nvme_root_t r;
+ __le32 system_eol_state;
+ __le32 user_eol_state;
+ __le32 format_corrupt_reason = cpu_to_le32(0xFFFFFFFF);
+ __le32 eol_status;
+ __le32 assert_status = cpu_to_le32(0xFFFFFFFF);
+ __le32 thermal_status = cpu_to_le32(0xFFFFFFFF);
+ __u64 capabilities = 0;
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+ if ((capabilities & WDC_DRIVE_CAP_DRIVE_STATUS) != WDC_DRIVE_CAP_DRIVE_STATUS) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ goto out;
+ }
+
+ /* verify the 0xC2 Device Manageability log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev,
+ WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_ID) == false) {
+ fprintf(stderr, "ERROR: WDC: 0xC2 Log Page not supported\n");
+ ret = -1;
+ goto out;
+ }
+
+ /* Get the assert dump present status */
+ if (!wdc_nvme_get_dev_status_log_data(r, dev, &assert_status,
+ WDC_C2_ASSERT_DUMP_PRESENT_ID))
+ fprintf(stderr, "ERROR: WDC: Get Assert Status Failed\n");
+
+ /* Get the thermal throttling status */
+ if (!wdc_nvme_get_dev_status_log_data(r, dev, &thermal_status,
+ WDC_C2_THERMAL_THROTTLE_STATUS_ID))
+ fprintf(stderr, "ERROR: WDC: Get Thermal Throttling Status Failed\n");
+
+ /* Get EOL status */
+ if (!wdc_nvme_get_dev_status_log_data(r, dev, &eol_status,
+ WDC_C2_USER_EOL_STATUS_ID)) {
+ fprintf(stderr, "ERROR: WDC: Get User EOL Status Failed\n");
+ eol_status = cpu_to_le32(-1);
+ }
+
+ /* Get Customer EOL state */
+ if (!wdc_nvme_get_dev_status_log_data(r, dev, &user_eol_state,
+ WDC_C2_USER_EOL_STATE_ID))
+ fprintf(stderr, "ERROR: WDC: Get User EOL State Failed\n");
+
+ /* Get System EOL state*/
+ if (!wdc_nvme_get_dev_status_log_data(r, dev, &system_eol_state,
+ WDC_C2_SYSTEM_EOL_STATE_ID))
+ fprintf(stderr, "ERROR: WDC: Get System EOL State Failed\n");
+
+ /* Get format corrupt reason*/
+ if (!wdc_nvme_get_dev_status_log_data(r, dev, &format_corrupt_reason,
+ WDC_C2_FORMAT_CORRUPT_REASON_ID))
+ fprintf(stderr, "ERROR: WDC: Get Format Corrupt Reason Failed\n");
+
+ printf(" Drive Status :-\n");
+ if ((int)le32_to_cpu(eol_status) >= 0)
+ printf(" Percent Used: %"PRIu32"%%\n",
+ le32_to_cpu(eol_status));
+ else
+ printf(" Percent Used: Unknown\n");
+ if (system_eol_state == WDC_EOL_STATUS_NORMAL && user_eol_state == WDC_EOL_STATUS_NORMAL)
+ printf(" Drive Life Status: Normal\n");
+ else if (system_eol_state == WDC_EOL_STATUS_END_OF_LIFE ||
+ user_eol_state == WDC_EOL_STATUS_END_OF_LIFE)
+ printf(" Drive Life Status: End Of Life\n");
+ else if (system_eol_state == WDC_EOL_STATUS_READ_ONLY ||
+ user_eol_state == WDC_EOL_STATUS_READ_ONLY)
+ printf(" Drive Life Status: Read Only\n");
+ else
+ printf(" Drive Life Status: Unknown : 0x%08x/0x%08x\n",
+ le32_to_cpu(user_eol_state), le32_to_cpu(system_eol_state));
+
+ if (assert_status == WDC_ASSERT_DUMP_PRESENT)
+ printf(" Assert Dump Status: Present\n");
+ else if (assert_status == WDC_ASSERT_DUMP_NOT_PRESENT)
+ printf(" Assert Dump Status: Not Present\n");
+ else
+ printf(" Assert Dump Status: Unknown : 0x%08x\n", le32_to_cpu(assert_status));
+
+ if (thermal_status == WDC_THERMAL_THROTTLING_OFF)
+ printf(" Thermal Throttling Status: Off\n");
+ else if (thermal_status == WDC_THERMAL_THROTTLING_ON)
+ printf(" Thermal Throttling Status: On\n");
+ else if (thermal_status == WDC_THERMAL_THROTTLING_UNAVAILABLE)
+ printf(" Thermal Throttling Status: Unavailable\n");
+ else
+ printf(" Thermal Throttling Status: Unknown : 0x%08x\n", le32_to_cpu(thermal_status));
+
+ if (format_corrupt_reason == WDC_FORMAT_NOT_CORRUPT)
+ printf(" Format Corrupt Reason: Format Not Corrupted\n");
+ else if (format_corrupt_reason == WDC_FORMAT_CORRUPT_FW_ASSERT)
+ printf(" Format Corrupt Reason: Format Corrupt due to FW Assert\n");
+ else if (format_corrupt_reason == WDC_FORMAT_CORRUPT_UNKNOWN)
+ printf(" Format Corrupt Reason: Format Corrupt for Unknown Reason\n");
+ else
+ printf(" Format Corrupt Reason: Unknown : 0x%08x\n", le32_to_cpu(format_corrupt_reason));
+
+out:
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_clear_assert_dump(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ char *desc = "Clear Assert Dump Present Status.";
+ struct nvme_dev *dev;
+ int ret = -1;
+ nvme_root_t r;
+ __le32 assert_status = cpu_to_le32(0xFFFFFFFF);
+ __u64 capabilities = 0;
+ struct nvme_passthru_cmd admin_cmd;
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+ if ((capabilities & WDC_DRIVE_CAP_CLEAR_ASSERT) != WDC_DRIVE_CAP_CLEAR_ASSERT) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ goto out;
+ }
+ if (!wdc_nvme_get_dev_status_log_data(r, dev, &assert_status,
+ WDC_C2_ASSERT_DUMP_PRESENT_ID)) {
+ fprintf(stderr, "ERROR: WDC: Get Assert Status Failed\n");
+ ret = -1;
+ goto out;
+ }
+
+ /* Get the assert dump present status */
+ if (assert_status == WDC_ASSERT_DUMP_PRESENT) {
+ memset(&admin_cmd, 0, sizeof(admin_cmd));
+ admin_cmd.opcode = WDC_NVME_CLEAR_ASSERT_DUMP_OPCODE;
+ admin_cmd.cdw12 = ((WDC_NVME_CLEAR_ASSERT_DUMP_SUBCMD << WDC_NVME_SUBCMD_SHIFT) |
+ WDC_NVME_CLEAR_ASSERT_DUMP_CMD);
+
+ ret = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd,
+ NULL);
+ nvme_show_status(ret);
+ } else
+ fprintf(stderr, "INFO: WDC: No Assert Dump Present\n");
+
+out:
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_get_fw_act_history(nvme_root_t r, struct nvme_dev *dev,
+ char *format)
+{
+ struct wdc_fw_act_history_log_hdr *fw_act_history_hdr;
+ enum nvme_print_flags fmt;
+ int ret;
+ __u8 *data;
+
+ if (!wdc_check_device(r, dev))
+ return -1;
+
+ ret = validate_output_format(format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC: invalid output format\n");
+ return ret;
+ }
+
+ /* verify the FW Activate History log page is supported */
+ if (!wdc_nvme_check_supported_log_page(r, dev, WDC_NVME_GET_FW_ACT_HISTORY_LOG_ID)) {
+ fprintf(stderr, "ERROR: WDC: %d Log Page not supported\n",
+ WDC_NVME_GET_FW_ACT_HISTORY_LOG_ID);
+ return -1;
+ }
+
+ data = (__u8 *)malloc(sizeof(__u8) * WDC_FW_ACT_HISTORY_LOG_BUF_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+
+ memset(data, 0, sizeof(__u8) * WDC_FW_ACT_HISTORY_LOG_BUF_LEN);
+
+ ret = nvme_get_log_simple(dev_fd(dev),
+ WDC_NVME_GET_FW_ACT_HISTORY_LOG_ID,
+ WDC_FW_ACT_HISTORY_LOG_BUF_LEN, data);
+
+ if (strcmp(format, "json"))
+ nvme_show_status(ret);
+
+ if (!ret) {
+ /* parse the data */
+ fw_act_history_hdr = (struct wdc_fw_act_history_log_hdr *)(data);
+
+ if ((fw_act_history_hdr->num_entries > 0) &&
+ (fw_act_history_hdr->num_entries <= WDC_MAX_NUM_ACT_HIST_ENTRIES)) {
+ ret = wdc_print_fw_act_history_log(data, fw_act_history_hdr->num_entries,
+ fmt, 0, 0, 0);
+ } else if (!fw_act_history_hdr->num_entries) {
+ fprintf(stderr, "INFO: WDC: No FW Activate History entries found.\n");
+ ret = 0;
+ } else {
+ fprintf(stderr,
+ "ERROR: WDC: Invalid number entries found in FW Activate History Log Page - %d\n",
+ fw_act_history_hdr->num_entries);
+ ret = -1;
+ }
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unable to read FW Activate History Log Page data\n");
+ ret = -1;
+ }
+
+ free(data);
+ return ret;
+}
+
+static __u32 wdc_get_fw_cust_id(nvme_root_t r, struct nvme_dev *dev)
+{
+
+ __u32 cust_id = WDC_INVALID_CUSTOMER_ID;
+ __u32 *cust_id_ptr = NULL;
+
+ if (!get_dev_mgment_cbs_data(r, dev, WDC_C2_CUSTOMER_ID_ID, (void *)&cust_id_ptr))
+ fprintf(stderr, "%s: ERROR: WDC: 0xC2 Log Page entry ID 0x%x not found\n",
+ __func__, WDC_C2_CUSTOMER_ID_ID);
+ else
+ cust_id = *cust_id_ptr;
+
+ free(cust_id_ptr);
+ return cust_id;
+}
+
+static int wdc_get_fw_act_history_C2(nvme_root_t r, struct nvme_dev *dev,
+ char *format)
+{
+ struct wdc_fw_act_history_log_format_c2 *fw_act_history_log;
+ __u32 tot_entries = 0, num_entries = 0;
+ __u32 vendor_id = 0, device_id = 0;
+ __u32 cust_id = 0;
+ enum nvme_print_flags fmt;
+ __u8 *data;
+ int ret;
+
+ if (!wdc_check_device(r, dev))
+ return -1;
+
+ ret = validate_output_format(format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC: invalid output format\n");
+ return ret;
+ }
+
+ ret = wdc_get_pci_ids(r, dev, &device_id, &vendor_id);
+
+ data = (__u8 *)malloc(sizeof(__u8) * WDC_FW_ACT_HISTORY_C2_LOG_BUF_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ return -1;
+ }
+
+ memset(data, 0, sizeof(__u8) * WDC_FW_ACT_HISTORY_C2_LOG_BUF_LEN);
+
+ ret = nvme_get_log_simple(dev_fd(dev),
+ WDC_NVME_GET_FW_ACT_HISTORY_C2_LOG_ID,
+ WDC_FW_ACT_HISTORY_C2_LOG_BUF_LEN, data);
+
+ if (strcmp(format, "json"))
+ nvme_show_status(ret);
+
+ if (!ret) {
+ /* parse the data */
+ fw_act_history_log = (struct wdc_fw_act_history_log_format_c2 *)(data);
+ tot_entries = le32_to_cpu(fw_act_history_log->num_entries);
+
+ if (tot_entries > 0) {
+ /* get the FW customer id */
+ if (!wdc_is_sn861(device_id)) {
+ cust_id = wdc_get_fw_cust_id(r, dev);
+ if (cust_id == WDC_INVALID_CUSTOMER_ID) {
+ fprintf(stderr,
+ "%s: ERROR: WDC: invalid customer id\n",
+ __func__);
+ ret = -1;
+ goto freeData;
+ }
+ }
+ num_entries = (tot_entries < WDC_MAX_NUM_ACT_HIST_ENTRIES) ? tot_entries :
+ WDC_MAX_NUM_ACT_HIST_ENTRIES;
+ ret = wdc_print_fw_act_history_log(data, num_entries,
+ fmt, cust_id, vendor_id, device_id);
+ } else {
+ fprintf(stderr, "INFO: WDC: No FW Activate History entries found.\n");
+ ret = 0;
+ }
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unable to read FW Activate History Log Page data\n");
+ ret = -1;
+ }
+
+freeData:
+ free(data);
+ return ret;
+}
+
+static int wdc_vs_fw_activate_history(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Retrieve FW activate history table.";
+ __u64 capabilities = 0;
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ int ret;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+ if (!(capabilities & WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY_MASK)) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ goto out;
+ }
+
+ if (capabilities & WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY) {
+ int uuid_index = 0;
+ bool c0GuidMatch = false;
+ __u8 *data;
+ int i;
+
+ /*
+ * check for the GUID in the 0xC0 log page to determine which log page to use to
+ * retrieve fw activate history data
+ */
+ data = (__u8 *)malloc(sizeof(__u8) * WDC_NVME_SMART_CLOUD_ATTR_LEN);
+ if (!data) {
+ fprintf(stderr, "ERROR: WDC: malloc: %s\n", strerror(errno));
+ ret = -1;
+ goto out;
+ }
+
+ /* Get the 0xC0 log data */
+ struct nvme_get_log_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .lid = WDC_NVME_GET_SMART_CLOUD_ATTR_LOG_ID,
+ .nsid = 0xFFFFFFFF,
+ .lpo = 0,
+ .lsp = NVME_LOG_LSP_NONE,
+ .lsi = 0,
+ .rae = false,
+ .uuidx = uuid_index,
+ .csi = NVME_CSI_NVM,
+ .ot = false,
+ .len = WDC_NVME_SMART_CLOUD_ATTR_LEN,
+ .log = data,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ };
+ ret = nvme_get_log(&args);
+
+ if (!ret) {
+ /* Verify GUID matches */
+ for (i = 0; i < 16; i++) {
+ if (scao_guid[i] != data[SCAO_LPG + i]) {
+ c0GuidMatch = false;
+ break;
+ }
+ }
+
+ if (i == 16)
+ c0GuidMatch = true;
+ }
+
+ free(data);
+ if (c0GuidMatch)
+ ret = wdc_get_fw_act_history_C2(r, dev, cfg.output_format);
+ else
+ ret = wdc_get_fw_act_history(r, dev, cfg.output_format);
+ } else {
+ ret = wdc_get_fw_act_history_C2(r, dev, cfg.output_format);
+ }
+
+ if (ret)
+ fprintf(stderr, "ERROR: WDC: Failure reading the FW Activate History, ret = %d\n", ret);
+out:
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_do_clear_fw_activate_history_vuc(int fd)
+{
+ int ret = -1;
+ struct nvme_passthru_cmd admin_cmd;
+
+ memset(&admin_cmd, 0, sizeof(admin_cmd));
+ admin_cmd.opcode = WDC_NVME_CLEAR_FW_ACT_HIST_OPCODE;
+ admin_cmd.cdw12 = ((WDC_NVME_CLEAR_FW_ACT_HIST_SUBCMD << WDC_NVME_SUBCMD_SHIFT) |
+ WDC_NVME_CLEAR_FW_ACT_HIST_CMD);
+
+ ret = nvme_submit_admin_passthru(fd, &admin_cmd, NULL);
+ nvme_show_status(ret);
+
+ return ret;
+}
+
+static int wdc_do_clear_fw_activate_history_fid(int fd)
+{
+ int ret = -1;
+ __u32 result;
+ __u32 value = 1 << 31; /* Bit 31 - Clear Firmware Update History Log */
+
+ ret = nvme_set_features_simple(fd, WDC_NVME_CLEAR_FW_ACT_HIST_VU_FID, 0, value,
+ false, &result);
+
+ nvme_show_status(ret);
+ return ret;
+}
+
+static int wdc_clear_fw_activate_history(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ char *desc = "Clear FW activate history table.";
+ __u64 capabilities = 0;
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ int ret;
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+ if (!(capabilities & WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY_MASK)) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ goto out;
+ }
+
+ if (capabilities & WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY)
+ ret = wdc_do_clear_fw_activate_history_vuc(dev_fd(dev));
+ else
+ ret = wdc_do_clear_fw_activate_history_fid(dev_fd(dev));
+
+out:
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_vs_telemetry_controller_option(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ char *desc = "Disable/Enable Controller Option of the Telemetry Log Page.";
+ char *disable = "Disable controller option of the telemetry log page.";
+ char *enable = "Enable controller option of the telemetry log page.";
+ char *status = "Displays the current state of the controller initiated log page.";
+ __u64 capabilities = 0;
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ __u32 result;
+ int ret = -1;
+
+
+ struct config {
+ bool disable;
+ bool enable;
+ bool status;
+ };
+
+ struct config cfg = {
+ .disable = false,
+ .enable = false,
+ .status = false,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("disable", 'd', &cfg.disable, disable),
+ OPT_FLAG("enable", 'e', &cfg.enable, enable),
+ OPT_FLAG("status", 's', &cfg.status, status),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+ if ((capabilities & WDC_DRVIE_CAP_DISABLE_CTLR_TELE_LOG) != WDC_DRVIE_CAP_DISABLE_CTLR_TELE_LOG) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ goto out;
+ }
+
+ /* allow only one option at a time */
+ if ((cfg.disable + cfg.enable + cfg.status) > 1) {
+
+ fprintf(stderr, "ERROR: WDC: Invalid option\n");
+ ret = -1;
+ goto out;
+ }
+
+ if (cfg.disable) {
+ ret = nvme_set_features_simple(dev_fd(dev),
+ WDC_VU_DISABLE_CNTLR_TELEMETRY_OPTION_FEATURE_ID,
+ 0, 1, false, &result);
+
+ wdc_clear_reason_id(dev);
+ } else {
+ if (cfg.enable) {
+ ret = nvme_set_features_simple(dev_fd(dev),
+ WDC_VU_DISABLE_CNTLR_TELEMETRY_OPTION_FEATURE_ID,
+ 0, 0, false, &result);
+ } else if (cfg.status) {
+ ret = nvme_get_features_simple(dev_fd(dev),
+ WDC_VU_DISABLE_CNTLR_TELEMETRY_OPTION_FEATURE_ID,
+ 0, &result);
+ if (!ret) {
+ if (result)
+ fprintf(stderr, "Controller Option Telemetry Log Page State: Disabled\n");
+ else
+ fprintf(stderr, "Controller Option Telemetry Log Page State: Enabled\n");
+ } else {
+ nvme_show_status(ret);
+ }
+ } else {
+ fprintf(stderr, "ERROR: WDC: unsupported option for this command\n");
+ fprintf(stderr, "Please provide an option, -d, -e or -s\n");
+ ret = -1;
+ goto out;
+ }
+ }
+
+out:
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+
+static int wdc_get_serial_and_fw_rev(struct nvme_dev *dev, char *sn, char *fw_rev)
+{
+ int i;
+ int ret;
+ struct nvme_id_ctrl ctrl;
+
+ i = sizeof(ctrl.sn) - 1;
+ memset(sn, 0, WDC_SERIAL_NO_LEN);
+ memset(fw_rev, 0, WDC_NVME_FIRMWARE_REV_LEN);
+ memset(&ctrl, 0, sizeof(struct nvme_id_ctrl));
+ ret = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: nvme_identify_ctrl() failed 0x%x\n", ret);
+ return -1;
+ }
+ /* Remove trailing spaces from the name */
+ while (i && ctrl.sn[i] == ' ') {
+ ctrl.sn[i] = '\0';
+ i--;
+ }
+ snprintf(sn, WDC_SERIAL_NO_LEN, "%s", ctrl.sn);
+ snprintf(fw_rev, WDC_NVME_FIRMWARE_REV_LEN, "%s", ctrl.fr);
+
+ return 0;
+}
+
+static int wdc_get_max_transfer_len(struct nvme_dev *dev, __u32 *maxTransferLen)
+{
+ int ret = 0;
+ struct nvme_id_ctrl ctrl;
+
+ __u32 maxTransferLenDevice = 0;
+
+ memset(&ctrl, 0, sizeof(struct nvme_id_ctrl));
+ ret = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: nvme_identify_ctrl() failed 0x%x\n", ret);
+ return -1;
+ }
+
+ maxTransferLenDevice = (1 << ctrl.mdts) * getpagesize();
+ *maxTransferLen = maxTransferLenDevice;
+
+ return ret;
+}
+
+static int wdc_de_VU_read_size(struct nvme_dev *dev, __u32 fileId, __u16 spiDestn, __u32 *logSize)
+{
+ int ret = WDC_STATUS_FAILURE;
+ struct nvme_passthru_cmd cmd;
+
+ if (!dev || !logSize) {
+ ret = WDC_STATUS_INVALID_PARAMETER;
+ goto end;
+ }
+
+ memset(&cmd, 0, sizeof(struct nvme_passthru_cmd));
+ cmd.opcode = WDC_DE_VU_READ_SIZE_OPCODE;
+ cmd.nsid = WDC_DE_DEFAULT_NAMESPACE_ID;
+ cmd.cdw13 = fileId << 16;
+ cmd.cdw14 = spiDestn;
+
+ ret = nvme_submit_admin_passthru(dev_fd(dev), &cmd, NULL);
+
+ if (!ret && logSize)
+ *logSize = cmd.result;
+ if (ret != WDC_STATUS_SUCCESS) {
+ fprintf(stderr, "ERROR: WDC: VUReadSize() failed, ");
+ nvme_show_status(ret);
+ }
+
+end:
+ return ret;
+}
+
+static int wdc_de_VU_read_buffer(struct nvme_dev *dev, __u32 fileId, __u16 spiDestn,
+ __u32 offsetInDwords, __u8 *dataBuffer, __u32 *bufferSize)
+{
+ int ret = WDC_STATUS_FAILURE;
+ struct nvme_passthru_cmd cmd;
+ __u32 noOfDwordExpected = 0;
+
+ if (!dev || !dataBuffer || !bufferSize) {
+ ret = WDC_STATUS_INVALID_PARAMETER;
+ goto end;
+ }
+
+ memset(&cmd, 0, sizeof(struct nvme_passthru_cmd));
+ noOfDwordExpected = *bufferSize / sizeof(__u32);
+ cmd.opcode = WDC_DE_VU_READ_BUFFER_OPCODE;
+ cmd.nsid = WDC_DE_DEFAULT_NAMESPACE_ID;
+ cmd.cdw10 = noOfDwordExpected;
+ cmd.cdw13 = fileId << 16;
+ cmd.cdw14 = spiDestn;
+ cmd.cdw15 = offsetInDwords;
+
+ cmd.addr = (__u64)(__u64)(uintptr_t)dataBuffer;
+ cmd.data_len = *bufferSize;
+
+ ret = nvme_submit_admin_passthru(dev_fd(dev), &cmd, NULL);
+
+ if (ret != WDC_STATUS_SUCCESS) {
+ fprintf(stderr, "ERROR: WDC: VUReadBuffer() failed, ");
+ nvme_show_status(ret);
+ }
+
+end:
+ return ret;
+}
+
+static int wdc_get_log_dir_max_entries(struct nvme_dev *dev, __u32 *maxNumOfEntries)
+{
+ int ret = WDC_STATUS_FAILURE;
+ __u32 headerPayloadSize = 0;
+ __u8 *fileIdOffsetsBuffer = NULL;
+ __u32 fileIdOffsetsBufferSize = 0;
+ __u32 fileNum = 0;
+ __u16 fileOffset = 0;
+
+
+ if (!dev || !maxNumOfEntries) {
+ ret = WDC_STATUS_INVALID_PARAMETER;
+ return ret;
+ }
+ /* 1.Get log directory first four bytes */
+ ret = wdc_de_VU_read_size(dev, 0, 5, (__u32 *)&headerPayloadSize);
+ if (ret != WDC_STATUS_SUCCESS) {
+ fprintf(stderr,
+ "ERROR: WDC: %s: Failed to get headerPayloadSize from file directory 0x%x\n",
+ __func__, ret);
+ return ret;
+ }
+
+ fileIdOffsetsBufferSize =
+ WDC_DE_FILE_HEADER_SIZE + (headerPayloadSize * WDC_DE_FILE_OFFSET_SIZE);
+ fileIdOffsetsBuffer = (__u8 *)calloc(1, fileIdOffsetsBufferSize);
+
+ /* 2.Read to get file offsets */
+ ret = wdc_de_VU_read_buffer(dev, 0, 5, 0, fileIdOffsetsBuffer, &fileIdOffsetsBufferSize);
+ if (ret != WDC_STATUS_SUCCESS) {
+ fprintf(stderr,
+ "ERROR: WDC: %s: Failed to get fileIdOffsets from file directory 0x%x\n",
+ __func__, ret);
+ goto end;
+ }
+ /* 3.Determine valid entries */
+ for (fileNum = 0;
+ fileNum < (headerPayloadSize - WDC_DE_FILE_HEADER_SIZE) / WDC_DE_FILE_OFFSET_SIZE;
+ fileNum++) {
+ fileOffset = (fileIdOffsetsBuffer[WDC_DE_FILE_HEADER_SIZE +
+ (fileNum * WDC_DE_FILE_OFFSET_SIZE)] << 8) +
+ fileIdOffsetsBuffer[WDC_DE_FILE_HEADER_SIZE +
+ (fileNum * WDC_DE_FILE_OFFSET_SIZE) + 1];
+ if (!fileOffset)
+ continue;
+ (*maxNumOfEntries)++;
+ }
+
+end:
+ free(fileIdOffsetsBuffer);
+ return ret;
+}
+
+static enum WDC_DRIVE_ESSENTIAL_TYPE wdc_get_essential_type(__u8 fileName[])
+{
+ enum WDC_DRIVE_ESSENTIAL_TYPE essentialType = WDC_DE_TYPE_NONE;
+
+ if (!wdc_UtilsStrCompare((char *)fileName, WDC_DE_CORE_DUMP_FILE_NAME))
+ essentialType = WDC_DE_TYPE_DUMPSNAPSHOT;
+ else if (!wdc_UtilsStrCompare((char *)fileName, WDC_DE_EVENT_LOG_FILE_NAME))
+ essentialType = WDC_DE_TYPE_EVENTLOG;
+ else if (!wdc_UtilsStrCompare((char *)fileName, WDC_DE_MANUFACTURING_INFO_PAGE_FILE_NAME))
+ essentialType = WDC_DE_TYPE_NVME_MANF_INFO;
+
+ return essentialType;
+}
+
+static int wdc_fetch_log_directory(struct nvme_dev *dev, struct WDC_DE_VU_LOG_DIRECTORY *directory)
+{
+ int ret = WDC_STATUS_FAILURE;
+ __u8 *fileOffset = NULL;
+ __u8 *fileDirectory = NULL;
+ __u32 headerSize = 0;
+ __u32 fileNum = 0, startIdx = 0;
+ __u16 fileOffsetTemp = 0;
+ __u32 entryId = 0;
+ __u32 fileDirectorySize = 0;
+
+ if (!dev || !directory) {
+ ret = WDC_STATUS_INVALID_PARAMETER;
+ goto end;
+ }
+
+ ret = wdc_de_VU_read_size(dev, 0, 5, &fileDirectorySize);
+ if (ret != WDC_STATUS_SUCCESS) {
+ fprintf(stderr,
+ "ERROR: WDC: %s: Failed to get filesystem directory size, ret = %d\n",
+ __func__, ret);
+ goto end;
+ }
+
+ fileDirectory = (__u8 *)calloc(1, fileDirectorySize);
+ ret = wdc_de_VU_read_buffer(dev, 0, 5, 0, fileDirectory, &fileDirectorySize);
+ if (ret != WDC_STATUS_SUCCESS) {
+ fprintf(stderr, "ERROR: WDC: %s: Failed to get filesystem directory, ret = %d\n",
+ __func__, ret);
+ goto end;
+ }
+
+ /* First four bytes of header directory is headerSize */
+ memcpy(&headerSize, fileDirectory, WDC_DE_FILE_HEADER_SIZE);
+
+ /* minimum buffer for 1 entry is required */
+ if (!directory->maxNumLogEntries) {
+ ret = WDC_STATUS_INVALID_PARAMETER;
+ goto end;
+ }
+
+ for (fileNum = 0;
+ fileNum < (headerSize - WDC_DE_FILE_HEADER_SIZE) / WDC_DE_FILE_OFFSET_SIZE;
+ fileNum++) {
+ if (entryId >= directory->maxNumLogEntries)
+ break;
+
+ startIdx = WDC_DE_FILE_HEADER_SIZE + (fileNum * WDC_DE_FILE_OFFSET_SIZE);
+ memcpy(&fileOffsetTemp, fileDirectory + startIdx, sizeof(fileOffsetTemp));
+ fileOffset = fileDirectory + fileOffsetTemp;
+
+ if (!fileOffsetTemp)
+ continue;
+
+ memset(&directory->logEntry[entryId], 0, sizeof(struct WDC_DRIVE_ESSENTIALS));
+ memcpy(&directory->logEntry[entryId].metaData, fileOffset, sizeof(struct __packed WDC_DE_VU_FILE_META_DATA));
+ directory->logEntry[entryId].metaData.fileName[WDC_DE_FILE_NAME_SIZE - 1] = '\0';
+ wdc_UtilsDeleteCharFromString((char *)directory->logEntry[entryId].metaData.fileName,
+ WDC_DE_FILE_NAME_SIZE, ' ');
+ if (!directory->logEntry[entryId].metaData.fileID)
+ continue;
+
+ directory->logEntry[entryId].essentialType = wdc_get_essential_type(directory->logEntry[entryId].metaData.fileName);
+ entryId++;
+ }
+
+ directory->numOfValidLogEntries = entryId;
+
+end:
+ if (fileDirectory)
+ free(fileDirectory);
+ return ret;
+}
+
+static int wdc_fetch_log_file_from_device(struct nvme_dev *dev, __u32 fileId,
+ __u16 spiDestn, __u64 fileSize, __u8 *dataBuffer)
+{
+ int ret = WDC_STATUS_FAILURE;
+ __u32 chunckSize = WDC_DE_VU_READ_BUFFER_STANDARD_OFFSET;
+ __u32 maximumTransferLength = 0;
+ __u32 buffSize = 0;
+ __u64 offsetIdx = 0;
+
+ if (!dev || !dataBuffer || !fileSize) {
+ ret = WDC_STATUS_INVALID_PARAMETER;
+ goto end;
+ }
+
+ if (wdc_get_max_transfer_len(dev, &maximumTransferLength) < 0) {
+ ret = WDC_STATUS_FAILURE;
+ goto end;
+ }
+
+ /* Fetch Log File Data */
+ if ((fileSize >= maximumTransferLength) || (fileSize > 0xFFFFFFFF)) {
+ chunckSize = WDC_DE_VU_READ_BUFFER_STANDARD_OFFSET;
+ if (maximumTransferLength < WDC_DE_VU_READ_BUFFER_STANDARD_OFFSET)
+ chunckSize = maximumTransferLength;
+
+ buffSize = chunckSize;
+ for (offsetIdx = 0; (offsetIdx * chunckSize) < fileSize; offsetIdx++) {
+ if (((offsetIdx * chunckSize) + buffSize) > fileSize)
+ buffSize = (__u32)(fileSize - (offsetIdx * chunckSize));
+ /* Limitation in VU read buffer - offsetIdx and bufferSize are not greater than u32 */
+ ret = wdc_de_VU_read_buffer(dev, fileId, spiDestn,
+ (__u32)((offsetIdx * chunckSize) / sizeof(__u32)), dataBuffer + (offsetIdx * chunckSize), &buffSize);
+ if (ret != WDC_STATUS_SUCCESS) {
+ fprintf(stderr, "ERROR: WDC: %s: wdc_de_VU_read_buffer failed with ret = %d, fileId = 0x%x, fileSize = 0x%lx\n",
+ __func__, ret, fileId, (unsigned long)fileSize);
+ break;
+ }
+ }
+ } else {
+ buffSize = (__u32)fileSize;
+ ret = wdc_de_VU_read_buffer(dev, fileId, spiDestn,
+ (__u32)((offsetIdx * chunckSize) / sizeof(__u32)),
+ dataBuffer, &buffSize);
+ if (ret != WDC_STATUS_SUCCESS) {
+ fprintf(stderr, "ERROR: WDC: %s: wdc_de_VU_read_buffer failed with ret = %d, fileId = 0x%x, fileSize = 0x%lx\n",
+ __func__, ret, fileId, (unsigned long)fileSize);
+ }
+ }
+
+end:
+ return ret;
+}
+
+static int wdc_de_get_dump_trace(struct nvme_dev *dev, char *filePath, __u16 binFileNameLen, char *binFileName)
+{
+ int ret = WDC_STATUS_FAILURE;
+ __u8 *readBuffer = NULL;
+ __u32 readBufferLen = 0;
+ __u32 lastPktReadBufferLen = 0;
+ __u32 maxTransferLen = 0;
+ __u32 dumptraceSize = 0;
+ __u32 chunkSize = 0;
+ __u32 chunks = 0;
+ __u32 offset = 0;
+ __u8 loop = 0;
+ __u16 i = 0;
+ __u32 maximumTransferLength = 0;
+
+ if (!dev || !binFileName || !filePath) {
+ ret = WDC_STATUS_INVALID_PARAMETER;
+ return ret;
+ }
+
+ if (wdc_get_max_transfer_len(dev, &maximumTransferLength) < 0)
+ return WDC_STATUS_FAILURE;
+
+ do {
+ /* Get dumptrace size */
+ ret = wdc_de_VU_read_size(dev, 0, WDC_DE_DUMPTRACE_DESTINATION, &dumptraceSize);
+ if (ret != WDC_STATUS_SUCCESS) {
+ fprintf(stderr, "ERROR: WDC: %s: wdc_de_VU_read_size failed with ret = %d\n",
+ __func__, ret);
+ break;
+ }
+
+ /* Make sure the size requested is greater than dword */
+ if (dumptraceSize < 4) {
+ ret = WDC_STATUS_FAILURE;
+ fprintf(stderr, "ERROR: WDC: %s: wdc_de_VU_read_size failed, read size is less than 4 bytes, dumptraceSize = 0x%x\n",
+ __func__, dumptraceSize);
+ break;
+ }
+
+ /* Choose the least max transfer length */
+ maxTransferLen = maximumTransferLength < WDC_DE_READ_MAX_TRANSFER_SIZE ? maximumTransferLength : WDC_DE_READ_MAX_TRANSFER_SIZE;
+
+ /* Comment from FW Team:
+ * The max non - block transfer size is 0xFFFF (16 bits allowed as the block size).Use 0x8000
+ * to keep it on a word - boundary.
+ * max_xfer = int(pow(2, id_data['MDTS'])) * 4096 # 4k page size as reported in pcie capabiltiies
+ */
+ chunkSize = dumptraceSize < maxTransferLen ? dumptraceSize : maxTransferLen;
+ chunks = (dumptraceSize / maxTransferLen) + ((dumptraceSize % maxTransferLen) ? 1 : 0);
+
+ readBuffer = (unsigned char *)calloc(dumptraceSize, sizeof(unsigned char));
+ readBufferLen = chunkSize;
+ lastPktReadBufferLen = (dumptraceSize % maxTransferLen) ? (dumptraceSize % maxTransferLen) : chunkSize;
+
+ if (!readBuffer) {
+ fprintf(stderr, "ERROR: WDC: %s: readBuffer calloc failed\n", __func__);
+ ret = WDC_STATUS_INSUFFICIENT_MEMORY;
+ break;
+ }
+
+ for (i = 0; i < chunks; i++) {
+ offset = ((i*chunkSize) / 4);
+
+ /* Last loop call, Assign readBufferLen to read only left over bytes */
+ if (i == (chunks - 1))
+ readBufferLen = lastPktReadBufferLen;
+
+ ret = wdc_de_VU_read_buffer(dev, 0, WDC_DE_DUMPTRACE_DESTINATION, 0,
+ readBuffer + offset, &readBufferLen);
+ if (ret != WDC_STATUS_SUCCESS) {
+ fprintf(stderr,
+ "ERROR: WDC: %s: wdc_de_VU_read_buffer failed, ret = %d on offset 0x%x\n",
+ __func__, ret, offset);
+ break;
+ }
+ }
+ } while (loop);
+
+ if (ret == WDC_STATUS_SUCCESS) {
+ ret = wdc_WriteToFile(binFileName, (char *)readBuffer, dumptraceSize);
+ if (ret != WDC_STATUS_SUCCESS)
+ fprintf(stderr, "ERROR: WDC: %s: wdc_WriteToFile failed, ret = %d\n",
+ __func__, ret);
+ } else {
+ fprintf(stderr, "ERROR: WDC: %s: Read Buffer Loop failed, ret = %d\n", __func__,
+ ret);
+ }
+
+ if (readBuffer)
+ free(readBuffer);
+
+ return ret;
+}
+
+int wdc_fetch_vu_file_directory(struct nvme_dev *dev,
+ struct WDC_DE_VU_LOG_DIRECTORY deEssentialsList,
+ __s8 *bufferFolderPath, __u8 *serialNo, __u8 *timeString)
+{
+ int ret = wdc_fetch_log_directory(dev, &deEssentialsList);
+ __u32 listIdx;
+ char *dataBuffer;
+ char fileName[MAX_PATH_LEN];
+
+ if (ret != WDC_STATUS_SUCCESS) {
+ fprintf(stderr, "WDC: wdc_fetch_log_directory failed, ret = %d\n", ret);
+ return ret;
+ }
+
+ /* Get Debug Data Files */
+ for (listIdx = 0; listIdx < deEssentialsList.numOfValidLogEntries; listIdx++) {
+ if (!deEssentialsList.logEntry[listIdx].metaData.fileSize) {
+ fprintf(stderr, "ERROR: WDC: File Size for %s is 0\n",
+ deEssentialsList.logEntry[listIdx].metaData.fileName);
+ ret = WDC_STATUS_FILE_SIZE_ZERO;
+ } else {
+ /* Fetch Log File Data */
+ dataBuffer = (char *)calloc(1, (size_t)deEssentialsList.logEntry[listIdx].metaData.fileSize);
+ ret = wdc_fetch_log_file_from_device(dev,
+ deEssentialsList.logEntry[listIdx].metaData.fileID,
+ WDC_DE_DESTN_SPI,
+ deEssentialsList.logEntry[listIdx].metaData.fileSize,
+ (__u8 *)dataBuffer);
+
+ /* Write databuffer to file */
+ if (ret == WDC_STATUS_SUCCESS) {
+ memset(fileName, 0, sizeof(fileName));
+ wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s_%s_%s.bin", bufferFolderPath, WDC_DE_PATH_SEPARATOR,
+ deEssentialsList.logEntry[listIdx].metaData.fileName, serialNo, timeString);
+ if (deEssentialsList.logEntry[listIdx].metaData.fileSize > 0xFFFFFFFF) {
+ wdc_WriteToFile(fileName, dataBuffer, 0xFFFFFFFF);
+ wdc_WriteToFile(fileName, dataBuffer + 0xFFFFFFFF, (__u32)(deEssentialsList.logEntry[listIdx].metaData.fileSize - 0xFFFFFFFF));
+ } else {
+ wdc_WriteToFile(fileName, dataBuffer, (__u32)deEssentialsList.logEntry[listIdx].metaData.fileSize);
+ }
+ } else {
+ fprintf(stderr, "ERROR: WDC: wdc_fetch_log_file_from_device: %s failed, ret = %d\n",
+ deEssentialsList.logEntry[listIdx].metaData.fileName, ret);
+ }
+ free(dataBuffer);
+ }
+ }
+
+ return ret;
+}
+
+int wdc_read_debug_directory(struct nvme_dev *dev, __s8 *bufferFolderPath, __u8 *serialNo,
+ __u8 *timeString)
+{
+ __u32 maxNumOfVUFiles = 0;
+ int ret = wdc_get_log_dir_max_entries(dev, &maxNumOfVUFiles);
+ struct WDC_DE_VU_LOG_DIRECTORY deEssentialsList;
+
+ if (ret != WDC_STATUS_SUCCESS) {
+ fprintf(stderr, "WDC: wdc_get_log_dir_max_entries failed, ret = %d\n", ret);
+ return ret;
+ }
+
+ memset(&deEssentialsList, 0, sizeof(deEssentialsList));
+ deEssentialsList.logEntry =
+ (struct WDC_DRIVE_ESSENTIALS *)calloc(1, sizeof(struct WDC_DRIVE_ESSENTIALS) * maxNumOfVUFiles);
+ deEssentialsList.maxNumLogEntries = maxNumOfVUFiles;
+
+ ret = wdc_fetch_vu_file_directory(dev, deEssentialsList, bufferFolderPath, serialNo,
+ timeString);
+
+ free(deEssentialsList.logEntry);
+ deEssentialsList.logEntry = NULL;
+
+ return ret;
+}
+
+static int wdc_do_drive_essentials(nvme_root_t r, struct nvme_dev *dev,
+ char *dir, char *key)
+{
+ int ret = 0;
+ void *retPtr;
+ char fileName[MAX_PATH_LEN];
+ __s8 bufferFolderPath[MAX_PATH_LEN];
+ char bufferFolderName[MAX_PATH_LEN];
+ char tarFileName[MAX_PATH_LEN];
+ char tarFiles[MAX_PATH_LEN];
+ char tarCmd[MAX_PATH_LEN+MAX_PATH_LEN];
+ UtilsTimeInfo timeInfo;
+ __u8 timeString[MAX_PATH_LEN];
+ __u8 serialNo[WDC_SERIAL_NO_LEN];
+ __u8 firmwareRevision[WDC_NVME_FIRMWARE_REV_LEN];
+ __u8 idSerialNo[WDC_SERIAL_NO_LEN];
+ __u8 idFwRev[WDC_NVME_FIRMWARE_REV_LEN];
+ __u8 featureIdBuff[4];
+ char currDir[MAX_PATH_LEN];
+ char *dataBuffer = NULL;
+ __u32 elogNumEntries, elogBufferSize;
+ __u32 dataBufferSize;
+ __u32 listIdx = 0;
+ __u32 vuLogIdx = 0;
+ __u32 result;
+ struct nvme_id_ctrl ctrl;
+ struct nvme_id_ns ns;
+ struct nvme_error_log_page *elogBuffer;
+ struct nvme_smart_log smart_log;
+ struct nvme_firmware_slot fw_log;
+ struct WDC_NVME_DE_VU_LOGPAGES *vuLogInput = NULL;
+
+ memset(bufferFolderPath, 0, sizeof(bufferFolderPath));
+ memset(bufferFolderName, 0, sizeof(bufferFolderName));
+ memset(tarFileName, 0, sizeof(tarFileName));
+ memset(tarFiles, 0, sizeof(tarFiles));
+ memset(tarCmd, 0, sizeof(tarCmd));
+ memset(&timeInfo, 0, sizeof(timeInfo));
+
+ if (wdc_get_serial_and_fw_rev(dev, (char *)idSerialNo, (char *)idFwRev)) {
+ fprintf(stderr, "ERROR: WDC: get serial # and fw revision failed\n");
+ return -1;
+ }
+
+ fprintf(stderr, "Get Drive Essentials Data for device serial #: %s and fw revision: %s\n",
+ idSerialNo, idFwRev);
+
+ /* Create Drive Essentials directory */
+ wdc_UtilsGetTime(&timeInfo);
+ memset(timeString, 0, sizeof(timeString));
+ wdc_UtilsSnprintf((char *)timeString, MAX_PATH_LEN, "%02u%02u%02u_%02u%02u%02u",
+ timeInfo.year, timeInfo.month, timeInfo.dayOfMonth,
+ timeInfo.hour, timeInfo.minute, timeInfo.second);
+
+ wdc_UtilsSnprintf((char *)serialNo, WDC_SERIAL_NO_LEN, (char *)idSerialNo);
+ /* Remove any space form serialNo */
+ wdc_UtilsDeleteCharFromString((char *)serialNo, WDC_SERIAL_NO_LEN, ' ');
+
+ memset(firmwareRevision, 0, sizeof(firmwareRevision));
+ wdc_UtilsSnprintf((char *)firmwareRevision, WDC_NVME_FIRMWARE_REV_LEN, (char *)idFwRev);
+ /* Remove any space form FirmwareRevision */
+ wdc_UtilsDeleteCharFromString((char *)firmwareRevision, WDC_NVME_FIRMWARE_REV_LEN, ' ');
+
+ wdc_UtilsSnprintf((char *)bufferFolderName, MAX_PATH_LEN, "%s_%s_%s_%s",
+ "DRIVE_ESSENTIALS", (char *)serialNo, (char *)firmwareRevision, (char *)timeString);
+
+ if (dir) {
+ wdc_UtilsSnprintf((char *)bufferFolderPath, MAX_PATH_LEN, "%s%s%s",
+ (char *)dir, WDC_DE_PATH_SEPARATOR, (char *)bufferFolderName);
+ } else {
+ retPtr = getcwd((char *)currDir, MAX_PATH_LEN);
+ if (retPtr) {
+ wdc_UtilsSnprintf((char *)bufferFolderPath, MAX_PATH_LEN, "%s%s%s",
+ (char *)currDir, WDC_DE_PATH_SEPARATOR, (char *)bufferFolderName);
+ } else {
+ fprintf(stderr, "ERROR: WDC: get current working directory failed\n");
+ return -1;
+ }
+ }
+
+ ret = wdc_UtilsCreateDir((char *)bufferFolderPath);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: create directory failed, ret = %d, dir = %s\n", ret, bufferFolderPath);
+ return -1;
+ }
+
+ fprintf(stderr, "Store Drive Essentials bin files in directory: %s\n", bufferFolderPath);
+
+ /* Get Identify Controller Data */
+ memset(&ctrl, 0, sizeof(struct nvme_id_ctrl));
+ ret = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: nvme_identify_ctrl() failed, ret = %d\n", ret);
+ return -1;
+ }
+
+ wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s_%s_%s.bin", (char *)bufferFolderPath,
+ WDC_DE_PATH_SEPARATOR, "IdentifyController", (char *)serialNo,
+ (char *)timeString);
+ wdc_WriteToFile(fileName, (char *)&ctrl, sizeof(struct nvme_id_ctrl));
+
+ memset(&ns, 0, sizeof(struct nvme_id_ns));
+ ret = nvme_identify_ns(dev_fd(dev), 1, &ns);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: nvme_identify_ns() failed, ret = %d\n", ret);
+ } else {
+ wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s_%s_%s.bin", (char *)bufferFolderPath, WDC_DE_PATH_SEPARATOR,
+ "IdentifyNamespace", (char *)serialNo, (char *)timeString);
+ wdc_WriteToFile(fileName, (char *)&ns, sizeof(struct nvme_id_ns));
+ }
+
+ /* Get Log Pages (0x01, 0x02, 0x03, 0xC0 and 0xE3) */
+ elogNumEntries = WDC_DE_DEFAULT_NUMBER_OF_ERROR_ENTRIES;
+ elogBufferSize = elogNumEntries*sizeof(struct nvme_error_log_page);
+ dataBuffer = calloc(1, elogBufferSize);
+ elogBuffer = (struct nvme_error_log_page *)dataBuffer;
+
+ ret = nvme_get_log_error(dev_fd(dev), elogNumEntries, false,
+ elogBuffer);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: nvme_error_log() failed, ret = %d\n", ret);
+ } else {
+ wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s_%s_%s.bin", (char *)bufferFolderPath, WDC_DE_PATH_SEPARATOR,
+ "ErrorLog", (char *)serialNo, (char *)timeString);
+ wdc_WriteToFile(fileName, (char *)elogBuffer, elogBufferSize);
+ }
+
+ free(dataBuffer);
+ dataBuffer = NULL;
+
+ /* Get Smart log page */
+ memset(&smart_log, 0, sizeof(struct nvme_smart_log));
+ ret = nvme_get_log_smart(dev_fd(dev), NVME_NSID_ALL, false,
+ &smart_log);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: nvme_smart_log() failed, ret = %d\n", ret);
+ } else {
+ wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s_%s_%s.bin", (char *)bufferFolderPath, WDC_DE_PATH_SEPARATOR,
+ "SmartLog", (char *)serialNo, (char *)timeString);
+ wdc_WriteToFile(fileName, (char *)&smart_log, sizeof(struct nvme_smart_log));
+ }
+
+ /* Get FW Slot log page */
+ memset(&fw_log, 0, sizeof(struct nvme_firmware_slot));
+ ret = nvme_get_log_fw_slot(dev_fd(dev), false, &fw_log);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: nvme_fw_log() failed, ret = %d\n", ret);
+ } else {
+ wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s_%s_%s.bin", (char *)bufferFolderPath, WDC_DE_PATH_SEPARATOR,
+ "FwSLotLog", (char *)serialNo, (char *)timeString);
+ wdc_WriteToFile(fileName, (char *)&fw_log, sizeof(struct nvme_firmware_slot));
+ }
+
+ /* Get VU log pages */
+ /* define inputs for vendor unique log pages */
+ vuLogInput = (struct WDC_NVME_DE_VU_LOGPAGES *)calloc(1, sizeof(struct WDC_NVME_DE_VU_LOGPAGES));
+ vuLogInput->numOfVULogPages = ARRAY_SIZE(deVULogPagesList);
+
+ for (vuLogIdx = 0; vuLogIdx < vuLogInput->numOfVULogPages; vuLogIdx++) {
+ dataBufferSize = deVULogPagesList[vuLogIdx].logPageLen;
+ dataBuffer = calloc(1, dataBufferSize);
+ memset(dataBuffer, 0, dataBufferSize);
+
+ ret = nvme_get_log_simple(dev_fd(dev),
+ deVULogPagesList[vuLogIdx].logPageId,
+ dataBufferSize, dataBuffer);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: nvme_get_log() for log page 0x%x failed, ret = %d\n",
+ deVULogPagesList[vuLogIdx].logPageId, ret);
+ } else {
+ wdc_UtilsDeleteCharFromString((char *)deVULogPagesList[vuLogIdx].logPageIdStr, 4, ' ');
+ wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s_%s_%s_%s.bin", (char *)bufferFolderPath, WDC_DE_PATH_SEPARATOR,
+ "LogPage", (char *)&deVULogPagesList[vuLogIdx].logPageIdStr, (char *)serialNo, (char *)timeString);
+ wdc_WriteToFile(fileName, (char *)dataBuffer, dataBufferSize);
+ }
+
+ free(dataBuffer);
+ dataBuffer = NULL;
+ }
+
+ free(vuLogInput);
+
+ /* Get NVMe Features (0x01, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C) */
+ for (listIdx = 1; listIdx < ARRAY_SIZE(deFeatureIdList); listIdx++) {
+ memset(featureIdBuff, 0, sizeof(featureIdBuff));
+ /* skipping LbaRangeType as it is an optional nvme command and not supported */
+ if (deFeatureIdList[listIdx].featureId == FID_LBA_RANGE_TYPE)
+ continue;
+ ret = nvme_get_features_data(dev_fd(dev),
+ (enum nvme_features_id)deFeatureIdList[listIdx].featureId,
+ WDC_DE_GLOBAL_NSID,
+ sizeof(featureIdBuff),
+ &featureIdBuff, &result);
+
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: nvme_get_feature id 0x%x failed, ret = %d\n",
+ deFeatureIdList[listIdx].featureId, ret);
+ } else {
+ wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s0x%x_%s_%s_%s.bin", (char *)bufferFolderPath, WDC_DE_PATH_SEPARATOR,
+ "FEATURE_ID_", deFeatureIdList[listIdx].featureId,
+ deFeatureIdList[listIdx].featureName, serialNo, timeString);
+ wdc_WriteToFile(fileName, (char *)featureIdBuff, sizeof(featureIdBuff));
+ }
+ }
+
+ ret = wdc_read_debug_directory(dev, bufferFolderPath, serialNo, timeString);
+
+ /* Get Dump Trace Data */
+ wdc_UtilsSnprintf(fileName, MAX_PATH_LEN, "%s%s%s_%s_%s.bin", (char *)bufferFolderPath, WDC_DE_PATH_SEPARATOR, "dumptrace", serialNo, timeString);
+ ret = wdc_de_get_dump_trace(dev, (char *)bufferFolderPath, 0, fileName);
+ if (ret != WDC_STATUS_SUCCESS)
+ fprintf(stderr, "ERROR: WDC: wdc_de_get_dump_trace failed, ret = %d\n", ret);
+
+ /* Tar the Drive Essentials directory */
+ wdc_UtilsSnprintf(tarFileName, sizeof(tarFileName), "%s%s", (char *)bufferFolderPath, WDC_DE_TAR_FILE_EXTN);
+ if (dir)
+ wdc_UtilsSnprintf(tarFiles, sizeof(tarFiles), "%s%s%s%s%s", (char *)dir,
+ WDC_DE_PATH_SEPARATOR, (char *)bufferFolderName,
+ WDC_DE_PATH_SEPARATOR, WDC_DE_TAR_FILES);
+ else
+ wdc_UtilsSnprintf(tarFiles, sizeof(tarFiles), "%s%s%s", (char *)bufferFolderName,
+ WDC_DE_PATH_SEPARATOR, WDC_DE_TAR_FILES);
+ wdc_UtilsSnprintf(tarCmd, sizeof(tarCmd), "%s %s %s", WDC_DE_TAR_CMD, (char *)tarFileName, (char *)tarFiles);
+
+ ret = system(tarCmd);
+
+ if (ret)
+ fprintf(stderr, "ERROR: WDC: Tar of Drive Essentials data failed, ret = %d\n",
+ ret);
+
+ fprintf(stderr, "Get of Drive Essentials data successful\n");
+ nvme_free_tree(r);
+ return 0;
+}
+
+static int wdc_drive_essentials(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ char *desc = "Capture Drive Essentials.";
+ char *dirName = "Output directory pathname.";
+ char d[PATH_MAX] = {0};
+ char k[PATH_MAX] = {0};
+ __u64 capabilities = 0;
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ char *d_ptr;
+ int ret;
+
+ struct config {
+ char *dirName;
+ };
+
+ struct config cfg = {
+ .dirName = NULL,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_STRING("dir-name", 'd', "DIRECTORY", &cfg.dirName, dirName),
+ OPT_END()
+ };
+
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+ if ((capabilities & WDC_DRIVE_CAP_DRIVE_ESSENTIALS) != WDC_DRIVE_CAP_DRIVE_ESSENTIALS) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ goto out;
+ }
+
+ if (cfg.dirName) {
+ strncpy(d, cfg.dirName, PATH_MAX - 1);
+ d_ptr = d;
+ } else {
+ d_ptr = NULL;
+ }
+
+ ret = wdc_do_drive_essentials(r, dev, d_ptr, k);
+out:
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_do_drive_resize(struct nvme_dev *dev, uint64_t new_size)
+{
+ int ret;
+ struct nvme_passthru_cmd admin_cmd;
+
+ memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd));
+ admin_cmd.opcode = WDC_NVME_DRIVE_RESIZE_OPCODE;
+ admin_cmd.cdw12 = ((WDC_NVME_DRIVE_RESIZE_SUBCMD << WDC_NVME_SUBCMD_SHIFT) |
+ WDC_NVME_DRIVE_RESIZE_CMD);
+ admin_cmd.cdw13 = new_size;
+
+ ret = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd, NULL);
+ return ret;
+}
+
+static int wdc_do_namespace_resize(struct nvme_dev *dev, __u32 nsid, __u32 op_option)
+{
+ int ret;
+ struct nvme_passthru_cmd admin_cmd;
+
+ memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd));
+ admin_cmd.opcode = WDC_NVME_NAMESPACE_RESIZE_OPCODE;
+ admin_cmd.nsid = nsid;
+ admin_cmd.cdw10 = op_option;
+
+ ret = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd, NULL);
+ return ret;
+}
+
+static int wdc_do_drive_info(struct nvme_dev *dev, __u32 *result)
+{
+ int ret;
+ struct nvme_passthru_cmd admin_cmd;
+
+ memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd));
+ admin_cmd.opcode = WDC_NVME_DRIVE_INFO_OPCODE;
+ admin_cmd.cdw12 = ((WDC_NVME_DRIVE_INFO_SUBCMD << WDC_NVME_SUBCMD_SHIFT) |
+ WDC_NVME_DRIVE_INFO_CMD);
+
+ ret = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd, NULL);
+
+ if (!ret && result)
+ *result = admin_cmd.result;
+
+ return ret;
+}
+
+static int wdc_drive_resize(int argc, char **argv,
+ struct command *command, struct plugin *plugin)
+{
+ const char *desc = "Send a Resize command.";
+ const char *size = "The new size (in GB) to resize the drive to.";
+ uint64_t capabilities = 0;
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ int ret;
+
+ struct config {
+ uint64_t size;
+ };
+
+ struct config cfg = {
+ .size = 0,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("size", 's', &cfg.size, size),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+ wdc_check_device(r, dev);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+ if ((capabilities & WDC_DRIVE_CAP_RESIZE) == WDC_DRIVE_CAP_RESIZE) {
+ ret = wdc_do_drive_resize(dev, cfg.size);
+ } else {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ }
+
+ if (!ret)
+ printf("New size: %" PRIu64 " GB\n", cfg.size);
+
+ nvme_show_status(ret);
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_namespace_resize(int argc, char **argv,
+ struct command *command, struct plugin *plugin)
+{
+ const char *desc = "Send a Namespace Resize command.";
+ const char *namespace_id = "The namespace id to resize.";
+ const char *op_option = "The over provisioning option to set for namespace.";
+ uint64_t capabilities = 0;
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ int ret;
+
+ struct config {
+ __u32 namespace_id;
+ __u32 op_option;
+ };
+
+ struct config cfg = {
+ .namespace_id = 0x1,
+ .op_option = 0xF,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_UINT("op-option", 'o', &cfg.op_option, op_option),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ if ((cfg.op_option != 0x1) && (cfg.op_option != 0x2) && (cfg.op_option != 0x3) &&
+ (cfg.op_option != 0xF)) {
+ fprintf(stderr, "ERROR: WDC: unsupported OP option parameter\n");
+ dev_close(dev);
+ return -1;
+ }
+
+ r = nvme_scan(NULL);
+ wdc_check_device(r, dev);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+ if ((capabilities & WDC_DRIVE_CAP_NS_RESIZE) == WDC_DRIVE_CAP_NS_RESIZE) {
+ ret = wdc_do_namespace_resize(dev, cfg.namespace_id,
+ cfg.op_option);
+
+ if (ret)
+ printf("ERROR: WDC: Namespace Resize of namespace id 0x%x, op option 0x%x failed\n", cfg.namespace_id, cfg.op_option);
+ } else {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ }
+
+ nvme_show_status(ret);
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_reason_identifier(int argc, char **argv,
+ struct command *command, struct plugin *plugin)
+{
+ const char *desc = "Retrieve telemetry log reason identifier.";
+ const char *log_id = "Log ID to retrieve - host - 7 or controller - 8";
+ const char *fname = "File name to save raw binary identifier";
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ int ret;
+ uint64_t capabilities = 0;
+ char f[PATH_MAX] = {0};
+ char fileSuffix[PATH_MAX] = {0};
+ UtilsTimeInfo timeInfo;
+ __u8 timeStamp[MAX_PATH_LEN];
+
+
+ struct config {
+ int log_id;
+ char *file;
+ };
+ struct config cfg = {
+ .log_id = 7,
+ .file = NULL,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("log-id", 'i', &cfg.log_id, log_id),
+ OPT_FILE("file", 'o', &cfg.file, fname),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+
+ if (cfg.log_id != NVME_LOG_LID_TELEMETRY_HOST &&
+ cfg.log_id != NVME_LOG_LID_TELEMETRY_CTRL) {
+ fprintf(stderr, "ERROR: WDC: Invalid Log ID. It must be 7 (Host) or 8 (Controller)\n");
+ ret = -1;
+ goto close_dev;
+ }
+
+ if (cfg.file) {
+ int verify_file;
+
+ /* verify the passed in file name and path is valid before getting the dump data */
+ verify_file = open(cfg.file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (verify_file < 0) {
+ fprintf(stderr, "ERROR: WDC: open: %s\n", strerror(errno));
+ ret = -1;
+ goto close_dev;
+ }
+ close(verify_file);
+ strncpy(f, cfg.file, PATH_MAX - 1);
+ } else {
+ wdc_UtilsGetTime(&timeInfo);
+ memset(timeStamp, 0, sizeof(timeStamp));
+ wdc_UtilsSnprintf((char *)timeStamp, MAX_PATH_LEN, "%02u%02u%02u_%02u%02u%02u",
+ timeInfo.year, timeInfo.month, timeInfo.dayOfMonth,
+ timeInfo.hour, timeInfo.minute, timeInfo.second);
+ if (cfg.log_id == NVME_LOG_LID_TELEMETRY_CTRL)
+ snprintf(fileSuffix, PATH_MAX, "_error_reason_identifier_ctlr_%s", (char *)timeStamp);
+ else
+ snprintf(fileSuffix, PATH_MAX, "_error_reason_identifier_host_%s", (char *)timeStamp);
+
+ if (wdc_get_serial_name(dev, f, PATH_MAX, fileSuffix) == -1) {
+ fprintf(stderr, "ERROR: WDC: failed to generate file name\n");
+ ret = -1;
+ goto close_dev;
+ }
+ if (strlen(f) > PATH_MAX - 5) {
+ fprintf(stderr, "ERROR: WDC: file name overflow\n");
+ ret = -1;
+ goto close_dev;
+ }
+ strcat(f, ".bin");
+ }
+
+ fprintf(stderr, "%s: filename = %s\n", __func__, f);
+
+ capabilities = wdc_get_drive_capabilities(r, dev);
+ if ((capabilities & WDC_DRIVE_CAP_REASON_ID) == WDC_DRIVE_CAP_REASON_ID) {
+ ret = wdc_do_get_reason_id(dev, f, cfg.log_id);
+ } else {
+ fprintf(stderr, "ERROR: WDC:unsupported device for this command\n");
+ ret = -1;
+ }
+
+ nvme_show_status(ret);
+
+close_dev:
+ dev_close(dev);
+ nvme_free_tree(r);
+ return ret;
+}
+
+static const char *nvme_log_id_to_string(__u8 log_id)
+{
+ switch (log_id) {
+ case NVME_LOG_LID_ERROR:
+ return "Error Information Log ID";
+ case NVME_LOG_LID_SMART:
+ return "Smart/Health Information Log ID";
+ case NVME_LOG_LID_FW_SLOT:
+ return "Firmware Slot Information Log ID";
+ case NVME_LOG_LID_CHANGED_NS:
+ return "Namespace Changed Log ID";
+ case NVME_LOG_LID_CMD_EFFECTS:
+ return "Commamds Supported and Effects Log ID";
+ case NVME_LOG_LID_DEVICE_SELF_TEST:
+ return "Device Self Test Log ID";
+ case NVME_LOG_LID_TELEMETRY_HOST:
+ return "Telemetry Host Initiated Log ID";
+ case NVME_LOG_LID_TELEMETRY_CTRL:
+ return "Telemetry Controller Generated Log ID";
+ case NVME_LOG_LID_ENDURANCE_GROUP:
+ return "Endurance Group Log ID";
+ case NVME_LOG_LID_ANA:
+ return "ANA Log ID";
+ case NVME_LOG_LID_PERSISTENT_EVENT:
+ return "Persistent Event Log ID";
+ case NVME_LOG_LID_DISCOVER:
+ return "Discovery Log ID";
+ case NVME_LOG_LID_RESERVATION:
+ return "Reservation Notification Log ID";
+ case NVME_LOG_LID_SANITIZE:
+ return "Sanitize Status Log ID";
+ case WDC_LOG_ID_C0:
+ return "WDC Vendor Unique Log ID C0";
+ case WDC_LOG_ID_C1:
+ return "WDC Vendor Unique Log ID C1";
+ case WDC_LOG_ID_C2:
+ return "WDC Vendor Unique Log ID C2";
+ case WDC_LOG_ID_C3:
+ return "WDC Vendor Unique Log ID C3";
+ case WDC_LOG_ID_C4:
+ return "WDC Vendor Unique Log ID C4";
+ case WDC_LOG_ID_C5:
+ return "WDC Vendor Unique Log ID C5";
+ case WDC_LOG_ID_C6:
+ return "WDC Vendor Unique Log ID C6";
+ case WDC_LOG_ID_C8:
+ return "WDC Vendor Unique Log ID C8";
+ case WDC_LOG_ID_CA:
+ return "WDC Vendor Unique Log ID CA";
+ case WDC_LOG_ID_CB:
+ return "WDC Vendor Unique Log ID CB";
+ case WDC_LOG_ID_D0:
+ return "WDC Vendor Unique Log ID D0";
+ case WDC_LOG_ID_D1:
+ return "WDC Vendor Unique Log ID D1";
+ case WDC_LOG_ID_D6:
+ return "WDC Vendor Unique Log ID D6";
+ case WDC_LOG_ID_D7:
+ return "WDC Vendor Unique Log ID D7";
+ case WDC_LOG_ID_D8:
+ return "WDC Vendor Unique Log ID D8";
+ case WDC_LOG_ID_DE:
+ return "WDC Vendor Unique Log ID DE";
+ case WDC_LOG_ID_F0:
+ return "WDC Vendor Unique Log ID F0";
+ case WDC_LOG_ID_F1:
+ return "WDC Vendor Unique Log ID F1";
+ case WDC_LOG_ID_F2:
+ return "WDC Vendor Unique Log ID F2";
+ case WDC_LOG_ID_FA:
+ return "WDC Vendor Unique Log ID FA";
+ default:
+ return "Unknown Log ID";
+ }
+}
+
+static void __json_log_page_directory(struct log_page_directory *directory)
+{
+ __u32 bitmap_idx;
+ __u8 log_id;
+ struct json_object *root;
+ struct json_object *entries;
+
+ root = json_create_object();
+
+ entries = json_create_array();
+ json_object_add_value_array(root, "Entries", entries);
+
+ for (bitmap_idx = 0; bitmap_idx < BYTE_TO_BIT(sizeof(__u64)); bitmap_idx++) {
+ log_id = bitmap_idx;
+ if (!log_page_name[log_id])
+ continue;
+ if (directory->supported_lid_bitmap & (1ULL << bitmap_idx)) {
+ struct json_object *json_entry = json_create_object();
+
+ json_object_add_value_uint(json_entry, "Log ID", log_id);
+ json_object_add_value_string(json_entry, "Log Page Name",
+ log_page_name[log_id]);
+
+ json_array_add_value_object(entries, json_entry);
+ }
+ }
+
+ for (bitmap_idx = 0; bitmap_idx < BYTE_TO_BIT(sizeof(__u64)); bitmap_idx++) {
+ log_id = NVME_LOG_NS_BASE + bitmap_idx;
+ if (!log_page_name[log_id])
+ continue;
+ if (directory->supported_ns_lid_bitmap & (1ULL << bitmap_idx)) {
+ struct json_object *json_entry = json_create_object();
+
+ json_object_add_value_uint(json_entry, "Log ID", log_id);
+ json_object_add_value_string(json_entry, "Log Page Name",
+ log_page_name[log_id]);
+
+ json_array_add_value_object(entries, json_entry);
+ }
+ }
+
+ for (bitmap_idx = 0; bitmap_idx < BYTE_TO_BIT(sizeof(__u64)); bitmap_idx++) {
+ log_id = NVME_LOG_VS_BASE + bitmap_idx;
+ if (!log_page_name[log_id])
+ continue;
+ if (directory->supported_vs_lid_bitmap & (1ULL << bitmap_idx)) {
+ struct json_object *json_entry = json_create_object();
+
+ json_object_add_value_uint(json_entry, "Log ID", log_id);
+ json_object_add_value_string(json_entry, "Log Page Name",
+ log_page_name[log_id]);
+
+ json_array_add_value_object(entries, json_entry);
+ }
+ }
+
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+}
+
+
+static void __show_log_page_directory(struct log_page_directory *directory)
+{
+ __u32 bitmap_idx;
+ __u8 log_id;
+
+ for (bitmap_idx = 0; bitmap_idx < BYTE_TO_BIT(sizeof(__u64)); bitmap_idx++) {
+ if (directory->supported_lid_bitmap & (1ULL << bitmap_idx)) {
+ log_id = bitmap_idx;
+ if (log_page_name[log_id])
+ printf("0x%02X: %s\n", log_id, log_page_name[log_id]);
+ }
+ }
+
+ for (bitmap_idx = 0; bitmap_idx < BYTE_TO_BIT(sizeof(__u64)); bitmap_idx++) {
+ if (directory->supported_ns_lid_bitmap & (1ULL << bitmap_idx)) {
+ log_id = NVME_LOG_NS_BASE + bitmap_idx;
+ if (log_page_name[log_id])
+ printf("0x%02X: %s\n", log_id, log_page_name[log_id]);
+ }
+ }
+
+ for (bitmap_idx = 0; bitmap_idx < BYTE_TO_BIT(sizeof(__u64)); bitmap_idx++) {
+ if (directory->supported_vs_lid_bitmap & (1ULL << bitmap_idx)) {
+ log_id = NVME_LOG_VS_BASE + bitmap_idx;
+ if (log_page_name[log_id])
+ printf("0x%02X: %s\n", log_id, log_page_name[log_id]);
+ }
+ }
+}
+
+static int wdc_log_page_directory(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Retrieve Log Page Directory.";
+ enum nvme_print_flags fmt;
+ struct nvme_dev *dev;
+ int ret = 0;
+ nvme_root_t r;
+ __u64 capabilities = 0;
+ struct wdc_c2_cbs_data *cbs_data = NULL;
+ int i;
+ __u8 log_id = 0;
+ __u32 device_id, read_vendor_id;
+ bool uuid_supported = false;
+ struct nvme_id_uuid_list uuid_list;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json|binary"),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ ret = validate_output_format(cfg.output_format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "%s: ERROR: WDC: invalid output format\n", __func__);
+ dev_close(dev);
+ return ret;
+ }
+
+ r = nvme_scan(NULL);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+
+ if (!(capabilities & WDC_DRIVE_CAP_LOG_PAGE_DIR)) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ } else {
+
+ memset(&uuid_list, 0, sizeof(struct nvme_id_uuid_list));
+ if (wdc_CheckUuidListSupport(dev, &uuid_list))
+ uuid_supported = true;
+
+ if (uuid_supported)
+ fprintf(stderr, "WDC: UUID lists supported\n");
+ else
+ fprintf(stderr, "WDC: UUID lists NOT supported\n");
+
+
+ ret = wdc_get_pci_ids(r, dev, &device_id, &read_vendor_id);
+ log_id = (device_id == WDC_NVME_ZN350_DEV_ID ||
+ device_id == WDC_NVME_ZN350_DEV_ID_1) ?
+ WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_ID_C8 :
+ WDC_NVME_GET_DEV_MGMNT_LOG_PAGE_ID;
+
+ if (!wdc_is_sn861(device_id)) {
+ /* verify the 0xC2 Device Manageability log page is supported */
+ if (wdc_nvme_check_supported_log_page(r, dev, log_id) == false) {
+ fprintf(stderr,
+ "%s: ERROR: WDC: 0x%x Log Page not supported\n",
+ __func__, log_id);
+ ret = -1;
+ goto out;
+ }
+
+ if (!get_dev_mgment_cbs_data(r, dev,
+ WDC_C2_LOG_PAGES_SUPPORTED_ID,
+ (void *)&cbs_data)) {
+ fprintf(stderr,
+ "%s: ERROR: WDC: 0xC2 Log Page entry ID 0x%x not found\n",
+ __func__, WDC_C2_LOG_PAGES_SUPPORTED_ID);
+ ret = -1;
+ goto out;
+ }
+ if (!cbs_data) {
+ fprintf(stderr, "%s: ERROR: WDC: NULL_data ptr\n", __func__);
+ ret = -1;
+ goto out;
+ }
+ printf("Log Page Directory\n");
+ /* print the supported pages */
+ if (!strcmp(cfg.output_format, "normal")) {
+ for (i = 0; i < le32_to_cpu(cbs_data->length); i++)
+ printf("0x%x - %s\n", cbs_data->data[i],
+ nvme_log_id_to_string(cbs_data->data[i]));
+ } else if (!strcmp(cfg.output_format, "binary")) {
+ d((__u8 *)cbs_data->data,
+ le32_to_cpu(cbs_data->length), 16, 1);
+ } else if (!strcmp(cfg.output_format, "json")) {
+ struct json_object *root = json_create_object();
+
+ for (i = 0; i < le32_to_cpu(cbs_data->length); i++) {
+ json_object_add_value_int(root,
+ nvme_log_id_to_string(cbs_data->data[i]),
+ cbs_data->data[i]);
+ }
+
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+ } else {
+ fprintf(stderr,
+ "%s: ERROR: WDC: Invalid format, format = %s\n",
+ __func__, cfg.output_format);
+ }
+
+ free(cbs_data);
+ } else {
+ struct log_page_directory *dir;
+ void *data = NULL;
+ __u32 result;
+
+ if (posix_memalign(&data, getpagesize(), 512)) {
+ fprintf(stderr,
+ "can not allocate log page directory payload\n");
+ ret = ENOMEM;
+ goto out;
+ }
+
+ dir = (struct log_page_directory *)data;
+ ret = nvme_admin_passthru(dev_fd(dev), WDC_NVME_ADMIN_VUC_OPCODE_D2, 0, 0,
+ 0, 0, 0, 8,
+ 0, WDC_VUC_SUBOPCODE_LOG_PAGE_DIR_D2, 0, 0, 0,
+ 32, data, 0, NULL,
+ 0, &result);
+
+ if (!ret) {
+ switch (fmt) {
+ case BINARY:
+ d_raw((unsigned char *)data, 32);
+ break;
+ case JSON:
+ __json_log_page_directory(dir);
+ break;
+ default:
+ __show_log_page_directory(dir);
+ }
+ } else {
+ fprintf(stderr, "NVMe Status:%s(%x)\n",
+ nvme_status_to_string(ret, false), ret);
+ }
+ }
+ }
+
+out:
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_get_drive_reason_id(struct nvme_dev *dev, char *drive_reason_id, size_t len)
+{
+ int i, j;
+ int ret;
+ int res_len = 0;
+ struct nvme_id_ctrl ctrl;
+ char *reason_id_str = "reason_id";
+
+ i = sizeof(ctrl.sn) - 1;
+ j = sizeof(ctrl.mn) - 1;
+ memset(drive_reason_id, 0, len);
+ memset(&ctrl, 0, sizeof(struct nvme_id_ctrl));
+ ret = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: nvme_identify_ctrl() failed 0x%x\n", ret);
+ return -1;
+ }
+ /* Remove trailing spaces from the sn and mn */
+ while (i && ctrl.sn[i] == ' ') {
+ ctrl.sn[i] = '\0';
+ i--;
+ }
+
+ while (j && ctrl.mn[j] == ' ') {
+ ctrl.mn[j] = '\0';
+ j--;
+ }
+
+ res_len = snprintf(drive_reason_id, len, "%s_%s_%s", ctrl.sn, ctrl.mn, reason_id_str);
+ if (len <= res_len) {
+ fprintf(stderr,
+ "ERROR: WDC: cannot format serial number due to data of unexpected length\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int wdc_save_reason_id(struct nvme_dev *dev, __u8 *rsn_ident, int size)
+{
+ int ret = 0;
+ char *reason_id_file;
+ char drive_reason_id[PATH_MAX] = {0};
+ char reason_id_path[PATH_MAX] = WDC_REASON_ID_PATH_NAME;
+ struct stat st = {0};
+
+ if (wdc_get_drive_reason_id(dev, drive_reason_id, PATH_MAX) == -1) {
+ fprintf(stderr, "%s: ERROR: failed to get drive reason id\n", __func__);
+ return -1;
+ }
+
+ /* make the nvmecli dir in /usr/local if it doesn't already exist */
+ if (stat(reason_id_path, &st) == -1) {
+ if (mkdir(reason_id_path, 0700) < 0) {
+ fprintf(stderr, "%s: ERROR: failed to mkdir %s: %s\n",
+ __func__, reason_id_path, strerror(errno));
+ return -1;
+ }
+ }
+
+ if (asprintf(&reason_id_file, "%s/%s%s", reason_id_path,
+ drive_reason_id, ".bin") < 0)
+ return -ENOMEM;
+
+ fprintf(stderr, "%s: reason id file = %s\n", __func__, reason_id_file);
+
+ /* save off the error reason identifier to a file in /usr/local/nvmecli */
+ ret = wdc_create_log_file(reason_id_file, rsn_ident, WDC_REASON_ID_ENTRY_LEN);
+ free(reason_id_file);
+
+ return ret;
+}
+
+static int wdc_clear_reason_id(struct nvme_dev *dev)
+{
+ int ret = -1;
+ int verify_file;
+ char *reason_id_file;
+ char drive_reason_id[PATH_MAX] = {0};
+
+ if (wdc_get_drive_reason_id(dev, drive_reason_id, PATH_MAX) == -1) {
+ fprintf(stderr, "%s: ERROR: failed to get drive reason id\n", __func__);
+ return -1;
+ }
+
+ if (asprintf(&reason_id_file, "%s/%s%s", WDC_REASON_ID_PATH_NAME,
+ drive_reason_id, ".bin") < 0)
+ return -ENOMEM;
+
+ /* verify the drive reason id file name and path is valid */
+ verify_file = open(reason_id_file, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (verify_file < 0) {
+ ret = -1;
+ goto free;
+ }
+ close(verify_file);
+
+ /* remove the reason id file */
+ ret = remove(reason_id_file);
+
+free:
+ free(reason_id_file);
+
+ return ret;
+}
+
+static int wdc_dump_telemetry_hdr(struct nvme_dev *dev, int log_id, struct nvme_telemetry_log *log_hdr)
+{
+ int ret = 0;
+
+ if (log_id == NVME_LOG_LID_TELEMETRY_HOST)
+ ret = nvme_get_log_create_telemetry_host(dev_fd(dev), log_hdr);
+ else
+ ret = nvme_get_log_telemetry_ctrl(dev_fd(dev), false, 0, 512,
+ (void *)log_hdr);
+
+ if (ret < 0) {
+ perror("get-telemetry-log");
+ } else if (ret > 0) {
+ nvme_show_status(ret);
+ fprintf(stderr, "%s: ERROR: Failed to acquire telemetry header, ret = %d!\n", __func__, ret);
+ }
+
+ return ret;
+}
+
+static int wdc_do_get_reason_id(struct nvme_dev *dev, char *file, int log_id)
+{
+ int ret;
+ struct nvme_telemetry_log *log_hdr;
+ __u32 log_hdr_size = sizeof(struct nvme_telemetry_log);
+ __u32 reason_id_size = 0;
+
+ log_hdr = (struct nvme_telemetry_log *)malloc(log_hdr_size);
+ if (!log_hdr) {
+ fprintf(stderr, "%s: ERROR: malloc failed, size : 0x%x, status: %s\n", __func__, log_hdr_size, strerror(errno));
+ ret = -1;
+ goto out;
+ }
+ memset(log_hdr, 0, log_hdr_size);
+
+ ret = wdc_dump_telemetry_hdr(dev, log_id, log_hdr);
+ if (ret) {
+ fprintf(stderr, "%s: ERROR: get telemetry header failed, ret : %d\n", __func__, ret);
+ ret = -1;
+ goto out;
+ }
+
+ reason_id_size = sizeof(log_hdr->rsnident);
+
+ if (log_id == NVME_LOG_LID_TELEMETRY_CTRL)
+ wdc_save_reason_id(dev, log_hdr->rsnident, reason_id_size);
+
+ ret = wdc_create_log_file(file, (__u8 *)log_hdr->rsnident, reason_id_size);
+
+out:
+ free(log_hdr);
+ return ret;
+}
+
+static void wdc_print_nand_stats_normal(__u16 version, void *data)
+{
+ struct wdc_nand_stats *nand_stats = (struct wdc_nand_stats *)(data);
+ struct wdc_nand_stats_V3 *nand_stats_v3 = (struct wdc_nand_stats_V3 *)(data);
+ __u64 temp_raw;
+ __u16 temp_norm;
+ __u64 *temp_ptr = NULL;
+
+ switch (version) {
+ case 0:
+ printf(" NAND Statistics :-\n");
+ printf(" NAND Writes TLC (Bytes) %s\n",
+ uint128_t_to_string(
+ le128_to_cpu(nand_stats->nand_write_tlc)));
+ printf(" NAND Writes SLC (Bytes) %s\n",
+ uint128_t_to_string(
+ le128_to_cpu(nand_stats->nand_write_slc)));
+ printf(" NAND Program Failures %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(nand_stats->nand_prog_failure));
+ printf(" NAND Erase Failures %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(nand_stats->nand_erase_failure));
+ printf(" Bad Block Count %"PRIu32"\n",
+ (uint32_t)le32_to_cpu(nand_stats->bad_block_count));
+ printf(" NAND XOR/RAID Recovery Trigger Events %"PRIu64"\n",
+ le64_to_cpu(nand_stats->nand_rec_trigger_event));
+ printf(" E2E Error Counter %"PRIu64"\n",
+ le64_to_cpu(nand_stats->e2e_error_counter));
+ printf(" Number Successful NS Resizing Events %"PRIu64"\n",
+ le64_to_cpu(nand_stats->successful_ns_resize_event));
+ printf(" log page version %"PRIu16"\n",
+ le16_to_cpu(nand_stats->log_page_version));
+ break;
+ case 3:
+ printf(" NAND Statistics V3:-\n");
+ printf(" TLC Units Written %s\n",
+ uint128_t_to_string(
+ le128_to_cpu(nand_stats_v3->nand_write_tlc)));
+ printf(" SLC Units Written %s\n",
+ uint128_t_to_string(
+ le128_to_cpu(nand_stats_v3->nand_write_slc)));
+ temp_ptr = (__u64 *)nand_stats_v3->bad_nand_block_count;
+ temp_norm = (__u16)(*temp_ptr & 0x000000000000FFFF);
+ temp_raw = ((*temp_ptr & 0xFFFFFFFFFFFF0000) >> 16);
+ printf(" Bad NAND Blocks Count - Normalized %"PRIu16"\n",
+ le16_to_cpu(temp_norm));
+ printf(" Bad NAND Blocks Count - Raw %"PRIu64"\n",
+ le64_to_cpu(temp_raw));
+ printf(" NAND XOR Recovery count %"PRIu64"\n",
+ le64_to_cpu(nand_stats_v3->xor_recovery_count));
+ printf(" UECC Read Error count %"PRIu64"\n",
+ le64_to_cpu(nand_stats_v3->uecc_read_error_count));
+ printf(" SSD End to End corrected errors %"PRIu64"\n",
+ le64_to_cpu(nand_stats_v3->ssd_correction_counts[0]));
+ printf(" SSD End to End detected errors %"PRIu32"\n",
+ le32_to_cpu(nand_stats_v3->ssd_correction_counts[8]));
+ printf(" SSD End to End uncorrected E2E errors %"PRIu32"\n",
+ le32_to_cpu(nand_stats_v3->ssd_correction_counts[12]));
+ printf(" System data %% life-used %u\n",
+ nand_stats_v3->percent_life_used);
+ printf(" User Data Erase Counts - TLC Min %"PRIu64"\n",
+ le64_to_cpu(nand_stats_v3->user_data_erase_counts[0]));
+ printf(" User Data Erase Counts - TLC Max %"PRIu64"\n",
+ le64_to_cpu(nand_stats_v3->user_data_erase_counts[1]));
+ printf(" User Data Erase Counts - SLC Min %"PRIu64"\n",
+ le64_to_cpu(nand_stats_v3->user_data_erase_counts[2]));
+ printf(" User Data Erase Counts - SLC Max %"PRIu64"\n",
+ le64_to_cpu(nand_stats_v3->user_data_erase_counts[3]));
+ temp_ptr = (__u64 *)nand_stats_v3->program_fail_count;
+ temp_norm = (__u16)(*temp_ptr & 0x000000000000FFFF);
+ temp_raw = ((*temp_ptr & 0xFFFFFFFFFFFF0000) >> 16);
+ printf(" Program Fail Count - Normalized %"PRIu16"\n",
+ le16_to_cpu(temp_norm));
+ printf(" Program Fail Count - Raw %"PRIu64"\n",
+ le64_to_cpu(temp_raw));
+ temp_ptr = (__u64 *)nand_stats_v3->erase_fail_count;
+ temp_norm = (__u16)(*temp_ptr & 0x000000000000FFFF);
+ temp_raw = ((*temp_ptr & 0xFFFFFFFFFFFF0000) >> 16);
+ printf(" Erase Fail Count - Normalized %"PRIu16"\n",
+ le16_to_cpu(temp_norm));
+ printf(" Erase Fail Count - Raw %"PRIu64"\n",
+ le64_to_cpu(temp_raw));
+ printf(" PCIe Correctable Error Count %"PRIu16"\n",
+ le16_to_cpu(nand_stats_v3->correctable_error_count));
+ printf(" %% Free Blocks (User) %u\n",
+ nand_stats_v3->percent_free_blocks_user);
+ printf(" Security Version Number %"PRIu64"\n",
+ le64_to_cpu(nand_stats_v3->security_version_number));
+ printf(" %% Free Blocks (System) %u\n",
+ nand_stats_v3->percent_free_blocks_system);
+ printf(" Data Set Management Commands %s\n",
+ uint128_t_to_string(
+ le128_to_cpu(nand_stats_v3->trim_completions)));
+ printf(" Estimate of Incomplete Trim Data %"PRIu64"\n",
+ le64_to_cpu(nand_stats_v3->trim_completions[16]));
+ printf(" %% of completed trim %u\n",
+ nand_stats_v3->trim_completions[24]);
+ printf(" Background Back-Pressure-Guage %u\n",
+ nand_stats_v3->back_pressure_guage);
+ printf(" Soft ECC Error Count %"PRIu64"\n",
+ le64_to_cpu(nand_stats_v3->soft_ecc_error_count));
+ printf(" Refresh Count %"PRIu64"\n",
+ le64_to_cpu(nand_stats_v3->refresh_count));
+ temp_ptr = (__u64 *)nand_stats_v3->bad_sys_nand_block_count;
+ temp_norm = (__u16)(*temp_ptr & 0x000000000000FFFF);
+ temp_raw = ((*temp_ptr & 0xFFFFFFFFFFFF0000) >> 16);
+ printf(" Bad System Nand Block Count - Normalized %"PRIu16"\n",
+ le16_to_cpu(temp_norm));
+ printf(" Bad System Nand Block Count - Raw %"PRIu64"\n",
+ le64_to_cpu(temp_raw));
+ printf(" Endurance Estimate %s\n",
+ uint128_t_to_string(
+ le128_to_cpu(nand_stats_v3->endurance_estimate)));
+ printf(" Thermal Throttling Count %u\n",
+ nand_stats_v3->thermal_throttling_st_ct[0]);
+ printf(" Thermal Throttling Status %u\n",
+ nand_stats_v3->thermal_throttling_st_ct[1]);
+ printf(" Unaligned I/O %"PRIu64"\n",
+ le64_to_cpu(nand_stats_v3->unaligned_IO));
+ printf(" Physical Media Units Read %s\n",
+ uint128_t_to_string(
+ le128_to_cpu(nand_stats_v3->physical_media_units)));
+ printf(" log page version %"PRIu16"\n",
+ le16_to_cpu(nand_stats_v3->log_page_version));
+ break;
+
+ default:
+ fprintf(stderr, "WDC: Nand Stats ERROR: Invalid version\n");
+ break;
+
+ }
+}
+
+static void wdc_print_nand_stats_json(__u16 version, void *data)
+{
+ struct wdc_nand_stats *nand_stats = (struct wdc_nand_stats *)(data);
+ struct wdc_nand_stats_V3 *nand_stats_v3 = (struct wdc_nand_stats_V3 *)(data);
+ struct json_object *root = json_create_object();
+ __u64 temp_raw;
+ __u16 temp_norm;
+ __u64 *temp_ptr = NULL;
+
+ switch (version) {
+ case 0:
+ json_object_add_value_uint128(root, "NAND Writes TLC (Bytes)",
+ le128_to_cpu(nand_stats->nand_write_tlc));
+ json_object_add_value_uint128(root, "NAND Writes SLC (Bytes)",
+ le128_to_cpu(nand_stats->nand_write_slc));
+ json_object_add_value_uint(root, "NAND Program Failures",
+ le32_to_cpu(nand_stats->nand_prog_failure));
+ json_object_add_value_uint(root, "NAND Erase Failures",
+ le32_to_cpu(nand_stats->nand_erase_failure));
+ json_object_add_value_uint(root, "Bad Block Count",
+ le32_to_cpu(nand_stats->bad_block_count));
+ json_object_add_value_uint64(root, "NAND XOR/RAID Recovery Trigger Events",
+ le64_to_cpu(nand_stats->nand_rec_trigger_event));
+ json_object_add_value_uint64(root, "E2E Error Counter",
+ le64_to_cpu(nand_stats->e2e_error_counter));
+ json_object_add_value_uint64(root, "Number Successful NS Resizing Events",
+ le64_to_cpu(nand_stats->successful_ns_resize_event));
+
+ json_print_object(root, NULL);
+ printf("\n");
+ break;
+ case 3:
+ json_object_add_value_uint128(root, "NAND Writes TLC (Bytes)",
+ le128_to_cpu(nand_stats_v3->nand_write_tlc));
+ json_object_add_value_uint128(root, "NAND Writes SLC (Bytes)",
+ le128_to_cpu(nand_stats_v3->nand_write_slc));
+ temp_ptr = (__u64 *)nand_stats_v3->bad_nand_block_count;
+ temp_norm = (__u16)(*temp_ptr & 0x000000000000FFFF);
+ temp_raw = ((*temp_ptr & 0xFFFFFFFFFFFF0000) >> 16);
+ json_object_add_value_uint(root, "Bad NAND Blocks Count - Normalized",
+ le16_to_cpu(temp_norm));
+ json_object_add_value_uint64(root, "Bad NAND Blocks Count - Raw",
+ le64_to_cpu(temp_raw));
+ json_object_add_value_uint64(root, "NAND XOR Recovery count",
+ le64_to_cpu(nand_stats_v3->xor_recovery_count));
+ json_object_add_value_uint64(root, "UECC Read Error count",
+ le64_to_cpu(nand_stats_v3->uecc_read_error_count));
+ json_object_add_value_uint64(root, "SSD End to End corrected errors",
+ le64_to_cpu(nand_stats_v3->ssd_correction_counts[0]));
+ json_object_add_value_uint(root, "SSD End to End detected errors",
+ le32_to_cpu(nand_stats_v3->ssd_correction_counts[8]));
+ json_object_add_value_uint(root, "SSD End to End uncorrected E2E errors",
+ le32_to_cpu(nand_stats_v3->ssd_correction_counts[12]));
+ json_object_add_value_uint(root, "System data % life-used",
+ nand_stats_v3->percent_life_used);
+ json_object_add_value_uint64(root, "User Data Erase Counts - SLC Min",
+ le64_to_cpu(nand_stats_v3->user_data_erase_counts[0]));
+ json_object_add_value_uint64(root, "User Data Erase Counts - SLC Max",
+ le64_to_cpu(nand_stats_v3->user_data_erase_counts[1]));
+ json_object_add_value_uint64(root, "User Data Erase Counts - TLC Min",
+ le64_to_cpu(nand_stats_v3->user_data_erase_counts[2]));
+ json_object_add_value_uint64(root, "User Data Erase Counts - TLC Max",
+ le64_to_cpu(nand_stats_v3->user_data_erase_counts[3]));
+ temp_ptr = (__u64 *)nand_stats_v3->program_fail_count;
+ temp_norm = (__u16)(*temp_ptr & 0x000000000000FFFF);
+ temp_raw = ((*temp_ptr & 0xFFFFFFFFFFFF0000) >> 16);
+ json_object_add_value_uint(root, "Program Fail Count - Normalized",
+ le16_to_cpu(temp_norm));
+ json_object_add_value_uint64(root, "Program Fail Count - Raw",
+ le64_to_cpu(temp_raw));
+ temp_ptr = (__u64 *)nand_stats_v3->erase_fail_count;
+ temp_norm = (__u16)(*temp_ptr & 0x000000000000FFFF);
+ temp_raw = ((*temp_ptr & 0xFFFFFFFFFFFF0000) >> 16);
+ json_object_add_value_uint(root, "Erase Fail Count - Normalized",
+ le16_to_cpu(temp_norm));
+ json_object_add_value_uint64(root, "Erase Fail Count - Raw",
+ le64_to_cpu(temp_raw));
+ json_object_add_value_uint(root, "PCIe Correctable Error Count",
+ le16_to_cpu(nand_stats_v3->correctable_error_count));
+ json_object_add_value_uint(root, "% Free Blocks (User)",
+ nand_stats_v3->percent_free_blocks_user);
+ json_object_add_value_uint64(root, "Security Version Number",
+ le64_to_cpu(nand_stats_v3->security_version_number));
+ json_object_add_value_uint(root, "% Free Blocks (System)",
+ nand_stats_v3->percent_free_blocks_system);
+ json_object_add_value_uint128(root, "Data Set Management Commands",
+ le128_to_cpu(nand_stats_v3->trim_completions));
+ json_object_add_value_uint64(root, "Estimate of Incomplete Trim Data",
+ le64_to_cpu(nand_stats_v3->trim_completions[16]));
+ json_object_add_value_uint(root, "%% of completed trim",
+ nand_stats_v3->trim_completions[24]);
+ json_object_add_value_uint(root, "Background Back-Pressure-Guage",
+ nand_stats_v3->back_pressure_guage);
+ json_object_add_value_uint64(root, "Soft ECC Error Count",
+ le64_to_cpu(nand_stats_v3->soft_ecc_error_count));
+ json_object_add_value_uint64(root, "Refresh Count",
+ le64_to_cpu(nand_stats_v3->refresh_count));
+ temp_ptr = (__u64 *)nand_stats_v3->bad_sys_nand_block_count;
+ temp_norm = (__u16)(*temp_ptr & 0x000000000000FFFF);
+ temp_raw = ((*temp_ptr & 0xFFFFFFFFFFFF0000) >> 16);
+ json_object_add_value_uint(root, "Bad System Nand Block Count - Normalized",
+ le16_to_cpu(temp_norm));
+ json_object_add_value_uint64(root, "Bad System Nand Block Count - Raw",
+ le64_to_cpu(temp_raw));
+ json_object_add_value_uint128(root, "Endurance Estimate",
+ le128_to_cpu(nand_stats_v3->endurance_estimate));
+ json_object_add_value_uint(root, "Thermal Throttling Status",
+ nand_stats_v3->thermal_throttling_st_ct[0]);
+ json_object_add_value_uint(root, "Thermal Throttling Count",
+ nand_stats_v3->thermal_throttling_st_ct[1]);
+ json_object_add_value_uint64(root, "Unaligned I/O",
+ le64_to_cpu(nand_stats_v3->unaligned_IO));
+ json_object_add_value_uint128(root, "Physical Media Units Read",
+ le128_to_cpu(nand_stats_v3->physical_media_units));
+ json_object_add_value_uint(root, "log page version",
+ le16_to_cpu(nand_stats_v3->log_page_version));
+
+ json_print_object(root, NULL);
+ printf("\n");
+ break;
+ default:
+ printf("%s: Invalid Stats Version = %d\n", __func__, version);
+ break;
+ }
+
+ json_free_object(root);
+
+}
+
+static void wdc_print_pcie_stats_normal(struct wdc_vs_pcie_stats *pcie_stats)
+{
+ printf(" PCIE Statistics :-\n");
+ printf(" Unsupported Request Error Counter %20"PRIu64"\n",
+ le64_to_cpu(pcie_stats->unsupportedRequestErrorCount));
+ printf(" ECRC Error Status Counter %20"PRIu64"\n",
+ le64_to_cpu(pcie_stats->ecrcErrorStatusCount));
+ printf(" Malformed TLP Status Counter %20"PRIu64"\n",
+ le64_to_cpu(pcie_stats->malformedTlpStatusCount));
+ printf(" Receiver Overflow Status Counter %20"PRIu64"\n",
+ le64_to_cpu(pcie_stats->receiverOverflowStatusCount));
+ printf(" Unexpected Completion Status Counter %20"PRIu64"\n",
+ le64_to_cpu(pcie_stats->unexpectedCmpltnStatusCount));
+ printf(" Complete Abort Status Counter %20"PRIu64"\n",
+ le64_to_cpu(pcie_stats->completeAbortStatusCount));
+ printf(" Completion Timeout Status Counter %20"PRIu64"\n",
+ le64_to_cpu(pcie_stats->cmpltnTimoutStatusCount));
+ printf(" Flow Control Error Status Counter %20"PRIu64"\n",
+ le64_to_cpu(pcie_stats->flowControlErrorStatusCount));
+ printf(" Poisoned TLP Status Counter %20"PRIu64"\n",
+ le64_to_cpu(pcie_stats->poisonedTlpStatusCount));
+ printf(" Dlink Protocol Error Status Counter %20"PRIu64"\n",
+ le64_to_cpu(pcie_stats->dLinkPrtclErrorStatusCount));
+ printf(" Advisory Non Fatal Error Status Counter %20"PRIu64"\n",
+ le64_to_cpu(pcie_stats->advsryNFatalErrStatusCount));
+ printf(" Replay Timer TO Status Counter %20"PRIu64"\n",
+ le64_to_cpu(pcie_stats->replayTimerToStatusCount));
+ printf(" Replay Number Rollover Status Counter %20"PRIu64"\n",
+ le64_to_cpu(pcie_stats->replayNumRolloverStCount));
+ printf(" Bad DLLP Status Counter %20"PRIu64"\n",
+ le64_to_cpu(pcie_stats->badDllpStatusCount));
+ printf(" Bad TLP Status Counter %20"PRIu64"\n",
+ le64_to_cpu(pcie_stats->badTlpStatusCount));
+ printf(" Receiver Error Status Counter %20"PRIu64"\n",
+ le64_to_cpu(pcie_stats->receiverErrStatusCount));
+
+}
+
+static void wdc_print_pcie_stats_json(struct wdc_vs_pcie_stats *pcie_stats)
+{
+ struct json_object *root = json_create_object();
+
+ json_object_add_value_uint64(root, "Unsupported Request Error Counter",
+ le64_to_cpu(pcie_stats->unsupportedRequestErrorCount));
+ json_object_add_value_uint64(root, "ECRC Error Status Counter",
+ le64_to_cpu(pcie_stats->ecrcErrorStatusCount));
+ json_object_add_value_uint64(root, "Malformed TLP Status Counter",
+ le64_to_cpu(pcie_stats->malformedTlpStatusCount));
+
+ json_object_add_value_uint64(root, "Receiver Overflow Status Counter",
+ le64_to_cpu(pcie_stats->receiverOverflowStatusCount));
+ json_object_add_value_uint64(root, "Unexpected Completion Status Counter",
+ le64_to_cpu(pcie_stats->unexpectedCmpltnStatusCount));
+ json_object_add_value_uint64(root, "Complete Abort Status Counter",
+ le64_to_cpu(pcie_stats->completeAbortStatusCount));
+ json_object_add_value_uint64(root, "Completion Timeout Status Counter",
+ le64_to_cpu(pcie_stats->cmpltnTimoutStatusCount));
+ json_object_add_value_uint64(root, "Flow Control Error Status Counter",
+ le64_to_cpu(pcie_stats->flowControlErrorStatusCount));
+ json_object_add_value_uint64(root, "Poisoned TLP Status Counter",
+ le64_to_cpu(pcie_stats->poisonedTlpStatusCount));
+ json_object_add_value_uint64(root, "Dlink Protocol Error Status Counter",
+ le64_to_cpu(pcie_stats->dLinkPrtclErrorStatusCount));
+ json_object_add_value_uint64(root, "Advisory Non Fatal Error Status Counter",
+ le64_to_cpu(pcie_stats->advsryNFatalErrStatusCount));
+ json_object_add_value_uint64(root, "Replay Timer TO Status Counter",
+ le64_to_cpu(pcie_stats->replayTimerToStatusCount));
+ json_object_add_value_uint64(root, "Replay Number Rollover Status Counter",
+ le64_to_cpu(pcie_stats->replayNumRolloverStCount));
+ json_object_add_value_uint64(root, "Bad DLLP Status Counter",
+ le64_to_cpu(pcie_stats->badDllpStatusCount));
+ json_object_add_value_uint64(root, "Bad TLP Status Counter",
+ le64_to_cpu(pcie_stats->badTlpStatusCount));
+ json_object_add_value_uint64(root, "Receiver Error Status Counter",
+ le64_to_cpu(pcie_stats->receiverErrStatusCount));
+
+ json_print_object(root, NULL);
+ printf("\n");
+
+ json_free_object(root);
+}
+
+static int wdc_do_vs_nand_stats_sn810_2(struct nvme_dev *dev, char *format)
+{
+ enum nvme_print_flags fmt;
+ uint8_t *data = NULL;
+ int ret;
+
+ data = NULL;
+ ret = nvme_get_ext_smart_cloud_log(dev_fd(dev), &data, 0,
+ NVME_NSID_ALL);
+
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: %s : Failed to retrieve NAND stats\n", __func__);
+ goto out;
+ } else {
+ ret = validate_output_format(format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC: %s : invalid output format\n", __func__);
+ goto out;
+ }
+
+ /* parse the data */
+ switch (fmt) {
+ case NORMAL:
+ wdc_print_ext_smart_cloud_log_normal(data, WDC_SCA_V1_NAND_STATS);
+ break;
+ case JSON:
+ wdc_print_ext_smart_cloud_log_json(data, WDC_SCA_V1_NAND_STATS);
+ break;
+ default:
+ break;
+ }
+ }
+
+out:
+ if (data)
+ free(data);
+ return ret;
+}
+
+static int wdc_do_vs_nand_stats(struct nvme_dev *dev, char *format)
+{
+ enum nvme_print_flags fmt;
+ uint8_t *output = NULL;
+ __u16 version = 0;
+ int ret;
+
+ output = (uint8_t *)calloc(WDC_NVME_NAND_STATS_SIZE, sizeof(uint8_t));
+ if (!output) {
+ fprintf(stderr, "ERROR: WDC: calloc: %s\n", strerror(errno));
+ ret = -1;
+ goto out;
+ }
+
+ ret = nvme_get_log_simple(dev_fd(dev), WDC_NVME_NAND_STATS_LOG_ID,
+ WDC_NVME_NAND_STATS_SIZE, (void *)output);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: %s : Failed to retrieve NAND stats\n", __func__);
+ goto out;
+ } else {
+ ret = validate_output_format(format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC: invalid output format\n");
+ goto out;
+ }
+
+ version = output[WDC_NVME_NAND_STATS_SIZE - 2];
+
+ /* parse the data */
+ switch (fmt) {
+ case NORMAL:
+ wdc_print_nand_stats_normal(version, output);
+ break;
+ case JSON:
+ wdc_print_nand_stats_json(version, output);
+ break;
+ default:
+ break;
+ }
+ }
+
+out:
+ free(output);
+ return ret;
+}
+
+static int wdc_vs_nand_stats(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Retrieve NAND statistics.";
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ __u64 capabilities = 0;
+ uint32_t read_device_id = 0, read_vendor_id = 0;
+ int ret;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+
+ if (!(capabilities & WDC_DRIVE_CAP_NAND_STATS)) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ } else {
+ ret = wdc_get_pci_ids(r, dev, &read_device_id, &read_vendor_id);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC: %s: failure to get pci ids, ret = %d\n", __func__, ret);
+ return -1;
+ }
+
+ switch (read_device_id) {
+ case WDC_NVME_SN820CL_DEV_ID:
+ ret = wdc_do_vs_nand_stats_sn810_2(dev,
+ cfg.output_format);
+ break;
+ default:
+ ret = wdc_do_vs_nand_stats(dev, cfg.output_format);
+ break;
+ }
+ }
+
+ if (ret)
+ fprintf(stderr, "ERROR: WDC: Failure reading NAND statistics, ret = %d\n", ret);
+
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_do_vs_pcie_stats(struct nvme_dev *dev,
+ struct wdc_vs_pcie_stats *pcieStatsPtr)
+{
+ int ret;
+ struct nvme_passthru_cmd admin_cmd;
+ int pcie_stats_size = sizeof(struct wdc_vs_pcie_stats);
+
+ memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd));
+ admin_cmd.opcode = WDC_NVME_PCIE_STATS_OPCODE;
+ admin_cmd.addr = (__u64)(uintptr_t)pcieStatsPtr;
+ admin_cmd.data_len = pcie_stats_size;
+
+ ret = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd, NULL);
+
+ return ret;
+}
+
+static int wdc_vs_pcie_stats(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Retrieve PCIE statistics.";
+ enum nvme_print_flags fmt;
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ int ret;
+ __u64 capabilities = 0;
+ _cleanup_huge_ struct nvme_mem_huge mh = { 0, };
+ struct wdc_vs_pcie_stats *pcieStatsPtr = NULL;
+ int pcie_stats_size = sizeof(struct wdc_vs_pcie_stats);
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+ ret = validate_output_format(cfg.output_format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC: invalid output format\n");
+ goto out;
+ }
+
+ pcieStatsPtr = nvme_alloc_huge(pcie_stats_size, &mh);
+ if (!pcieStatsPtr) {
+ fprintf(stderr, "ERROR: WDC: PCIE Stats alloc: %s\n", strerror(errno));
+ ret = -1;
+ goto out;
+ }
+
+ memset((void *)pcieStatsPtr, 0, pcie_stats_size);
+
+ capabilities = wdc_get_drive_capabilities(r, dev);
+
+ if (!(capabilities & WDC_DRIVE_CAP_PCIE_STATS)) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ } else {
+ ret = wdc_do_vs_pcie_stats(dev, pcieStatsPtr);
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC: Failure reading PCIE statistics, ret = 0x%x\n", ret);
+ } else {
+ /* parse the data */
+ switch (fmt) {
+ case NORMAL:
+ wdc_print_pcie_stats_normal(pcieStatsPtr);
+ break;
+ case JSON:
+ wdc_print_pcie_stats_json(pcieStatsPtr);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+out:
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_vs_drive_info(int argc, char **argv,
+ struct command *command, struct plugin *plugin)
+{
+ const char *desc = "Send a vs-drive-info command.";
+ enum nvme_print_flags fmt;
+ nvme_root_t r;
+ uint64_t capabilities = 0;
+ struct nvme_dev *dev;
+ int ret;
+ __le32 result;
+ __u16 size;
+ double rev;
+ struct nvme_id_ctrl ctrl;
+ char vsData[32] = {0};
+ char major_rev = 0, minor_rev = 0;
+ __u8 *data = NULL;
+ __u32 ftl_unit_size = 0, tcg_dev_ownership = 0;
+ __u16 boot_spec_major = 0, boot_spec_minor = 0;
+ struct json_object *root = NULL;
+ char formatter[41] = { 0 };
+ char rev_str[16] = { 0 };
+ uint32_t read_device_id = -1, read_vendor_id = -1;
+ struct __packed wdc_nvme_ext_smart_log * ext_smart_log_ptr = NULL;
+ struct ocp_drive_info info;
+ __u32 data_len = 0;
+ unsigned int num_dwords = 0;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ ret = validate_output_format(cfg.output_format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC %s invalid output format\n", __func__);
+ dev_close(dev);
+ return ret;
+ }
+
+ /* get the id ctrl data used to fill in drive info below */
+ ret = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+
+ if (ret) {
+ fprintf(stderr, "ERROR: WDC %s: Identify Controller failed\n", __func__);
+ dev_close(dev);
+ return ret;
+ }
+
+ r = nvme_scan(NULL);
+ wdc_check_device(r, dev);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+ if ((capabilities & WDC_DRIVE_CAP_INFO) == WDC_DRIVE_CAP_INFO) {
+ ret = wdc_get_pci_ids(r, dev, &read_device_id, &read_vendor_id);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC: %s: failure to get pci ids, ret = %d\n", __func__, ret);
+ goto out;
+ }
+
+ switch (read_device_id) {
+ case WDC_NVME_SN640_DEV_ID:
+ case WDC_NVME_SN640_DEV_ID_1:
+ case WDC_NVME_SN640_DEV_ID_2:
+ case WDC_NVME_SN640_DEV_ID_3:
+ case WDC_NVME_SN650_DEV_ID:
+ case WDC_NVME_SN650_DEV_ID_1:
+ case WDC_NVME_SN650_DEV_ID_2:
+ case WDC_NVME_SN650_DEV_ID_3:
+ case WDC_NVME_SN650_DEV_ID_4:
+ case WDC_NVME_SN655_DEV_ID:
+ case WDC_NVME_SN560_DEV_ID_1:
+ case WDC_NVME_SN560_DEV_ID_2:
+ case WDC_NVME_SN560_DEV_ID_3:
+ case WDC_NVME_SN550_DEV_ID:
+ case WDC_NVME_ZN350_DEV_ID:
+ case WDC_NVME_ZN350_DEV_ID_1:
+ ret = wdc_do_drive_info(dev, &result);
+
+ if (!ret) {
+ size = (__u16)((cpu_to_le32(result) & 0xffff0000) >> 16);
+ rev = (double)(cpu_to_le32(result) & 0x0000ffff);
+
+ if (fmt == NORMAL) {
+ printf("Drive HW Revision: %4.1f\n", (.1 * rev));
+ printf("FTL Unit Size: 0x%x KB\n", size);
+ printf("Customer SN: %-.*s\n", (int)sizeof(ctrl.sn), &ctrl.sn[0]);
+ } else if (fmt == JSON) {
+ root = json_create_object();
+ sprintf(rev_str, "%4.1f", (.1 * rev));
+ json_object_add_value_string(root, "Drive HW Revision", rev_str);
+
+ json_object_add_value_int(root, "FTL Unit Size", le16_to_cpu(size));
+ wdc_StrFormat(formatter, sizeof(formatter), &ctrl.sn[0], sizeof(ctrl.sn));
+ json_object_add_value_string(root, "Customer SN", formatter);
+
+ json_print_object(root, NULL);
+ printf("\n");
+
+ json_free_object(root);
+ }
+ }
+ break;
+ case WDC_NVME_SN730_DEV_ID:
+ memcpy(vsData, &ctrl.vs[0], 32);
+
+ major_rev = ctrl.sn[12];
+ minor_rev = ctrl.sn[13];
+
+ if (fmt == NORMAL) {
+ printf("Drive HW Revision: %c.%c\n", major_rev, minor_rev);
+ printf("Customer SN: %-.*s\n", 14, &ctrl.sn[0]);
+ } else if (fmt == JSON) {
+ root = json_create_object();
+ sprintf(rev_str, "%c.%c", major_rev, minor_rev);
+ json_object_add_value_string(root, "Drive HW Revison", rev_str);
+ wdc_StrFormat(formatter, sizeof(formatter), &ctrl.sn[0], 14);
+ json_object_add_value_string(root, "Customer SN", formatter);
+
+ json_print_object(root, NULL);
+ printf("\n");
+
+ json_free_object(root);
+ }
+ break;
+ case WDC_NVME_SN820CL_DEV_ID:
+ /* Get the Drive HW Rev from the C6 Log page */
+ ret = nvme_get_hw_rev_log(dev_fd(dev), &data, 0,
+ NVME_NSID_ALL);
+ if (!ret) {
+ struct wdc_nvme_hw_rev_log *log_data = (struct wdc_nvme_hw_rev_log *)data;
+
+ major_rev = log_data->hw_rev_gdr;
+
+ free(data);
+ data = NULL;
+ } else {
+ fprintf(stderr, "ERROR: WDC: %s: failure to get hw revision log\n", __func__);
+ ret = -1;
+ goto out;
+ }
+
+ /* Get the Smart C0 log page */
+ if (!(capabilities & WDC_DRIVE_CAP_CLOUD_LOG_PAGE)) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ goto out;
+ }
+
+ ret = nvme_get_ext_smart_cloud_log(dev_fd(dev), &data,
+ 0, NVME_NSID_ALL);
+
+ if (!ret) {
+ ext_smart_log_ptr = (struct __packed wdc_nvme_ext_smart_log *)data;
+
+ /* Set the FTL Unit size */
+ ftl_unit_size = le32_to_cpu(ext_smart_log_ptr->ext_smart_ftlus);
+
+ /* Set the Boot Spec Version */
+ boot_spec_major = le16_to_cpu(ext_smart_log_ptr->ext_smart_maj);
+ boot_spec_minor = le16_to_cpu(ext_smart_log_ptr->ext_smart_min);
+
+ /* Set the Drive Ownership Status */
+ tcg_dev_ownership = le32_to_cpu(ext_smart_log_ptr->ext_smart_tcgos);
+ free(data);
+ } else {
+ fprintf(stderr, "ERROR: WDC: %s: failure to get extended smart cloud log\n", __func__);
+ ret = -1;
+ goto out;
+ }
+
+ if (fmt == NORMAL) {
+ printf("Drive HW Revision: %2d\n", major_rev);
+ printf("FTL Unit Size: %d\n", ftl_unit_size);
+ printf("HyperScale Boot Version Spec: %d.%d\n", boot_spec_major, boot_spec_minor);
+ printf("TCG Device Ownership Status: %2d\n", tcg_dev_ownership);
+
+ } else if (fmt == JSON) {
+ root = json_create_object();
+
+ json_object_add_value_int(root, "Drive HW Revison", major_rev);
+ json_object_add_value_int(root, "FTL Unit Size", ftl_unit_size);
+ sprintf(rev_str, "%d.%d", boot_spec_major, boot_spec_minor);
+ json_object_add_value_string(root, "HyperScale Boot Version Spec", rev_str);
+ json_object_add_value_int(root, "TCG Device Ownership Status", tcg_dev_ownership);
+
+ json_print_object(root, NULL);
+ printf("\n");
+
+ json_free_object(root);
+ }
+
+ break;
+ case WDC_NVME_SN861_DEV_ID:
+ fallthrough;
+ case WDC_NVME_SN861_DEV_ID_1:
+ data_len = sizeof(info);
+ num_dwords = data_len / 4;
+ if (data_len % 4 != 0)
+ num_dwords += 1;
+
+ ret = nvme_admin_passthru(dev_fd(dev),
+ WDC_NVME_ADMIN_VUC_OPCODE_D2,
+ 0, 0, 0, 0, 0, num_dwords, 0,
+ WDC_VUC_SUBOPCODE_VS_DRIVE_INFO_D2,
+ 0, 0, 0, data_len, &info, 0,
+ NULL, 0, NULL);
+
+ if (!ret) {
+ __u16 hw_rev_major, hw_rev_minor;
+
+ hw_rev_major = le32_to_cpu(info.hw_revision) / 10;
+ hw_rev_minor = le32_to_cpu(info.hw_revision) % 10;
+ if (fmt == NORMAL) {
+ printf("HW Revision : %" PRIu32 ".%" PRIu32 "\n",
+ hw_rev_major, hw_rev_minor);
+ printf("FTL Unit Size : %" PRIu32 "\n",
+ le32_to_cpu(info.ftl_unit_size));
+ } else if (fmt == JSON) {
+ char buf[20];
+
+ root = json_create_object();
+
+ memset((void *)buf, 0, 20);
+ sprintf(buf, "%" PRIu32 ".%" PRIu32,
+ hw_rev_major, hw_rev_minor);
+
+ json_object_add_value_string(root,
+ "hw_revision", buf);
+ json_object_add_value_uint(root,
+ "ftl_unit_size",
+ le32_to_cpu(info.ftl_unit_size));
+
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+ }
+ }
+ break;
+ default:
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ break;
+ }
+ } else {
+ fprintf(stderr, "ERROR: WDC: capability not supported by this device\n");
+ ret = -1;
+ }
+
+out:
+ nvme_show_status(ret);
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_vs_temperature_stats(int argc, char **argv,
+ struct command *command, struct plugin *plugin)
+{
+ const char *desc = "Send a vs-temperature-stats command.";
+ struct nvme_smart_log smart_log;
+ struct nvme_id_ctrl id_ctrl;
+ enum nvme_print_flags fmt;
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ uint64_t capabilities = 0;
+ __u32 hctm_tmt;
+ int temperature, temp_tmt1, temp_tmt2;
+ int ret;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, "Output Format: normal|json"),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ r = nvme_scan(NULL);
+ ret = validate_output_format(cfg.output_format, &fmt);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: WDC: invalid output format\n");
+ goto out;
+ }
+
+ /* check if command is supported */
+ wdc_check_device(r, dev);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+ if ((capabilities & WDC_DRIVE_CAP_TEMP_STATS) != WDC_DRIVE_CAP_TEMP_STATS) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ ret = -1;
+ goto out;
+ }
+
+ /* get the temperature stats or report errors */
+ ret = nvme_identify_ctrl(dev_fd(dev), &id_ctrl);
+ if (ret)
+ goto out;
+ ret = nvme_get_log_smart(dev_fd(dev), NVME_NSID_ALL, false,
+ &smart_log);
+ if (ret)
+ goto out;
+
+ /* convert from kelvins to degrees Celsius */
+ temperature = ((smart_log.temperature[1] << 8) | smart_log.temperature[0]) - 273;
+
+ /* retrieve HCTM Thermal Management Temperatures */
+ nvme_get_features_simple(dev_fd(dev), 0x10, 0, &hctm_tmt);
+ temp_tmt1 = ((hctm_tmt >> 16) & 0xffff) ? ((hctm_tmt >> 16) & 0xffff) - 273 : 0;
+ temp_tmt2 = (hctm_tmt & 0xffff) ? (hctm_tmt & 0xffff) - 273 : 0;
+
+ if (fmt == NORMAL) {
+ /* print the temperature stats */
+ printf("Temperature Stats for NVME device:%s namespace-id:%x\n",
+ dev->name, WDC_DE_GLOBAL_NSID);
+
+ printf("Current Composite Temperature : %d °C\n", temperature);
+ printf("WCTEMP : %"PRIu16" °C\n", id_ctrl.wctemp - 273);
+ printf("CCTEMP : %"PRIu16" °C\n", id_ctrl.cctemp - 273);
+ printf("DITT support : 0\n");
+ printf("HCTM support : %"PRIu16"\n", id_ctrl.hctma);
+
+ printf("HCTM Light (TMT1) : %"PRIu16" °C\n", temp_tmt1);
+ printf("TMT1 Transition Counter : %"PRIu32"\n", smart_log.thm_temp1_trans_count);
+ printf("TMT1 Total Time : %"PRIu32"\n", smart_log.thm_temp1_total_time);
+
+ printf("HCTM Heavy (TMT2) : %"PRIu16" °C\n", temp_tmt2);
+ printf("TMT2 Transition Counter : %"PRIu32"\n", smart_log.thm_temp2_trans_count);
+ printf("TMT2 Total Time : %"PRIu32"\n", smart_log.thm_temp2_total_time);
+ printf("Thermal Shutdown Threshold : 95 °C\n");
+ } else if (fmt == JSON) {
+ struct json_object *root;
+
+ root = json_create_object();
+
+ json_object_add_value_int(root, "Current Composite Temperature", le32_to_cpu(temperature));
+ json_object_add_value_int(root, "WCTEMP", le16_to_cpu(id_ctrl.wctemp - 273));
+ json_object_add_value_int(root, "CCTEMP", le16_to_cpu(id_ctrl.cctemp - 273));
+ json_object_add_value_int(root, "DITT support", 0);
+ json_object_add_value_int(root, "HCTM support", le16_to_cpu(id_ctrl.hctma));
+
+ json_object_add_value_int(root, "HCTM Light (TMT1)", le16_to_cpu(temp_tmt1));
+ json_object_add_value_int(root, "TMT1 Transition Counter", le32_to_cpu(smart_log.thm_temp1_trans_count));
+ json_object_add_value_int(root, "TMT1 Total Time", le32_to_cpu(smart_log.thm_temp1_total_time));
+
+ json_object_add_value_int(root, "HCTM Light (TMT2)", le16_to_cpu(temp_tmt2));
+ json_object_add_value_int(root, "TMT2 Transition Counter", le32_to_cpu(smart_log.thm_temp2_trans_count));
+ json_object_add_value_int(root, "TMT2 Total Time", le32_to_cpu(smart_log.thm_temp2_total_time));
+ json_object_add_value_int(root, "Thermal Shutdown Threshold", 95);
+
+ json_print_object(root, NULL);
+ printf("\n");
+
+ json_free_object(root);
+ } else {
+ printf("%s: Invalid format\n", __func__);
+ }
+
+out:
+ nvme_show_status(ret);
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_capabilities(int argc, char **argv, struct command *command, struct plugin *plugin)
+{
+ const char *desc = "Send a capabilities command.";
+ uint64_t capabilities = 0;
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ int ret;
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ /* get capabilities */
+ r = nvme_scan(NULL);
+ wdc_check_device(r, dev);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+
+ /* print command and supported status */
+ printf("WDC Plugin Capabilities for NVME device:%s\n", dev->name);
+ printf("cap-diag : %s\n",
+ capabilities & WDC_DRIVE_CAP_CAP_DIAG ? "Supported" : "Not Supported");
+ printf("drive-log : %s\n",
+ capabilities & WDC_DRIVE_CAP_DRIVE_LOG ? "Supported" : "Not Supported");
+ printf("get-crash-dump : %s\n",
+ capabilities & WDC_DRIVE_CAP_CRASH_DUMP ? "Supported" : "Not Supported");
+ printf("get-pfail-dump : %s\n",
+ capabilities & WDC_DRIVE_CAP_PFAIL_DUMP ? "Supported" : "Not Supported");
+ printf("id-ctrl : Supported\n");
+ printf("purge : %s\n",
+ capabilities & WDC_DRIVE_CAP_PURGE ? "Supported" : "Not Supported");
+ printf("purge-monitor : %s\n",
+ capabilities & WDC_DRIVE_CAP_PURGE ? "Supported" : "Not Supported");
+ printf("vs-internal-log : %s\n",
+ capabilities & WDC_DRIVE_CAP_INTERNAL_LOG_MASK ? "Supported" : "Not Supported");
+ printf("vs-nand-stats : %s\n",
+ capabilities & WDC_DRIVE_CAP_NAND_STATS ? "Supported" : "Not Supported");
+ printf("vs-smart-add-log : %s\n",
+ capabilities & WDC_DRIVE_CAP_SMART_LOG_MASK ? "Supported" : "Not Supported");
+ printf("--C0 Log Page : %s\n",
+ capabilities & WDC_DRIVE_CAP_C0_LOG_PAGE ? "Supported" : "Not Supported");
+ printf("--C1 Log Page : %s\n",
+ capabilities & WDC_DRIVE_CAP_C1_LOG_PAGE ? "Supported" : "Not Supported");
+ printf("--C3 Log Page : %s\n",
+ capabilities & WDC_DRIVE_CAP_C3_LOG_PAGE ? "Supported" : "Not Supported");
+ printf("--CA Log Page : %s\n",
+ capabilities & WDC_DRIVE_CAP_CA_LOG_PAGE ? "Supported" : "Not Supported");
+ printf("--D0 Log Page : %s\n",
+ capabilities & WDC_DRIVE_CAP_D0_LOG_PAGE ? "Supported" : "Not Supported");
+ printf("clear-pcie-correctable-errors : %s\n",
+ capabilities & WDC_DRIVE_CAP_CLEAR_PCIE_MASK ? "Supported" : "Not Supported");
+ printf("drive-essentials : %s\n",
+ capabilities & WDC_DRIVE_CAP_DRIVE_ESSENTIALS ? "Supported" : "Not Supported");
+ printf("get-drive-status : %s\n",
+ capabilities & WDC_DRIVE_CAP_DRIVE_STATUS ? "Supported" : "Not Supported");
+ printf("clear-assert-dump : %s\n",
+ capabilities & WDC_DRIVE_CAP_CLEAR_ASSERT ? "Supported" : "Not Supported");
+ printf("drive-resize : %s\n",
+ capabilities & WDC_DRIVE_CAP_RESIZE ? "Supported" : "Not Supported");
+ printf("vs-fw-activate-history : %s\n",
+ capabilities & WDC_DRIVE_CAP_FW_ACTIVATE_HISTORY_MASK ? "Supported" : "Not Supported");
+ printf("clear-fw-activate-history : %s\n",
+ capabilities & WDC_DRIVE_CAP_CLEAR_FW_ACT_HISTORY_MASK ? "Supported" : "Not Supported");
+ printf("vs-telemetry-controller-option: %s\n",
+ capabilities & WDC_DRVIE_CAP_DISABLE_CTLR_TELE_LOG ? "Supported" : "Not Supported");
+ printf("vs-error-reason-identifier : %s\n",
+ capabilities & WDC_DRIVE_CAP_REASON_ID ? "Supported" : "Not Supported");
+ printf("log-page-directory : %s\n",
+ capabilities & WDC_DRIVE_CAP_LOG_PAGE_DIR ? "Supported" : "Not Supported");
+ printf("namespace-resize : %s\n",
+ capabilities & WDC_DRIVE_CAP_NS_RESIZE ? "Supported" : "Not Supported");
+ printf("vs-drive-info : %s\n",
+ capabilities & WDC_DRIVE_CAP_INFO ? "Supported" : "Not Supported");
+ printf("vs-temperature-stats : %s\n",
+ capabilities & WDC_DRIVE_CAP_TEMP_STATS ? "Supported" : "Not Supported");
+ printf("cloud-SSD-plugin-version : %s\n",
+ capabilities & WDC_DRIVE_CAP_CLOUD_SSD_VERSION ? "Supported" : "Not Supported");
+ printf("vs-pcie-stats : %s\n",
+ capabilities & WDC_DRIVE_CAP_PCIE_STATS ? "Supported" : "Not Supported");
+ printf("get-error-recovery-log : %s\n",
+ capabilities & WDC_DRIVE_CAP_OCP_C1_LOG_PAGE ? "Supported" : "Not Supported");
+ printf("get-dev-capabilities-log : %s\n",
+ capabilities & WDC_DRIVE_CAP_OCP_C4_LOG_PAGE ? "Supported" : "Not Supported");
+ printf("get-unsupported-reqs-log : %s\n",
+ capabilities & WDC_DRIVE_CAP_OCP_C5_LOG_PAGE ? "Supported" : "Not Supported");
+ printf("get-latency-monitor-log : %s\n",
+ capabilities & WDC_DRIVE_CAP_C3_LOG_PAGE ? "Supported" : "Not Supported");
+ printf("cloud-boot-SSD-version : %s\n",
+ capabilities & WDC_DRIVE_CAP_CLOUD_BOOT_SSD_VERSION ? "Supported" : "Not Supported");
+ printf("vs-cloud-log : %s\n",
+ capabilities & WDC_DRIVE_CAP_CLOUD_LOG_PAGE ? "Supported" : "Not Supported");
+ printf("vs-hw-rev-log : %s\n",
+ capabilities & WDC_DRIVE_CAP_HW_REV_LOG_PAGE ? "Supported" : "Not Supported");
+ printf("vs-device_waf : %s\n",
+ capabilities & WDC_DRIVE_CAP_DEVICE_WAF ? "Supported" : "Not Supported");
+ printf("set-latency-monitor-feature : %s\n",
+ capabilities & WDC_DRIVE_CAP_SET_LATENCY_MONITOR ? "Supported" : "Not Supported");
+ printf("capabilities : Supported\n");
+ nvme_free_tree(r);
+ dev_close(dev);
+ return 0;
+}
+
+static int wdc_cloud_ssd_plugin_version(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Get Cloud SSD Plugin Version command.";
+ uint64_t capabilities = 0;
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ int ret;
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ /* get capabilities */
+ r = nvme_scan(NULL);
+ wdc_check_device(r, dev);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+
+ if ((capabilities & WDC_DRIVE_CAP_CLOUD_SSD_VERSION) == WDC_DRIVE_CAP_CLOUD_SSD_VERSION) {
+ /* print command and supported status */
+ printf("WDC Cloud SSD Plugin Version: 1.0\n");
+ } else {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ }
+
+ nvme_free_tree(r);
+ dev_close(dev);
+ return 0;
+}
+
+static int wdc_cloud_boot_SSD_version(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Get Cloud Boot SSD Version command.";
+ const char *namespace_id = "desired namespace id";
+ nvme_root_t r;
+ uint64_t capabilities = 0;
+ struct nvme_dev *dev;
+ int ret;
+ int major = 0, minor = 0;
+ __u8 *data = NULL;
+ struct __packed wdc_nvme_ext_smart_log * ext_smart_log_ptr = NULL;
+
+ struct config {
+ __u32 namespace_id;
+ };
+
+ struct config cfg = {
+ .namespace_id = NVME_NSID_ALL,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+ if (ret)
+ return ret;
+
+ /* get capabilities */
+ r = nvme_scan(NULL);
+ wdc_check_device(r, dev);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+
+ if ((capabilities & WDC_DRIVE_CAP_CLOUD_BOOT_SSD_VERSION) == WDC_DRIVE_CAP_CLOUD_BOOT_SSD_VERSION) {
+ /* Get the 0xC0 Smart Cloud Attribute V1 log data */
+ ret = nvme_get_ext_smart_cloud_log(dev_fd(dev), &data, 0,
+ cfg.namespace_id);
+
+ ext_smart_log_ptr = (struct __packed wdc_nvme_ext_smart_log *)data;
+ if (!ret) {
+ major = le16_to_cpu(ext_smart_log_ptr->ext_smart_maj);
+ minor = le16_to_cpu(ext_smart_log_ptr->ext_smart_min);
+
+ /* print the version returned from the log page */
+ printf("HyperScale Boot Version: %d.%d\n", major, minor);
+ } else {
+ fprintf(stderr, "ERROR: WDC: Unable to read Extended Smart/C0 Log Page data\n");
+ ret = -1;
+ }
+
+ if (data)
+ free(data);
+ } else {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ }
+
+ nvme_free_tree(r);
+ dev_close(dev);
+ return ret;
+}
+
+static int wdc_enc_get_log(int argc, char **argv, struct command *command, struct plugin *plugin)
+{
+ char *desc = "Get Enclosure Log.";
+ char *file = "Output file pathname.";
+ char *size = "Data retrieval transfer size.";
+ char *log = "Enclosure Log Page ID.";
+ struct nvme_dev *dev;
+ FILE *output_fd;
+ int xfer_size = 0;
+ int len;
+ int err = 0;
+
+ struct config {
+ char *file;
+ __u32 xfer_size;
+ __u32 log_id;
+ };
+
+ struct config cfg = {
+ .file = NULL,
+ .xfer_size = 0,
+ .log_id = 0xffffffff,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FILE("output-file", 'o', &cfg.file, file),
+ OPT_UINT("transfer-size", 's', &cfg.xfer_size, size),
+ OPT_UINT("log-id", 'l', &cfg.log_id, log),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ goto ret;
+
+ if (!wdc_enc_check_model(dev)) {
+ err = -EINVAL;
+ goto closed_fd;
+ }
+
+ if (cfg.log_id > 0xff) {
+ fprintf(stderr,
+ "Invalid log identifier: %d. Valid 0xd1, 0xd2, 0xd3, 0xd4, 0xe2, 0xe4\n",
+ cfg.log_id);
+ goto closed_fd;
+ }
+
+ if (cfg.xfer_size) {
+ xfer_size = cfg.xfer_size;
+ if (!wdc_check_power_of_2(cfg.xfer_size)) {
+ fprintf(stderr, "%s: ERROR: xfer-size (%d) must be a power of 2\n",
+ __func__, cfg.xfer_size);
+ err = -EINVAL;
+ goto closed_fd;
+ }
+ }
+
+ /* Log IDs are only for specific enclosures */
+ if (cfg.log_id) {
+ xfer_size = (xfer_size) ? xfer_size : WDC_NVME_ENC_LOG_SIZE_CHUNK;
+ len = !cfg.file ? 0 : strlen(cfg.file);
+ if (len > 0) {
+ output_fd = fopen(cfg.file, "wb");
+ if (!output_fd) {
+ fprintf(stderr, "%s: ERROR: opening:%s: %s\n", __func__, cfg.file,
+ strerror(errno));
+ err = -EINVAL;
+ goto closed_fd;
+ }
+ } else {
+ output_fd = stdout;
+ }
+ if (cfg.log_id == WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_1 ||
+ cfg.log_id == WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_2 ||
+ cfg.log_id == WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_3 ||
+ cfg.log_id == WDC_ENC_NIC_CRASH_DUMP_ID_SLOT_4) {
+ fprintf(stderr, "args - sz:%x logid:%x of:%s\n", xfer_size, cfg.log_id,
+ cfg.file);
+ err = wdc_enc_get_nic_log(dev, cfg.log_id, xfer_size,
+ WDC_NVME_ENC_NIC_LOG_SIZE, output_fd);
+ } else {
+ fprintf(stderr, "args - sz:%x logid:%x of:%s\n", xfer_size, cfg.log_id,
+ cfg.file);
+ err = wdc_enc_submit_move_data(dev, NULL, 0, xfer_size, output_fd,
+ cfg.log_id, 0, 0);
+ }
+
+ if (err == WDC_RESULT_NOT_AVAILABLE) {
+ fprintf(stderr, "No Log/Crashdump available\n");
+ err = 0;
+ } else if (err) {
+ fprintf(stderr, "ERROR: 0x%x Failed to collect log-id:%x\n", err,
+ cfg.log_id);
+ }
+ }
+closed_fd:
+ dev_close(dev);
+ret:
+ return err;
+}
+
+static int wdc_enc_submit_move_data(struct nvme_dev *dev, char *cmd, int len,
+ int xfer_size, FILE *out, int log_id,
+ int cdw14, int cdw15)
+{
+ struct timespec time;
+ uint32_t response_size, more;
+ int err;
+ int handle;
+ uint32_t offset = 0;
+ char *buf;
+
+ buf = (char *)malloc(sizeof(__u8) * xfer_size);
+ if (!buf) {
+ fprintf(stderr, "%s: ERROR: malloc: %s\n", __func__, strerror(errno));
+ return -1;
+ }
+ /* send something no matter what */
+ cmd = (len) ? cmd : buf;
+ len = (len) ? len : 0x20;
+
+ struct nvme_passthru_cmd nvme_cmd = {
+ .opcode = WDC_NVME_ADMIN_ENC_MGMT_SND,
+ .nsid = 0,
+ .addr = (__u64)(uintptr_t) cmd,
+ .data_len = ((len + sizeof(uint32_t) - 1) / sizeof(uint32_t)) * sizeof(uint32_t),
+ .cdw10 = len,
+ .cdw12 = log_id,
+ .cdw13 = 0,
+ .cdw14 = cdw14,
+ .cdw15 = cdw15,
+ };
+
+ clock_gettime(CLOCK_REALTIME, &time);
+ srand(time.tv_nsec);
+ handle = random(); /* Handle to associate send request with receive request */
+ nvme_cmd.cdw11 = handle;
+
+#ifdef WDC_NVME_CLI_DEBUG
+ unsigned char *d = (unsigned char *)nvme_cmd.addr;
+ unsigned char *md = (unsigned char *)nvme_cmd.metadata;
+
+ printf("NVME_ADMIN_COMMAND:\n");
+ printf("opcode: 0x%02x, flags: 0x%02x, rsvd: 0x%04x, nsid: 0x%08x, cdw2: 0x%08x, ",
+ nvme_cmd.opcode, nvme_cmd.flags, nvme_cmd.rsvd1, nvme_cmd.nsid, nvme_cmd.cdw2);
+ printf("cdw3: 0x%08x, metadata_len: 0x%08x, data_len: 0x%08x, cdw10: 0x%08x, "
+ nvme_cmd.cdw3, nvme_cmd.metadata_len, nvme_cmd.data_len, nvme_cmd.cdw10);
+ printf("cdw11: 0x%08x, cdw12: 0x%08x, cdw13: 0x%08x, cdw14: 0x%08x, cdw15: 0x%08x, "
+ nvme_cmd.cdw11, nvme_cmd.cdw12, nvme_cmd.cdw13, nvme_cmd.cdw14, nvme_cmd.cdw15);
+ printf("timeout_ms: 0x%08x, result: 0x%08x, metadata: %s, data: %s\n",
+ nvme_cmd.timeout_ms, nvme_cmd.result, md, d);
+#endif
+ nvme_cmd.result = 0;
+ err = nvme_submit_admin_passthru(dev_fd(dev), &nvme_cmd, NULL);
+ if (nvme_status_equals(err, NVME_STATUS_TYPE_NVME, NVME_SC_INTERNAL)) {
+ fprintf(stderr, "%s: WARNING : WDC: No log ID:x%x available\n", __func__, log_id);
+ } else if (err) {
+ fprintf(stderr, "%s: ERROR: WDC: NVMe Snd Mgmt\n", __func__);
+ nvme_show_status(err);
+ } else {
+ if (nvme_cmd.result == WDC_RESULT_NOT_AVAILABLE) {
+ free(buf);
+ return WDC_RESULT_NOT_AVAILABLE;
+ }
+
+ do {
+ /* Sent request, now go retrieve response */
+ nvme_cmd.flags = 0;
+ nvme_cmd.opcode = WDC_NVME_ADMIN_ENC_MGMT_RCV;
+ nvme_cmd.addr = (__u64)(uintptr_t) buf;
+ nvme_cmd.data_len = xfer_size;
+ nvme_cmd.cdw10 = xfer_size / sizeof(uint32_t);
+ nvme_cmd.cdw11 = handle;
+ nvme_cmd.cdw12 = log_id;
+ nvme_cmd.cdw13 = offset / sizeof(uint32_t);
+ nvme_cmd.cdw14 = cdw14;
+ nvme_cmd.cdw15 = cdw15;
+ nvme_cmd.result = 0; /* returned result !=0 indicates more data available */
+ err = nvme_submit_admin_passthru(dev_fd(dev),
+ &nvme_cmd, NULL);
+ if (err) {
+ more = 0;
+ fprintf(stderr, "%s: ERROR: WDC: NVMe Rcv Mgmt ", __func__);
+ nvme_show_status(err);
+ } else {
+ more = nvme_cmd.result & WDC_RESULT_MORE_DATA;
+ response_size = nvme_cmd.result & ~WDC_RESULT_MORE_DATA;
+ fwrite(buf, response_size, 1, out);
+ offset += response_size;
+ if (more && (response_size & (sizeof(uint32_t)-1))) {
+ fprintf(stderr, "%s: ERROR: WDC: NVMe Rcv Mgmt response size:x%x not LW aligned\n",
+ __func__, response_size);
+ }
+ }
+ } while (more);
+ }
+
+ free(buf);
+ return err;
+}
+
+static int wdc_enc_get_nic_log(struct nvme_dev *dev, __u8 log_id, __u32 xfer_size, __u32 data_len, FILE *out)
+{
+ __u8 *dump_data;
+ __u32 curr_data_offset, curr_data_len;
+ int i, ret = -1;
+ struct nvme_passthru_cmd admin_cmd;
+ __u32 dump_length = data_len;
+ __u32 numd;
+ __u16 numdu, numdl;
+
+ dump_data = (__u8 *)malloc(sizeof(__u8) * dump_length);
+ if (!dump_data) {
+ fprintf(stderr, "%s: ERROR: malloc: %s\n", __func__, strerror(errno));
+ return -1;
+ }
+ memset(dump_data, 0, sizeof(__u8) * dump_length);
+ memset(&admin_cmd, 0, sizeof(struct nvme_passthru_cmd));
+ curr_data_offset = 0;
+ curr_data_len = xfer_size;
+ i = 0;
+
+ numd = (curr_data_len >> 2) - 1;
+ numdu = numd >> 16;
+ numdl = numd & 0xffff;
+ admin_cmd.opcode = nvme_admin_get_log_page;
+ admin_cmd.nsid = curr_data_offset;
+ admin_cmd.addr = (__u64)(uintptr_t) dump_data;
+ admin_cmd.data_len = curr_data_len;
+ admin_cmd.cdw10 = log_id | (numdl << 16);
+ admin_cmd.cdw11 = numdu;
+
+ while (curr_data_offset < data_len) {
+#ifdef WDC_NVME_CLI_DEBUG
+ fprintf(stderr,
+ "nsid 0x%08x addr 0x%08llx, data_len 0x%08x, cdw10 0x%08x, cdw11 0x%08x, cdw12 0x%08x, cdw13 0x%08x, cdw14 0x%08x\n",
+ admin_cmd.nsid, admin_cmd.addr, admin_cmd.data_len, admin_cmd.cdw10,
+ admin_cmd.cdw11, admin_cmd.cdw12, admin_cmd.cdw13, admin_cmd.cdw14);
+#endif
+ ret = nvme_submit_admin_passthru(dev_fd(dev), &admin_cmd, NULL);
+ if (ret) {
+ nvme_show_status(ret);
+ fprintf(stderr, "%s: ERROR: WDC: Get chunk %d, size = 0x%x, offset = 0x%x, addr = 0x%lx\n",
+ __func__, i, admin_cmd.data_len, curr_data_offset, (unsigned long)admin_cmd.addr);
+ break;
+ }
+
+ if ((curr_data_offset + xfer_size) <= data_len)
+ curr_data_len = xfer_size;
+ else
+ curr_data_len = data_len - curr_data_offset; /* last transfer */
+
+ curr_data_offset += curr_data_len;
+ numd = (curr_data_len >> 2) - 1;
+ numdu = numd >> 16;
+ numdl = numd & 0xffff;
+ admin_cmd.addr = (__u64)(uintptr_t)dump_data + (__u64)curr_data_offset;
+ admin_cmd.nsid = curr_data_offset;
+ admin_cmd.data_len = curr_data_len;
+ admin_cmd.cdw10 = log_id | (numdl << 16);
+ admin_cmd.cdw11 = numdu;
+ i++;
+ }
+ fwrite(dump_data, data_len, 1, out);
+ free(dump_data);
+ return ret;
+}
+
+//------------------------------------------------------------------------------------
+// Description: set latency monitor feature
+//
+int wdc_set_latency_monitor_feature(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ const char *desc = "Set Latency Monitor feature.";
+
+ uint64_t capabilities = 0;
+ struct nvme_dev *dev;
+ nvme_root_t r;
+ int ret;
+ __u32 result;
+ struct feature_latency_monitor buf = {0,};
+
+ const char *active_bucket_timer_threshold =
+ "This is the value that loads the Active Bucket Timer Threshold.";
+ const char *active_threshold_a =
+ "This is the value that loads into the Active Threshold A.";
+ const char *active_threshold_b =
+ "This is the value that loads into the Active Threshold B.";
+ const char *active_threshold_c =
+ "This is the value that loads into the Active Threshold C.";
+ const char *active_threshold_d =
+ "This is the value that loads into the Active Threshold D.";
+ const char *active_latency_config =
+ "This is the value that loads into the Active Latency Configuration.";
+ const char *active_latency_minimum_window =
+ "This is the value that loads into the Active Latency Minimum Window.";
+ const char *debug_log_trigger_enable =
+ "This is the value that loads into the Debug Log Trigger Enable.";
+ const char *discard_debug_log = "Discard Debug Log.";
+ const char *latency_monitor_feature_enable = "Latency Monitor Feature Enable.";
+
+ struct config {
+ __u16 active_bucket_timer_threshold;
+ __u8 active_threshold_a;
+ __u8 active_threshold_b;
+ __u8 active_threshold_c;
+ __u8 active_threshold_d;
+ __u16 active_latency_config;
+ __u8 active_latency_minimum_window;
+ __u16 debug_log_trigger_enable;
+ __u8 discard_debug_log;
+ __u8 latency_monitor_feature_enable;
+ };
+
+ struct config cfg = {
+ .active_bucket_timer_threshold = 0x7E0,
+ .active_threshold_a = 0x5,
+ .active_threshold_b = 0x13,
+ .active_threshold_c = 0x1E,
+ .active_threshold_d = 0x2E,
+ .active_latency_config = 0xFFF,
+ .active_latency_minimum_window = 0xA,
+ .debug_log_trigger_enable = 0,
+ .discard_debug_log = 0,
+ .latency_monitor_feature_enable = 0x7,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("active_bucket_timer_threshold", 't',
+ &cfg.active_bucket_timer_threshold,
+ active_bucket_timer_threshold),
+ OPT_UINT("active_threshold_a", 'a', &cfg.active_threshold_a,
+ active_threshold_a),
+ OPT_UINT("active_threshold_b", 'b', &cfg.active_threshold_b,
+ active_threshold_b),
+ OPT_UINT("active_threshold_c", 'c', &cfg.active_threshold_c,
+ active_threshold_c),
+ OPT_UINT("active_threshold_d", 'd', &cfg.active_threshold_d,
+ active_threshold_d),
+ OPT_UINT("active_latency_config", 'f',
+ &cfg.active_latency_config, active_latency_config),
+ OPT_UINT("active_latency_minimum_window", 'w',
+ &cfg.active_latency_minimum_window,
+ active_latency_minimum_window),
+ OPT_UINT("debug_log_trigger_enable", 'r',
+ &cfg.debug_log_trigger_enable, debug_log_trigger_enable),
+ OPT_UINT("discard_debug_log", 'l', &cfg.discard_debug_log,
+ discard_debug_log),
+ OPT_UINT("latency_monitor_feature_enable", 'e',
+ &cfg.latency_monitor_feature_enable,
+ latency_monitor_feature_enable),
+ OPT_END()
+ };
+
+ ret = parse_and_open(&dev, argc, argv, desc, opts);
+
+ if (ret < 0)
+ return ret;
+
+ /* get capabilities */
+ r = nvme_scan(NULL);
+ wdc_check_device(r, dev);
+ capabilities = wdc_get_drive_capabilities(r, dev);
+
+ if (!(capabilities & WDC_DRIVE_CAP_SET_LATENCY_MONITOR)) {
+ fprintf(stderr, "ERROR: WDC: unsupported device for this command\n");
+ return -1;
+ }
+
+ memset(&buf, 0, sizeof(struct feature_latency_monitor));
+
+ buf.active_bucket_timer_threshold = cfg.active_bucket_timer_threshold;
+ buf.active_threshold_a = cfg.active_threshold_a;
+ buf.active_threshold_b = cfg.active_threshold_b;
+ buf.active_threshold_c = cfg.active_threshold_c;
+ buf.active_threshold_d = cfg.active_threshold_d;
+ buf.active_latency_config = cfg.active_latency_config;
+ buf.active_latency_minimum_window = cfg.active_latency_minimum_window;
+ buf.debug_log_trigger_enable = cfg.debug_log_trigger_enable;
+ buf.discard_debug_log = cfg.discard_debug_log;
+ buf.latency_monitor_feature_enable = cfg.latency_monitor_feature_enable;
+
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = NVME_FEAT_OCP_LATENCY_MONITOR,
+ .nsid = 0,
+ .cdw12 = 0,
+ .save = 1,
+ .data_len = sizeof(struct feature_latency_monitor),
+ .data = (void *)&buf,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ ret = nvme_set_features(&args);
+
+ if (ret < 0) {
+ perror("set-feature");
+ } else if (!ret) {
+ printf("NVME_FEAT_OCP_LATENCY_MONITOR: 0x%02x\n",
+ NVME_FEAT_OCP_LATENCY_MONITOR);
+ printf("active bucket timer threshold: 0x%x\n",
+ buf.active_bucket_timer_threshold);
+ printf("active threshold a: 0x%x\n", buf.active_threshold_a);
+ printf("active threshold b: 0x%x\n", buf.active_threshold_b);
+ printf("active threshold c: 0x%x\n", buf.active_threshold_c);
+ printf("active threshold d: 0x%x\n", buf.active_threshold_d);
+ printf("active latency config: 0x%x\n", buf.active_latency_config);
+ printf("active latency minimum window: 0x%x\n",
+ buf.active_latency_minimum_window);
+ printf("debug log trigger enable: 0x%x\n",
+ buf.debug_log_trigger_enable);
+ printf("discard debug log: 0x%x\n", buf.discard_debug_log);
+ printf("latency monitor feature enable: 0x%x\n",
+ buf.latency_monitor_feature_enable);
+ } else if (ret > 0)
+ fprintf(stderr, "NVMe Status:%s(%x)\n",
+ nvme_status_to_string(ret, false), ret);
+
+ return ret;
+}
diff --git a/plugins/wdc/wdc-nvme.h b/plugins/wdc/wdc-nvme.h
new file mode 100644
index 0000000..d3692bc
--- /dev/null
+++ b/plugins/wdc/wdc-nvme.h
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/wdc/wdc-nvme
+
+#if !defined(WDC_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define WDC_NVME
+
+#define WDC_PLUGIN_VERSION "2.7.0"
+#include "cmd.h"
+
+PLUGIN(NAME("wdc", "Western Digital vendor specific extensions", WDC_PLUGIN_VERSION),
+ COMMAND_LIST(
+ ENTRY("cap-diag", "WDC Capture-Diagnostics", wdc_cap_diag)
+ ENTRY("drive-log", "WDC Drive Log", wdc_drive_log)
+ ENTRY("get-crash-dump", "WDC Crash Dump", wdc_get_crash_dump)
+ ENTRY("get-pfail-dump", "WDC Pfail Dump", wdc_get_pfail_dump)
+ ENTRY("id-ctrl", "WDC identify controller", wdc_id_ctrl)
+ ENTRY("purge", "WDC Purge", wdc_purge)
+ ENTRY("purge-monitor", "WDC Purge Monitor", wdc_purge_monitor)
+ ENTRY("vs-internal-log", "WDC Internal Firmware Log",
+ wdc_vs_internal_fw_log)
+ ENTRY("vs-nand-stats", "WDC NAND Statistics", wdc_vs_nand_stats)
+ ENTRY("vs-smart-add-log", "WDC Additional Smart Log",
+ wdc_vs_smart_add_log)
+ ENTRY("clear-pcie-correctable-errors",
+ "WDC Clear PCIe Correctable Error Count",
+ wdc_clear_pcie_correctable_errors)
+ ENTRY("drive-essentials", "WDC Drive Essentials",
+ wdc_drive_essentials)
+ ENTRY("get-drive-status", "WDC Get Drive Status",
+ wdc_drive_status)
+ ENTRY("clear-assert-dump", "WDC Clear Assert Dump",
+ wdc_clear_assert_dump)
+ ENTRY("drive-resize", "WDC Drive Resize", wdc_drive_resize)
+ ENTRY("vs-fw-activate-history", "WDC Get FW Activate History",
+ wdc_vs_fw_activate_history)
+ ENTRY("clear-fw-activate-history",
+ "WDC Clear FW Activate History",
+ wdc_clear_fw_activate_history)
+ ENTRY("enc-get-log", "WDC Get Enclosure Log", wdc_enc_get_log)
+ ENTRY("vs-telemetry-controller-option",
+ "WDC Enable/Disable Controller Initiated Telemetry Log",
+ wdc_vs_telemetry_controller_option)
+ ENTRY("vs-error-reason-identifier",
+ "WDC Telemetry Reason Identifier",
+ wdc_reason_identifier)
+ ENTRY("log-page-directory", "WDC Get Log Page Directory",
+ wdc_log_page_directory)
+ ENTRY("namespace-resize", "WDC NamespaceDrive Resize",
+ wdc_namespace_resize)
+ ENTRY("vs-drive-info", "WDC Get Drive Info", wdc_vs_drive_info)
+ ENTRY("vs-temperature-stats", "WDC Get Temperature Stats",
+ wdc_vs_temperature_stats)
+ ENTRY("capabilities", "WDC Device Capabilities",
+ wdc_capabilities)
+ ENTRY("cloud-SSD-plugin-version",
+ "WDC Cloud SSD Plugin Version",
+ wdc_cloud_ssd_plugin_version)
+ ENTRY("vs-pcie-stats", "WDC VS PCIE Statistics",
+ wdc_vs_pcie_stats)
+ ENTRY("get-latency-monitor-log",
+ "WDC Get Latency Monitor Log Page",
+ wdc_get_latency_monitor_log)
+ ENTRY("get-error-recovery-log",
+ "WDC Get Error Recovery Log Page",
+ wdc_get_error_recovery_log)
+ ENTRY("get-dev-capabilities-log",
+ "WDC Get Device Capabilities Log Page",
+ wdc_get_dev_capabilities_log)
+ ENTRY("get-unsupported-reqs-log",
+ "WDC Get Unsupported Requirements Log Page",
+ wdc_get_unsupported_reqs_log)
+ ENTRY("cloud-boot-SSD-version",
+ "WDC Get the Cloud Boot SSD Version",
+ wdc_cloud_boot_SSD_version)
+ ENTRY("vs-cloud-log", "WDC Get the Cloud Log Page",
+ wdc_vs_cloud_log)
+ ENTRY("vs-hw-rev-log", "WDC Get the Hardware Revision Log Page",
+ wdc_vs_hw_rev_log)
+ ENTRY("vs-device-waf",
+ "WDC Calculate Device Write Amplication Factor",
+ wdc_vs_device_waf)
+ ENTRY("set-latency-monitor-feature",
+ "WDC set Latency Monitor feature",
+ wdc_set_latency_monitor_feature)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/wdc/wdc-utils.c b/plugins/wdc/wdc-utils.c
new file mode 100644
index 0000000..414a06a
--- /dev/null
+++ b/plugins/wdc/wdc-utils.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2017-2018 Western Digital Corporation or its affiliates.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ * Author: Jeff Lien <jeff.lien@wdc.com>,
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include "nvme.h"
+#include "libnvme.h"
+#include "nvme-print.h"
+#include "wdc-utils.h"
+
+int wdc_UtilsSnprintf(char *buffer, unsigned int sizeOfBuffer, const char *format, ...)
+{
+ int res = 0;
+ va_list vArgs;
+
+ va_start(vArgs, format);
+ res = vsnprintf(buffer, sizeOfBuffer, format, vArgs);
+ va_end(vArgs);
+
+ return res;
+}
+
+void wdc_UtilsDeleteCharFromString(char *buffer, int buffSize, char charToRemove)
+{
+ int i = 0;
+ int count = 0;
+
+ if (!buffer || !buffSize)
+ return;
+
+ /*
+ * Traverse the given string. If current character is not charToRemove,
+ * then place it at index count++
+ */
+ for (i = 0; ((i < buffSize) && (buffer[i] != '\0')); i++) {
+ if (buffer[i] != charToRemove)
+ buffer[count++] = buffer[i];
+ }
+ buffer[count] = '\0';
+}
+
+int wdc_UtilsGetTime(PUtilsTimeInfo timeInfo)
+{
+ time_t currTime;
+ struct tm currTimeInfo;
+
+ if (!timeInfo)
+ return WDC_STATUS_INVALID_PARAMETER;
+
+ tzset();
+ time(&currTime);
+ localtime_r(&currTime, &currTimeInfo);
+
+ timeInfo->year = currTimeInfo.tm_year + 1900;
+ timeInfo->month = currTimeInfo.tm_mon + 1;
+ timeInfo->dayOfWeek = currTimeInfo.tm_wday;
+ timeInfo->dayOfMonth = currTimeInfo.tm_mday;
+ timeInfo->hour = currTimeInfo.tm_hour;
+ timeInfo->minute = currTimeInfo.tm_min;
+ timeInfo->second = currTimeInfo.tm_sec;
+ timeInfo->msecs = 0;
+ timeInfo->isDST = currTimeInfo.tm_isdst;
+#if defined(__GLIBC__) && !defined(__UCLIBC__) && !defined(__MUSL__)
+ timeInfo->zone = -currTimeInfo.tm_gmtoff / 60;
+#else
+ timeInfo->zone = -1 * (timezone / SECONDS_IN_MIN);
+#endif
+
+ return WDC_STATUS_SUCCESS;
+}
+
+int wdc_UtilsCreateDir(char *path)
+{
+ int retStatus;
+ int status = WDC_STATUS_SUCCESS;
+
+ if (!path)
+ return WDC_STATUS_INVALID_PARAMETER;
+
+ retStatus = mkdir(path, 0x999);
+ if (retStatus < 0) {
+ if (errno == EEXIST)
+ status = WDC_STATUS_DIR_ALREADY_EXISTS;
+ else if (errno == ENOENT)
+ status = WDC_STATUS_PATH_NOT_FOUND;
+ else
+ status = WDC_STATUS_CREATE_DIRECTORY_FAILED;
+ }
+
+ return status;
+}
+
+int wdc_WriteToFile(char *fileName, char *buffer, unsigned int bufferLen)
+{
+ int status = WDC_STATUS_SUCCESS;
+ FILE *file;
+ size_t bytesWritten = 0;
+
+ file = fopen(fileName, "ab+");
+ if (!file) {
+ status = WDC_STATUS_UNABLE_TO_OPEN_FILE;
+ goto end;
+ }
+
+ bytesWritten = fwrite(buffer, 1, bufferLen, file);
+ if (bytesWritten != bufferLen)
+ status = WDC_STATUS_UNABLE_TO_WRITE_ALL_DATA;
+
+end:
+ if (file)
+ fclose(file);
+ return status;
+}
+
+/**
+ * Compares the strings ignoring their cases.
+ *
+ * @param pcSrc Points to a null terminated string for comparing.
+ * @param pcDst Points to a null terminated string for comparing.
+ *
+ * @returns zero if the string matches or
+ * 1 if the pcSrc string is lexically higher than pcDst or
+ * -1 if the pcSrc string is lexically lower than pcDst.
+ */
+int wdc_UtilsStrCompare(char *pcSrc, char *pcDst)
+{
+ while ((toupper(*pcSrc) == toupper(*pcDst)) && (*pcSrc != '\0')) {
+ pcSrc++;
+ pcDst++;
+ }
+ return *pcSrc - *pcDst;
+}
+
+void wdc_StrFormat(char *formatter, size_t fmt_sz, char *tofmt, size_t tofmtsz)
+{
+ fmt_sz = snprintf(formatter, fmt_sz, "%-*.*s", (int)tofmtsz, (int)tofmtsz, tofmt);
+ /* trim() the obnoxious trailing white lines */
+ while (fmt_sz) {
+ if (formatter[fmt_sz - 1] != ' ' && formatter[fmt_sz - 1] != '\0') {
+ formatter[fmt_sz] = '\0';
+ break;
+ }
+ fmt_sz--;
+ }
+}
+
+bool wdc_CheckUuidListSupport(struct nvme_dev *dev, struct nvme_id_uuid_list *uuid_list)
+{
+ int err;
+ struct nvme_id_ctrl ctrl;
+
+ memset(&ctrl, 0, sizeof(struct nvme_id_ctrl));
+ err = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (err) {
+ fprintf(stderr, "ERROR: WDC: nvme_identify_ctrl() failed 0x%x\n", err);
+ return false;
+ }
+
+ if ((ctrl.ctratt & NVME_CTRL_CTRATT_UUID_LIST) == NVME_CTRL_CTRATT_UUID_LIST) {
+ err = nvme_identify_uuid(dev_fd(dev), uuid_list);
+ if (!err)
+ return true;
+ else if (err > 0)
+ nvme_show_status(err);
+ else
+ nvme_show_error("identify UUID list: %s", nvme_strerror(errno));
+ }
+
+ return false;
+}
+
+bool wdc_UuidEqual(struct nvme_id_uuid_list_entry *entry1, struct nvme_id_uuid_list_entry *entry2)
+{
+ return !memcmp(entry1, entry2, NVME_UUID_LEN);
+}
diff --git a/plugins/wdc/wdc-utils.h b/plugins/wdc/wdc-utils.h
new file mode 100644
index 0000000..3fc47ca
--- /dev/null
+++ b/plugins/wdc/wdc-utils.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2017-2018 Western Digital Corporation or its affiliates.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ * Author: Jeff Lien <jeff.lien@wdc.com>,
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Create Dir Command Status */
+#define WDC_STATUS_SUCCESS 0
+#define WDC_STATUS_FAILURE -1
+#define WDC_STATUS_INSUFFICIENT_MEMORY -2
+#define WDC_STATUS_INVALID_PARAMETER -3
+#define WDC_STATUS_FILE_SIZE_ZERO -27
+#define WDC_STATUS_UNABLE_TO_WRITE_ALL_DATA -34
+#define WDC_STATUS_DIR_ALREADY_EXISTS -36
+#define WDC_STATUS_PATH_NOT_FOUND -37
+#define WDC_STATUS_CREATE_DIRECTORY_FAILED -38
+#define WDC_STATUS_DELETE_DIRECTORY_FAILED -39
+#define WDC_STATUS_UNABLE_TO_OPEN_FILE -40
+#define WDC_STATUS_UNABLE_TO_OPEN_ZIP_FILE -41
+#define WDC_STATUS_UNABLE_TO_ARCHIVE_EXCEEDED_FILES_LIMIT -256
+#define WDC_STATUS_NO_DATA_FILE_AVAILABLE_TO_ARCHIVE -271
+
+#define WDC_NVME_FIRMWARE_REV_LEN 9 /* added 1 for end delimiter */
+#define WDC_SERIAL_NO_LEN 20
+#define SECONDS_IN_MIN 60
+#define MAX_PATH_LEN 256
+
+typedef struct _UtilsTimeInfo
+{
+ unsigned int year;
+ unsigned int month;
+ unsigned int dayOfWeek;
+ unsigned int dayOfMonth;
+ unsigned int hour;
+ unsigned int minute;
+ unsigned int second;
+ unsigned int msecs;
+ unsigned char isDST; /*0 or 1 */
+ int zone; /* Zone value like +530 or -300 */
+} UtilsTimeInfo, *PUtilsTimeInfo;
+
+int wdc_UtilsSnprintf(char *buffer, unsigned int sizeOfBuffer, const char *format, ...);
+void wdc_UtilsDeleteCharFromString(char* buffer, int buffSize, char charToRemove);
+int wdc_UtilsGetTime(PUtilsTimeInfo timeInfo);
+int wdc_UtilsStrCompare(char *pcSrc, char *pcDst);
+int wdc_UtilsCreateDir(char *path);
+int wdc_WriteToFile(char *fileName, char *buffer, unsigned int bufferLen);
+void wdc_StrFormat(char *formatter, size_t fmt_sz, char *tofmt, size_t tofmtsz);
+bool wdc_CheckUuidListSupport(struct nvme_dev *dev, struct nvme_id_uuid_list *uuid_list);
+bool wdc_UuidEqual(struct nvme_id_uuid_list_entry *entry1, struct nvme_id_uuid_list_entry *entry2);
diff --git a/plugins/ymtc/ymtc-nvme.c b/plugins/ymtc/ymtc-nvme.c
new file mode 100644
index 0000000..5568c80
--- /dev/null
+++ b/plugins/ymtc/ymtc-nvme.c
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "nvme-print.h"
+
+#define CREATE_CMD
+#include "ymtc-nvme.h"
+#include "ymtc-utils.h"
+
+static void get_ymtc_smart_info(struct nvme_ymtc_smart_log *smart, int index, u8 *nm_val, u8 *raw_val)
+{
+ memcpy(nm_val, smart->itemArr[index].nmVal, NM_SIZE);
+ memcpy(raw_val, smart->itemArr[index].rawVal, RAW_SIZE);
+}
+
+static int show_ymtc_smart_log(struct nvme_dev *dev, __u32 nsid,
+ struct nvme_ymtc_smart_log *smart)
+{
+ struct nvme_id_ctrl ctrl;
+ char fw_ver[10];
+ int err = 0;
+
+ u8 *nm = malloc(NM_SIZE * sizeof(u8));
+ u8 *raw = malloc(RAW_SIZE * sizeof(u8));
+
+ if (!nm) {
+ if (raw)
+ free(raw);
+ return -1;
+ }
+ if (!raw) {
+ free(nm);
+ return -1;
+ }
+ err = nvme_identify_ctrl(dev_fd(dev), &ctrl);
+ if (err) {
+ free(nm);
+ free(raw);
+ return err;
+ }
+
+ snprintf(fw_ver, sizeof(fw_ver), "%c.%c%c.%c%c%c%c",
+ ctrl.fr[0], ctrl.fr[1], ctrl.fr[2], ctrl.fr[3],
+ ctrl.fr[4], ctrl.fr[5], ctrl.fr[6]);
+
+ /* Table Title */
+ printf("Additional Smart Log for NVME device:%s namespace-id:%x\n",
+ dev->name, nsid);
+ /* Column Name*/
+ printf("key normalized raw\n");
+ /* 00 SI_VD_PROGRAM_FAIL */
+ get_ymtc_smart_info(smart, SI_VD_PROGRAM_FAIL, nm, raw);
+ printf("program_fail_count : %3d%% %"PRIu64"\n", *nm, int48_to_long(raw));
+ /* 01 SI_VD_ERASE_FAIL */
+ get_ymtc_smart_info(smart, SI_VD_ERASE_FAIL, nm, raw);
+ printf("erase_fail_count : %3d%% %"PRIu64"\n", *nm, int48_to_long(raw));
+ /* 02 SI_VD_WEARLEVELING_COUNT */
+ get_ymtc_smart_info(smart, SI_VD_WEARLEVELING_COUNT, nm, raw);
+ printf("wear_leveling : %3d%% min: %u, max: %u, avg: %u\n", *nm,
+ *(uint16_t *)raw, *(uint16_t *)(raw+2), *(uint16_t *)(raw+4));
+ /* 03 SI_VD_E2E_DECTECTION_COUNT */
+ get_ymtc_smart_info(smart, SI_VD_E2E_DECTECTION_COUNT, nm, raw);
+ printf("end_to_end_error_detection_count: %3d%% %"PRIu64"\n", *nm, int48_to_long(raw));
+ /* 04 SI_VD_PCIE_CRC_ERR_COUNT */
+ get_ymtc_smart_info(smart, SI_VD_PCIE_CRC_ERR_COUNT, nm, raw);
+ printf("crc_error_count : %3d%% %"PRIu32"\n", *nm, *(uint32_t *)raw);
+ /* 08 SI_VD_THERMAL_THROTTLE_STATUS */
+ get_ymtc_smart_info(smart, SI_VD_THERMAL_THROTTLE_STATUS, nm, raw);
+ printf("thermal_throttle_status : %3d%% %d%%, cnt: %"PRIu32"\n", *nm,
+ *raw, *(uint32_t *)(raw+1));
+ /* 11 SI_VD_TOTAL_WRITE */
+ get_ymtc_smart_info(smart, SI_VD_TOTAL_WRITE, nm, raw);
+ printf("nand_bytes_written : %3d%% sectors: %"PRIu64"\n", *nm, int48_to_long(raw));
+ /* 12 SI_VD_HOST_WRITE */
+ get_ymtc_smart_info(smart, SI_VD_HOST_WRITE, nm, raw);
+ printf("host_bytes_written : %3d%% sectors: %"PRIu64"\n", *nm, int48_to_long(raw));
+ /* 14 SI_VD_TOTAL_READ */
+ get_ymtc_smart_info(smart, SI_VD_TOTAL_READ, nm, raw);
+ printf("nand_bytes_read : %3d%% %"PRIu64"\n", *nm, int48_to_long(raw));
+ /* 15 SI_VD_TEMPT_SINCE_BORN */
+ get_ymtc_smart_info(smart, SI_VD_TEMPT_SINCE_BORN, nm, raw);
+ printf("tempt_since_born : %3d%% max: %u, min: %u, curr: %u\n", *nm,
+ *(uint16_t *)raw-273, *(uint16_t *)(raw+2)-273, *(int16_t *)(raw+4)-273);
+ /* 16 SI_VD_POWER_CONSUMPTION */
+ get_ymtc_smart_info(smart, SI_VD_POWER_CONSUMPTION, nm, raw);
+ printf("power_consumption : %3d%% max: %u, min: %u, curr: %u\n", *nm,
+ *(uint16_t *)raw, *(uint16_t *)(raw+2), *(uint16_t *)(raw+4));
+ /* 17 SI_VD_TEMPT_SINCE_BOOTUP */
+ get_ymtc_smart_info(smart, SI_VD_TEMPT_SINCE_BOOTUP, nm, raw);
+ printf("tempt_since_bootup : %3d%% max: %u, min: %u, curr: %u\n", *nm,
+ *(uint16_t *)raw-273, *(uint16_t *)(raw+2)-273, *(uint16_t *)(raw+4)-273);
+ /* 18 SI_VD_POWER_LOSS_PROTECTION */
+ get_ymtc_smart_info(smart, SI_VD_POWER_LOSS_PROTECTION, nm, raw);
+ printf("power_loss_protection : %3d%% %"PRIu64"\n", *nm, int48_to_long(raw));
+ /* 19 SI_VD_READ_FAIL */
+ get_ymtc_smart_info(smart, SI_VD_READ_FAIL, nm, raw);
+ printf("read_fail : %3d%% %"PRIu64"\n", *nm, int48_to_long(raw));
+ /* 20 SI_VD_THERMAL_THROTTLE_TIME */
+ get_ymtc_smart_info(smart, SI_VD_THERMAL_THROTTLE_TIME, nm, raw);
+ printf("thermal_throttle_time : %3d%% %u, time: %"PRIu32"\n", *nm,
+ *raw, *(uint32_t *)(raw+1));
+ /* 21 SI_VD_FLASH_MEDIA_ERROR */
+ get_ymtc_smart_info(smart, SI_VD_FLASH_MEDIA_ERROR, nm, raw);
+ printf("flash_error_media_count : %3d%% %"PRIu64"\n", *nm, int48_to_long(raw));
+
+ free(nm);
+ free(raw);
+
+ return err;
+}
+
+static int get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ struct nvme_ymtc_smart_log smart_log;
+ char *desc =
+ "Get Ymtc vendor specific additional smart log (optionally, for the specified namespace), and show it.";
+ const char *namespace = "(optional) desired namespace";
+ const char *raw = "dump output in binary format";
+ struct nvme_dev *dev;
+ struct config {
+ __u32 namespace_id;
+ bool raw_binary;
+ };
+ int err;
+
+ struct config cfg = {
+ .namespace_id = NVME_NSID_ALL,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace),
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = nvme_get_nsid_log(dev_fd(dev), false, 0xca, cfg.namespace_id,
+ sizeof(smart_log), &smart_log);
+ if (!err) {
+ if (!cfg.raw_binary)
+ err = show_ymtc_smart_log(dev, cfg.namespace_id, &smart_log);
+ else
+ d_raw((unsigned char *)&smart_log, sizeof(smart_log));
+ }
+ if (err > 0)
+ nvme_show_status(err);
+
+ dev_close(dev);
+ return err;
+}
diff --git a/plugins/ymtc/ymtc-nvme.h b/plugins/ymtc/ymtc-nvme.h
new file mode 100644
index 0000000..df5d598
--- /dev/null
+++ b/plugins/ymtc/ymtc-nvme.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/ymtc/ymtc-nvme
+
+#if !defined(YMTC_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define YMTC_NVME
+
+#include "cmd.h"
+#include "common.h"
+
+#include <ctype.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+PLUGIN(NAME("ymtc", "Ymtc vendor specific extensions", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("smart-log-add", "Retrieve Ymtc SMART Log, show it", get_additional_smart_log)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
+
diff --git a/plugins/ymtc/ymtc-utils.h b/plugins/ymtc/ymtc-utils.h
new file mode 100644
index 0000000..e9a3572
--- /dev/null
+++ b/plugins/ymtc/ymtc-utils.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __YMTC_UTILS_H__
+#define __YMTC_UTILS_H__
+
+#define SMART_INFO_SIZE 4096
+
+#define ID_SIZE 3
+#define NM_SIZE 2
+#define RAW_SIZE 7
+
+typedef unsigned char u8;
+
+/* Additional smart external ID */
+#define SI_VD_PROGRAM_FAIL_ID 0xAB
+#define SI_VD_ERASE_FAIL_ID 0xAC
+#define SI_VD_WEARLEVELING_COUNT_ID 0xAD
+#define SI_VD_E2E_DECTECTION_COUNT_ID 0xB8
+#define SI_VD_PCIE_CRC_ERR_COUNT_ID 0xC7
+#define SI_VD_TIMED_WORKLOAD_MEDIA_WEAR_ID 0xE2
+#define SI_VD_TIMED_WORKLOAD_HOST_READ_ID 0xE3
+#define SI_VD_TIMED_WORKLOAD_TIMER_ID 0xE4
+#define SI_VD_THERMAL_THROTTLE_STATUS_ID 0xEA
+#define SI_VD_RETRY_BUFF_OVERFLOW_COUNT_ID 0xF0
+#define SI_VD_PLL_LOCK_LOSS_COUNT_ID 0xF3
+#define SI_VD_TOTAL_WRITE_ID 0xF4
+#define SI_VD_HOST_WRITE_ID 0xF5
+#define SI_VD_SYSTEM_AREA_LIFE_LEFT_ID 0xF6
+#define SI_VD_TOTAL_READ_ID 0xFA
+#define SI_VD_TEMPT_SINCE_BORN_ID 0xE7
+#define SI_VD_POWER_CONSUMPTION_ID 0xE8
+#define SI_VD_TEMPT_SINCE_BOOTUP_ID 0xAF
+#define SI_VD_POWER_LOSS_PROTECTION_ID 0xEC
+#define SI_VD_READ_FAIL_ID 0xF2
+#define SI_VD_THERMAL_THROTTLE_TIME_ID 0xEB
+#define SI_VD_FLASH_MEDIA_ERROR_ID 0xED
+
+/* Additional smart internal ID */
+typedef enum
+{
+ /* smart attr following intel */
+ SI_VD_PROGRAM_FAIL = 0, /* 0xAB */
+ SI_VD_ERASE_FAIL = 1, /* 0xAC */
+ SI_VD_WEARLEVELING_COUNT = 2,/* 0xAD */
+ SI_VD_E2E_DECTECTION_COUNT = 3, /* 0xB8 */
+ SI_VD_PCIE_CRC_ERR_COUNT = 4, /* 0xC7, 2 port data in one attribute */
+ SI_VD_THERMAL_THROTTLE_STATUS = 8, /* 0xEA */
+ SI_VD_TOTAL_WRITE = 11, /* 0xF4, unit is 32MiB */
+ SI_VD_HOST_WRITE = 12, /* 0xF5, unit is 32MiB */
+ SI_VD_TOTAL_READ = 14, /* 0xFA, unit is 32MiB */
+
+ /* smart attr self defined */
+ SI_VD_TEMPT_SINCE_BORN = 15, /* 0xE7 */
+ SI_VD_POWER_CONSUMPTION = 16, /* 0xE8 */
+ SI_VD_TEMPT_SINCE_BOOTUP = 17, /* 0xAF */
+ SI_VD_POWER_LOSS_PROTECTION = 18, /* 0xEC */
+ SI_VD_READ_FAIL = 19, /* 0xF2 */
+ SI_VD_THERMAL_THROTTLE_TIME = 20, /* 0xEB */
+ SI_VD_FLASH_MEDIA_ERROR = 21, /* 0xED */
+ NR_SMART_ITEMS,
+} si_vendor_smart_item_e;
+
+// Intel Format
+struct nvme_ymtc_smart_log_item
+{
+ /* Item identifier */
+ u8 id[ID_SIZE];
+ /* Normalized value or percentage. In the range from 0 to 100. */
+ u8 nmVal[NM_SIZE];
+ /* raw value */
+ u8 rawVal[RAW_SIZE];
+};
+
+struct nvme_ymtc_smart_log
+{
+ struct nvme_ymtc_smart_log_item itemArr[NR_SMART_ITEMS];
+
+ u8 resv[SMART_INFO_SIZE - sizeof(struct nvme_ymtc_smart_log_item) * NR_SMART_ITEMS];
+};
+
+#endif // __YMTC_UTILS_H__
+
diff --git a/plugins/zns/zns.c b/plugins/zns/zns.c
new file mode 100644
index 0000000..a7a3766
--- /dev/null
+++ b/plugins/zns/zns.c
@@ -0,0 +1,1274 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <linux/fs.h>
+#include <sys/stat.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "nvme-print.h"
+#include "util/cleanup.h"
+
+#define CREATE_CMD
+#include "zns.h"
+
+static const char *namespace_id = "Namespace identifier to use";
+static const char dash[100] = { [0 ... 99] = '-' };
+
+static int detect_zns(nvme_ns_t ns, int *out_supported)
+{
+ int err = 0;
+ char *zoned;
+
+ *out_supported = 0;
+
+ zoned = nvme_get_attr(nvme_ns_get_sysfs_dir(ns), "queue/zoned");
+ if (!zoned) {
+ *out_supported = 0;
+ return err;
+ }
+
+ *out_supported = strcmp("host-managed", zoned) == 0;
+ free(zoned);
+
+ return err;
+}
+
+static int print_zns_list_ns(nvme_ns_t ns)
+{
+ int supported;
+ int err = 0;
+
+ err = detect_zns(ns, &supported);
+ if (err) {
+ perror("Failed to enumerate namespace");
+ return err;
+ }
+
+ if (supported)
+ nvme_show_list_item(ns);
+
+ return err;
+}
+
+static int print_zns_list(nvme_root_t nvme_root)
+{
+ int err = 0;
+ nvme_host_t h;
+ nvme_subsystem_t s;
+ nvme_ctrl_t c;
+ nvme_ns_t n;
+
+ nvme_for_each_host(nvme_root, h) {
+ nvme_for_each_subsystem(h, s) {
+ nvme_subsystem_for_each_ns(s, n) {
+ err = print_zns_list_ns(n);
+ if (err)
+ return err;
+ }
+
+ nvme_subsystem_for_each_ctrl(s, c) {
+ nvme_ctrl_for_each_ns(c, n) {
+ err = print_zns_list_ns(n);
+ if (err)
+ return err;
+ }
+ }
+ }
+ }
+
+ return err;
+}
+
+static int list(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ int err = 0;
+ nvme_root_t nvme_root;
+
+ printf("%-21s %-20s %-40s %-9s %-26s %-16s %-8s\n", "Node", "SN",
+ "Model", "Namespace", "Usage", "Format", "FW Rev");
+ printf("%-.21s %-.20s %-.40s %-.9s %-.26s %-.16s %-.8s\n", dash, dash,
+ dash, dash, dash, dash, dash);
+
+ nvme_root = nvme_scan(NULL);
+ if (nvme_root) {
+ err = print_zns_list(nvme_root);
+ nvme_free_tree(nvme_root);
+ } else {
+ fprintf(stderr, "Failed to scan nvme subsystems\n");
+ err = -errno;
+ }
+
+ return err;
+}
+
+static int id_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Send a ZNS specific Identify Controller command to\n"
+ "the given device and report information about the specified\n"
+ "controller in various formats.";
+
+ enum nvme_print_flags flags;
+ struct nvme_zns_id_ctrl ctrl;
+ struct nvme_dev *dev;
+ int err = -1;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return errno;
+
+ err = validate_output_format(cfg.output_format, &flags);
+ if (err < 0)
+ goto close_dev;
+
+ err = nvme_zns_identify_ctrl(dev_fd(dev), &ctrl);
+ if (!err)
+ nvme_show_zns_id_ctrl(&ctrl, flags);
+ else if (err > 0)
+ nvme_show_status(err);
+ else
+ perror("zns identify controller");
+close_dev:
+ dev_close(dev);
+ return err;
+}
+
+static int id_ns(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Send a ZNS specific Identify Namespace command to\n"
+ "the given device and report information about the specified\n"
+ "namespace in varios formats.";
+ const char *vendor_specific = "dump binary vendor fields";
+ const char *human_readable = "show identify in readable format";
+
+ enum nvme_print_flags flags;
+ struct nvme_zns_id_ns ns;
+ struct nvme_id_ns id_ns;
+ struct nvme_dev *dev;
+ int err = -1;
+
+ struct config {
+ char *output_format;
+ __u32 namespace_id;
+ bool human_readable;
+ bool vendor_specific;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_FLAG("vendor-specific", 'v', &cfg.vendor_specific, vendor_specific),
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_FLAG("human-readable", 'H', &cfg.human_readable, human_readable),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return errno;
+
+ err = validate_output_format(cfg.output_format, &flags);
+ if (err < 0)
+ goto close_dev;
+ if (cfg.vendor_specific)
+ flags |= VS;
+ if (cfg.human_readable)
+ flags |= VERBOSE;
+
+ if (!cfg.namespace_id) {
+ err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id);
+ if (err < 0) {
+ perror("get-namespace-id");
+ goto close_dev;
+ }
+ }
+
+ err = nvme_identify_ns(dev_fd(dev), cfg.namespace_id, &id_ns);
+ if (err) {
+ nvme_show_status(err);
+ goto close_dev;
+ }
+
+ err = nvme_zns_identify_ns(dev_fd(dev), cfg.namespace_id, &ns);
+ if (!err)
+ nvme_show_zns_id_ns(&ns, &id_ns, flags);
+ else if (err > 0)
+ nvme_show_status(err);
+ else
+ perror("zns identify namespace");
+close_dev:
+ dev_close(dev);
+ return err;
+}
+
+static int zns_mgmt_send(int argc, char **argv, struct command *cmd, struct plugin *plugin,
+ const char *desc, enum nvme_zns_send_action zsa)
+{
+ const char *zslba = "starting LBA of the zone for this command";
+ const char *select_all = "send command to all zones";
+ const char *timeout = "timeout value, in milliseconds";
+ struct nvme_dev *dev;
+ int err, zcapc = 0;
+ char *command;
+ __u32 result;
+
+ struct config {
+ __u64 zslba;
+ __u32 namespace_id;
+ bool select_all;
+ __u32 timeout;
+ };
+
+ struct config cfg = {};
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_SUFFIX("start-lba", 's', &cfg.zslba, zslba),
+ OPT_FLAG("select-all", 'a', &cfg.select_all, select_all),
+ OPT_UINT("timeout", 't', &cfg.timeout, timeout),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ goto ret;
+
+ err = asprintf(&command, "%s-%s", plugin->name, cmd->name);
+ if (err < 0)
+ goto close_dev;
+
+ if (!cfg.namespace_id) {
+ err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id);
+ if (err < 0) {
+ perror("get-namespace-id");
+ goto free;
+ }
+ }
+
+ struct nvme_zns_mgmt_send_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .nsid = cfg.namespace_id,
+ .slba = cfg.zslba,
+ .zsa = zsa,
+ .select_all = cfg.select_all,
+ .zsaso = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = cfg.timeout,
+ .result = &result,
+ };
+ err = nvme_zns_mgmt_send(&args);
+ if (!err) {
+ if (zsa == NVME_ZNS_ZSA_RESET)
+ zcapc = result & 0x1;
+
+ printf("%s: Success, action:%d zone:%"PRIx64" all:%d zcapc:%u nsid:%d\n",
+ command, zsa, (uint64_t)cfg.zslba, (int)cfg.select_all,
+ zcapc, cfg.namespace_id);
+ } else if (err > 0) {
+ nvme_show_status(err);
+ } else {
+ perror(desc);
+ }
+free:
+ free(command);
+close_dev:
+ dev_close(dev);
+ret:
+ return err;
+}
+
+static int get_zdes_bytes(int fd, __u32 nsid)
+{
+ struct nvme_zns_id_ns ns;
+ struct nvme_id_ns id_ns;
+ __u8 lbaf;
+ int err;
+
+ err = nvme_identify_ns(fd, nsid, &id_ns);
+ if (err > 0) {
+ nvme_show_status(err);
+ return -1;
+ } else if (err < 0) {
+ perror("identify namespace");
+ return -1;
+ }
+
+ err = nvme_zns_identify_ns(fd, nsid, &ns);
+ if (err > 0) {
+ nvme_show_status(err);
+ return -1;
+ } else if (err < 0) {
+ perror("zns identify namespace");
+ return -1;
+ }
+
+ nvme_id_ns_flbas_to_lbaf_inuse(id_ns.flbas, &lbaf);
+ return ns.lbafe[lbaf].zdes << 6;
+}
+
+static int zone_mgmt_send(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Zone Management Send";
+ const char *zslba =
+ "starting LBA of the zone for this command(for flush action, last lba to flush)";
+ const char *zsaso = "Zone Send Action Specific Option";
+ const char *select_all = "send command to all zones";
+ const char *zsa = "zone send action";
+ const char *data_len = "buffer length if data required";
+ const char *data = "optional file for data (default stdin)";
+ const char *timeout = "timeout value, in milliseconds";
+
+ int ffd = STDIN_FILENO, err = -1;
+ struct nvme_dev *dev;
+ void *buf = NULL;
+
+ struct config {
+ __u64 zslba;
+ __u32 namespace_id;
+ bool zsaso;
+ bool select_all;
+ __u8 zsa;
+ int data_len;
+ char *file;
+ __u32 timeout;
+ };
+
+ struct config cfg = {};
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_SUFFIX("start-lba", 's', &cfg.zslba, zslba),
+ OPT_FLAG("zsaso", 'o', &cfg.zsaso, zsaso),
+ OPT_FLAG("select-all", 'a', &cfg.select_all, select_all),
+ OPT_BYTE("zsa", 'z', &cfg.zsa, zsa),
+ OPT_UINT("data-len", 'l', &cfg.data_len, data_len),
+ OPT_FILE("data", 'd', &cfg.file, data),
+ OPT_UINT("timeout", 't', &cfg.timeout, timeout),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return errno;
+
+ if (!cfg.namespace_id) {
+ err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id);
+ if (err < 0) {
+ perror("get-namespace-id");
+ goto close_dev;
+ }
+ }
+
+ if (!cfg.zsa) {
+ fprintf(stderr, "zone send action must be specified\n");
+ err = -EINVAL;
+ goto close_dev;
+ }
+
+ if (cfg.zsa == NVME_ZNS_ZSA_SET_DESC_EXT) {
+ if (!cfg.data_len) {
+ int data_len = get_zdes_bytes(dev_fd(dev),
+ cfg.namespace_id);
+
+ if (data_len == 0) {
+ fprintf(stderr, "Zone Descriptor Extensions are not supported\n");
+ goto close_dev;
+ } else if (data_len < 0) {
+ err = data_len;
+ goto close_dev;
+ }
+ cfg.data_len = data_len;
+ }
+ if (posix_memalign(&buf, getpagesize(), cfg.data_len)) {
+ fprintf(stderr, "can not allocate feature payload\n");
+ goto close_dev;
+ }
+ memset(buf, 0, cfg.data_len);
+
+ if (cfg.file) {
+ ffd = open(cfg.file, O_RDONLY);
+ if (ffd < 0) {
+ perror(cfg.file);
+ goto free;
+ }
+ }
+
+ err = read(ffd, (void *)buf, cfg.data_len);
+ if (err < 0) {
+ perror("read");
+ goto close_ffd;
+ }
+ } else {
+ if (cfg.file || cfg.data_len) {
+ fprintf(stderr, "data, data_len only valid with set extended descriptor\n");
+ err = -EINVAL;
+ goto close_dev;
+ }
+ }
+
+ struct nvme_zns_mgmt_send_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .nsid = cfg.namespace_id,
+ .slba = cfg.zslba,
+ .zsa = cfg.zsa,
+ .select_all = cfg.select_all,
+ .zsaso = cfg.zsaso,
+ .data_len = cfg.data_len,
+ .data = buf,
+ .timeout = cfg.timeout,
+ .result = NULL,
+ };
+ err = nvme_zns_mgmt_send(&args);
+ if (!err)
+ printf("zone-mgmt-send: Success, action:%d zone:%"PRIx64" all:%d nsid:%d\n",
+ cfg.zsa, (uint64_t)cfg.zslba, (int)cfg.select_all, cfg.namespace_id);
+ else if (err > 0)
+ nvme_show_status(err);
+ else
+ perror("zns zone-mgmt-send");
+
+close_ffd:
+ if (cfg.file)
+ close(ffd);
+free:
+ free(buf);
+close_dev:
+ dev_close(dev);
+ return err;
+}
+
+static int close_zone(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Close zones\n";
+
+ return zns_mgmt_send(argc, argv, cmd, plugin, desc, NVME_ZNS_ZSA_CLOSE);
+}
+
+static int finish_zone(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Finish zones\n";
+
+ return zns_mgmt_send(argc, argv, cmd, plugin, desc, NVME_ZNS_ZSA_FINISH);
+}
+
+static int open_zone(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Open zones\n";
+ const char *zslba = "starting LBA of the zone for this command";
+ const char *zrwaa = "Allocate Zone Random Write Area to zone";
+ const char *select_all = "send command to all zones";
+ const char *timeout = "timeout value, in milliseconds";
+ struct nvme_dev *dev;
+ int err;
+
+ struct config {
+ __u64 zslba;
+ __u32 namespace_id;
+ bool zrwaa;
+ bool select_all;
+ __u32 timeout;
+ };
+
+ struct config cfg = {
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_SUFFIX("start-lba", 's', &cfg.zslba, zslba),
+ OPT_FLAG("zrwaa", 'r', &cfg.zrwaa, zrwaa),
+ OPT_FLAG("select-all", 'a', &cfg.select_all, select_all),
+ OPT_UINT("timeout", 't', &cfg.timeout, timeout),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return errno;
+
+ if (!cfg.namespace_id) {
+ err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id);
+ if (err < 0) {
+ perror("get-namespace-id");
+ goto close_dev;
+ }
+ }
+
+ struct nvme_zns_mgmt_send_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .nsid = cfg.namespace_id,
+ .slba = cfg.zslba,
+ .zsa = NVME_ZNS_ZSA_OPEN,
+ .select_all = cfg.select_all,
+ .zsaso = cfg.zrwaa,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = cfg.timeout,
+ .result = NULL,
+ };
+ err = nvme_zns_mgmt_send(&args);
+ if (!err)
+ printf("zns-open-zone: Success zone slba:%"PRIx64" nsid:%d\n",
+ (uint64_t)cfg.zslba, cfg.namespace_id);
+ else
+ nvme_show_status(err);
+close_dev:
+ dev_close(dev);
+ return err;
+}
+
+static int reset_zone(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Reset zones\n";
+
+ return zns_mgmt_send(argc, argv, cmd, plugin, desc, NVME_ZNS_ZSA_RESET);
+}
+
+static int offline_zone(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Offline zones\n";
+
+ return zns_mgmt_send(argc, argv, cmd, plugin, desc, NVME_ZNS_ZSA_OFFLINE);
+}
+
+static int set_zone_desc(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Set Zone Descriptor Extension\n";
+ const char *zslba = "starting LBA of the zone for this command";
+ const char *zrwaa = "Allocate Zone Random Write Area to zone";
+ const char *data = "optional file for zone extension data (default stdin)";
+ const char *timeout = "timeout value, in milliseconds";
+
+ int ffd = STDIN_FILENO, err;
+ struct nvme_dev *dev;
+ void *buf = NULL;
+ int data_len;
+
+ struct config {
+ __u64 zslba;
+ bool zrwaa;
+ __u32 namespace_id;
+ char *file;
+ __u32 timeout;
+ };
+
+ struct config cfg = {};
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_SUFFIX("start-lba", 's', &cfg.zslba, zslba),
+ OPT_FLAG("zrwaa", 'r', &cfg.zrwaa, zrwaa),
+ OPT_FILE("data", 'd', &cfg.file, data),
+ OPT_UINT("timeout", 't', &cfg.timeout, timeout),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return errno;
+
+ if (!cfg.namespace_id) {
+ err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id);
+ if (err < 0) {
+ perror("get-namespace-id");
+ goto close_dev;
+ }
+ }
+
+ data_len = get_zdes_bytes(dev_fd(dev), cfg.namespace_id);
+
+ if (!data_len || data_len < 0) {
+ fprintf(stderr,
+ "zone format does not provide descriptor extension\n");
+ errno = EINVAL;
+ err = -1;
+ goto close_dev;
+ }
+
+ buf = calloc(1, data_len);
+ if (!buf) {
+ perror("could not alloc memory for zone desc");
+ err = -ENOMEM;
+ goto close_dev;
+ }
+
+ if (cfg.file) {
+ ffd = open(cfg.file, O_RDONLY);
+ if (ffd < 0) {
+ perror(cfg.file);
+ err = -1;
+ goto free;
+ }
+ }
+
+ err = read(ffd, (void *)buf, data_len);
+ if (err < 0) {
+ perror("read");
+ goto close_ffd;
+ }
+
+ struct nvme_zns_mgmt_send_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .nsid = cfg.namespace_id,
+ .slba = cfg.zslba,
+ .zsa = NVME_ZNS_ZSA_SET_DESC_EXT,
+ .select_all = 0,
+ .zsaso = cfg.zrwaa,
+ .data_len = data_len,
+ .data = buf,
+ .timeout = cfg.timeout,
+ .result = NULL,
+ };
+ err = nvme_zns_mgmt_send(&args);
+ if (!err)
+ printf("set-zone-desc: Success, zone:%"PRIx64" nsid:%d\n",
+ (uint64_t)cfg.zslba, cfg.namespace_id);
+ else if (err > 0)
+ nvme_show_status(err);
+ else
+ perror("zns set-zone-desc");
+close_ffd:
+ if (cfg.file)
+ close(ffd);
+free:
+ free(buf);
+close_dev:
+ dev_close(dev);
+ return err;
+}
+
+
+static int zrwa_flush_zone(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Flush Explicit ZRWA Range";
+ const char *slba = "LBA to flush up to";
+ const char *timeout = "timeout value, in milliseconds";
+ struct nvme_dev *dev;
+ int err;
+
+ struct config {
+ __u64 lba;
+ __u32 namespace_id;
+ __u32 timeout;
+ };
+
+ struct config cfg = {};
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_SUFFIX("lba", 'l', &cfg.lba, slba),
+ OPT_UINT("timeout", 't', &cfg.timeout, timeout),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return errno;
+
+ if (!cfg.namespace_id) {
+ err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id);
+ if (err < 0) {
+ perror("get-namespace-id");
+ goto close_dev;
+ }
+ }
+
+ struct nvme_zns_mgmt_send_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .nsid = cfg.namespace_id,
+ .slba = cfg.lba,
+ .zsa = NVME_ZNS_ZSA_ZRWA_FLUSH,
+ .select_all = 0,
+ .zsaso = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = cfg.timeout,
+ .result = NULL,
+ };
+ err = nvme_zns_mgmt_send(&args);
+ if (!err)
+ printf("zrwa-flush-zone: Success, lba:%"PRIx64" nsid:%d\n",
+ (uint64_t)cfg.lba, cfg.namespace_id);
+ else
+ nvme_show_status(err);
+close_dev:
+ dev_close(dev);
+ return err;
+}
+
+static int zone_mgmt_recv(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Zone Management Receive";
+ const char *zslba = "starting LBA of the zone";
+ const char *zra = "Zone Receive Action";
+ const char *zrasf = "Zone Receive Action Specific Field(Reporting Options)";
+ const char *partial = "Zone Receive Action Specific Features(Partial Report)";
+ const char *data_len = "length of data in bytes";
+
+ enum nvme_print_flags flags;
+ struct nvme_dev *dev;
+ void *data = NULL;
+ int err = -1;
+
+ struct config {
+ char *output_format;
+ __u64 zslba;
+ __u32 namespace_id;
+ __u8 zra;
+ __u8 zrasf;
+ bool partial;
+ __u32 data_len;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_SUFFIX("start-lba", 's', &cfg.zslba, zslba),
+ OPT_BYTE("zra", 'z', &cfg.zra, zra),
+ OPT_BYTE("zrasf", 'S', &cfg.zrasf, zrasf),
+ OPT_FLAG("partial", 'p', &cfg.partial, partial),
+ OPT_UINT("data-len", 'l', &cfg.data_len, data_len),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return errno;
+
+ err = validate_output_format(cfg.output_format, &flags);
+ if (err < 0)
+ goto close_dev;
+
+ if (!cfg.namespace_id) {
+ err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id);
+ if (err < 0) {
+ perror("get-namespace-id");
+ goto close_dev;
+ }
+ }
+
+ if (cfg.zra == NVME_ZNS_ZRA_REPORT_ZONES && !cfg.data_len) {
+ fprintf(stderr, "error: data len is needed for NVME_ZRA_ZONE_REPORT\n");
+ err = -EINVAL;
+ goto close_dev;
+ }
+ if (cfg.data_len) {
+ data = calloc(1, cfg.data_len);
+ if (!data) {
+ perror("could not alloc memory for zone mgmt receive data");
+ err = -ENOMEM;
+ goto close_dev;
+ }
+ }
+
+ struct nvme_zns_mgmt_recv_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .nsid = cfg.namespace_id,
+ .slba = cfg.zslba,
+ .zra = cfg.zra,
+ .zrasf = cfg.zrasf,
+ .zras_feat = cfg.partial,
+ .data_len = cfg.data_len,
+ .data = data,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ };
+ err = nvme_zns_mgmt_recv(&args);
+ if (!err)
+ printf("zone-mgmt-recv: Success, action:%d zone:%"PRIx64" nsid:%d\n",
+ cfg.zra, (uint64_t)cfg.zslba, cfg.namespace_id);
+ else if (err > 0)
+ nvme_show_status(err);
+ else
+ perror("zns zone-mgmt-recv");
+
+ free(data);
+close_dev:
+ dev_close(dev);
+ return err;
+}
+
+static int report_zones(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Retrieve the Report Zones data structure";
+ const char *zslba = "starting LBA of the zone";
+ const char *num_descs = "number of descriptors to retrieve (default: all of them)";
+ const char *state = "state of zones to list";
+ const char *ext = "set to use the extended report zones";
+ const char *part = "set to use the partial report";
+ const char *verbose = "show report zones verbosity";
+
+ enum nvme_print_flags flags;
+ int zdes = 0, err = -1;
+ struct nvme_dev *dev;
+ __u32 report_size;
+ struct nvme_zone_report *report, *buff;
+ _cleanup_huge_ struct nvme_mem_huge mh = { 0, };
+
+ unsigned int nr_zones_chunks = 1024, /* 1024 entries * 64 bytes per entry = 64k byte transfer */
+ nr_zones_retrieved = 0,
+ nr_zones,
+ log_len;
+ __u64 offset;
+ int total_nr_zones = 0;
+ struct nvme_zns_id_ns id_zns;
+ struct nvme_id_ns id_ns;
+ uint8_t lbaf;
+ __le64 zsze;
+ struct json_object *zone_list = NULL;
+
+ struct config {
+ char *output_format;
+ __u64 zslba;
+ __u32 namespace_id;
+ int num_descs;
+ int state;
+ bool verbose;
+ bool extended;
+ bool partial;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ .num_descs = -1,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_SUFFIX("start-lba", 's', &cfg.zslba, zslba),
+ OPT_UINT("descs", 'd', &cfg.num_descs, num_descs),
+ OPT_UINT("state", 'S', &cfg.state, state),
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_FLAG("verbose", 'v', &cfg.verbose, verbose),
+ OPT_FLAG("extended", 'e', &cfg.extended, ext),
+ OPT_FLAG("partial", 'p', &cfg.partial, part),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return errno;
+
+ err = validate_output_format(cfg.output_format, &flags);
+ if (err < 0)
+ goto close_dev;
+ if (cfg.verbose)
+ flags |= VERBOSE;
+
+ if (!cfg.namespace_id) {
+ err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id);
+ if (err < 0) {
+ perror("get-namespace-id");
+ goto close_dev;
+ }
+ }
+
+ if (cfg.extended) {
+ zdes = get_zdes_bytes(dev_fd(dev), cfg.namespace_id);
+ if (zdes < 0) {
+ err = zdes;
+ goto close_dev;
+ }
+ }
+
+ err = nvme_identify_ns(dev_fd(dev), cfg.namespace_id, &id_ns);
+ if (err) {
+ nvme_show_status(err);
+ goto close_dev;
+ }
+
+ err = nvme_zns_identify_ns(dev_fd(dev), cfg.namespace_id, &id_zns);
+ if (!err) {
+ /* get zsze field from zns id ns data - needed for offset calculation */
+ nvme_id_ns_flbas_to_lbaf_inuse(id_ns.flbas, &lbaf);
+ zsze = le64_to_cpu(id_zns.lbafe[lbaf].zsze);
+ } else {
+ nvme_show_status(err);
+ goto close_dev;
+ }
+
+ log_len = sizeof(struct nvme_zone_report);
+ buff = calloc(1, log_len);
+ if (!buff) {
+ err = -ENOMEM;
+ goto close_dev;
+ }
+
+ err = nvme_zns_report_zones(dev_fd(dev), cfg.namespace_id, 0,
+ cfg.state, false, false,
+ log_len, buff,
+ NVME_DEFAULT_IOCTL_TIMEOUT, NULL);
+ if (err > 0) {
+ nvme_show_status(err);
+ goto free_buff;
+ } else if (err < 0) {
+ perror("zns report-zones");
+ goto free_buff;
+ }
+
+ total_nr_zones = le64_to_cpu(buff->nr_zones);
+
+ if (cfg.num_descs == -1)
+ cfg.num_descs = total_nr_zones;
+
+ nr_zones = cfg.num_descs;
+ if (nr_zones < nr_zones_chunks)
+ nr_zones_chunks = nr_zones;
+
+ log_len = sizeof(struct nvme_zone_report) + ((sizeof(struct nvme_zns_desc) * nr_zones_chunks) + (nr_zones_chunks * zdes));
+ report_size = log_len;
+
+ report = nvme_alloc_huge(report_size, &mh);
+ if (!report) {
+ perror("alloc");
+ err = -ENOMEM;
+ goto close_dev;
+ }
+
+ offset = cfg.zslba;
+
+ nvme_zns_start_zone_list(total_nr_zones, &zone_list, flags);
+
+ while (nr_zones_retrieved < nr_zones) {
+ if (nr_zones_retrieved >= nr_zones)
+ break;
+
+ if (nr_zones_retrieved + nr_zones_chunks > nr_zones) {
+ nr_zones_chunks = nr_zones - nr_zones_retrieved;
+ log_len = sizeof(struct nvme_zone_report) + ((sizeof(struct nvme_zns_desc) * nr_zones_chunks) + (nr_zones_chunks * zdes));
+ }
+
+ err = nvme_zns_report_zones(dev_fd(dev), cfg.namespace_id,
+ offset,
+ cfg.state, cfg.extended,
+ cfg.partial, log_len, report,
+ NVME_DEFAULT_IOCTL_TIMEOUT, NULL);
+ if (err > 0) {
+ nvme_show_status(err);
+ break;
+ }
+
+ if (!err)
+ nvme_show_zns_report_zones(report, nr_zones_chunks,
+ zdes, log_len, zone_list, flags);
+
+ nr_zones_retrieved += nr_zones_chunks;
+ offset = le64_to_cpu(report->entries[nr_zones_chunks-1].zslba) + zsze;
+ }
+
+ nvme_zns_finish_zone_list(total_nr_zones, zone_list, flags);
+
+free_buff:
+ free(buff);
+close_dev:
+ dev_close(dev);
+ return err;
+}
+
+static int zone_append(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "The zone append command is used to write to a zone\n"
+ "using the slba of the zone, and the write will be appended from the\n"
+ "write pointer of the zone";
+ const char *zslba = "starting LBA of the zone";
+ const char *data = "file containing data to write";
+ const char *metadata = "file with metadata to be written";
+ const char *limited_retry = "limit media access attempts";
+ const char *fua = "force unit access";
+ const char *prinfo = "protection information action and checks field";
+ const char *piremap = "protection information remap (for type 1 PI)";
+ const char *ref_tag = "reference tag for end-to-end PI";
+ const char *lbat = "logical block application tag for end-to-end PI";
+ const char *lbatm = "logical block application tag mask for end-to-end PI";
+ const char *metadata_size = "size of metadata in bytes";
+ const char *data_size = "size of data in bytes";
+ const char *latency = "output latency statistics";
+
+ int err = -1, dfd = STDIN_FILENO, mfd = STDIN_FILENO;
+ unsigned int lba_size, meta_size;
+ void *buf = NULL, *mbuf = NULL;
+ __u16 nblocks, control = 0;
+ struct nvme_dev *dev;
+ __u64 result;
+ __u8 lba_index;
+ struct timeval start_time, end_time;
+
+ struct nvme_id_ns ns;
+
+ struct config {
+ char *data;
+ char *metadata;
+ __u64 zslba;
+ __u64 data_size;
+ __u64 metadata_size;
+ bool limited_retry;
+ bool fua;
+ __u32 namespace_id;
+ __u64 ref_tag;
+ __u16 lbat;
+ __u16 lbatm;
+ __u8 prinfo;
+ bool piremap;
+ bool latency;
+ };
+
+ struct config cfg = {};
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_SUFFIX("zslba", 's', &cfg.zslba, zslba),
+ OPT_SUFFIX("data-size", 'z', &cfg.data_size, data_size),
+ OPT_SUFFIX("metadata-size", 'y', &cfg.metadata_size, metadata_size),
+ OPT_FILE("data", 'd', &cfg.data, data),
+ OPT_FILE("metadata", 'M', &cfg.metadata, metadata),
+ OPT_FLAG("limited-retry", 'l', &cfg.limited_retry, limited_retry),
+ OPT_FLAG("force-unit-access", 'f', &cfg.fua, fua),
+ OPT_SUFFIX("ref-tag", 'r', &cfg.ref_tag, ref_tag),
+ OPT_SHRT("app-tag-mask", 'm', &cfg.lbatm, lbatm),
+ OPT_SHRT("app-tag", 'a', &cfg.lbat, lbat),
+ OPT_BYTE("prinfo", 'p', &cfg.prinfo, prinfo),
+ OPT_FLAG("piremap", 'P', &cfg.piremap, piremap),
+ OPT_FLAG("latency", 't', &cfg.latency, latency),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return errno;
+
+ if (!cfg.data_size) {
+ fprintf(stderr, "Append size not provided\n");
+ errno = EINVAL;
+ goto close_dev;
+ }
+
+ if (!cfg.namespace_id) {
+ err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id);
+ if (err < 0) {
+ perror("get-namespace-id");
+ goto close_dev;
+ }
+ }
+
+ err = nvme_identify_ns(dev_fd(dev), cfg.namespace_id, &ns);
+ if (err) {
+ nvme_show_status(err);
+ goto close_dev;
+ }
+
+ nvme_id_ns_flbas_to_lbaf_inuse(ns.flbas, &lba_index);
+ lba_size = 1 << ns.lbaf[lba_index].ds;
+ if (cfg.data_size & (lba_size - 1)) {
+ fprintf(stderr,
+ "Data size:%#"PRIx64" not aligned to lba size:%#x\n",
+ (uint64_t)cfg.data_size, lba_size);
+ errno = EINVAL;
+ goto close_dev;
+ }
+
+ meta_size = ns.lbaf[lba_index].ms;
+ if (meta_size && !(meta_size == 8 && (cfg.prinfo & 0x8)) &&
+ (!cfg.metadata_size || cfg.metadata_size % meta_size)) {
+ fprintf(stderr,
+ "Metadata size:%#"PRIx64" not aligned to metadata size:%#x\n",
+ (uint64_t)cfg.metadata_size, meta_size);
+ errno = EINVAL;
+ goto close_dev;
+ }
+
+ if (cfg.prinfo > 0xf) {
+ fprintf(stderr, "Invalid value for prinfo:%#x\n", cfg.prinfo);
+ errno = EINVAL;
+ goto close_dev;
+ }
+
+ if (cfg.data) {
+ dfd = open(cfg.data, O_RDONLY);
+ if (dfd < 0) {
+ perror(cfg.data);
+ goto close_dev;
+ }
+ }
+
+ if (posix_memalign(&buf, getpagesize(), cfg.data_size)) {
+ fprintf(stderr, "No memory for data size:%"PRIx64"\n",
+ (uint64_t)cfg.data_size);
+ goto close_dfd;
+ }
+
+ memset(buf, 0, cfg.data_size);
+ err = read(dfd, buf, cfg.data_size);
+ if (err < 0) {
+ perror("read-data");
+ goto free_data;
+ }
+
+ if (cfg.metadata) {
+ mfd = open(cfg.metadata, O_RDONLY);
+ if (mfd < 0) {
+ perror(cfg.metadata);
+ err = -1;
+ goto free_data;
+ }
+ }
+
+ if (cfg.metadata_size) {
+ if (posix_memalign(&mbuf, getpagesize(), meta_size)) {
+ fprintf(stderr, "No memory for metadata size:%d\n",
+ meta_size);
+ err = -1;
+ goto close_mfd;
+ }
+
+ memset(mbuf, 0, cfg.metadata_size);
+ err = read(mfd, mbuf, cfg.metadata_size);
+ if (err < 0) {
+ perror("read-metadata");
+ goto free_meta;
+ }
+ }
+
+ nblocks = (cfg.data_size / lba_size) - 1;
+ control |= (cfg.prinfo << 10);
+ if (cfg.limited_retry)
+ control |= NVME_IO_LR;
+ if (cfg.fua)
+ control |= NVME_IO_FUA;
+ if (cfg.piremap)
+ control |= NVME_IO_ZNS_APPEND_PIREMAP;
+
+ struct nvme_zns_append_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .nsid = cfg.namespace_id,
+ .zslba = cfg.zslba,
+ .nlb = nblocks,
+ .control = control,
+ .ilbrt_u64 = cfg.ref_tag,
+ .lbat = cfg.lbat,
+ .lbatm = cfg.lbatm,
+ .data_len = cfg.data_size,
+ .data = buf,
+ .metadata_len = cfg.metadata_size,
+ .metadata = mbuf,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ gettimeofday(&start_time, NULL);
+ err = nvme_zns_append(&args);
+ gettimeofday(&end_time, NULL);
+ if (cfg.latency)
+ printf(" latency: zone append: %llu us\n",
+ elapsed_utime(start_time, end_time));
+
+ if (!err)
+ printf("Success appended data to LBA %"PRIx64"\n", (uint64_t)result);
+ else if (err > 0)
+ nvme_show_status(err);
+ else
+ perror("zns zone-append");
+
+free_meta:
+ free(mbuf);
+close_mfd:
+ if (cfg.metadata)
+ close(mfd);
+free_data:
+ free(buf);
+close_dfd:
+ if (cfg.data)
+ close(dfd);
+close_dev:
+ dev_close(dev);
+ return err;
+}
+
+static int changed_zone_list(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Retrieve Changed Zone log for the given device";
+ const char *rae = "retain an asynchronous event";
+
+ struct nvme_zns_changed_zone_log log;
+ enum nvme_print_flags flags;
+ struct nvme_dev *dev;
+ int err = -1;
+
+ struct config {
+ char *output_format;
+ __u32 namespace_id;
+ bool rae;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_FLAG("rae", 'r', &cfg.rae, rae),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return errno;
+
+ err = validate_output_format(cfg.output_format, &flags);
+ if (err < 0)
+ goto close_dev;
+
+ if (!cfg.namespace_id) {
+ err = nvme_get_nsid(dev_fd(dev), &cfg.namespace_id);
+ if (err < 0) {
+ perror("get-namespace-id");
+ goto close_dev;
+ }
+ }
+
+ err = nvme_get_log_zns_changed_zones(dev_fd(dev), cfg.namespace_id,
+ cfg.rae, &log);
+ if (!err)
+ nvme_show_zns_changed(&log, flags);
+ else if (err > 0)
+ nvme_show_status(err);
+ else
+ perror("zns changed-zone-list");
+
+close_dev:
+ dev_close(dev);
+ return err;
+}
diff --git a/plugins/zns/zns.h b/plugins/zns/zns.h
new file mode 100644
index 0000000..43c4ecd
--- /dev/null
+++ b/plugins/zns/zns.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/zns/zns
+
+#if !defined(ZNS_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define ZNS_NVME
+
+#include "cmd.h"
+
+PLUGIN(NAME("zns", "Zoned Namespace Command Set", NVME_VERSION),
+ COMMAND_LIST(
+ ENTRY("list", "List all NVMe devices with Zoned Namespace Command Set support", list)
+ ENTRY("id-ctrl", "Send NVMe Identify Zoned Namespace Controller, display structure", id_ctrl)
+ ENTRY("id-ns", "Send NVMe Identify Zoned Namespace Namespace, display structure", id_ns)
+ ENTRY("report-zones", "Report zones associated to a Zoned Namespace", report_zones)
+ ENTRY("reset-zone", "Reset one or more zones", reset_zone)
+ ENTRY("close-zone", "Close one or more zones", close_zone)
+ ENTRY("finish-zone", "Finish one or more zones", finish_zone)
+ ENTRY("open-zone", "Open one or more zones", open_zone)
+ ENTRY("offline-zone", "Offline one or more zones", offline_zone)
+ ENTRY("set-zone-desc", "Attach zone descriptor extension data to a zone", set_zone_desc)
+ ENTRY("zrwa-flush-zone", "Flush LBAs associated with a ZRWA to a zone.", zrwa_flush_zone)
+ ENTRY("changed-zone-list", "Retrieve the changed zone list log", changed_zone_list)
+ ENTRY("zone-mgmt-recv", "Send the zone management receive command", zone_mgmt_recv)
+ ENTRY("zone-mgmt-send", "Send the zone management send command", zone_mgmt_send)
+ ENTRY("zone-append", "Append data and metadata (if applicable) to a zone", zone_append)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"