summaryrefslogtreecommitdiffstats
path: root/plugins/memblaze/memblaze-nvme.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 19:41:32 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 19:41:32 +0000
commitf26f66d866ba1a9f3204e6fdfe2b07e67b5492ad (patch)
treec953c007cbe4f60a147ab62f97937d58abb2e9ca /plugins/memblaze/memblaze-nvme.c
parentInitial commit. (diff)
downloadnvme-cli-f26f66d866ba1a9f3204e6fdfe2b07e67b5492ad.tar.xz
nvme-cli-f26f66d866ba1a9f3204e6fdfe2b07e67b5492ad.zip
Adding upstream version 2.8.upstream/2.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plugins/memblaze/memblaze-nvme.c')
-rw-r--r--plugins/memblaze/memblaze-nvme.c1842
1 files changed, 1842 insertions, 0 deletions
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;
+}