diff options
Diffstat (limited to 'plugins/memblaze/memblaze-nvme.c')
-rw-r--r-- | plugins/memblaze/memblaze-nvme.c | 1842 |
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, ¶m1, ¶m2)) { + printf("setfeature: invalid formats %s\n", cfg.param); + dev_close(dev); + return -EINVAL; + } + if ((param1 == 1) && (param2 < P2MIN || param2 > P2MAX)) { + printf("setfeature: invalid high io latency threshold %d\n", param2); + dev_close(dev); + return -EINVAL; + } + cfg.value = (param1 << MB_FEAT_HIGH_LATENCY_VALUE_SHIFT) | param2; + + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = cfg.feature_id, + .nsid = 0, + .cdw11 = cfg.value, + .cdw12 = 0, + .save = false, + .uuidx = 0, + .cdw15 = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + err = nvme_set_features(&args); + if (err < 0) + perror("set-feature"); + if (!err) + printf("set-feature:0x%02X (%s), value:%#08x\n", cfg.feature_id, + mb_feature_to_string(cfg.feature_id), cfg.value); + else if (err > 0) + nvme_show_status(err); + + dev_close(dev); + return err; +} + +static int glp_high_latency_show_bar(FILE *fdi, int print) +{ + fPRINT_PARAM1("Memblaze High Latency Log\n"); + fPRINT_PARAM1("---------------------------------------------------------------------------------------------\n"); + fPRINT_PARAM1("Timestamp Type QID CID NSID StartLBA NumLBA Latency\n"); + fPRINT_PARAM1("---------------------------------------------------------------------------------------------\n"); + return 0; +} + +/* + * High latency log page definition + * Total 32 bytes + */ +struct log_page_high_latency { + __u8 port; + __u8 revision; + __u16 rsvd; + __u8 opcode; + __u8 sqe; + __u16 cid; + __u32 nsid; + __u32 latency; + __u64 sLBA; + __u16 numLBA; + __u16 timestampH; + __u32 timestampL; +}; /* total 32 bytes */ + +static int find_deadbeef(char *buf) +{ + if (((*(buf + 0) & 0xff) == 0xef) && ((*(buf + 1) & 0xff) == 0xbe) && + ((*(buf + 2) & 0xff) == 0xad) && ((*(buf + 3) & 0xff) == 0xde)) + return 1; + return 0; +} + +#define TIME_STR_SIZE (44) +static int glp_high_latency(FILE *fdi, char *buf, int buflen, int print) +{ + struct log_page_high_latency *logEntry; + char string[TIME_STR_SIZE]; + int i, entrySize; + __u64 timestamp; + time_t tt = 0; + struct tm *t = NULL; + int millisec = 0; + + if (find_deadbeef(buf)) + return 0; + + entrySize = sizeof(struct log_page_high_latency); + for (i = 0; i < buflen; i += entrySize) { + logEntry = (struct log_page_high_latency *)(buf + i); + + if (logEntry->latency == 0 && logEntry->revision == 0) + return 1; + + if (!logEntry->timestampH) { /* generate host time string */ + snprintf(string, sizeof(string), "%d", logEntry->timestampL); + } else { /* sort */ + timestamp = logEntry->timestampH; + timestamp = timestamp << 32; + timestamp += logEntry->timestampL; + tt = timestamp / 1000; + millisec = timestamp % 1000; + t = gmtime(&tt); + snprintf(string, sizeof(string), "%4d%02d%02d--%02d:%02d:%02d.%03d UTC", + 1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday, t->tm_hour, + t->tm_min, t->tm_sec, millisec); + } + + if (fdi) + fprintf(fdi, "%-32s %-7x %-6x %-6x %-8x %4x%08x %-8x %-d\n", + string, logEntry->opcode, logEntry->sqe, + logEntry->cid, logEntry->nsid, + (__u32)(logEntry->sLBA >> 32), + (__u32)logEntry->sLBA, logEntry->numLBA, + logEntry->latency); + if (print) + printf("%-32s %-7x %-6x %-6x %-8x %4x%08x %-8x %-d\n", + string, logEntry->opcode, logEntry->sqe, logEntry->cid, + logEntry->nsid, (__u32)(logEntry->sLBA >> 32), (__u32)logEntry->sLBA, + logEntry->numLBA, logEntry->latency); + } + return 1; +} + +static int mb_high_latency_log_print(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = "Get Memblaze high latency log"; + char buf[LOG_PAGE_SIZE]; + struct nvme_dev *dev; + FILE *fdi = NULL; + int err; + + OPT_ARGS(opts) = { + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + fdi = fopen(FID_C3_LOG_FILENAME, "w+"); + + glp_high_latency_show_bar(fdi, DO_PRINT_FLAG); + err = nvme_get_log_simple(dev_fd(dev), GLP_ID_VU_GET_HIGH_LATENCY_LOG, + sizeof(buf), &buf); + + while (1) { + if (!glp_high_latency(fdi, buf, LOG_PAGE_SIZE, DO_PRINT_FLAG)) + break; + err = nvme_get_log_simple(dev_fd(dev), GLP_ID_VU_GET_HIGH_LATENCY_LOG, + sizeof(buf), &buf); + if (err) { + nvme_show_status(err); + break; + } + } + + if (fdi) + fclose(fdi); + dev_close(dev); + return err; +} + +static int memblaze_fw_commit(int fd, int select) +{ + struct nvme_passthru_cmd cmd = { + .opcode = nvme_admin_fw_commit, + .cdw10 = 8, + .cdw12 = select, + }; + + return nvme_submit_admin_passthru(fd, &cmd, NULL); +} + +static int mb_selective_download(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + const char *desc = + "This performs a selective firmware download, which allows the user to\n" + "select which firmware binary to update for 9200 devices. This requires a power cycle once the\n" + "update completes. The options available are:\n\n" + "OOB - This updates the OOB and main firmware\n" + "EEP - This updates the eeprom and main firmware\n" + "ALL - This updates the eeprom, OOB, and main firmware"; + const char *fw = "firmware file (required)"; + const char *select = "FW Select (e.g., --select=OOB, EEP, ALL)"; + int xfer = 4096; + void *fw_buf; + int selectNo, fw_fd, fw_size, err, offset = 0; + struct nvme_dev *dev; + struct stat sb; + int i; + + struct config { + char *fw; + char *select; + }; + + struct config cfg = { + .fw = "", + .select = "\0", + }; + + OPT_ARGS(opts) = { + OPT_STRING("fw", 'f', "FILE", &cfg.fw, fw), + OPT_STRING("select", 's', "flag", &cfg.select, select), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + if (strlen(cfg.select) != 3) { + fprintf(stderr, "Invalid select flag\n"); + err = EINVAL; + goto out; + } + + for (i = 0; i < 3; i++) + cfg.select[i] = toupper(cfg.select[i]); + + if (!strncmp(cfg.select, "OOB", 3)) { + selectNo = 18; + } else if (!strncmp(cfg.select, "EEP", 3)) { + selectNo = 10; + } else if (!strncmp(cfg.select, "ALL", 3)) { + selectNo = 26; + } else { + fprintf(stderr, "Invalid select flag\n"); + err = EINVAL; + goto out; + } + + fw_fd = open(cfg.fw, O_RDONLY); + if (fw_fd < 0) { + fprintf(stderr, "no firmware file provided\n"); + err = EINVAL; + goto out; + } + + err = fstat(fw_fd, &sb); + if (err < 0) { + perror("fstat"); + err = errno; + goto out_close; + } + + fw_size = sb.st_size; + if (fw_size & 0x3) { + fprintf(stderr, "Invalid size:%d for f/w image\n", fw_size); + err = EINVAL; + goto out_close; + } + + if (posix_memalign(&fw_buf, getpagesize(), fw_size)) { + fprintf(stderr, "No memory for f/w size:%d\n", fw_size); + err = ENOMEM; + goto out_close; + } + + if (read(fw_fd, fw_buf, fw_size) != ((ssize_t)(fw_size))) { + err = errno; + goto out_free; + } + + while (fw_size > 0) { + xfer = min(xfer, fw_size); + + struct nvme_fw_download_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .offset = offset, + .data_len = xfer, + .data = fw_buf, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = NULL, + }; + err = nvme_fw_download(&args); + if (err < 0) { + perror("fw-download"); + goto out_free; + } else if (err != 0) { + nvme_show_status(err); + goto out_free; + } + fw_buf += xfer; + fw_size -= xfer; + offset += xfer; + } + + err = memblaze_fw_commit(dev_fd(dev), selectNo); + + if (err == 0x10B || err == 0x20B) { + err = 0; + fprintf(stderr, "Update successful! Please power cycle for changes to take effect\n"); + } + +out_free: + free(fw_buf); +out_close: + close(fw_fd); +out: + dev_close(dev); + return err; +} + +static void ioLatencyHistogramOutput(FILE *fd, int index, int start, int end, char *unit0, + char *unit1, unsigned int *pHistogram, int print) +{ + int len; + char string[64], subString0[12], subString1[12]; + + snprintf(subString0, sizeof(subString0), "%d%s", start, unit0); + if (end != 0x7FFFFFFF) + snprintf(subString1, sizeof(subString1), "%d%s", end, unit1); + else + snprintf(subString1, sizeof(subString1), "%s", "+INF"); + len = snprintf(string, sizeof(string), "%-11d %-11s %-11s %-11u\n", + index, subString0, subString1, + pHistogram[index]); + fwrite(string, 1, len, fd); + if (print) + printf("%s", string); +} + +int io_latency_histogram(char *file, char *buf, int print, int logid) +{ + FILE *fdi = fopen(file, "w+"); + int i, index; + char unit[2][3]; + unsigned int *revision = (unsigned int *)buf; + + if (logid == GLP_ID_VU_GET_READ_LATENCY_HISTOGRAM) + fPRINT_PARAM1("Memblaze IO Read Command Latency Histogram\n"); + else if (logid == GLP_ID_VU_GET_WRITE_LATENCY_HISTOGRAM) + fPRINT_PARAM1("Memblaze IO Write Command Latency Histogram\n"); + fPRINT_PARAM2("Major Revision : %d\n", revision[1]); + fPRINT_PARAM2("Minor Revision : %d\n", revision[0]); + buf += 8; + + if (revision[1] == 1 && revision[0] == 0) { + fPRINT_PARAM1("--------------------------------------------------\n"); + fPRINT_PARAM1("Bucket Start End Value\n"); + fPRINT_PARAM1("--------------------------------------------------\n"); + index = 0; + strcpy(unit[0], "us"); + strcpy(unit[1], "us"); + for (i = 0; i < 32; i++, index++) { + if (i == 31) { + strcpy(unit[1], "ms"); + ioLatencyHistogramOutput(fdi, index, i * 32, 1, unit[0], unit[1], + (unsigned int *)buf, print); + } else { + ioLatencyHistogramOutput(fdi, index, i * 32, (i + 1) * 32, unit[0], + unit[1], (unsigned int *)buf, print); + } + } + + strcpy(unit[0], "ms"); + strcpy(unit[1], "ms"); + for (i = 1; i < 32; i++, index++) + ioLatencyHistogramOutput(fdi, index, i, i + 1, unit[0], unit[1], (unsigned int *)buf, print); + + for (i = 1; i < 32; i++, index++) { + if (i == 31) { + strcpy(unit[1], "s"); + ioLatencyHistogramOutput(fdi, index, i * 32, 1, unit[0], unit[1], + (unsigned int *)buf, print); + } else { + ioLatencyHistogramOutput(fdi, index, i * 32, (i + 1) * 32, unit[0], + unit[1], (unsigned int *)buf, print); + } + } + + strcpy(unit[0], "s"); + strcpy(unit[1], "s"); + for (i = 1; i < 4; i++, index++) + ioLatencyHistogramOutput(fdi, index, i, i + 1, unit[0], unit[1], (unsigned int *)buf, print); + + ioLatencyHistogramOutput(fdi, index, i, 0x7FFFFFFF, unit[0], unit[1], (unsigned int *)buf, print); + } else { + fPRINT_PARAM1("Unsupported io latency histogram revision\n"); + } + + if (fdi) + fclose(fdi); + return 1; +} + +static int mb_lat_stats_log_print(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + char stats[LOG_PAGE_SIZE]; + char f1[] = FID_C1_LOG_FILENAME; + char f2[] = FID_C2_LOG_FILENAME; + struct nvme_dev *dev; + int err; + + const char *desc = "Get Latency Statistics log and show it."; + const char *write = "Get write statistics (read default)"; + + struct config { + bool write; + }; + struct config cfg = { + .write = 0, + }; + + OPT_ARGS(opts) = { + OPT_FLAG("write", 'w', &cfg.write, write), + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + err = nvme_get_log_simple(dev_fd(dev), cfg.write ? 0xc2 : 0xc1, + sizeof(stats), &stats); + if (!err) + io_latency_histogram(cfg.write ? f2 : f1, stats, DO_PRINT_FLAG, + cfg.write ? GLP_ID_VU_GET_WRITE_LATENCY_HISTOGRAM : + GLP_ID_VU_GET_READ_LATENCY_HISTOGRAM); + else + nvme_show_status(err); + + dev_close(dev); + return err; +} + +static int memblaze_clear_error_log(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + char *desc = "Clear Memblaze devices error log."; + struct nvme_dev *dev; + int err; + + __u32 result; + + struct config { + __u32 feature_id; + __u32 value; + int save; + }; + + struct config cfg = { + .feature_id = 0xf7, + .value = 0x534d0001, + .save = 0, + }; + + OPT_ARGS(opts) = { + OPT_END() + }; + + err = parse_and_open(&dev, argc, argv, desc, opts); + if (err) + return err; + + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = cfg.feature_id, + .nsid = 0, + .cdw11 = cfg.value, + .cdw12 = 0, + .save = cfg.save, + .uuidx = 0, + .cdw15 = 0, + .data_len = 0, + .data = NULL, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + err = nvme_set_features(&args); + if (err < 0) + perror("set-feature"); + if (!err) + printf("set-feature:%02x (%s), value:%#08x\n", cfg.feature_id, mb_feature_to_string(cfg.feature_id), cfg.value); + else if (err > 0) + nvme_show_status(err); + + dev_close(dev); + return err; +} + +static int mb_set_lat_stats(int argc, char **argv, + struct command *command, struct plugin *plugin) +{ + const char *desc = ( + "Enable/Disable Latency Statistics Tracking.\n" + "No argument prints current status."); + const char *enable_desc = "Enable LST"; + const char *disable_desc = "Disable LST"; + const __u32 nsid = 0; + const __u8 fid = 0xe2; + const __u8 sel = 0; + const __u32 cdw11 = 0x0; + const __u32 cdw12 = 0x0; + const __u32 data_len = 32; + const __u32 save = 0; + struct nvme_dev *dev; + void *buf = NULL; + __u32 result; + int err; + + struct config { + bool enable, disable; + }; + + struct config cfg = { + .enable = false, + .disable = false, + }; + + struct argconfig_commandline_options command_line_options[] = { + {"enable", 'e', "", CFG_FLAG, &cfg.enable, no_argument, enable_desc}, + {"disable", 'd', "", CFG_FLAG, &cfg.disable, no_argument, disable_desc}, + {NULL} + }; + + err = parse_and_open(&dev, argc, argv, desc, command_line_options); + + enum Option { + None = -1, + True = 1, + False = 0, + }; + enum Option option = None; + + if (cfg.enable && cfg.disable) + printf("Cannot enable and disable simultaneously."); + else if (cfg.enable || cfg.disable) + option = cfg.enable; + + struct nvme_get_features_args args_get = { + .args_size = sizeof(args_get), + .fd = dev_fd(dev), + .fid = fid, + .nsid = nsid, + .sel = sel, + .cdw11 = cdw11, + .uuidx = 0, + .data_len = data_len, + .data = buf, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + struct nvme_set_features_args args_set = { + .args_size = sizeof(args_set), + .fd = dev_fd(dev), + .fid = fid, + .nsid = nsid, + .cdw11 = option, + .cdw12 = cdw12, + .save = save, + .uuidx = 0, + .cdw15 = 0, + .data_len = data_len, + .data = buf, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + if (err) + return err; + switch (option) { + case None: + err = nvme_get_features(&args_get); + if (!err) { + printf( + "Latency Statistics Tracking (FID 0x%X) is currently (%i).\n", + fid, result); + } else { + printf("Could not read feature id 0xE2.\n"); + dev_close(dev); + return err; + } + break; + case True: + case False: + err = nvme_set_features(&args_set); + if (err > 0) { + nvme_show_status(err); + } else if (err < 0) { + perror("Enable latency tracking"); + fprintf(stderr, "Command failed while parsing.\n"); + } else { + printf("Successfully set enable bit for FID (0x%X) to %i.\n", + 0xe2, option); + } + break; + default: + printf("%d not supported.\n", option); + err = EINVAL; + } + dev_close(dev); + return err; +} + +// Global definitions + +static inline int K2C(int k) // KELVINS_2_CELSIUS +{ + return (k - 273); +}; + +// Global ID definitions + +enum { + // feature ids + FID_LATENCY_FEATURE = 0xd0, + + // log ids + LID_SMART_LOG_ADD = 0xca, + LID_LATENCY_STATISTICS = 0xd0, + LID_HIGH_LATENCY_LOG = 0xd1, + LID_PERFORMANCE_STATISTICS = 0xd2, +}; + +// smart-log-add + +struct smart_log_add_item { + uint32_t index; + char *attr; +}; + +struct __packed wear_level { + __le16 min; + __le16 max; + __le16 avg; +}; + +struct __packed smart_log_add_item_12 { + uint8_t id; + uint8_t rsvd[2]; + uint8_t norm; + uint8_t rsvd1; + union { + struct wear_level wear_level; // 0xad + struct temp_since_born { // 0xe7 + __le16 max; + __le16 min; + __le16 curr; + } temp_since_born; + struct power_consumption { // 0xe8 + __le16 max; + __le16 min; + __le16 curr; + } power_consumption; + struct temp_since_power_on { // 0xaf + __le16 max; + __le16 min; + __le16 curr; + } temp_since_power_on; + uint8_t raw[6]; + }; + uint8_t rsvd2; +}; + +struct __packed smart_log_add_item_10 { + uint8_t id; + uint8_t norm; + union { + struct wear_level wear_level; // 0xad + uint8_t raw[6]; + }; + uint8_t rsvd[2]; +}; + +struct smart_log_add { + union { + union { + struct smart_log_add_v0 { + struct smart_log_add_item_12 program_fail_count; + struct smart_log_add_item_12 erase_fail_count; + struct smart_log_add_item_12 wear_leveling_count; + struct smart_log_add_item_12 end_to_end_error_count; + struct smart_log_add_item_12 crc_error_count; + struct smart_log_add_item_12 timed_workload_media_wear; + struct smart_log_add_item_12 timed_workload_host_reads; + struct smart_log_add_item_12 timed_workload_timer; + struct smart_log_add_item_12 thermal_throttle_status; + struct smart_log_add_item_12 retry_buffer_overflow_counter; + struct smart_log_add_item_12 pll_lock_loss_count; + struct smart_log_add_item_12 nand_bytes_written; + struct smart_log_add_item_12 host_bytes_written; + struct smart_log_add_item_12 system_area_life_remaining; + struct smart_log_add_item_12 nand_bytes_read; + struct smart_log_add_item_12 temperature; + struct smart_log_add_item_12 power_consumption; + struct smart_log_add_item_12 power_on_temperature; + struct smart_log_add_item_12 power_loss_protection; + struct smart_log_add_item_12 read_fail_count; + struct smart_log_add_item_12 thermal_throttle_time; + struct smart_log_add_item_12 flash_error_media_count; + } v0; + + struct smart_log_add_item_12 v0_raw[22]; + }; + + union { + struct smart_log_add_v2 { + struct smart_log_add_item_12 program_fail_count; + struct smart_log_add_item_12 erase_fail_count; + struct smart_log_add_item_12 wear_leveling_count; + struct smart_log_add_item_12 end_to_end_error_count; + struct smart_log_add_item_12 crc_error_count; + struct smart_log_add_item_12 timed_workload_media_wear; + struct smart_log_add_item_12 timed_workload_host_reads; + struct smart_log_add_item_12 timed_workload_timer; + struct smart_log_add_item_12 thermal_throttle_status; + struct smart_log_add_item_12 lifetime_write_amplification; + struct smart_log_add_item_12 pll_lock_loss_count; + struct smart_log_add_item_12 nand_bytes_written; + struct smart_log_add_item_12 host_bytes_written; + struct smart_log_add_item_12 system_area_life_remaining; + struct smart_log_add_item_12 firmware_update_count; + struct smart_log_add_item_12 dram_cecc_count; + struct smart_log_add_item_12 dram_uecc_count; + struct smart_log_add_item_12 xor_pass_count; + struct smart_log_add_item_12 xor_fail_count; + struct smart_log_add_item_12 xor_invoked_count; + struct smart_log_add_item_12 inflight_read_io_cmd; + struct smart_log_add_item_12 flash_error_media_count; + struct smart_log_add_item_12 nand_bytes_read; + struct smart_log_add_item_12 temp_since_born; + struct smart_log_add_item_12 power_consumption; + struct smart_log_add_item_12 temp_since_bootup; + struct smart_log_add_item_12 thermal_throttle_time; + } v2; + + struct smart_log_add_item_12 v2_raw[27]; + }; + + union { + struct smart_log_add_v3 { + struct smart_log_add_item_10 program_fail_count; + struct smart_log_add_item_10 erase_fail_count; + struct smart_log_add_item_10 wear_leveling_count; + struct smart_log_add_item_10 ext_e2e_err_count; + struct smart_log_add_item_10 crc_err_count; + struct smart_log_add_item_10 nand_bytes_written; + struct smart_log_add_item_10 host_bytes_written; + struct smart_log_add_item_10 reallocated_sector_count; + struct smart_log_add_item_10 uncorrectable_sector_count; + struct smart_log_add_item_10 nand_uecc_detection; + struct smart_log_add_item_10 nand_xor_correction; + struct smart_log_add_item_10 gc_count; + struct smart_log_add_item_10 dram_uecc_detection_count; + struct smart_log_add_item_10 sram_uecc_detection_count; + struct smart_log_add_item_10 internal_raid_recovery_fail_count; + struct smart_log_add_item_10 inflight_cmds; + struct smart_log_add_item_10 internal_e2e_err_count; + struct smart_log_add_item_10 die_fail_count; + struct smart_log_add_item_10 wear_leveling_execution_count; + struct smart_log_add_item_10 read_disturb_count; + struct smart_log_add_item_10 data_retention_count; + struct smart_log_add_item_10 capacitor_health; + } v3; + + struct smart_log_add_item_10 v3_raw[24]; + }; + + uint8_t raw[512]; + }; +}; + +static void smart_log_add_v0_print(struct smart_log_add_item_12 *item, int item_count) +{ + static const struct smart_log_add_item items[0xff] = { + [0xab] = {0, "program_fail_count" }, + [0xac] = {1, "erase_fail_count" }, + [0xad] = {2, "wear_leveling_count" }, + [0xb8] = {3, "end_to_end_error_count" }, + [0xc7] = {4, "crc_error_count" }, + [0xe2] = {5, "timed_workload_media_wear" }, + [0xe3] = {6, "timed_workload_host_reads" }, + [0xe4] = {7, "timed_workload_timer" }, + [0xea] = {8, "thermal_throttle_status" }, + [0xf0] = {9, "retry_buffer_overflow_counter"}, + [0xf3] = {10, "pll_lock_loss_count" }, + [0xf4] = {11, "nand_bytes_written" }, + [0xf5] = {12, "host_bytes_written" }, + [0xf6] = {13, "system_area_life_remaining" }, + [0xfa] = {14, "nand_bytes_read" }, + [0xe7] = {15, "temperature" }, + [0xe8] = {16, "power_consumption" }, + [0xaf] = {17, "power_on_temperature" }, + [0xec] = {18, "power_loss_protection" }, + [0xf2] = {19, "read_fail_count" }, + [0xeb] = {20, "thermal_throttle_time" }, + [0xed] = {21, "flash_error_media_count" }, + }; + + for (int i = 0; i < item_count; i++, item++) { + if (item->id == 0) + continue; + + printf("%#-12" PRIx8 "%-36s%-12d", item->id, items[item->id].attr, item->norm); + switch (item->id) { + case 0xad: + printf("min: %d, max: %d, avg: %d\n", + le16_to_cpu(item->wear_level.min), + le16_to_cpu(item->wear_level.max), + le16_to_cpu(item->wear_level.avg)); + break; + case 0xe7: + printf("max: %d °C (%d K), min: %d °C (%d K), curr: %d °C (%d K)\n", + K2C(le16_to_cpu(item->temp_since_born.max)), + le16_to_cpu(item->temp_since_born.max), + K2C(le16_to_cpu(item->temp_since_born.min)), + le16_to_cpu(item->temp_since_born.min), + K2C(le16_to_cpu(item->temp_since_born.curr)), + le16_to_cpu(item->temp_since_born.curr)); + break; + case 0xe8: + printf("max: %d, min: %d, curr: %d\n", + le16_to_cpu(item->power_consumption.max), + le16_to_cpu(item->power_consumption.min), + le16_to_cpu(item->power_consumption.curr)); + break; + case 0xaf: + printf("max: %d °C (%d K), min: %d °C (%d K), curr: %d °C (%d K)\n", + K2C(le16_to_cpu(item->temp_since_power_on.max)), + le16_to_cpu(item->temp_since_power_on.max), + K2C(le16_to_cpu(item->temp_since_power_on.min)), + le16_to_cpu(item->temp_since_power_on.min), + K2C(le16_to_cpu(item->temp_since_power_on.curr)), + le16_to_cpu(item->temp_since_power_on.curr)); + break; + default: + printf("%" PRIu64 "\n", int48_to_long(item->raw)); + break; + } + } +} + +static void smart_log_add_v2_print(struct smart_log_add_item_12 *item, int item_count) +{ + static const struct smart_log_add_item items[0xff] = { + [0xab] = {0, "program_fail_count" }, + [0xac] = {1, "erase_fail_count" }, + [0xad] = {2, "wear_leveling_count" }, + [0xb8] = {3, "end_to_end_error_count" }, + [0xc7] = {4, "crc_error_count" }, + [0xe2] = {5, "timed_workload_media_wear" }, + [0xe3] = {6, "timed_workload_host_reads" }, + [0xe4] = {7, "timed_workload_timer" }, + [0xea] = {8, "thermal_throttle_status" }, + [0xf0] = {9, "lifetime_write_amplification"}, + [0xf3] = {10, "pll_lock_loss_count" }, + [0xf4] = {11, "nand_bytes_written" }, + [0xf5] = {12, "host_bytes_written" }, + [0xf6] = {13, "system_area_life_remaining" }, + [0xf9] = {14, "firmware_update_count" }, + [0xfa] = {15, "dram_cecc_count" }, + [0xfb] = {16, "dram_uecc_count" }, + [0xfc] = {17, "xor_pass_count" }, + [0xfd] = {18, "xor_fail_count" }, + [0xfe] = {19, "xor_invoked_count" }, + [0xe5] = {20, "inflight_read_io_cmd" }, + [0xe6] = {21, "flash_error_media_count" }, + [0xf8] = {22, "nand_bytes_read" }, + [0xe7] = {23, "temp_since_born" }, + [0xe8] = {24, "power_consumption" }, + [0xaf] = {25, "temp_since_bootup" }, + [0xeb] = {26, "thermal_throttle_time" }, + }; + + for (int i = 0; i < item_count; i++, item++) { + if (item->id == 0) + continue; + + printf("%#-12" PRIx8 "%-36s%-12d", item->id, items[item->id].attr, item->norm); + switch (item->id) { + case 0xad: + printf("min: %d, max: %d, avg: %d\n", + le16_to_cpu(item->wear_level.min), + le16_to_cpu(item->wear_level.max), + le16_to_cpu(item->wear_level.avg)); + break; + case 0xe7: + printf("max: %d °C (%d K), min: %d °C (%d K), curr: %d °C (%d K)\n", + K2C(le16_to_cpu(item->temp_since_born.max)), + le16_to_cpu(item->temp_since_born.max), + K2C(le16_to_cpu(item->temp_since_born.min)), + le16_to_cpu(item->temp_since_born.min), + K2C(le16_to_cpu(item->temp_since_born.curr)), + le16_to_cpu(item->temp_since_born.curr)); + break; + case 0xe8: + printf("max: %d, min: %d, curr: %d\n", + le16_to_cpu(item->power_consumption.max), + le16_to_cpu(item->power_consumption.min), + le16_to_cpu(item->power_consumption.curr)); + break; + case 0xaf: + printf("max: %d °C (%d K), min: %d °C (%d K), curr: %d °C (%d K)\n", + K2C(le16_to_cpu(item->temp_since_power_on.max)), + le16_to_cpu(item->temp_since_power_on.max), + K2C(le16_to_cpu(item->temp_since_power_on.min)), + le16_to_cpu(item->temp_since_power_on.min), + K2C(le16_to_cpu(item->temp_since_power_on.curr)), + le16_to_cpu(item->temp_since_power_on.curr)); + break; + default: + printf("%" PRIu64 "\n", int48_to_long(item->raw)); + break; + } + } +} + +static void smart_log_add_v3_print(struct smart_log_add_item_10 *item, int item_count) +{ + static const struct smart_log_add_item items[0xff] = { + [0xab] = {0, "program_fail_count" }, + [0xac] = {1, "erase_fail_count" }, + [0xad] = {2, "wear_leveling_count" }, + [0xb8] = {3, "ext_e2e_err_count" }, + [0xc7] = {4, "crc_err_count" }, + [0xf4] = {5, "nand_bytes_written" }, + [0xf5] = {6, "host_bytes_written" }, + [0xd0] = {7, "reallocated_sector_count" }, + [0xd1] = {8, "uncorrectable_sector_count" }, + [0xd2] = {9, "nand_uecc_detection" }, + [0xd3] = {10, "nand_xor_correction" }, + [0xd4] = {12, "gc_count" }, // 11 is reserved + [0xd5] = {13, "dram_uecc_detection_count" }, + [0xd6] = {14, "sram_uecc_detection_count" }, + [0xd7] = {15, "internal_raid_recovery_fail_count"}, + [0xd8] = {16, "inflight_cmds" }, + [0xd9] = {17, "internal_e2e_err_count" }, + [0xda] = {19, "die_fail_count" }, // 18 is reserved + [0xdb] = {20, "wear_leveling_execution_count" }, + [0xdc] = {21, "read_disturb_count" }, + [0xdd] = {22, "data_retention_count" }, + [0xde] = {23, "capacitor_health" }, + }; + + for (int i = 0; i < item_count; i++, item++) { + if (item->id == 0) + continue; + + printf("%#-12" PRIx8 "%-36s%-12d", item->id, items[item->id].attr, item->norm); + switch (item->id) { + case 0xad: + printf("min: %d, max: %d, avg: %d\n", + le16_to_cpu(item->wear_level.min), + le16_to_cpu(item->wear_level.max), + le16_to_cpu(item->wear_level.avg)); + break; + default: + printf("%" PRIu64 "\n", int48_to_long(item->raw)); + break; + } + } +} + +static void smart_log_add_print(struct smart_log_add *log, const char *devname) +{ + uint8_t version = log->raw[511]; + + printf("Version: %u\n", version); + printf("\n"); + printf("Additional Smart Log for NVMe device: %s\n", devname); + printf("\n"); + + printf("%-12s%-36s%-12s%s\n", "Id", "Key", "Normalized", "Raw"); + + switch (version) { + case 0: + return smart_log_add_v0_print(&log->v0_raw[0], + sizeof(struct smart_log_add_v0) / sizeof(struct smart_log_add_item_12)); + case 2: + return smart_log_add_v2_print(&log->v2_raw[0], + sizeof(struct smart_log_add_v2) / sizeof(struct smart_log_add_item_12)); + case 3: + return smart_log_add_v3_print(&log->v3_raw[0], + sizeof(struct smart_log_add_v3) / sizeof(struct smart_log_add_item_10)); + + case 1: + fprintf(stderr, "Version %d: N/A\n", version); + break; + default: + fprintf(stderr, "Version %d: Not supported yet\n", version); + break; + } +} + +static int mb_get_smart_log_add(int argc, char **argv, struct command *cmd, struct plugin *plugin) +{ + int err = 0; + + // Get the configuration + + struct config { + bool raw_binary; + }; + + struct config cfg = {0}; + + OPT_ARGS(opts) = { + OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, "dump the whole log buffer in binary format"), + OPT_END()}; + + // Open device + + struct nvme_dev *dev = NULL; + + err = parse_and_open(&dev, argc, argv, cmd->help, opts); + if (err) + return err; + + // Get log + + struct smart_log_add log = {0}; + + err = nvme_get_log_simple(dev_fd(dev), LID_SMART_LOG_ADD, sizeof(struct smart_log_add), &log); + if (!err) { + if (!cfg.raw_binary) + smart_log_add_print(&log, dev->name); + else + d_raw((unsigned char *)&log, sizeof(struct smart_log_add)); + } else if (err > 0) { + nvme_show_status(err); + } else { + nvme_show_error("%s: %s", cmd->name, nvme_strerror(errno)); + } + + // Close device + + dev_close(dev); + return err; +} + +// performance-monitor + +struct latency_stats_bucket { + char *start_threshold; + char *end_threshold; +}; + +struct __packed latency_stats { + union { + struct latency_stats_v2_0 { + uint32_t minor_version; + uint32_t major_version; + uint32_t bucket_read_data[32]; + uint32_t rsvd[32]; + uint32_t bucket_write_data[32]; + uint32_t rsvd1[32]; + uint32_t bucket_trim_data[32]; + uint32_t rsvd2[32]; + uint8_t rsvd3[248]; + } v2_0; + uint8_t raw[1024]; + }; +}; + +struct __packed high_latency_log { + union { + struct high_latency_log_v1 { + uint32_t version; + struct high_latency_log_entry { + uint64_t timestamp; // ms + uint32_t latency; + uint32_t qid; + uint32_t opcode : 8; + uint32_t fuse : 2; + uint32_t psdt : 2; + uint32_t cid : 16; + uint32_t rsvd : 4; + uint32_t nsid; + uint64_t slba; + uint32_t nlb : 16; + uint32_t dtype : 8; + uint32_t pinfo : 4; + uint32_t fua : 1; + uint32_t lr : 1; + uint32_t rsvd1 : 2; + uint8_t rsvd2[28]; + } entries[1024]; + } v1; + uint8_t raw[4 + 1024 * 64]; + }; +}; + +struct __packed performance_stats { + union { + struct performance_stats_v1 { + uint8_t version; + uint8_t rsvd[3]; + struct performance_stats_timestamp { + uint8_t timestamp[6]; + struct performance_stats_entry { + uint16_t read_iops; // K IOPS + uint16_t read_bandwidth; // MiB + uint32_t read_latency; // us + uint32_t read_latency_max; // us + uint16_t write_iops; // K IOPS + uint16_t write_bandwidth; // MiB + uint32_t write_latency; // us + uint32_t write_latency_max; // us + } entries[3600]; + } timestamps[24]; + } v1; + uint8_t raw[4 + 24 * (6 + 3600 * 24)]; + }; +}; + +static int mb_set_latency_feature(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + int err = 0; + + // Get the configuration + + struct config { + uint32_t perf_monitor; + uint32_t cmd_mask; + uint32_t read_threshold; + uint32_t write_threshold; + uint32_t de_allocate_trim_threshold; + }; + + struct config cfg = {0}; + + OPT_ARGS(opts) = { + OPT_UINT("sel-perf-log", 's', &cfg.perf_monitor, + "Select features to turn on, default: Disable\n" + " bit 0: latency statistics\n" + " bit 1: high latency log\n" + " bit 2: Performance stat"), + OPT_UINT("set-commands-mask", 'm', &cfg.cmd_mask, + "Set Enable, default: Disable\n" + " bit 0: Read commands\n" + " bit 1: high Write commands\n" + " bit 2: De-allocate/TRIM (this bit is not worked for Performance stat.)"), + OPT_UINT("set-read-threshold", 'r', &cfg.read_threshold, + "set read high latency log threshold, it's a 0-based value and unit is 10ms"), + OPT_UINT("set-write-threshold", 'w', &cfg.write_threshold, + "set write high latency log threshold, it's a 0-based value and unit is 10ms"), + OPT_UINT("set-trim-threshold", 't', &cfg.de_allocate_trim_threshold, + "set trim high latency log threshold, it's a 0-based value and unit is 10ms"), + OPT_END()}; + + // Open device + + struct nvme_dev *dev = NULL; + + err = parse_and_open(&dev, argc, argv, cmd->help, opts); + if (err) + return err; + + + // Set feature + + uint32_t result = 0; + + struct nvme_set_features_args args = { + .args_size = sizeof(args), + .fd = dev_fd(dev), + .fid = FID_LATENCY_FEATURE, + .nsid = 0, + .cdw11 = 0 | cfg.perf_monitor, + .cdw12 = 0 | cfg.cmd_mask, + .cdw13 = 0 | + (cfg.read_threshold & 0xff) | + ((cfg.write_threshold & 0xff) << 8) | + ((cfg.de_allocate_trim_threshold & 0xff) << 16), + .cdw15 = 0, + .save = 0, + .uuidx = 0, + .data = NULL, + .data_len = 0, + .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, + .result = &result, + }; + + err = nvme_set_features(&args); + if (!err) + printf("%s have done successfully. result = %#" PRIx32 ".\n", cmd->name, result); + else if (err > 0) + nvme_show_status(err); + else + nvme_show_error("%s: %s", cmd->name, nvme_strerror(errno)); + + // Close device + + dev_close(dev); + return err; +} + +static int mb_get_latency_feature(int argc, char **argv, struct command *cmd, + struct plugin *plugin) +{ + int err = 0; + + // Get the configuration + + OPT_ARGS(opts) = { + OPT_END()}; + + // Open device + + struct nvme_dev *dev = NULL; + + err = parse_and_open(&dev, argc, argv, cmd->help, opts); + if (err) + return err; + + // Get feature + + uint32_t result = 0; + + err = nvme_get_features_simple(dev_fd(dev), FID_LATENCY_FEATURE, 0, &result); + if (!err) { + printf("%s have done successfully. result = %#" PRIx32 ".\n", cmd->name, result); + + printf("latency statistics enable status = %d\n", (result & (0x01 << 0)) >> 0); + printf("high latency enable status = %d\n", (result & (0x01 << 1)) >> 1); + printf("performance stat enable status = %d\n", (result & (0x01 << 2)) >> 2); + + printf("Monitor Read command = %d\n", (result & (0x01 << 4)) >> 4); + printf("Monitor Write command = %d\n", (result & (0x01 << 5)) >> 5); + printf("Monitor Trim command = %d\n", (result & (0x01 << 6)) >> 6); + + printf("Threshold for Read = %dms\n", (((result & (0xff << 8)) >> 8) + 1) * 10); + printf("Threshold for Write = %dms\n", (((result & (0xff << 16)) >> 16) + 1) * 10); + printf("Threshold for Trim = %dms\n", (((result & (0xff << 24)) >> 24) + 1) * 10); + } else if (err > 0) { + nvme_show_status(err); + } else { + nvme_show_error("%s: %s", cmd->name, nvme_strerror(errno)); + } + + // Close device + + dev_close(dev); + return err; +} |