summaryrefslogtreecommitdiffstats
path: root/plugins/solidigm
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/solidigm')
-rw-r--r--plugins/solidigm/meson.build5
-rw-r--r--plugins/solidigm/solidigm-garbage-collection.c111
-rw-r--r--plugins/solidigm/solidigm-garbage-collection.h8
-rw-r--r--plugins/solidigm/solidigm-latency-tracking.c470
-rw-r--r--plugins/solidigm/solidigm-latency-tracking.h9
-rw-r--r--plugins/solidigm/solidigm-nvme.c30
-rw-r--r--plugins/solidigm/solidigm-nvme.h28
-rw-r--r--plugins/solidigm/solidigm-smart.c250
-rw-r--r--plugins/solidigm/solidigm-smart.h8
9 files changed, 919 insertions, 0 deletions
diff --git a/plugins/solidigm/meson.build b/plugins/solidigm/meson.build
new file mode 100644
index 0000000..fb0f6a9
--- /dev/null
+++ b/plugins/solidigm/meson.build
@@ -0,0 +1,5 @@
+sources += [
+ 'plugins/solidigm/solidigm-smart.c',
+ 'plugins/solidigm/solidigm-garbage-collection.c',
+ 'plugins/solidigm/solidigm-latency-tracking.c',
+]
diff --git a/plugins/solidigm/solidigm-garbage-collection.c b/plugins/solidigm/solidigm-garbage-collection.c
new file mode 100644
index 0000000..c7f1286
--- /dev/null
+++ b/plugins/solidigm/solidigm-garbage-collection.c
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "nvme-print.h"
+#include "solidigm-garbage-collection.h"
+
+typedef struct __attribute__((packed)) gc_item {
+ __le32 timer_type;
+ __le64 timestamp;
+} gc_item_t;
+
+#define VU_GC_MAX_ITEMS 100
+typedef struct garbage_control_collection_log {
+ __le16 version_major;
+ __le16 version_minor;
+ gc_item_t item[VU_GC_MAX_ITEMS];
+ __u8 reserved[2892];
+} garbage_control_collection_log_t;
+
+static void vu_gc_log_show_json(garbage_control_collection_log_t *payload, const char *devname)
+{
+ struct json_object *gc_entries = json_create_array();
+
+ for (int i = 0; i < VU_GC_MAX_ITEMS; i++) {
+ gc_item_t item = payload->item[i];
+ struct json_object *entry = json_create_object();
+ json_object_add_value_int(entry, "timestamp", le64_to_cpu(item.timestamp));
+ json_object_add_value_int(entry, "timer_type", le32_to_cpu(item.timer_type));
+ json_array_add_value_object(gc_entries, entry);
+ }
+
+ json_print_object(gc_entries, NULL);
+ json_free_object(gc_entries);
+}
+
+static void vu_gc_log_show(garbage_control_collection_log_t *payload, const char *devname)
+{
+ printf("Solidigm Garbage Collection Log for NVME device: %s\n", devname);
+ printf("Timestamp Timer Type\n");
+
+ for (int i = 0; i < VU_GC_MAX_ITEMS; i++) {
+ gc_item_t item = payload->item[i];
+ printf("%-13lu %d\n",le64_to_cpu(item.timestamp), le32_to_cpu(item.timer_type));
+ }
+}
+
+int solidigm_get_garbage_collection_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Get and parse Solidigm vendor specific garbage collection event log.";
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_END()
+ };
+
+ int fd = parse_and_open(argc, argv, desc, opts);
+ if (fd < 0) {
+ return fd;
+ }
+
+ enum nvme_print_flags flags = validate_output_format(cfg.output_format);
+ if (flags == -EINVAL) {
+ fprintf(stderr, "Invalid output format '%s'\n", cfg.output_format);
+ close(fd);
+ return fd;
+ }
+
+ garbage_control_collection_log_t gc_log;
+ const int solidigm_vu_gc_log_id = 0xfd;
+
+ int err = nvme_get_log_simple(fd, solidigm_vu_gc_log_id, sizeof(gc_log), &gc_log);
+ if (!err) {
+ if (flags & BINARY) {
+ d_raw((unsigned char *)&gc_log, sizeof(gc_log));
+ } else if (flags & JSON) {
+ vu_gc_log_show_json(&gc_log, devicename);
+ } else {
+ vu_gc_log_show(&gc_log, devicename);
+ }
+ }
+ else if (err > 0) {
+ nvme_show_status(err);
+ }
+
+ close(fd);
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-garbage-collection.h b/plugins/solidigm/solidigm-garbage-collection.h
new file mode 100644
index 0000000..a3e34b2
--- /dev/null
+++ b/plugins/solidigm/solidigm-garbage-collection.h
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+int solidigm_get_garbage_collection_log(int argc, char **argv, struct command *cmd, struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-latency-tracking.c b/plugins/solidigm/solidigm-latency-tracking.c
new file mode 100644
index 0000000..0bfd611
--- /dev/null
+++ b/plugins/solidigm/solidigm-latency-tracking.c
@@ -0,0 +1,470 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "nvme-print.h"
+
+#define BUCKET_LIST_SIZE_4_0 152
+#define BUCKET_LIST_SIZE_4_1 1216
+
+#define BASE_RANGE_BITS_4_0 3
+#define BASE_RANGE_BITS_4_1 6
+
+struct latency_statistics {
+ __u16 version_major;
+ __u16 version_minor;
+ __u32 data[BUCKET_LIST_SIZE_4_1];
+ __u64 average_latency;
+};
+
+struct config {
+ bool enable;
+ bool disable;
+ bool read;
+ bool write;
+ unsigned char type;
+ char *output_format;
+};
+
+struct latency_tracker {
+ int fd;
+ struct config cfg;
+ enum nvme_print_flags print_flags;
+ struct latency_statistics stats;
+ struct json_object *bucket_list;
+ __u32 bucket_list_size;
+ __u8 base_range_bits;
+ bool has_average_latency_field;
+};
+
+/* COL_WIDTH controls width of columns in NORMAL output. */
+#define COL_WIDTH 12
+#define BUCKET_LABEL_MAX_SIZE 10
+
+#define US_IN_S 1000000
+#define US_IN_MS 1000
+
+/*
+ * Edge buckets may have range [#s, inf) in some
+ * latency statistics formats.
+ */
+static void get_time_unit_label(char *label, __u32 microseconds,
+ bool bonded)
+{
+ char *string = "us";
+ int divisor = 1;
+
+ if (!bonded) {
+ snprintf(label, BUCKET_LABEL_MAX_SIZE, "%s", "+INF");
+ return;
+ }
+
+ if (microseconds > US_IN_S) {
+ string = "s";
+ divisor = US_IN_S;
+ } else if (microseconds > US_IN_MS) {
+ string = "ms";
+ divisor = US_IN_MS;
+ }
+
+ snprintf(label, BUCKET_LABEL_MAX_SIZE, "%4.2f%s", (float) microseconds / divisor,
+ string);
+}
+
+static void latency_tracker_bucket_parse(const struct latency_tracker *lt, int id,
+ __u32 lower_us, __u32 upper_us, bool upper_bounded)
+{
+ char buffer[BUCKET_LABEL_MAX_SIZE] = "";
+ __u32 bucket_data = le32_to_cpu(lt->stats.data[id]);
+
+ if (lt->print_flags == NORMAL) {
+
+ printf("%-*d", COL_WIDTH, id);
+
+ get_time_unit_label(buffer, lower_us, true);
+ printf("%-*s", COL_WIDTH, buffer);
+
+ get_time_unit_label(buffer, upper_us, upper_bounded);
+ printf("%-*s", COL_WIDTH, buffer);
+
+ printf("%-*d\n", COL_WIDTH, bucket_data);
+ }
+
+ if (lt->print_flags == JSON) {
+ /*
+ * Creates a bucket under the "values" json_object. Format is:
+ * "values" : {
+ * "bucket" : {
+ * "id" : #,
+ * "start" : string,
+ * "end" : string,
+ * "value" : 0,
+ * },
+ */
+ struct json_object *bucket = json_create_object();
+
+ json_object_array_add(lt->bucket_list, bucket);
+ json_object_add_value_int(bucket, "id", id);
+
+ get_time_unit_label(buffer, lower_us, true);
+ json_object_add_value_string(bucket, "start", buffer);
+
+ get_time_unit_label(buffer, upper_us, upper_bounded);
+ json_object_add_value_string(bucket, "end", buffer);
+
+ json_object_add_value_int(bucket, "value", bucket_data);
+ }
+}
+
+static void latency_tracker_parse_linear(const struct latency_tracker *lt,
+ __u32 start_offset, __u32 end_offset,
+ __u32 bytes_per, __u32 us_step,
+ bool nonzero_print)
+{
+ for (int i = (start_offset / bytes_per) - 1;
+ i < end_offset / bytes_per; i++) {
+ if (nonzero_print && lt->stats.data[i] == 0)
+ continue;
+ latency_tracker_bucket_parse(lt, i, us_step * i,
+ us_step * (i + 1), true);
+ }
+}
+
+/*
+ * Calculates bucket time slot. Valid starting on 4.0 revision.
+ */
+
+static int latency_tracker_bucket_pos2us(const struct latency_tracker *lt, int i)
+{
+ __u32 base_val = 1 << lt->base_range_bits;
+ if (i < (base_val << 1))
+ return i;
+
+ int error_bits = (i >> lt->base_range_bits) - 1;
+ int base = 1 << (error_bits + lt->base_range_bits);
+ int k = i % base_val;
+
+ return base + ((k + 0.5) * (1 << error_bits));
+}
+
+/*
+ * Creates a subroot in the following manner:
+ * {
+ * "latstats" : {
+ * "type" : "write" or "read",
+ * "values" : {
+ */
+static void latency_tracker_populate_json_root(const struct latency_tracker *lt,
+ struct json_object *root)
+{
+ struct json_object *subroot = json_create_object();
+
+ json_object_add_value_object(root, "latstats", subroot);
+ json_object_add_value_string(subroot, "type", lt->cfg.write ? "write" : "read");
+ if (lt->has_average_latency_field) {
+ json_object_add_value_uint64(subroot, "average_latency", le64_to_cpu(lt->stats.average_latency));
+ }
+ json_object_add_value_object(subroot, "values", lt->bucket_list);
+}
+
+static void latency_tracker_parse_3_0(const struct latency_tracker *lt)
+{
+ latency_tracker_parse_linear(lt, 4, 131, 4, 32, false);
+ latency_tracker_parse_linear(lt, 132, 255, 4, 1024, false);
+ latency_tracker_parse_linear(lt, 256, 379, 4, 32768, false);
+ latency_tracker_parse_linear(lt, 380, 383, 4, 32, true);
+ latency_tracker_parse_linear(lt, 384, 387, 4, 32, true);
+ latency_tracker_parse_linear(lt, 388, 391, 4, 32, true);
+}
+
+static void latency_tracker_parse_4_0(const struct latency_tracker *lt)
+{
+ for (unsigned int i = 0; i < lt->bucket_list_size; i++) {
+ int lower_us = latency_tracker_bucket_pos2us(lt, i);
+ int upper_us = latency_tracker_bucket_pos2us(lt, i + 1);
+
+ latency_tracker_bucket_parse(lt, i, lower_us,
+ upper_us,
+ i < (lt->bucket_list_size - 1));
+ }
+}
+
+static void print_dash_separator()
+{
+ printf("--------------------------------------------------\n");
+}
+
+static void latency_tracker_pre_parse(struct latency_tracker *lt)
+{
+ if (lt->print_flags == NORMAL) {
+ printf("Solidigm IO %s Command Latency Tracking Statistics type %d\n",
+ lt->cfg.write ? "Write" : "Read", lt->cfg.type);
+ printf("Major Revision: %u\nMinor Revision: %u\n",
+ le16_to_cpu(lt->stats.version_major), le16_to_cpu(lt->stats.version_minor));
+ if (lt->has_average_latency_field) {
+ printf("Average Latency: %lu\n", le64_to_cpu(lt->stats.average_latency));
+ }
+ print_dash_separator();
+ printf("%-12s%-12s%-12s%-20s\n", "Bucket", "Start", "End", "Value");
+ print_dash_separator();
+ }
+ if (lt->print_flags == JSON) {
+ lt->bucket_list = json_object_new_array();
+ }
+}
+
+static void latency_tracker_post_parse(struct latency_tracker *lt)
+{
+ if (lt->print_flags == JSON) {
+ struct json_object *root = json_create_object();
+
+ latency_tracker_populate_json_root(lt, root);
+ json_print_object(root, NULL);
+ json_free_object(root);
+ printf("\n");
+ }
+}
+
+static void latency_tracker_parse(struct latency_tracker *lt)
+{
+ __u16 version_major = le16_to_cpu(lt->stats.version_major);
+ __u16 version_minor = le16_to_cpu(lt->stats.version_minor);
+
+ switch (version_major) {
+ case 3:
+ latency_tracker_pre_parse(lt);
+ latency_tracker_parse_3_0(lt);
+ break;
+ case 4:
+ if (version_minor >= 8){
+ lt->has_average_latency_field = true;
+ }
+ latency_tracker_pre_parse(lt);
+ if (version_minor == 0){
+ lt->base_range_bits = BASE_RANGE_BITS_4_0;
+ lt->bucket_list_size = BUCKET_LIST_SIZE_4_0;
+ }
+ latency_tracker_parse_4_0(lt);
+ break;
+ default:
+ printf("Unsupported revision (%u.%u)\n",
+ version_major, version_minor);
+ break;
+ }
+
+ latency_tracker_post_parse(lt);
+}
+
+#define LATENCY_TRACKING_FID 0xe2
+#define LATENCY_TRACKING_FID_DATA_LEN 32
+
+static int latency_tracking_is_enable(struct latency_tracker *lt, __u32 * enabled)
+{
+ struct nvme_get_features_args args_get = {
+ .args_size = sizeof(args_get),
+ .fd = lt->fd,
+ .fid = LATENCY_TRACKING_FID,
+ .nsid = 0,
+ .sel = 0,
+ .cdw11 = 0,
+ .uuidx = 0,
+ .data_len = LATENCY_TRACKING_FID_DATA_LEN,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = enabled,
+ };
+ return nvme_get_features(&args_get);
+}
+
+static int latency_tracking_enable(struct latency_tracker *lt)
+{
+ __u32 result;
+ int err;
+
+ if (!(lt->cfg.enable || lt->cfg.disable)){
+ return 0;
+ }
+
+ if (lt->cfg.enable && lt->cfg.disable){
+ fprintf(stderr,"Cannot enable and disable simultaneously.\n");
+ return EINVAL;
+ }
+
+ struct nvme_set_features_args args_set = {
+ .args_size = sizeof(args_set),
+ .fd = lt->fd,
+ .fid = LATENCY_TRACKING_FID,
+ .nsid = 0,
+ .cdw11 = lt->cfg.enable,
+ .cdw12 = 0,
+ .save = 0,
+ .uuidx = 0,
+ .cdw15 = 0,
+ .data_len = LATENCY_TRACKING_FID_DATA_LEN,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ err = nvme_set_features(&args_set);
+ if (err > 0) {
+ nvme_show_status(err);
+ } else if (err < 0) {
+ perror("Enable latency tracking");
+ fprintf(stderr, "Command failed while parsing.\n");
+ } else {
+ if (lt->print_flags == NORMAL) {
+ printf("Successfully set enable bit for FID (0x%X) to %i.\n",
+ LATENCY_TRACKING_FID, lt->cfg.enable);
+ }
+ }
+ return err;
+}
+
+#define READ_LOG_ID 0xc1
+#define WRITE_LOG_ID 0xc2
+
+static int latency_tracker_get_log(struct latency_tracker *lt)
+{
+ int err;
+
+ if (lt->cfg.read && lt->cfg.write){
+ fprintf(stderr,"Cannot capture read and write logs simultaneously.\n");
+ return EINVAL;
+ }
+
+ if (!(lt->cfg.read || lt->cfg.write))
+ return 0;
+
+ struct nvme_get_log_args args = {
+ .lpo = 0,
+ .result = NULL,
+ .log = &lt->stats,
+ .args_size = sizeof(args),
+ .fd = lt->fd,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .lid = lt->cfg.write ? WRITE_LOG_ID : READ_LOG_ID,
+ .len = sizeof(lt->stats),
+ .nsid = NVME_NSID_ALL,
+ .csi = NVME_CSI_NVM,
+ .lsi = NVME_LOG_LSI_NONE,
+ .lsp = lt->cfg.type,
+ .uuidx = NVME_UUID_NONE,
+ .rae = false,
+ .ot = false,
+ };
+
+ err = nvme_get_log(&args);
+ if (err)
+ return err;
+
+ if (lt->print_flags & BINARY)
+ d_raw((unsigned char *)&lt->stats,
+ sizeof(lt->stats));
+ else {
+ latency_tracker_parse(lt);
+ }
+ return err;
+}
+
+int solidigm_get_latency_tracking_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ const char *desc = "Get and Parse Solidigm Latency Tracking Statistics log.";
+ __u32 enabled;
+ int err;
+
+ struct latency_tracker lt = {
+ .cfg = {
+ .output_format = "normal",
+ },
+ .base_range_bits = BASE_RANGE_BITS_4_1,
+ .bucket_list_size = BUCKET_LIST_SIZE_4_1,
+ .has_average_latency_field = false,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("enable", 'e', &lt.cfg.enable, "Enable Latency Tracking"),
+ OPT_FLAG("disable", 'd', &lt.cfg.disable, "Disable Latency Tracking"),
+ OPT_FLAG("read", 'r', &lt.cfg.read, "Get read statistics"),
+ OPT_FLAG("write", 'w', &lt.cfg.write, "Get write statistics"),
+ OPT_BYTE("type", 't', &lt.cfg.type, "Log type to get"),
+ OPT_FMT("output-format", 'o', &lt.cfg.output_format, output_format),
+ OPT_END()
+ };
+
+ lt.fd = parse_and_open(argc, argv, desc, opts);
+ if (lt.fd < 0)
+ return lt.fd;
+
+ lt.print_flags = validate_output_format(lt.cfg.output_format);
+ if (lt.print_flags == -EINVAL) {
+ fprintf(stderr, "Invalid output format '%s'\n", lt.cfg.output_format);
+ close(lt.fd);
+ return EINVAL;
+ }
+
+ if (lt.cfg.type > 0xf) {
+ fprintf(stderr, "Invalid Log type value '%d'\n", lt.cfg.type);
+ close(lt.fd);
+ return EINVAL;
+ }
+
+ if (lt.cfg.type && !(lt.cfg.read || lt.cfg.write)) {
+ fprintf(stderr, "Log type option valid only when retrieving statistics\n");
+ close(lt.fd);
+ return EINVAL;
+ }
+
+ err = latency_tracking_enable(&lt);
+ if (err){
+ close(lt.fd);
+ return err;
+ }
+
+ err = latency_tracker_get_log(&lt);
+ if (err){
+ close(lt.fd);
+ return err;
+ }
+
+ if ((lt.cfg.read || lt.cfg.write || lt.cfg.enable || lt.cfg.disable)) {
+ close(lt.fd);
+ return 0;
+ }
+
+ err = latency_tracking_is_enable(&lt, &enabled);
+ if (!err) {
+ if (lt.print_flags == JSON) {
+ struct json_object *root = json_create_object();
+ json_object_add_value_int(root,"enabled", enabled);
+ json_print_object(root, NULL);
+ json_free_object(root);
+ printf("\n");
+ } else if (lt.print_flags == BINARY) {
+ putchar(enabled);
+ } else {
+ printf(
+ "Latency Statistics Tracking (FID 0x%X) is currently (%i).\n",
+ LATENCY_TRACKING_FID, enabled);
+ }
+ } else {
+ fprintf(stderr, "Could not read feature id 0xE2.\n");
+ }
+ close(lt.fd);
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-latency-tracking.h b/plugins/solidigm/solidigm-latency-tracking.h
new file mode 100644
index 0000000..9a763a9
--- /dev/null
+++ b/plugins/solidigm/solidigm-latency-tracking.h
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+int solidigm_get_latency_tracking_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-nvme.c b/plugins/solidigm/solidigm-nvme.c
new file mode 100644
index 0000000..3eb3cc9
--- /dev/null
+++ b/plugins/solidigm/solidigm-nvme.c
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include "nvme.h"
+
+#define CREATE_CMD
+#include "solidigm-nvme.h"
+
+#include "solidigm-smart.h"
+#include "solidigm-garbage-collection.h"
+#include "solidigm-latency-tracking.h"
+
+static int get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return solidigm_get_additional_smart_log(argc, argv, cmd, plugin);
+}
+
+static int get_garbage_collection_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return solidigm_get_garbage_collection_log(argc, argv, cmd, plugin);
+}
+
+static int get_latency_tracking_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return solidigm_get_latency_tracking_log(argc, argv, cmd, plugin);
+} \ No newline at end of file
diff --git a/plugins/solidigm/solidigm-nvme.h b/plugins/solidigm/solidigm-nvme.h
new file mode 100644
index 0000000..9fc8b45
--- /dev/null
+++ b/plugins/solidigm/solidigm-nvme.h
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/solidigm/solidigm-nvme
+
+#if !defined(SOLIDIGM_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define SOLIDIGM_NVME
+
+#include "cmd.h"
+
+#define SOLIDIGM_PLUGIN_VERSION "0.4"
+
+PLUGIN(NAME("solidigm", "Solidigm vendor specific extensions", SOLIDIGM_PLUGIN_VERSION),
+ COMMAND_LIST(
+ ENTRY("smart-log-add", "Retrieve Solidigm SMART Log", get_additional_smart_log)
+ ENTRY("garbage-collect-log", "Retrieve Garbage Collection Log", get_garbage_collection_log)
+ ENTRY("latency-tracking-log", "Enable/Retrieve Latency tracking Log", get_latency_tracking_log)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/solidigm/solidigm-smart.c b/plugins/solidigm/solidigm-smart.c
new file mode 100644
index 0000000..4b30cab
--- /dev/null
+++ b/plugins/solidigm/solidigm-smart.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <endian.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "nvme-print.h"
+
+#include "solidigm-smart.h"
+
+struct __attribute__((packed)) nvme_additional_smart_log_item {
+ __u8 id;
+ __u8 _kp[2];
+ __u8 normalized;
+ __u8 _np;
+ union __attribute__((packed)) {
+ __u8 raw[6];
+ struct __attribute__((packed)) wear_level {
+ __le16 min;
+ __le16 max;
+ __le16 avg;
+ } wear_level;
+ struct __attribute__((packed)) thermal_throttle {
+ __u8 pct;
+ __u32 count;
+ } thermal_throttle;
+ } ;
+ __u8 _rp;
+} ;
+typedef struct nvme_additional_smart_log_item smart_log_item_t;
+
+#define VU_SMART_PAGE_SIZE 512
+#define VU_SMART_MAX_ITEMS VU_SMART_PAGE_SIZE / sizeof(smart_log_item_t)
+typedef struct vu_smart_log {
+ smart_log_item_t item[VU_SMART_MAX_ITEMS];
+} vu_smart_log_t;
+
+static char *id_to_name(__u8 id)
+{
+ switch (id) {
+ case 0x0D:
+ return "soft_ecc_error_rate";
+ case 0x05:
+ return "relocatable_sector_count";
+ case 0xAB:
+ return "program_fail_count";
+ case 0xAC:
+ return "erase_fail_count";
+ case 0xAD:
+ return "wear_leveling_count";
+ case 0xAE:
+ return "unexpected_power_loss";
+ case 0xB8:
+ return "e2e_error_detect_count";
+ case 0xC7:
+ return "crc_error_count";
+ case 0xE2:
+ return "media_wear_percentage";
+ case 0xE3:
+ return "host_reads";
+ case 0xE4:
+ return "timed_work_load";
+ case 0xE5:
+ return "read_commands_in_flight_counter";
+ case 0xE6:
+ return "write_commands_in_flight_counter";
+ case 0xEA:
+ return "thermal_throttle_status";
+ case 0xF0:
+ return "retry_buffer_overflow_counter";
+ case 0xF3:
+ return "pll_lock_loss_counter";
+ case 0xF4:
+ return "nand_bytes_written";
+ case 0xF5:
+ return "host_bytes_written";
+ case 0xF6:
+ return "host_context_wear_used";
+ case 0xF7:
+ return "performance_status_indicator";
+ case 0xF8:
+ return "media_bytes_read";
+ case 0xF9:
+ return "available_fw_downgrades";
+ case 0xFA:
+ return "host_read_collision_count";
+ case 0xFB:
+ return "host_write_collision_count";
+ case 0xFC:
+ return "xor_pass_count";
+ case 0xFD:
+ return "xor_fail_count";
+ case 0xFE:
+ return "xor_invoked_count";
+ default:
+ return "unknown";
+ }
+}
+
+static void smart_log_item_print(smart_log_item_t *item)
+{
+ if (!item->id) {
+ return;
+ }
+
+ printf("%#x %-45s %3d ",
+ item->id, id_to_name(item->id), item->normalized);
+
+ switch (item->id) {
+ case 0xAD:
+ printf("min: %u, max: %u, avg: %u\n",
+ le16_to_cpu(item->wear_level.min),
+ le16_to_cpu(item->wear_level.max),
+ le16_to_cpu(item->wear_level.avg));
+ return;
+ case 0xEA:
+ printf("%u%%, cnt: %u\n",
+ item->thermal_throttle.pct,
+ le32_to_cpu(item->thermal_throttle.count));
+ return;
+ default:
+ printf("%"PRIu64"\n", int48_to_long(item->raw));
+ }
+}
+
+static void smart_log_item_add_json(smart_log_item_t *item, struct json_object *dev_stats)
+{
+ struct json_object *entry_stats = json_create_object();
+
+ if (!item->id) {
+ return;
+ }
+
+ json_object_add_value_int(entry_stats, "normalized", item->normalized);
+
+ switch (item->id) {
+ case 0xAD:
+ json_object_add_value_int(entry_stats, "min", le16_to_cpu(item->wear_level.min));
+ json_object_add_value_int(entry_stats, "max", le16_to_cpu(item->wear_level.max));
+ json_object_add_value_int(entry_stats, "avg", le16_to_cpu(item->wear_level.avg));
+ break;
+ case 0xEA:
+ json_object_add_value_int(entry_stats, "percentage", item->thermal_throttle.pct);
+ json_object_add_value_int(entry_stats, "count", le32_to_cpu(item->thermal_throttle.count));
+ break;
+ default:
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(item->raw));
+ }
+ json_object_add_value_object(dev_stats, id_to_name(item->id), entry_stats);
+}
+
+static void vu_smart_log_show_json(vu_smart_log_t *payload, unsigned int nsid, const char *devname)
+{
+ struct json_object *dev_stats = json_create_object();
+ smart_log_item_t *item = payload->item;
+ struct json_object *root;
+
+ for (int i = 0; i < VU_SMART_MAX_ITEMS; i++) {
+ smart_log_item_add_json(&item[i], dev_stats);
+ }
+
+ root = json_create_object();
+ json_object_add_value_string(root, "Solidigm SMART log", devname);
+ json_object_add_value_object(root, "Device stats", dev_stats);
+
+ json_print_object(root, NULL);
+ json_free_object(root);
+}
+
+static void vu_smart_log_show(vu_smart_log_t *payload, unsigned int nsid, const char *devname)
+{
+ smart_log_item_t *item = payload->item;
+
+ printf("Additional Smart Log for NVME device:%s namespace-id:%x\n",
+ devname, nsid);
+ printf("ID KEY Normalized Raw\n");
+
+ for (int i = 0; i < VU_SMART_MAX_ITEMS; i++) {
+ smart_log_item_print(&item[i]);
+ }
+}
+
+int solidigm_get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Get Solidigm vendor specific smart log (optionally, "\
+ "for the specified namespace), and show it.";
+ const int solidigm_vu_smart_log_id = 0xCA;
+ vu_smart_log_t smart_log_payload;
+ enum nvme_print_flags flags;
+ int fd, err;
+
+ struct config {
+ __u32 namespace_id;
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .namespace_id = NVME_NSID_ALL,
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, "(optional) desired namespace"),
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_END()
+ };
+
+ fd = parse_and_open(argc, argv, desc, opts);
+ if (fd < 0) {
+ return fd;
+ }
+
+ flags = validate_output_format(cfg.output_format);
+ if (flags == -EINVAL) {
+ fprintf(stderr, "Invalid output format '%s'\n", cfg.output_format);
+ close(fd);
+ return fd;
+ }
+
+ err = nvme_get_log_simple(fd, solidigm_vu_smart_log_id, sizeof(smart_log_payload), &smart_log_payload);
+ if (!err) {
+ if (flags & JSON) {
+ vu_smart_log_show_json(&smart_log_payload, cfg.namespace_id, devicename);
+ } else if (flags & BINARY) {
+ d_raw((unsigned char *)&smart_log_payload, sizeof(smart_log_payload));
+ } else {
+ vu_smart_log_show(&smart_log_payload, cfg.namespace_id, devicename);
+ }
+ } else if (err > 0) {
+ nvme_show_status(err);
+ }
+
+ close(fd);
+ return err;
+}
+
diff --git a/plugins/solidigm/solidigm-smart.h b/plugins/solidigm/solidigm-smart.h
new file mode 100644
index 0000000..e19ebe5
--- /dev/null
+++ b/plugins/solidigm/solidigm-smart.h
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+int solidigm_get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin);