summaryrefslogtreecommitdiffstats
path: root/plugins/solidigm
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--plugins/solidigm/meson.build16
-rw-r--r--plugins/solidigm/solidigm-garbage-collection.c138
-rw-r--r--plugins/solidigm/solidigm-garbage-collection.h8
-rw-r--r--plugins/solidigm/solidigm-get-drive-info.c79
-rw-r--r--plugins/solidigm/solidigm-get-drive-info.h8
-rw-r--r--plugins/solidigm/solidigm-id-ctrl.c73
-rw-r--r--plugins/solidigm/solidigm-id-ctrl.h10
-rw-r--r--plugins/solidigm/solidigm-internal-logs.c657
-rw-r--r--plugins/solidigm/solidigm-internal-logs.h8
-rw-r--r--plugins/solidigm/solidigm-latency-tracking.c474
-rw-r--r--plugins/solidigm/solidigm-latency-tracking.h9
-rw-r--r--plugins/solidigm/solidigm-log-page-dir.c286
-rw-r--r--plugins/solidigm/solidigm-log-page-dir.h17
-rw-r--r--plugins/solidigm/solidigm-market-log.c63
-rw-r--r--plugins/solidigm/solidigm-market-log.h8
-rw-r--r--plugins/solidigm/solidigm-nvme.c109
-rw-r--r--plugins/solidigm/solidigm-nvme.h40
-rw-r--r--plugins/solidigm/solidigm-ocp-version.c25
-rw-r--r--plugins/solidigm/solidigm-ocp-version.h8
-rw-r--r--plugins/solidigm/solidigm-smart.c271
-rw-r--r--plugins/solidigm/solidigm-smart.h8
-rw-r--r--plugins/solidigm/solidigm-telemetry.c189
-rw-r--r--plugins/solidigm/solidigm-telemetry.h8
-rw-r--r--plugins/solidigm/solidigm-telemetry/cod.c190
-rw-r--r--plugins/solidigm/solidigm-telemetry/cod.h9
-rw-r--r--plugins/solidigm/solidigm-telemetry/config.c76
-rw-r--r--plugins/solidigm/solidigm-telemetry/config.h19
-rw-r--r--plugins/solidigm/solidigm-telemetry/data-area.c472
-rw-r--r--plugins/solidigm/solidigm-telemetry/data-area.h10
-rw-r--r--plugins/solidigm/solidigm-telemetry/header.c223
-rw-r--r--plugins/solidigm/solidigm-telemetry/header.h9
-rw-r--r--plugins/solidigm/solidigm-telemetry/meson.build7
-rw-r--r--plugins/solidigm/solidigm-telemetry/nlog.c131
-rw-r--r--plugins/solidigm/solidigm-telemetry/nlog.h11
-rw-r--r--plugins/solidigm/solidigm-telemetry/telemetry-log.h31
-rw-r--r--plugins/solidigm/solidigm-temp-stats.c108
-rw-r--r--plugins/solidigm/solidigm-temp-stats.h8
-rw-r--r--plugins/solidigm/solidigm-util.c20
-rw-r--r--plugins/solidigm/solidigm-util.h12
39 files changed, 3848 insertions, 0 deletions
diff --git a/plugins/solidigm/meson.build b/plugins/solidigm/meson.build
new file mode 100644
index 0000000..052afa1
--- /dev/null
+++ b/plugins/solidigm/meson.build
@@ -0,0 +1,16 @@
+sources += [
+ 'plugins/solidigm/solidigm-id-ctrl.c',
+ 'plugins/solidigm/solidigm-util.c',
+ 'plugins/solidigm/solidigm-smart.c',
+ 'plugins/solidigm/solidigm-garbage-collection.c',
+ 'plugins/solidigm/solidigm-latency-tracking.c',
+ 'plugins/solidigm/solidigm-log-page-dir.c',
+ 'plugins/solidigm/solidigm-telemetry.c',
+ 'plugins/solidigm/solidigm-internal-logs.c',
+ 'plugins/solidigm/solidigm-market-log.c',
+ 'plugins/solidigm/solidigm-temp-stats.c',
+ 'plugins/solidigm/solidigm-get-drive-info.c',
+ 'plugins/solidigm/solidigm-ocp-version.c',
+]
+subdir('solidigm-telemetry')
+
diff --git a/plugins/solidigm/solidigm-garbage-collection.c b/plugins/solidigm/solidigm-garbage-collection.c
new file mode 100644
index 0000000..a37e9c5
--- /dev/null
+++ b/plugins/solidigm/solidigm-garbage-collection.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "nvme-print.h"
+#include "solidigm-garbage-collection.h"
+#include "solidigm-util.h"
+
+struct __packed gc_item {
+ __le32 timer_type;
+ __le64 timestamp;
+};
+
+#define VU_GC_MAX_ITEMS 100
+struct garbage_control_collection_log {
+ __le16 version_major;
+ __le16 version_minor;
+ struct __packed gc_item item[VU_GC_MAX_ITEMS];
+ __u8 reserved[2892];
+};
+
+static void vu_gc_log_show_json(struct garbage_control_collection_log *payload, const char *devname)
+{
+ struct json_object *gc_entries = json_create_array();
+
+ for (int i = 0; i < VU_GC_MAX_ITEMS; i++) {
+ struct __packed gc_item item = payload->item[i];
+ struct json_object *entry = json_create_object();
+
+ json_object_add_value_int(entry, "timestamp", le64_to_cpu(item.timestamp));
+ json_object_add_value_int(entry, "timer_type", le32_to_cpu(item.timer_type));
+ json_array_add_value_object(gc_entries, entry);
+ }
+
+ json_print_object(gc_entries, NULL);
+ json_free_object(gc_entries);
+}
+
+static void vu_gc_log_show(struct garbage_control_collection_log *payload, const char *devname,
+ __u8 uuid_index)
+{
+ printf("Solidigm Garbage Collection Log for NVME device:%s UUID-idx:%d\n", devname,
+ uuid_index);
+ printf("Timestamp Timer Type\n");
+
+ for (int i = 0; i < VU_GC_MAX_ITEMS; i++) {
+ struct __packed gc_item item = payload->item[i];
+
+ printf("%-13" PRIu64 " %d\n", le64_to_cpu(item.timestamp), le32_to_cpu(item.timer_type));
+ }
+}
+
+int solidigm_get_garbage_collection_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Get and parse Solidigm vendor specific garbage collection event log.";
+ enum nvme_print_flags flags;
+ struct nvme_dev *dev;
+ int err;
+ __u8 uuid_index;
+
+ struct config {
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = validate_output_format(cfg.output_format, &flags);
+ if (err) {
+ fprintf(stderr, "Invalid output format '%s'\n", cfg.output_format);
+ dev_close(dev);
+ return -EINVAL;
+ }
+
+ uuid_index = solidigm_get_vu_uuid_index(dev);
+
+ struct garbage_control_collection_log gc_log;
+ const int solidigm_vu_gc_log_id = 0xfd;
+ struct nvme_get_log_args args = {
+ .lpo = 0,
+ .result = NULL,
+ .log = &gc_log,
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .lid = solidigm_vu_gc_log_id,
+ .len = sizeof(gc_log),
+ .nsid = NVME_NSID_ALL,
+ .csi = NVME_CSI_NVM,
+ .lsi = NVME_LOG_LSI_NONE,
+ .lsp = NVME_LOG_LSP_NONE,
+ .uuidx = uuid_index,
+ .rae = false,
+ .ot = false,
+ };
+
+ err = nvme_get_log(&args);
+ if (!err) {
+ if (flags & BINARY)
+ d_raw((unsigned char *)&gc_log, sizeof(gc_log));
+ else if (flags & JSON)
+ vu_gc_log_show_json(&gc_log, dev->name);
+ else
+ vu_gc_log_show(&gc_log, dev->name, uuid_index);
+ } else if (err > 0) {
+ nvme_show_status(err);
+ }
+
+ /* Redundant close() to make static code analysis happy */
+ close(dev->direct.fd);
+ dev_close(dev);
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-garbage-collection.h b/plugins/solidigm/solidigm-garbage-collection.h
new file mode 100644
index 0000000..a3e34b2
--- /dev/null
+++ b/plugins/solidigm/solidigm-garbage-collection.h
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+int solidigm_get_garbage_collection_log(int argc, char **argv, struct command *cmd, struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-get-drive-info.c b/plugins/solidigm/solidigm-get-drive-info.c
new file mode 100644
index 0000000..21f59bb
--- /dev/null
+++ b/plugins/solidigm/solidigm-get-drive-info.c
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Authors: leonardo.da.cunha@solidigm.com
+ */
+
+#include <errno.h>
+#include "nvme-print.h"
+#include "nvme-wrap.h"
+#include "common.h"
+
+int sldgm_get_drive_info(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ _cleanup_nvme_dev_ struct nvme_dev *dev = NULL;
+ const char *desc = "Get drive HW information";
+ const char *FTL_unit_size_str = "FTL_unit_size";
+ char *output_format = "normal";
+ enum nvme_print_flags flags;
+ nvme_root_t r;
+ nvme_ctrl_t c;
+ nvme_ns_t n;
+ struct nvme_id_ns ns = { 0 };
+ __u8 flbaf_inUse;
+ __u16 lba_size;
+ __u16 ftl_unit_size;
+ int err;
+
+ OPT_ARGS(opts) = {
+ OPT_FMT("output-format", 'o', &output_format, "normal|json"),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = validate_output_format(output_format, &flags);
+ if ((err < 0) || !(flags == NORMAL || flags == JSON)) {
+ nvme_show_error("Invalid output format");
+ return err;
+ }
+
+ r = nvme_scan(NULL);
+ c = nvme_scan_ctrl(r, dev->name);
+ n = c ? nvme_ctrl_first_ns(c) : nvme_scan_namespace(dev->name);
+ if (!n) {
+ nvme_show_error("solidigm-vs-drive-info: drive missing namespace");
+ return -EINVAL;
+ }
+
+ err = nvme_ns_identify(n, &ns);
+ if (err) {
+ nvme_show_error("identify namespace: %s", nvme_strerror(errno));
+ return err;
+ }
+
+ if (!(ns.nsfeat & 0x10)) {
+ nvme_show_error("solidigm-vs-drive-info: performance options not available");
+ return -EINVAL;
+ }
+
+ nvme_id_ns_flbas_to_lbaf_inuse(ns.flbas, &flbaf_inUse);
+ lba_size = 1 << ns.lbaf[flbaf_inUse].ds;
+ ftl_unit_size = (le16_to_cpu(ns.npwg) + 1) * lba_size / 1024;
+
+ if (flags == JSON) {
+ struct json_object *root = json_create_object();
+
+ json_object_add_value_int(root, FTL_unit_size_str, ftl_unit_size);
+ json_print_object(root, NULL);
+ printf("\n");
+ json_free_object(root);
+ } else {
+ printf("%s: %d\n", FTL_unit_size_str, ftl_unit_size);
+ }
+
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-get-drive-info.h b/plugins/solidigm/solidigm-get-drive-info.h
new file mode 100644
index 0000000..ffc1bd2
--- /dev/null
+++ b/plugins/solidigm/solidigm-get-drive-info.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+int sldgm_get_drive_info(int argc, char **argv, struct command *cmd, struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-id-ctrl.c b/plugins/solidigm/solidigm-id-ctrl.c
new file mode 100644
index 0000000..f45e758
--- /dev/null
+++ b/plugins/solidigm/solidigm-id-ctrl.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <inttypes.h>
+#include "common.h"
+#include "solidigm-id-ctrl.h"
+
+struct __packed nvme_vu_id_ctrl_field { /* CDR MR5 */
+ __u8 rsvd1[3];
+ __u8 ss;
+ char health[20];
+ __u8 cls;
+ __u8 nlw;
+ __u8 scap;
+ __u8 sstat;
+ char bl[8];
+ __u8 rsvd2[38];
+ __le64 ww;
+ char mic_bl[4];
+ char mic_fw[4];
+};
+
+void sldgm_id_ctrl(uint8_t *vs, struct json_object *root)
+{
+ // text output aligns nicely with property name up to 10 chars
+ const char *str_ss = "stripeSize";
+ const char *str_health = "health";
+ const char *str_cls = "linkSpeed";
+ const char *str_nlw = "negLnkWdth";
+ const char *str_scap = "secCapab";
+ const char *str_sstat = "secStatus";
+ const char *str_bl = "bootLoader";
+ const char *str_ww = "wwid";
+ const char *str_mic_bl = "bwLimGran";
+ const char *str_mic_fw = "ioLimGran";
+
+ struct nvme_vu_id_ctrl_field *id = (struct nvme_vu_id_ctrl_field *)vs;
+
+ const char str_heathy[sizeof(id->health)] = "healthy";
+ const char *health = id->health[0] ? id->health : str_heathy;
+
+ if (root == NULL) {
+ printf("%-10s: %u\n", str_ss, id->ss);
+ printf("%-10s: %.*s\n", str_health, (int)sizeof(id->health), health);
+ printf("%-10s: %u\n", str_cls, id->cls);
+ printf("%-10s: %u\n", str_nlw, id->nlw);
+ printf("%-10s: %u\n", str_scap, id->scap);
+ printf("%-10s: %u\n", str_sstat, id->sstat);
+ printf("%-10s: %.*s\n", str_bl, (int)sizeof(id->bl), id->bl);
+ printf("%-10s: 0x%016"PRIx64"\n", str_ww, le64_to_cpu(id->ww));
+ printf("%-10s: %.*s\n", str_mic_bl, (int)sizeof(id->mic_bl), id->mic_bl);
+ printf("%-10s: %.*s\n", str_mic_fw, (int)sizeof(id->mic_fw), id->mic_fw);
+ return;
+ }
+
+ json_object_add_value_uint(root, str_ss, id->ss);
+ json_object_object_add(root, str_health,
+ json_object_new_string_len(health, sizeof(id->health)));
+ json_object_add_value_uint(root, str_cls, id->cls);
+ json_object_add_value_uint(root, str_nlw, id->nlw);
+ json_object_add_value_uint(root, str_scap, id->scap);
+ json_object_add_value_uint(root, str_sstat, id->sstat);
+ json_object_object_add(root, str_bl, json_object_new_string_len(id->bl, sizeof(id->bl)));
+ json_object_add_value_uint64(root, str_ww, le64_to_cpu(id->ww));
+ json_object_object_add(root, str_mic_bl,
+ json_object_new_string_len(id->mic_bl, sizeof(id->mic_bl)));
+ json_object_object_add(root, str_mic_fw,
+ json_object_new_string_len(id->mic_fw, sizeof(id->mic_fw)));
+}
diff --git a/plugins/solidigm/solidigm-id-ctrl.h b/plugins/solidigm/solidigm-id-ctrl.h
new file mode 100644
index 0000000..ed6e438
--- /dev/null
+++ b/plugins/solidigm/solidigm-id-ctrl.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <inttypes.h>
+#include "util/json.h"
+void sldgm_id_ctrl(uint8_t *vs, struct json_object *root);
diff --git a/plugins/solidigm/solidigm-internal-logs.c b/plugins/solidigm/solidigm-internal-logs.c
new file mode 100644
index 0000000..c604761
--- /dev/null
+++ b/plugins/solidigm/solidigm-internal-logs.c
@@ -0,0 +1,657 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Authors: leonardo.da.cunha@solidigm.com
+ * shankaralingegowda.singonahalli@solidigm.com
+ */
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <linux/limits.h>
+#include <time.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "nvme-print.h"
+#include "solidigm-util.h"
+
+#define DWORD_SIZE 4
+
+enum log_type {
+ NLOG = 0,
+ EVENTLOG = 1,
+ ASSERTLOG = 2,
+};
+
+#pragma pack(push, internal_logs, 1)
+struct version {
+ __u16 major;
+ __u16 minor;
+};
+
+struct event_dump_instance {
+ __u32 numeventdumps;
+ __u32 coresize;
+ __u32 coreoffset;
+ __u32 eventidoffset[16];
+ __u8 eventIdValidity[16];
+};
+
+struct commom_header {
+ struct version ver;
+ __u32 header_size;
+ __u32 log_size;
+ __u32 numcores;
+};
+
+struct event_dump_header {
+ struct commom_header header;
+ __u32 eventidsize;
+ struct event_dump_instance edumps[0];
+};
+
+struct assert_dump_core {
+ __u32 coreoffset;
+ __u32 assertsize;
+ __u8 assertdumptype;
+ __u8 assertvalid;
+ __u8 reserved[2];
+};
+
+struct assert_dump_header {
+ struct commom_header header;
+ struct assert_dump_core core[];
+};
+
+struct nlog_dump_header_common {
+ struct version ver;
+ __u32 logselect;
+ __u32 totalnlogs;
+ __u32 nlognum;
+ char nlogname[4];
+ __u32 nlogbytesize;
+ __u32 nlogprimarybuffsize;
+ __u32 tickspersecond;
+ __u32 corecount;
+};
+
+struct nlog_dump_header3_0 {
+ struct nlog_dump_header_common common;
+ __u32 nlogpausestatus;
+ __u32 selectoffsetref;
+ __u32 selectnlogpause;
+ __u32 selectaddedoffset;
+ __u32 nlogbufnum;
+ __u32 nlogbufnummax;
+};
+
+struct nlog_dump_header4_0 {
+ struct nlog_dump_header_common common;
+ __u64 nlogpausestatus;
+ __u32 selectoffsetref;
+ __u32 selectnlogpause;
+ __u32 selectaddedoffset;
+ __u32 nlogbufnum;
+ __u32 nlogbufnummax;
+ __u32 coreselected;
+ __u32 reserved[2];
+};
+
+struct nlog_dump_header4_1 {
+ struct nlog_dump_header_common common;
+ __u64 nlogpausestatus;
+ __u32 selectoffsetref;
+ __u32 selectnlogpause;
+ __u32 selectaddedoffset;
+ __u32 nlogbufnum;
+ __u32 nlogbufnummax;
+ __u32 coreselected;
+ __u32 lpaPointer1High;
+ __u32 lpaPointer1Low;
+ __u32 lpaPointer2High;
+ __u32 lpaPointer2Low;
+};
+
+#pragma pack(pop, internal_logs)
+
+struct config {
+ __u32 namespace_id;
+ char *dir_prefix;
+ char *type;
+ bool verbose;
+};
+
+static void print_nlog_header(__u8 *buffer)
+{
+ struct nlog_dump_header_common *nlog_header = (struct nlog_dump_header_common *) buffer;
+
+ if (nlog_header->ver.major >= 3) {
+ printf("Version Major %u\n", nlog_header->ver.major);
+ printf("Version Minor %u\n", nlog_header->ver.minor);
+ printf("Log_select %u\n", nlog_header->logselect);
+ printf("totalnlogs %u\n", nlog_header->totalnlogs);
+ printf("nlognum %u\n", nlog_header->nlognum);
+ printf("nlogname %c%c%c%c\n", nlog_header->nlogname[3], nlog_header->nlogname[2],
+ nlog_header->nlogname[1], nlog_header->nlogname[0]);
+ printf("nlogbytesize %u\n", nlog_header->nlogbytesize);
+ printf("nlogprimarybuffsize %u\n", nlog_header->nlogprimarybuffsize);
+ printf("tickspersecond %u\n", nlog_header->tickspersecond);
+ printf("corecount %u\n", nlog_header->corecount);
+ }
+ if (nlog_header->ver.major >= 4) {
+ struct nlog_dump_header4_0 *nlog_header = (struct nlog_dump_header4_0 *) buffer;
+
+ printf("nlogpausestatus %"PRIu64"\n", (uint64_t)nlog_header->nlogpausestatus);
+ printf("selectoffsetref %u\n", nlog_header->selectoffsetref);
+ printf("selectnlogpause %u\n", nlog_header->selectnlogpause);
+ printf("selectaddedoffset %u\n", nlog_header->selectaddedoffset);
+ printf("nlogbufnum %u\n", nlog_header->nlogbufnum);
+ printf("nlogbufnummax %u\n", nlog_header->nlogbufnummax);
+ printf("coreselected %u\n\n", nlog_header->coreselected);
+ }
+}
+
+#define INTERNAL_LOG_MAX_BYTE_TRANSFER 4096
+#define INTERNAL_LOG_MAX_DWORD_TRANSFER (INTERNAL_LOG_MAX_BYTE_TRANSFER / 4)
+
+static int cmd_dump_repeat(struct nvme_passthru_cmd *cmd, __u32 total_dw_size,
+ int out_fd, int ioctl_fd, bool force_max_transfer)
+{
+ int err = 0;
+
+ while (total_dw_size > 0) {
+ size_t dword_tfer = min(INTERNAL_LOG_MAX_DWORD_TRANSFER, total_dw_size);
+
+ cmd->cdw10 = force_max_transfer ? INTERNAL_LOG_MAX_DWORD_TRANSFER : dword_tfer;
+ cmd->data_len = dword_tfer * 4;
+ err = nvme_submit_admin_passthru(ioctl_fd, cmd, NULL);
+ if (err)
+ return err;
+
+ if (out_fd > 0) {
+ err = write(out_fd, (const void *)(uintptr_t)cmd->addr, cmd->data_len);
+ if (err < 0) {
+ perror("write failure");
+ return err;
+ }
+ err = 0;
+ }
+ total_dw_size -= dword_tfer;
+ cmd->cdw13 += dword_tfer;
+ }
+ return err;
+}
+
+static int write_header(__u8 *buf, int fd, size_t amnt)
+{
+ if (write(fd, buf, amnt) < 0)
+ return 1;
+ return 0;
+}
+
+static int read_header(struct nvme_passthru_cmd *cmd, int ioctl_fd)
+{
+ memset((void *)(uintptr_t)cmd->addr, 0, INTERNAL_LOG_MAX_BYTE_TRANSFER);
+ return cmd_dump_repeat(cmd, INTERNAL_LOG_MAX_DWORD_TRANSFER, -1, ioctl_fd, false);
+}
+
+static int get_serial_number(char *str, int fd)
+{
+ struct nvme_id_ctrl ctrl = {0};
+ int err;
+
+ err = nvme_identify_ctrl(fd, &ctrl);
+ if (err)
+ return err;
+
+ /* Remove trailing spaces */
+ for (int i = sizeof(ctrl.sn) - 1; i && ctrl.sn[i] == ' '; i--)
+ ctrl.sn[i] = '\0';
+ sprintf(str, "%-.*s", (int)sizeof(ctrl.sn), ctrl.sn);
+ return err;
+}
+
+static int dump_assert_logs(struct nvme_dev *dev, struct config cfg)
+{
+ __u8 buf[INTERNAL_LOG_MAX_BYTE_TRANSFER];
+ __u8 head_buf[INTERNAL_LOG_MAX_BYTE_TRANSFER];
+ char file_path[PATH_MAX];
+ char file_name[] = "AssertLog.bin";
+ struct assert_dump_header *ad = (struct assert_dump_header *) head_buf;
+ struct nvme_passthru_cmd cmd = {
+ .opcode = 0xd2,
+ .nsid = cfg.namespace_id,
+ .addr = (unsigned long)(void *)head_buf,
+ .cdw12 = ASSERTLOG,
+ .cdw13 = 0,
+ };
+ int output, err;
+
+ err = read_header(&cmd, dev_fd(dev));
+ if (err)
+ return err;
+
+ snprintf(file_path, sizeof(file_path), "%.*s/%s",
+ (int) (sizeof(file_path) - sizeof(file_name) - 1), cfg.dir_prefix, file_name);
+ output = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (output < 0)
+ return -errno;
+ err = write_header((__u8 *)ad, output, ad->header.header_size * DWORD_SIZE);
+ if (err) {
+ perror("write failure");
+ close(output);
+ return err;
+ }
+ cmd.addr = (unsigned long)(void *)buf;
+
+ if (cfg.verbose) {
+ printf("Assert Log, cores: %d log size: %d header size: %d\n", ad->header.numcores,
+ ad->header.log_size * DWORD_SIZE, ad->header.header_size * DWORD_SIZE);
+ for (__u32 i = 0; i < ad->header.numcores; i++)
+ printf("core %d assert size: %d\n", i, ad->core[i].assertsize * DWORD_SIZE);
+ }
+
+ for (__u32 i = 0; i < ad->header.numcores; i++) {
+ if (!ad->core[i].assertvalid)
+ continue;
+ cmd.cdw13 = ad->core[i].coreoffset;
+ err = cmd_dump_repeat(&cmd, ad->core[i].assertsize,
+ output,
+ dev_fd(dev), false);
+ if (err) {
+ close(output);
+ return err;
+ }
+ }
+ close(output);
+ printf("Successfully wrote log to %s\n", file_path);
+ return err;
+}
+
+static int dump_event_logs(struct nvme_dev *dev, struct config cfg)
+{
+ __u8 buf[INTERNAL_LOG_MAX_BYTE_TRANSFER];
+ __u8 head_buf[INTERNAL_LOG_MAX_BYTE_TRANSFER];
+ char file_path[PATH_MAX];
+ struct event_dump_header *ehdr = (struct event_dump_header *) head_buf;
+ struct nvme_passthru_cmd cmd = {
+ .opcode = 0xd2,
+ .nsid = cfg.namespace_id,
+ .addr = (unsigned long)(void *)head_buf,
+ .cdw12 = EVENTLOG,
+ .cdw13 = 0,
+ };
+ int output;
+ int core_num, err;
+
+ err = read_header(&cmd, dev_fd(dev));
+ if (err)
+ return err;
+ snprintf(file_path, sizeof(file_path), "%s/EventLog.bin", cfg.dir_prefix);
+ output = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (output < 0)
+ return -errno;
+ err = write_header(head_buf, output, INTERNAL_LOG_MAX_BYTE_TRANSFER);
+
+ core_num = ehdr->header.numcores;
+
+ if (err) {
+ close(output);
+ return err;
+ }
+ cmd.addr = (unsigned long)(void *)buf;
+
+ if (cfg.verbose)
+ printf("Event Log, cores: %d log size: %d\n", core_num, ehdr->header.log_size * 4);
+
+ for (__u32 j = 0; j < core_num; j++) {
+ if (cfg.verbose) {
+ for (int k = 0 ; k < 16; k++) {
+ printf("core: %d event: %d ", j, k);
+ printf("validity: %d ", ehdr->edumps[j].eventIdValidity[k]);
+ printf("offset: %d\n", ehdr->edumps[j].eventidoffset[k]);
+ }
+ }
+ cmd.cdw13 = ehdr->edumps[j].coreoffset;
+ err = cmd_dump_repeat(&cmd, ehdr->edumps[j].coresize,
+ output, dev_fd(dev), false);
+ if (err) {
+ close(output);
+ return err;
+ }
+ }
+ close(output);
+ printf("Successfully wrote log to %s\n", file_path);
+ return err;
+}
+
+static size_t get_nlog_header_size(struct nlog_dump_header_common *nlog_header)
+{
+ switch (nlog_header->ver.major) {
+ case 3:
+ return sizeof(struct nlog_dump_header3_0);
+ case 4:
+ if (nlog_header->ver.minor == 0)
+ return sizeof(struct nlog_dump_header4_0);
+ return sizeof(struct nlog_dump_header4_1);
+ default:
+ return INTERNAL_LOG_MAX_BYTE_TRANSFER;
+ }
+
+}
+
+/* dumps nlogs from specified core or all cores when core = -1 */
+static int dump_nlogs(struct nvme_dev *dev, struct config cfg, int core)
+{
+ int err = 0;
+ __u32 count, core_num;
+ __u8 buf[INTERNAL_LOG_MAX_BYTE_TRANSFER];
+ char file_path[PATH_MAX];
+ struct nlog_dump_header_common *nlog_header = (struct nlog_dump_header_common *)buf;
+ struct nvme_passthru_cmd cmd = {
+ .opcode = 0xd2,
+ .nsid = cfg.namespace_id,
+ .addr = (unsigned long)(void *)buf
+ };
+
+ struct dump_select {
+ union {
+ struct {
+ __u32 selectLog : 3;
+ __u32 selectCore : 2;
+ __u32 selectNlog : 8;
+ };
+ __u32 raw;
+ };
+ } log_select;
+ int output;
+ bool is_open = false;
+ size_t header_size = 0;
+
+ log_select.selectCore = core < 0 ? 0 : core;
+ do {
+ log_select.selectNlog = 0;
+ do {
+ cmd.cdw13 = 0;
+ cmd.cdw12 = log_select.raw;
+ err = read_header(&cmd, dev_fd(dev));
+ if (err) {
+ if (is_open)
+ close(output);
+ return err;
+ }
+ count = nlog_header->totalnlogs;
+ core_num = core < 0 ? nlog_header->corecount : 0;
+ if (!header_size) {
+ snprintf(file_path, sizeof(file_path), "%s/NLog.bin",
+ cfg.dir_prefix);
+ output = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (output < 0)
+ return -errno;
+ header_size = get_nlog_header_size(nlog_header);
+ is_open = true;
+ }
+ err = write_header(buf, output, header_size);
+ if (err)
+ break;
+ if (cfg.verbose)
+ print_nlog_header(buf);
+ cmd.cdw13 = 0x400;
+ err = cmd_dump_repeat(&cmd, nlog_header->nlogbytesize / 4,
+ output, dev_fd(dev), true);
+ if (err)
+ break;
+ } while (++log_select.selectNlog < count);
+ if (err)
+ break;
+ } while (++log_select.selectCore < core_num);
+ if (is_open) {
+ close(output);
+ printf("Successfully wrote log to %s\n", file_path);
+ }
+ return err;
+}
+
+enum telemetry_type {
+ HOSTGENOLD,
+ HOSTGENNEW,
+ CONTROLLER
+};
+
+static int dump_telemetry(struct nvme_dev *dev, struct config cfg, enum telemetry_type ttype)
+{
+ _cleanup_free_ struct nvme_telemetry_log *log = NULL;
+ size_t log_size = 0;
+ int err = 0;
+ __u8 *buffer = NULL;
+ size_t bytes_remaining = 0;
+ enum nvme_telemetry_da da;
+ size_t max_data_tx;
+ char file_path[PATH_MAX];
+ char *file_name;
+ char *log_descr;
+ struct stat sb;
+
+ _cleanup_file_ int output = -1;
+
+ switch (ttype) {
+ case HOSTGENNEW:
+ file_name = "lid_0x07_lsp_0x01_lsi_0x0000.bin";
+ log_descr = "Generated Host Initiated";
+ break;
+ case HOSTGENOLD:
+ file_name = "lid_0x07_lsp_0x00_lsi_0x0000.bin";
+ log_descr = "Existing Host Initiated";
+ break;
+ case CONTROLLER:
+ file_name = "lid_0x08_lsp_0x00_lsi_0x0000.bin";
+ log_descr = "Controller Initiated";
+ break;
+ default:
+ return -EINVAL;
+ }
+ err = nvme_get_telemetry_max(dev_fd(dev), &da, &max_data_tx);
+ if (err)
+ return err;
+
+ if (max_data_tx > DRIVER_MAX_TX_256K)
+ max_data_tx = DRIVER_MAX_TX_256K;
+
+ switch (ttype) {
+ case HOSTGENNEW:
+ err = nvme_get_telemetry_log(dev_fd(dev), true, false, false, max_data_tx, da,
+ &log, &log_size);
+ break;
+ case HOSTGENOLD:
+ err = nvme_get_telemetry_log(dev_fd(dev), false, false, false, max_data_tx, da,
+ &log, &log_size);
+ break;
+ case CONTROLLER:
+ err = nvme_get_telemetry_log(dev_fd(dev), false, true, true, max_data_tx, da, &log,
+ &log_size);
+ break;
+ }
+
+ if (err)
+ return err;
+
+ snprintf(file_path, sizeof(file_path), "%s/log_pages", cfg.dir_prefix);
+ if (!(stat(file_path, &sb) == 0 && S_ISDIR(sb.st_mode))) {
+ if (mkdir(file_path, 777) != 0) {
+ perror(file_path);
+ return -errno;
+ }
+ }
+
+ snprintf(file_path, sizeof(file_path), "%s/log_pages/%s", cfg.dir_prefix, file_name);
+ output = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (output < 0)
+ return -errno;
+
+ bytes_remaining = log_size;
+ buffer = (__u8 *)log;
+
+ while (bytes_remaining) {
+ ssize_t bytes_written = write(output, buffer, bytes_remaining);
+
+ if (bytes_written < 0) {
+ err = -errno;
+ goto tele_close_output;
+ }
+
+ bytes_remaining -= bytes_written;
+ buffer += bytes_written;
+ }
+ printf("Successfully wrote %s Telemetry log to %s\n", log_descr, file_path);
+
+tele_close_output:
+ close(output);
+ return err;
+}
+
+int solidigm_get_internal_log(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ char folder[PATH_MAX];
+ char zip_name[PATH_MAX];
+ char *output_path;
+ char sn_prefix[sizeof(((struct nvme_id_ctrl *)0)->sn)+1];
+ int log_count = 0;
+ int err;
+ _cleanup_nvme_dev_ struct nvme_dev *dev = NULL;
+ bool all = false;
+ time_t t;
+ struct tm tm;
+
+ const char *desc = "Get Debug Firmware Logs and save them.";
+ const char *type =
+ "Log type: ALL, CONTROLLERINITTELEMETRY, HOSTINITTELEMETRY, HOSTINITTELEMETRYNOGEN, NLOG, ASSERT, EVENT. Defaults to ALL.";
+ const char *prefix = "Output dir prefix; defaults to device serial number.";
+ const char *verbose = "To print out verbose info.";
+ const char *namespace_id = "Namespace to get logs from.";
+
+
+ struct config cfg = {
+ .namespace_id = NVME_NSID_ALL,
+ .dir_prefix = NULL,
+ .type = NULL,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_STR("type", 't', &cfg.type, type),
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace_id),
+ OPT_FILE("dir-prefix", 'p', &cfg.dir_prefix, prefix),
+ OPT_FLAG("verbose", 'v', &cfg.verbose, verbose),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ if (!cfg.dir_prefix) {
+ err = get_serial_number(sn_prefix, dev_fd(dev));
+ if (err)
+ return err;
+ cfg.dir_prefix = sn_prefix;
+ }
+ t = time(NULL);
+ tm = *localtime(&t);
+ snprintf(folder, sizeof(folder), "%s-%d%02d%02d%02d%02d%02d", cfg.dir_prefix,
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
+ if (mkdir(folder, 0777) != 0) {
+ perror("mkdir");
+ return -errno;
+ }
+ cfg.dir_prefix = folder;
+ output_path = folder;
+
+ if (!cfg.type)
+ cfg.type = "ALL";
+ else {
+ for (char *p = cfg.type; *p; ++p)
+ *p = toupper(*p);
+ }
+
+ if (!strcmp(cfg.type, "ALL")) {
+ all = true;
+ }
+ if (all || !strcmp(cfg.type, "ASSERT")) {
+ err = dump_assert_logs(dev, cfg);
+ if (err == 0)
+ log_count++;
+ else if (err < 0)
+ perror("Error retrieving Assert log");
+ }
+ if (all || !strcmp(cfg.type, "EVENT")) {
+ err = dump_event_logs(dev, cfg);
+ if (err == 0)
+ log_count++;
+ else if (err < 0)
+ perror("Error retrieving Event log");
+ }
+ if (all || !strcmp(cfg.type, "NLOG")) {
+ err = dump_nlogs(dev, cfg, -1);
+ if (err == 0)
+ log_count++;
+ else if (err < 0)
+ perror("Error retrieving Nlog");
+ }
+ if (all || !strcmp(cfg.type, "CONTROLLERINITTELEMETRY")) {
+ err = dump_telemetry(dev, cfg, CONTROLLER);
+ if (err == 0)
+ log_count++;
+ else if (err < 0)
+ perror("Error retrieving Telemetry Controller Initiated");
+ }
+ if (all || !strcmp(cfg.type, "HOSTINITTELEMETRYNOGEN")) {
+ err = dump_telemetry(dev, cfg, HOSTGENOLD);
+ if (err == 0)
+ log_count++;
+ else if (err < 0)
+ perror("Error retrieving previously existing Telemetry Host Initiated");
+ }
+ if (all || !strcmp(cfg.type, "HOSTINITTELEMETRY")) {
+ err = dump_telemetry(dev, cfg, HOSTGENNEW);
+ if (err == 0)
+ log_count++;
+ else if (err < 0)
+ perror("Error retrieving Telemetry Host Initiated");
+ }
+
+ if (log_count > 0) {
+ int ret_cmd;
+ char cmd[ARG_MAX];
+ char *where_err = cfg.verbose ? "" : ">/dev/null 2>&1";
+
+ snprintf(zip_name, sizeof(zip_name), "%s.zip", cfg.dir_prefix);
+ snprintf(cmd, sizeof(cmd), "cd \"%s\" && zip -r \"../%s\" ./* %s", cfg.dir_prefix,
+ zip_name, where_err);
+ printf("Compressing logs to %s\n", zip_name);
+ ret_cmd = system(cmd);
+ if (ret_cmd == -1)
+ perror(cmd);
+ else {
+ output_path = zip_name;
+ snprintf(cmd, sizeof(cmd), "rm -rf %s", cfg.dir_prefix);
+ printf("Removing %s\n", cfg.dir_prefix);
+ if (system(cmd) != 0)
+ perror("Failed removing logs folder");
+ }
+ }
+
+ if (log_count == 0) {
+ if (err > 0)
+ nvme_show_status(err);
+ } else if ((log_count > 1) || cfg.verbose)
+ printf("Total: %d log files in %s\n", log_count, output_path);
+
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-internal-logs.h b/plugins/solidigm/solidigm-internal-logs.h
new file mode 100644
index 0000000..801af24
--- /dev/null
+++ b/plugins/solidigm/solidigm-internal-logs.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+int solidigm_get_internal_log(int argc, char **argv, struct command *cmd, struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-latency-tracking.c b/plugins/solidigm/solidigm-latency-tracking.c
new file mode 100644
index 0000000..66f3c56
--- /dev/null
+++ b/plugins/solidigm/solidigm-latency-tracking.c
@@ -0,0 +1,474 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "nvme-print.h"
+#include "solidigm-util.h"
+
+#define BUCKET_LIST_SIZE_4_0 152
+#define BUCKET_LIST_SIZE_4_1 1216
+
+#define BASE_RANGE_BITS_4_0 3
+#define BASE_RANGE_BITS_4_1 6
+
+struct latency_statistics {
+ __u16 version_major;
+ __u16 version_minor;
+ __u32 data[BUCKET_LIST_SIZE_4_1];
+ __u64 average_latency;
+};
+
+struct config {
+ bool enable;
+ bool disable;
+ bool read;
+ bool write;
+ unsigned char type;
+ char *output_format;
+};
+
+struct latency_tracker {
+ int fd;
+ __u8 uuid_index;
+ struct config cfg;
+ enum nvme_print_flags print_flags;
+ struct latency_statistics stats;
+ struct json_object *bucket_list;
+ __u32 bucket_list_size;
+ __u8 base_range_bits;
+ bool has_average_latency_field;
+};
+
+/* COL_WIDTH controls width of columns in NORMAL output. */
+#define COL_WIDTH 12
+#define BUCKET_LABEL_MAX_SIZE 10
+
+#define US_IN_S 1000000
+#define US_IN_MS 1000
+
+/*
+ * Edge buckets may have range [#s, inf) in some
+ * latency statistics formats.
+ */
+static void get_time_unit_label(char *label, __u32 microseconds,
+ bool bonded)
+{
+ char *string = "us";
+ int divisor = 1;
+
+ if (!bonded) {
+ snprintf(label, BUCKET_LABEL_MAX_SIZE, "%s", "+INF");
+ return;
+ }
+
+ if (microseconds > US_IN_S) {
+ string = "s";
+ divisor = US_IN_S;
+ } else if (microseconds > US_IN_MS) {
+ string = "ms";
+ divisor = US_IN_MS;
+ }
+
+ snprintf(label, BUCKET_LABEL_MAX_SIZE, "%4.2f%s", (float) microseconds / divisor,
+ string);
+}
+
+static void latency_tracker_bucket_parse(const struct latency_tracker *lt, int id,
+ __u32 lower_us, __u32 upper_us, bool upper_bounded)
+{
+ char buffer[BUCKET_LABEL_MAX_SIZE] = "";
+ __u32 bucket_data = le32_to_cpu(lt->stats.data[id]);
+
+ if (lt->print_flags == NORMAL) {
+ printf("%-*d", COL_WIDTH, id);
+
+ get_time_unit_label(buffer, lower_us, true);
+ printf("%-*s", COL_WIDTH, buffer);
+
+ get_time_unit_label(buffer, upper_us, upper_bounded);
+ printf("%-*s", COL_WIDTH, buffer);
+
+ printf("%-*d\n", COL_WIDTH, bucket_data);
+ }
+
+ if (lt->print_flags == JSON) {
+ /*
+ * Creates a bucket under the "values" json_object. Format is:
+ * "values" : {
+ * "bucket" : {
+ * "id" : #,
+ * "start" : string,
+ * "end" : string,
+ * "value" : 0,
+ * },
+ */
+ struct json_object *bucket = json_create_object();
+
+ json_object_array_add(lt->bucket_list, bucket);
+ json_object_add_value_int(bucket, "id", id);
+
+ get_time_unit_label(buffer, lower_us, true);
+ json_object_add_value_string(bucket, "start", buffer);
+
+ get_time_unit_label(buffer, upper_us, upper_bounded);
+ json_object_add_value_string(bucket, "end", buffer);
+
+ json_object_add_value_int(bucket, "value", bucket_data);
+ }
+}
+
+static void latency_tracker_parse_linear(const struct latency_tracker *lt,
+ __u32 start_offset, __u32 end_offset,
+ __u32 bytes_per, __u32 us_step,
+ bool nonzero_print)
+{
+ for (int i = (start_offset / bytes_per) - 1; i < end_offset / bytes_per; i++) {
+ if (nonzero_print && !lt->stats.data[i])
+ continue;
+ latency_tracker_bucket_parse(lt, i, us_step * i, us_step * (i + 1), true);
+ }
+}
+
+/*
+ * Calculates bucket time slot. Valid starting on 4.0 revision.
+ */
+
+static int latency_tracker_bucket_pos2us(const struct latency_tracker *lt, int i)
+{
+ __u32 base_val = 1 << lt->base_range_bits;
+
+ if (i < (base_val << 1))
+ return i;
+
+ int error_bits = (i >> lt->base_range_bits) - 1;
+ int base = 1 << (error_bits + lt->base_range_bits);
+ int k = i % base_val;
+
+ return base + ((k + 0.5) * (1 << error_bits));
+}
+
+/*
+ * Creates a subroot in the following manner:
+ * {
+ * "latstats" : {
+ * "type" : "write" or "read",
+ * "values" : {
+ */
+static void latency_tracker_populate_json_root(const struct latency_tracker *lt,
+ struct json_object *root)
+{
+ struct json_object *subroot = json_create_object();
+
+ json_object_add_value_object(root, "latstats", subroot);
+ json_object_add_value_string(subroot, "type", lt->cfg.write ? "write" : "read");
+ if (lt->has_average_latency_field)
+ json_object_add_value_uint64(subroot, "average_latency",
+ le64_to_cpu(lt->stats.average_latency));
+ json_object_add_value_object(subroot, "values", lt->bucket_list);
+}
+
+static void latency_tracker_parse_3_0(const struct latency_tracker *lt)
+{
+ latency_tracker_parse_linear(lt, 4, 131, 4, 32, false);
+ latency_tracker_parse_linear(lt, 132, 255, 4, 1024, false);
+ latency_tracker_parse_linear(lt, 256, 379, 4, 32768, false);
+ latency_tracker_parse_linear(lt, 380, 383, 4, 32, true);
+ latency_tracker_parse_linear(lt, 384, 387, 4, 32, true);
+ latency_tracker_parse_linear(lt, 388, 391, 4, 32, true);
+}
+
+static void latency_tracker_parse_4_0(const struct latency_tracker *lt)
+{
+ for (unsigned int i = 0; i < lt->bucket_list_size; i++) {
+ int lower_us = latency_tracker_bucket_pos2us(lt, i);
+ int upper_us = latency_tracker_bucket_pos2us(lt, i + 1);
+
+ latency_tracker_bucket_parse(lt, i, lower_us, upper_us,
+ i < (lt->bucket_list_size - 1));
+ }
+}
+
+static void print_dash_separator(void)
+{
+ printf("--------------------------------------------------\n");
+}
+
+static void latency_tracker_pre_parse(struct latency_tracker *lt)
+{
+ if (lt->print_flags == NORMAL) {
+ printf("Solidigm IO %s Command Latency Tracking Statistics type %d\n",
+ lt->cfg.write ? "Write" : "Read", lt->cfg.type);
+ printf("UUID-idx: %d\n", lt->uuid_index);
+ printf("Major Revision: %u\nMinor Revision: %u\n",
+ le16_to_cpu(lt->stats.version_major), le16_to_cpu(lt->stats.version_minor));
+ if (lt->has_average_latency_field)
+ printf("Average Latency: %" PRIu64 "\n", le64_to_cpu(lt->stats.average_latency));
+ print_dash_separator();
+ printf("%-12s%-12s%-12s%-20s\n", "Bucket", "Start", "End", "Value");
+ print_dash_separator();
+ }
+ if (lt->print_flags == JSON)
+ lt->bucket_list = json_object_new_array();
+}
+
+static void latency_tracker_post_parse(struct latency_tracker *lt)
+{
+ if (lt->print_flags == JSON) {
+ struct json_object *root = json_create_object();
+
+ latency_tracker_populate_json_root(lt, root);
+ json_print_object(root, NULL);
+ json_free_object(root);
+ printf("\n");
+ }
+}
+
+static void latency_tracker_parse(struct latency_tracker *lt)
+{
+ __u16 version_major = le16_to_cpu(lt->stats.version_major);
+ __u16 version_minor = le16_to_cpu(lt->stats.version_minor);
+
+ switch (version_major) {
+ case 3:
+ latency_tracker_pre_parse(lt);
+ latency_tracker_parse_3_0(lt);
+ break;
+ case 4:
+ if (version_minor >= 8)
+ lt->has_average_latency_field = true;
+ latency_tracker_pre_parse(lt);
+ if (!version_minor) {
+ lt->base_range_bits = BASE_RANGE_BITS_4_0;
+ lt->bucket_list_size = BUCKET_LIST_SIZE_4_0;
+ }
+ latency_tracker_parse_4_0(lt);
+ break;
+ default:
+ printf("Unsupported revision (%u.%u)\n",
+ version_major, version_minor);
+ break;
+ }
+
+ latency_tracker_post_parse(lt);
+}
+
+#define LATENCY_TRACKING_FID 0xe2
+#define LATENCY_TRACKING_FID_DATA_LEN 32
+
+static int latency_tracking_is_enable(struct latency_tracker *lt, __u32 *enabled)
+{
+ struct nvme_get_features_args args_get = {
+ .args_size = sizeof(args_get),
+ .fd = lt->fd,
+ .uuidx = lt->uuid_index,
+ .fid = LATENCY_TRACKING_FID,
+ .nsid = 0,
+ .sel = 0,
+ .cdw11 = 0,
+ .data_len = LATENCY_TRACKING_FID_DATA_LEN,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = enabled,
+ };
+ return nvme_get_features(&args_get);
+}
+
+static int latency_tracking_enable(struct latency_tracker *lt)
+{
+ __u32 result;
+ int err;
+
+ if (!(lt->cfg.enable || lt->cfg.disable))
+ return 0;
+
+ if (lt->cfg.enable && lt->cfg.disable) {
+ fprintf(stderr, "Cannot enable and disable simultaneously.\n");
+ return -EINVAL;
+ }
+
+ struct nvme_set_features_args args_set = {
+ .args_size = sizeof(args_set),
+ .fd = lt->fd,
+ .uuidx = lt->uuid_index,
+ .fid = LATENCY_TRACKING_FID,
+ .nsid = 0,
+ .cdw11 = lt->cfg.enable,
+ .cdw12 = 0,
+ .save = 0,
+ .cdw15 = 0,
+ .data_len = LATENCY_TRACKING_FID_DATA_LEN,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+
+ err = nvme_set_features(&args_set);
+ if (err > 0) {
+ nvme_show_status(err);
+ } else if (err < 0) {
+ perror("Enable latency tracking");
+ fprintf(stderr, "Command failed while parsing.\n");
+ } else {
+ if (lt->print_flags == NORMAL) {
+ printf("Successfully set enable bit for UUID-idx:%d FID:0x%X, to %i.\n",
+ lt->uuid_index, LATENCY_TRACKING_FID, lt->cfg.enable);
+ }
+ }
+ return err;
+}
+
+#define READ_LOG_ID 0xc1
+#define WRITE_LOG_ID 0xc2
+
+static int latency_tracker_get_log(struct latency_tracker *lt)
+{
+ int err;
+
+ if (lt->cfg.read && lt->cfg.write) {
+ fprintf(stderr, "Cannot capture read and write logs simultaneously.\n");
+ return -EINVAL;
+ }
+
+ if (!(lt->cfg.read || lt->cfg.write))
+ return 0;
+
+ struct nvme_get_log_args args = {
+ .lpo = 0,
+ .result = NULL,
+ .log = &lt->stats,
+ .args_size = sizeof(args),
+ .fd = lt->fd,
+ .uuidx = lt->uuid_index,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .lid = lt->cfg.write ? WRITE_LOG_ID : READ_LOG_ID,
+ .len = sizeof(lt->stats),
+ .nsid = NVME_NSID_ALL,
+ .csi = NVME_CSI_NVM,
+ .lsi = NVME_LOG_LSI_NONE,
+ .lsp = lt->cfg.type,
+ .rae = false,
+ .ot = false,
+ };
+
+ err = nvme_get_log(&args);
+ if (err)
+ return err;
+
+ if (lt->print_flags & BINARY)
+ d_raw((unsigned char *)&lt->stats,
+ sizeof(lt->stats));
+ else {
+ latency_tracker_parse(lt);
+ }
+ return err;
+}
+
+int solidigm_get_latency_tracking_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ const char *desc = "Get and Parse Solidigm Latency Tracking Statistics log.";
+ struct nvme_dev *dev;
+ __u32 enabled;
+ int err;
+
+ struct latency_tracker lt = {
+ .uuid_index = 0,
+ .cfg = {
+ .output_format = "normal",
+ },
+ .base_range_bits = BASE_RANGE_BITS_4_1,
+ .bucket_list_size = BUCKET_LIST_SIZE_4_1,
+ .has_average_latency_field = false,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("enable", 'e', &lt.cfg.enable, "Enable Latency Tracking"),
+ OPT_FLAG("disable", 'd', &lt.cfg.disable, "Disable Latency Tracking"),
+ OPT_FLAG("read", 'r', &lt.cfg.read, "Get read statistics"),
+ OPT_FLAG("write", 'w', &lt.cfg.write, "Get write statistics"),
+ OPT_BYTE("type", 't', &lt.cfg.type, "Log type to get"),
+ OPT_FMT("output-format", 'o', &lt.cfg.output_format, output_format),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ lt.fd = dev_fd(dev);
+
+ err = validate_output_format(lt.cfg.output_format, &lt.print_flags);
+ if (err < 0) {
+ fprintf(stderr, "Invalid output format '%s'\n", lt.cfg.output_format);
+ dev_close(dev);
+ return -EINVAL;
+ }
+
+ if (lt.cfg.type > 0xf) {
+ fprintf(stderr, "Invalid Log type value '%d'\n", lt.cfg.type);
+ dev_close(dev);
+ return -EINVAL;
+ }
+
+ if (lt.cfg.type && !(lt.cfg.read || lt.cfg.write)) {
+ fprintf(stderr, "Log type option valid only when retrieving statistics\n");
+ dev_close(dev);
+ return -EINVAL;
+ }
+
+ lt.uuid_index = solidigm_get_vu_uuid_index(dev);
+
+ err = latency_tracking_enable(&lt);
+ if (err) {
+ dev_close(dev);
+ return err;
+ }
+
+ err = latency_tracker_get_log(&lt);
+ if (err) {
+ dev_close(dev);
+ return err;
+ }
+
+ if ((lt.cfg.read || lt.cfg.write || lt.cfg.enable || lt.cfg.disable)) {
+ dev_close(dev);
+ return 0;
+ }
+
+ err = latency_tracking_is_enable(&lt, &enabled);
+ if (!err) {
+ if (lt.print_flags == JSON) {
+ struct json_object *root = json_create_object();
+
+ json_object_add_value_int(root, "enabled", enabled);
+ json_print_object(root, NULL);
+ json_free_object(root);
+ printf("\n");
+ } else if (lt.print_flags == BINARY) {
+ putchar(enabled);
+ } else {
+ printf("Latency Statistics Tracking (UUID-idx:%d, FID:0x%X) is currently %i.\n",
+ lt.uuid_index, LATENCY_TRACKING_FID, enabled);
+ }
+ } else {
+ fprintf(stderr, "Could not read feature id 0xE2.\n");
+ }
+ /* Redundant close() to make static code analysis happy */
+ close(dev->direct.fd);
+ dev_close(dev);
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-latency-tracking.h b/plugins/solidigm/solidigm-latency-tracking.h
new file mode 100644
index 0000000..9a763a9
--- /dev/null
+++ b/plugins/solidigm/solidigm-latency-tracking.h
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+int solidigm_get_latency_tracking_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-log-page-dir.c b/plugins/solidigm/solidigm-log-page-dir.c
new file mode 100644
index 0000000..bf272f8
--- /dev/null
+++ b/plugins/solidigm/solidigm-log-page-dir.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: karl.dedow@solidigm.com
+ */
+
+#include "solidigm-log-page-dir.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "nvme-print.h"
+
+#include "plugins/ocp/ocp-utils.h"
+
+#define MIN_VENDOR_LID 0xC0
+#define SOLIDIGM_MAX_UUID 2
+
+static const char dash[100] = {[0 ... 99] = '-'};
+
+struct lid_dir {
+ struct __packed {
+ bool supported;
+ const char *str;
+ } lid[NVME_LOG_SUPPORTED_LOG_PAGES_MAX];
+};
+
+static void init_lid_dir(struct lid_dir *lid_dir)
+{
+ static const char *unknown_str = "Unknown";
+
+ for (int lid = 0; lid < NVME_LOG_SUPPORTED_LOG_PAGES_MAX; lid++) {
+ lid_dir->lid[lid].supported = false;
+ lid_dir->lid[lid].str = unknown_str;
+ }
+}
+
+static bool is_invalid_uuid(const struct nvme_id_uuid_list_entry entry)
+{
+ static const unsigned char ALL_ZERO_UUID[NVME_UUID_LEN] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ return memcmp(ALL_ZERO_UUID, entry.uuid, NVME_UUID_LEN) == 0;
+}
+
+static bool is_solidigm_uuid(const struct nvme_id_uuid_list_entry entry)
+{
+ static const unsigned char SOLIDIGM_UUID[NVME_UUID_LEN] = {
+ 0x96, 0x19, 0x58, 0x6e, 0xc1, 0x1b, 0x43, 0xad,
+ 0xaa, 0xaa, 0x65, 0x41, 0x87, 0xf6, 0xbb, 0xb2
+ };
+
+ return memcmp(SOLIDIGM_UUID, entry.uuid, NVME_UUID_LEN) == 0;
+}
+
+static bool is_ocp_uuid(const struct nvme_id_uuid_list_entry entry)
+{
+ static const unsigned char OCP_UUID[NVME_UUID_LEN] = {
+ 0xc1, 0x94, 0xd5, 0x5b, 0xe0, 0x94, 0x47, 0x94,
+ 0xa2, 0x1d, 0x29, 0x99, 0x8f, 0x56, 0xbe, 0x6f
+ };
+
+ return memcmp(OCP_UUID, entry.uuid, NVME_UUID_LEN) == 0;
+}
+
+static int get_supported_log_pages_log(struct nvme_dev *dev, int uuid_index,
+ struct nvme_supported_log_pages *supported)
+{
+ static const __u8 LID;
+
+ memset(supported, 0, sizeof(*supported));
+ struct nvme_get_log_args args = {
+ .lpo = 0,
+ .result = NULL,
+ .log = supported,
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .lid = LID,
+ .len = sizeof(*supported),
+ .nsid = NVME_NSID_ALL,
+ .csi = NVME_CSI_NVM,
+ .lsi = NVME_LOG_LSI_NONE,
+ .lsp = 0,
+ .uuidx = uuid_index,
+ .rae = false,
+ .ot = false,
+ };
+
+ return nvme_get_log(&args);
+}
+
+static struct lid_dir *get_standard_lids(struct nvme_supported_log_pages *supported)
+{
+ static struct lid_dir standard_dir = { 0 };
+
+ init_lid_dir(&standard_dir);
+
+ for (int lid = 0; lid < NVME_LOG_SUPPORTED_LOG_PAGES_MAX; lid++) {
+ if (!supported->lid_support[lid] || lid >= MIN_VENDOR_LID)
+ continue;
+
+ standard_dir.lid[lid].supported = true;
+ standard_dir.lid[lid].str = nvme_log_to_string(lid);
+ }
+
+ return &standard_dir;
+}
+
+static void update_vendor_lid_supported(struct nvme_supported_log_pages *supported,
+ struct lid_dir *lid_dir)
+{
+ for (int lid = 0; lid < NVME_LOG_SUPPORTED_LOG_PAGES_MAX; lid++) {
+ if (!supported->lid_support[lid] || lid < MIN_VENDOR_LID)
+ continue;
+
+ lid_dir->lid[lid].supported = true;
+ }
+}
+
+static struct lid_dir *get_solidigm_lids(struct nvme_supported_log_pages *supported)
+{
+ static struct lid_dir solidigm_dir = { 0 };
+
+ init_lid_dir(&solidigm_dir);
+ solidigm_dir.lid[0xC1].str = "Read Commands Latency Statistics";
+ solidigm_dir.lid[0xC2].str = "Write Commands Latency Statistics";
+ solidigm_dir.lid[0xC4].str = "Endurance Manager Statistics";
+ solidigm_dir.lid[0xC5].str = "Temperature Statistics";
+ solidigm_dir.lid[0xCA].str = "SMART Attributes";
+ solidigm_dir.lid[0xCB].str = "VU NVMe IO Queue Metrics Log Page";
+ solidigm_dir.lid[0xDD].str = "VU Marketing Description Log Page";
+ solidigm_dir.lid[0xEF].str = "Performance Rating and LBA Access Histogram";
+ solidigm_dir.lid[0xF2].str = "Get Power Usage Log Page";
+ solidigm_dir.lid[0xF6].str = "Vt Histo Get Log Page";
+ solidigm_dir.lid[0xF9].str = "Workload Tracker Get Log Page";
+ solidigm_dir.lid[0xFD].str = "Garbage Control Collection Log Page";
+ solidigm_dir.lid[0xFE].str = "Latency Outlier Log Page";
+
+ update_vendor_lid_supported(supported, &solidigm_dir);
+
+ return &solidigm_dir;
+}
+
+static struct lid_dir *get_ocp_lids(struct nvme_supported_log_pages *supported)
+{
+ static struct lid_dir ocp_dir = { 0 };
+
+ init_lid_dir(&ocp_dir);
+ ocp_dir.lid[0xC0].str = "OCP SMART / Health Information Extended";
+ ocp_dir.lid[0xC1].str = "OCP Error Recovery";
+ ocp_dir.lid[0xC2].str = "OCP Firmware Activation History";
+ ocp_dir.lid[0xC3].str = "OCP Latency Monitor";
+ ocp_dir.lid[0xC4].str = "OCP Device Capabilities";
+ ocp_dir.lid[0xC5].str = "OCP Unsupported Requirements";
+
+ update_vendor_lid_supported(supported, &ocp_dir);
+
+ return &ocp_dir;
+}
+
+static void supported_log_pages_normal(struct lid_dir *lid_dir[SOLIDIGM_MAX_UUID + 1])
+{
+ printf("%-5s %-4s %-42s\n", "uuidx", "LID", "Description");
+ printf("%-.5s %-.4s %-.42s\n", dash, dash, dash);
+
+ for (int uuid_index = 0; uuid_index <= SOLIDIGM_MAX_UUID; uuid_index++) {
+ if (!lid_dir[uuid_index])
+ continue;
+
+ for (int lid = 0; lid < NVME_LOG_SUPPORTED_LOG_PAGES_MAX; lid++) {
+ if (!lid_dir[uuid_index]->lid[lid].supported)
+ continue;
+
+ printf("%-5d 0x%02x %s\n", le32_to_cpu(uuid_index), le32_to_cpu(lid),
+ lid_dir[uuid_index]->lid[lid].str);
+ }
+ }
+}
+
+static void supported_log_pages_json(struct lid_dir *lid_dir[SOLIDIGM_MAX_UUID + 1])
+{
+ struct json_object *root = json_create_array();
+
+ for (int uuid_index = 0; uuid_index <= SOLIDIGM_MAX_UUID; uuid_index++) {
+ if (!lid_dir[uuid_index])
+ continue;
+
+ for (int lid = 0; lid < NVME_LOG_SUPPORTED_LOG_PAGES_MAX; lid++) {
+ if (!lid_dir[uuid_index]->lid[lid].supported)
+ continue;
+
+ struct json_object *lid_obj = json_create_object();
+
+ json_object_add_value_uint(lid_obj, "uuidx", le32_to_cpu(uuid_index));
+ json_object_add_value_uint(lid_obj, "lid", le32_to_cpu(lid));
+ json_object_add_value_string(lid_obj, "description",
+ lid_dir[uuid_index]->lid[lid].str);
+ json_array_add_value_object(root, lid_obj);
+ }
+ }
+
+ json_print_object(root, NULL);
+ json_free_object(root);
+ printf("\n");
+}
+
+int solidigm_get_log_page_directory_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ const int NO_UUID_INDEX = 0;
+ const char *description = "Retrieves list of supported log pages for each UUID index.";
+ char *format = "normal";
+
+ OPT_ARGS(options) = {
+ OPT_FMT("output-format", 'o', &format, "output format : normal | json"),
+ OPT_END()
+ };
+
+ struct nvme_dev *dev = NULL;
+ int err = parse_and_open(&dev, argc, argv, description, options);
+
+ if (err)
+ return err;
+
+ struct lid_dir *lid_dirs[SOLIDIGM_MAX_UUID + 1] = { 0 };
+ struct nvme_id_uuid_list uuid_list = { 0 };
+ struct nvme_supported_log_pages supported = { 0 };
+
+ err = get_supported_log_pages_log(dev, NO_UUID_INDEX, &supported);
+
+ if (!err) {
+ lid_dirs[NO_UUID_INDEX] = get_standard_lids(&supported);
+
+ // Assume VU logs are the Solidigm log pages if UUID not supported.
+ if (nvme_identify_uuid(dev_fd(dev), &uuid_list)) {
+ struct lid_dir *solidigm_lid_dir = get_solidigm_lids(&supported);
+
+ // Transfer supported Solidigm lids to lid directory at UUID index 0
+ for (int lid = 0; lid < NVME_LOG_SUPPORTED_LOG_PAGES_MAX; lid++) {
+ if (solidigm_lid_dir->lid[lid].supported)
+ lid_dirs[NO_UUID_INDEX]->lid[lid] = solidigm_lid_dir->lid[lid];
+ }
+ } else {
+ for (int uuid_index = 1; uuid_index <= SOLIDIGM_MAX_UUID; uuid_index++) {
+ if (is_invalid_uuid(uuid_list.entry[uuid_index - 1]))
+ break;
+ else if (get_supported_log_pages_log(dev, uuid_index, &supported))
+ continue;
+
+ if (is_solidigm_uuid(uuid_list.entry[uuid_index - 1]))
+ lid_dirs[uuid_index] = get_solidigm_lids(&supported);
+ else if (is_ocp_uuid(uuid_list.entry[uuid_index - 1]))
+ lid_dirs[uuid_index] = get_ocp_lids(&supported);
+ }
+ }
+ } else {
+ nvme_show_status(err);
+ }
+
+ if (!err) {
+ enum nvme_print_flags print_flag;
+
+ err = validate_output_format(format, &print_flag);
+ if (err < 0) {
+ fprintf(stderr, "Error: Invalid output format specified: %s.\n", format);
+ return err;
+ }
+
+ if (print_flag == NORMAL) {
+ supported_log_pages_normal(lid_dirs);
+ } else if (print_flag == JSON) {
+ supported_log_pages_json(lid_dirs);
+ }
+ }
+
+ /* Redundant close() to make static code analysis happy */
+ close(dev->direct.fd);
+ dev_close(dev);
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-log-page-dir.h b/plugins/solidigm/solidigm-log-page-dir.h
new file mode 100644
index 0000000..48777df
--- /dev/null
+++ b/plugins/solidigm/solidigm-log-page-dir.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Authors: karl.dedow@solidigm.com
+ */
+
+#ifndef SOLIDIGM_LOG_PAGE_DIRECTORY_H
+#define SOLIDIGM_LOG_PAGE_DIRECTORY_H
+
+struct command;
+struct plugin;
+
+int solidigm_get_log_page_directory_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin);
+
+#endif
diff --git a/plugins/solidigm/solidigm-market-log.c b/plugins/solidigm/solidigm-market-log.c
new file mode 100644
index 0000000..d7d38da
--- /dev/null
+++ b/plugins/solidigm/solidigm-market-log.c
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Authors: leonardo.da.cunha@solidigm.com
+ * Hardeep.Dhillon@solidigm.com
+ */
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include <linux/limits.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "nvme-print.h"
+
+#define MARKET_LOG_MAX_SIZE 512
+
+int sldgm_get_market_log(int argc, char **argv, struct command *command,
+ struct plugin *plugin)
+{
+ const char *desc = "Get Solidigm Marketing Name log and show it.";
+ const char *raw = "dump output in binary format";
+ struct nvme_dev *dev;
+ char log[MARKET_LOG_MAX_SIZE];
+ int err;
+
+ struct config {
+ bool raw_binary;
+ };
+
+ struct config cfg = {
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = nvme_get_log_simple(dev_fd(dev), 0xdd, sizeof(log), log);
+ if (!err) {
+ if (!cfg.raw_binary)
+ printf("Solidigm Marketing Name Log:\n%s\n", log);
+ else
+ d_raw((unsigned char *)&log, sizeof(log));
+ } else if (err > 0)
+
+ nvme_show_status(err);
+ /* Redundant close() to make static code analysis happy */
+ close(dev->direct.fd);
+ dev_close(dev);
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-market-log.h b/plugins/solidigm/solidigm-market-log.h
new file mode 100644
index 0000000..6f808c4
--- /dev/null
+++ b/plugins/solidigm/solidigm-market-log.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: hardeep.dhillon@solidigm.com
+ */
+
+int sldgm_get_market_log(int argc, char **argv, struct command *cmd, struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-nvme.c b/plugins/solidigm/solidigm-nvme.c
new file mode 100644
index 0000000..3fb86f5
--- /dev/null
+++ b/plugins/solidigm/solidigm-nvme.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022-2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include "nvme.h"
+
+#define CREATE_CMD
+#include "solidigm-nvme.h"
+
+#include "solidigm-id-ctrl.h"
+#include "solidigm-smart.h"
+#include "solidigm-internal-logs.h"
+#include "solidigm-garbage-collection.h"
+#include "solidigm-latency-tracking.h"
+#include "solidigm-telemetry.h"
+#include "solidigm-log-page-dir.h"
+#include "solidigm-market-log.h"
+#include "solidigm-temp-stats.h"
+#include "solidigm-get-drive-info.h"
+#include "solidigm-ocp-version.h"
+
+#include "plugins/ocp/ocp-clear-features.h"
+#include "plugins/ocp/ocp-smart-extended-log.h"
+#include "plugins/ocp/ocp-fw-activation-history.h"
+
+static int id_ctrl(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return __id_ctrl(argc, argv, cmd, plugin, sldgm_id_ctrl);
+}
+
+static int get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return solidigm_get_additional_smart_log(argc, argv, cmd, plugin);
+}
+
+static int get_internal_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return solidigm_get_internal_log(argc, argv, cmd, plugin);
+}
+
+static int get_garbage_collection_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return solidigm_get_garbage_collection_log(argc, argv, cmd, plugin);
+}
+
+static int get_latency_tracking_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return solidigm_get_latency_tracking_log(argc, argv, cmd, plugin);
+}
+
+static int get_telemetry_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return solidigm_get_telemetry_log(argc, argv, cmd, plugin);
+}
+
+static int clear_fw_update_history(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ return ocp_clear_fw_update_history(argc, argv, cmd, plugin);
+}
+
+static int clear_pcie_correctable_error_counters(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ return ocp_clear_pcie_correctable_errors(argc, argv, cmd, plugin);
+}
+
+static int smart_cloud(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ return ocp_smart_add_log(argc, argv, cmd, plugin);
+}
+
+static int fw_activation_history(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ return ocp_fw_activation_history_log(argc, argv, cmd, plugin);
+}
+
+static int get_log_page_directory_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ return solidigm_get_log_page_directory_log(argc, argv, cmd, plugin);
+}
+
+static int get_market_log(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ return sldgm_get_market_log(argc, argv, cmd, plugin);
+}
+
+static int get_temp_stats_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return sldgm_get_temp_stats_log(argc, argv, cmd, plugin);
+}
+
+static int get_drive_info(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ return sldgm_get_drive_info(argc, argv, cmd, plugin);
+}
+
+static int get_cloud_SSDplugin_version(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ return sldgm_ocp_version(argc, argv, cmd, plugin);
+}
diff --git a/plugins/solidigm/solidigm-nvme.h b/plugins/solidigm/solidigm-nvme.h
new file mode 100644
index 0000000..bee8266
--- /dev/null
+++ b/plugins/solidigm/solidigm-nvme.h
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022-2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#undef CMD_INC_FILE
+#define CMD_INC_FILE plugins/solidigm/solidigm-nvme
+
+#if !defined(SOLIDIGM_NVME) || defined(CMD_HEADER_MULTI_READ)
+#define SOLIDIGM_NVME
+
+#include "cmd.h"
+
+#define SOLIDIGM_PLUGIN_VERSION "1.1"
+
+PLUGIN(NAME("solidigm", "Solidigm vendor specific extensions", SOLIDIGM_PLUGIN_VERSION),
+ COMMAND_LIST(
+ ENTRY("id-ctrl", "Send NVMe Identify Controller", id_ctrl)
+ ENTRY("smart-log-add", "Retrieve Solidigm SMART Log", get_additional_smart_log)
+ ENTRY("vs-smart-add-log", "Get SMART / health extended log (redirects to ocp plug-in)", smart_cloud)
+ ENTRY("vs-internal-log", "Retrieve Debug log binaries", get_internal_log)
+ ENTRY("garbage-collect-log", "Retrieve Garbage Collection Log", get_garbage_collection_log)
+ ENTRY("market-log", "Retrieve Market Log", get_market_log)
+ ENTRY("latency-tracking-log", "Enable/Retrieve Latency tracking Log", get_latency_tracking_log)
+ ENTRY("parse-telemetry-log", "Parse Telemetry Log binary", get_telemetry_log)
+ ENTRY("clear-pcie-correctable-errors ", "Clear PCIe Correctable Error Counters (redirects to ocp plug-in)", clear_pcie_correctable_error_counters)
+ ENTRY("clear-fw-activate-history", "Clear firmware update history log (redirects to ocp plug-in)", clear_fw_update_history)
+ ENTRY("vs-fw-activate-history", "Get firmware activation history log (redirects to ocp plug-in)", fw_activation_history)
+ ENTRY("log-page-directory", "Retrieve log page directory", get_log_page_directory_log)
+ ENTRY("temp-stats", "Retrieve Temperature Statistics log", get_temp_stats_log)
+ ENTRY("vs-drive-info", "Retrieve drive information", get_drive_info)
+ ENTRY("cloud-SSDplugin-version", "Prints plug-in OCP version", get_cloud_SSDplugin_version)
+ )
+);
+
+#endif
+
+#include "define_cmd.h"
diff --git a/plugins/solidigm/solidigm-ocp-version.c b/plugins/solidigm/solidigm-ocp-version.c
new file mode 100644
index 0000000..4048cc1
--- /dev/null
+++ b/plugins/solidigm/solidigm-ocp-version.c
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <stdio.h>
+#include "nvme.h"
+
+int sldgm_ocp_version(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Prints OCP extensions version of Solidigm plugin";
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ int err = argconfig_parse(argc, argv, desc, opts);
+
+ if (!err)
+ printf("1.0\n");
+
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-ocp-version.h b/plugins/solidigm/solidigm-ocp-version.h
new file mode 100644
index 0000000..d79452c
--- /dev/null
+++ b/plugins/solidigm/solidigm-ocp-version.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+int sldgm_ocp_version(int argc, char **argv, struct command *cmd, struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-smart.c b/plugins/solidigm/solidigm-smart.c
new file mode 100644
index 0000000..62245fa
--- /dev/null
+++ b/plugins/solidigm/solidigm-smart.c
@@ -0,0 +1,271 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "nvme-print.h"
+
+#include "solidigm-smart.h"
+#include "solidigm-util.h"
+
+struct __packed nvme_additional_smart_log_item {
+ __u8 id;
+ __u8 _kp[2];
+ __u8 normalized;
+ __u8 _np;
+ union __packed {
+ __u8 raw[6];
+ struct __packed wear_level {
+ __le16 min;
+ __le16 max;
+ __le16 avg;
+ } wear_level;
+ struct __packed thermal_throttle {
+ __u8 pct;
+ __u32 count;
+ } thermal_throttle;
+ };
+ __u8 _rp;
+};
+
+#define VU_SMART_PAGE_SIZE 512
+#define VU_SMART_MAX_ITEMS (VU_SMART_PAGE_SIZE / sizeof(struct nvme_additional_smart_log_item))
+struct vu_smart_log {
+ struct nvme_additional_smart_log_item item[VU_SMART_MAX_ITEMS];
+};
+
+static char *id_to_name(__u8 id)
+{
+ switch (id) {
+ case 0x0D:
+ return "soft_ecc_error_rate";
+ case 0x05:
+ return "relocatable_sector_count";
+ case 0xAB:
+ return "program_fail_count";
+ case 0xAC:
+ return "erase_fail_count";
+ case 0xAD:
+ return "wear_leveling_count";
+ case 0xAE:
+ return "unexpected_power_loss";
+ case 0xB8:
+ return "e2e_error_detect_count";
+ case 0xC7:
+ return "crc_error_count";
+ case 0xE2:
+ return "media_wear_percentage";
+ case 0xE3:
+ return "timed_work_load_host_reads";
+ case 0xE4:
+ return "timed_work_load_timer";
+ case 0xE5:
+ return "read_commands_in_flight_counter";
+ case 0xE6:
+ return "write_commands_in_flight_counter";
+ case 0xEA:
+ return "thermal_throttle_status";
+ case 0xEE:
+ return "re_sku_count";
+ case 0xF0:
+ return "retry_buffer_overflow_counter";
+ case 0xF3:
+ return "pll_lock_loss_counter";
+ case 0xF4:
+ return "nand_bytes_written";
+ case 0xF5:
+ return "host_bytes_written";
+ case 0xF6:
+ return "host_context_wear_used";
+ case 0xF7:
+ return "performance_status_indicator";
+ case 0xF8:
+ return "media_bytes_read";
+ case 0xF9:
+ return "available_fw_downgrades";
+ case 0xFA:
+ return "host_read_collision_count";
+ case 0xFB:
+ return "host_write_collision_count";
+ case 0xFC:
+ return "xor_pass_count";
+ case 0xFD:
+ return "xor_fail_count";
+ case 0xFE:
+ return "xor_invoked_count";
+ default:
+ return "unknown";
+ }
+}
+
+static void smart_log_item_print(struct nvme_additional_smart_log_item *item)
+{
+ if (!item->id)
+ return;
+
+ printf("%#x %-45s %3d ",
+ item->id, id_to_name(item->id), item->normalized);
+
+ switch (item->id) {
+ case 0xAD:
+ printf("min: %u, max: %u, avg: %u\n",
+ le16_to_cpu(item->wear_level.min),
+ le16_to_cpu(item->wear_level.max),
+ le16_to_cpu(item->wear_level.avg));
+ return;
+ case 0xEA:
+ printf("%u%%, cnt: %u\n",
+ item->thermal_throttle.pct,
+ le32_to_cpu(item->thermal_throttle.count));
+ return;
+ default:
+ printf("%"PRIu64"\n", int48_to_long(item->raw));
+ }
+}
+
+static void smart_log_item_add_json(struct nvme_additional_smart_log_item *item, struct json_object *dev_stats)
+{
+ struct json_object *entry_stats = json_create_object();
+
+ if (!item->id)
+ return;
+
+ json_object_add_value_int(entry_stats, "normalized", item->normalized);
+
+ switch (item->id) {
+ case 0xAD:
+ json_object_add_value_int(entry_stats, "min", le16_to_cpu(item->wear_level.min));
+ json_object_add_value_int(entry_stats, "max", le16_to_cpu(item->wear_level.max));
+ json_object_add_value_int(entry_stats, "avg", le16_to_cpu(item->wear_level.avg));
+ break;
+ case 0xEA:
+ json_object_add_value_int(entry_stats, "percentage", item->thermal_throttle.pct);
+ json_object_add_value_int(entry_stats, "count", le32_to_cpu(item->thermal_throttle.count));
+ break;
+ default:
+ json_object_add_value_int(entry_stats, "raw", int48_to_long(item->raw));
+ }
+ json_object_add_value_object(dev_stats, id_to_name(item->id), entry_stats);
+}
+
+static void vu_smart_log_show_json(struct vu_smart_log *payload, unsigned int nsid, const char *devname)
+{
+ struct json_object *dev_stats = json_create_object();
+ struct nvme_additional_smart_log_item *item = payload->item;
+ struct json_object *root;
+
+ for (int i = 0; i < VU_SMART_MAX_ITEMS; i++)
+ smart_log_item_add_json(&item[i], dev_stats);
+
+ root = json_create_object();
+ json_object_add_value_string(root, "Solidigm SMART log", devname);
+ json_object_add_value_object(root, "Device stats", dev_stats);
+
+ json_print_object(root, NULL);
+ json_free_object(root);
+}
+
+static void vu_smart_log_show(struct vu_smart_log *payload, unsigned int nsid, const char *devname,
+ __u8 uuid_index)
+{
+ struct nvme_additional_smart_log_item *item = payload->item;
+
+ printf("Additional Smart Log for NVMe device:%s namespace-id:%x UUID-idx:%d\n",
+ devname, nsid, uuid_index);
+ printf("ID KEY Normalized Raw\n");
+
+ for (int i = 0; i < VU_SMART_MAX_ITEMS; i++)
+ smart_log_item_print(&item[i]);
+}
+
+int solidigm_get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc =
+ "Get Solidigm vendor specific smart log (optionally, for the specified namespace), and show it.";
+ const int solidigm_vu_smart_log_id = 0xCA;
+ struct vu_smart_log smart_log_payload;
+ enum nvme_print_flags flags;
+ struct nvme_dev *dev;
+ int err;
+ __u8 uuid_index;
+
+ struct config {
+ __u32 namespace_id;
+ char *output_format;
+ };
+
+ struct config cfg = {
+ .namespace_id = NVME_NSID_ALL,
+ .output_format = "normal",
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, "(optional) desired namespace"),
+ OPT_FMT("output-format", 'o', &cfg.output_format, output_format),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ err = validate_output_format(cfg.output_format, &flags);
+ if (err < 0) {
+ fprintf(stderr, "Invalid output format '%s'\n", cfg.output_format);
+ dev_close(dev);
+ return err;
+ }
+
+ uuid_index = solidigm_get_vu_uuid_index(dev);
+
+ struct nvme_get_log_args args = {
+ .lpo = 0,
+ .result = NULL,
+ .log = &smart_log_payload,
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .lid = solidigm_vu_smart_log_id,
+ .len = sizeof(smart_log_payload),
+ .nsid = NVME_NSID_ALL,
+ .csi = NVME_CSI_NVM,
+ .lsi = NVME_LOG_LSI_NONE,
+ .lsp = NVME_LOG_LSP_NONE,
+ .uuidx = uuid_index,
+ .rae = false,
+ .ot = false,
+ };
+
+ err = nvme_get_log(&args);
+ if (!err) {
+ if (flags & JSON)
+ vu_smart_log_show_json(&smart_log_payload,
+ cfg.namespace_id, dev->name);
+ else if (flags & BINARY)
+ d_raw((unsigned char *)&smart_log_payload, sizeof(smart_log_payload));
+ else
+ vu_smart_log_show(&smart_log_payload, cfg.namespace_id,
+ dev->name, uuid_index);
+ } else if (err > 0) {
+ nvme_show_status(err);
+ }
+
+ /* Redundant close() to make static code analysis happy */
+ close(dev->direct.fd);
+ dev_close(dev);
+ return err;
+}
+
diff --git a/plugins/solidigm/solidigm-smart.h b/plugins/solidigm/solidigm-smart.h
new file mode 100644
index 0000000..e19ebe5
--- /dev/null
+++ b/plugins/solidigm/solidigm-smart.h
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+int solidigm_get_additional_smart_log(int argc, char **argv, struct command *cmd, struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-telemetry.c b/plugins/solidigm/solidigm-telemetry.c
new file mode 100644
index 0000000..2bebccc
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "nvme-print.h"
+#include "solidigm-telemetry.h"
+#include "solidigm-telemetry/telemetry-log.h"
+#include "solidigm-telemetry/cod.h"
+#include "solidigm-telemetry/header.h"
+#include "solidigm-telemetry/config.h"
+#include "solidigm-telemetry/data-area.h"
+#include "solidigm-util.h"
+
+static int read_file2buffer(char *file_name, char **buffer, size_t *length)
+{
+ FILE *fd = fopen(file_name, "rb");
+
+ if (!fd)
+ return errno;
+
+ fseek(fd, 0, SEEK_END);
+ size_t length_bytes = ftell(fd);
+
+ fseek(fd, 0, SEEK_SET);
+
+ *buffer = malloc(length_bytes);
+ if (!*buffer) {
+ fclose(fd);
+ return errno;
+ }
+ *length = fread(*buffer, 1, length_bytes, fd);
+ fclose(fd);
+ return 0;
+}
+
+struct config {
+ __u32 host_gen;
+ bool ctrl_init;
+ int data_area;
+ char *cfg_file;
+ bool is_input_file;
+};
+
+int solidigm_get_telemetry_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ const char *desc = "Parse Solidigm Telemetry log";
+ const char *hgen = "Controls when to generate new host initiated report. Default value '1' generates new host initiated report, value '0' causes retrieval of existing log.";
+ const char *cgen = "Gather report generated by the controller.";
+ const char *dgen = "Pick which telemetry data area to report. Default is 3 to fetch areas 1-3. Valid options are 1, 2, 3, 4.";
+ const char *cfile = "JSON configuration file";
+ const char *sfile = "data source <device> is binary file containing log dump instead of block or character device";
+ struct nvme_dev *dev;
+
+ struct telemetry_log tl = {
+ .root = json_create_object(),
+ .log = NULL,
+ };
+
+ struct config cfg = {
+ .host_gen = 1,
+ .ctrl_init = false,
+ .data_area = -1,
+ .cfg_file = NULL,
+ .is_input_file = false,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("host-generate", 'g', &cfg.host_gen, hgen),
+ OPT_FLAG("controller-init", 'c', &cfg.ctrl_init, cgen),
+ OPT_UINT("data-area", 'd', &cfg.data_area, dgen),
+ OPT_FILE("config-file", 'j', &cfg.cfg_file, cfile),
+ OPT_FLAG("source-file", 's', &cfg.is_input_file, sfile),
+ OPT_END()
+ };
+
+ int err = argconfig_parse(argc, argv, desc, opts);
+
+ if (err)
+ goto ret;
+
+ /* When not selected on the command line, get minimum data area required */
+ if (cfg.data_area == -1)
+ cfg.data_area = cfg.cfg_file ? 3 : 1;
+
+ if (cfg.is_input_file) {
+ if (optind >= argc) {
+ err = errno = EINVAL;
+ perror(argv[0]);
+ goto ret;
+ }
+ char *binary_file_name = argv[optind];
+
+ err = read_file2buffer(binary_file_name, (char **)&tl.log, &tl.log_size);
+ } else {
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ }
+ if (err)
+ goto ret;
+
+ if (cfg.host_gen > 1) {
+ SOLIDIGM_LOG_WARNING("Invalid host-generate value '%d'", cfg.host_gen);
+ err = EINVAL;
+ goto close_fd;
+ }
+
+ if (cfg.cfg_file) {
+ char *conf_str = NULL;
+ size_t length = 0;
+
+ err = read_file2buffer(cfg.cfg_file, &conf_str, &length);
+ if (err) {
+ SOLIDIGM_LOG_WARNING("Failed to open JSON configuration file %s: %s!",
+ cfg.cfg_file, strerror(err));
+ goto close_fd;
+ }
+ struct json_tokener *jstok = json_tokener_new();
+
+ tl.configuration = json_tokener_parse_ex(jstok, conf_str, length);
+ free(conf_str);
+ if (jstok->err != json_tokener_success) {
+ SOLIDIGM_LOG_WARNING("Parsing error on JSON configuration file %s: %s (at offset %d)",
+ cfg.cfg_file,
+ json_tokener_error_desc(jstok->err),
+ jstok->char_offset);
+ json_tokener_free(jstok);
+ err = EINVAL;
+ goto close_fd;
+ }
+ json_tokener_free(jstok);
+ }
+
+ if (!cfg.is_input_file) {
+ size_t max_data_tx;
+
+ err = nvme_get_telemetry_max(dev_fd(dev), NULL, &max_data_tx);
+ if (err < 0) {
+ SOLIDIGM_LOG_WARNING("identify_ctrl: %s",
+ nvme_strerror(errno));
+ goto close_fd;
+ } else if (err > 0) {
+ nvme_show_status(err);
+ SOLIDIGM_LOG_WARNING("Failed to acquire identify ctrl %d!", err);
+ goto close_fd;
+ }
+ if (max_data_tx > DRIVER_MAX_TX_256K)
+ max_data_tx = DRIVER_MAX_TX_256K;
+
+ err = nvme_get_telemetry_log(dev_fd(dev), cfg.host_gen, cfg.ctrl_init, true,
+ max_data_tx, cfg.data_area, &tl.log, &tl.log_size);
+ if (err < 0) {
+ SOLIDIGM_LOG_WARNING("get-telemetry-log: %s",
+ nvme_strerror(errno));
+ goto close_fd;
+ } else if (err > 0) {
+ nvme_show_status(err);
+ SOLIDIGM_LOG_WARNING("Failed to acquire telemetry log %d!", err);
+ goto close_fd;
+ }
+ }
+ solidigm_telemetry_log_data_areas_parse(&tl, cfg.data_area);
+
+ json_print_object(tl.root, NULL);
+ json_free_object(tl.root);
+ printf("\n");
+
+close_fd:
+ if (!cfg.is_input_file) {
+ /* Redundant close() to make static code analysis happy */
+ close(dev->direct.fd);
+ dev_close(dev);
+ }
+ret:
+ json_free_object(tl.configuration);
+ free(tl.log);
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-telemetry.h b/plugins/solidigm/solidigm-telemetry.h
new file mode 100644
index 0000000..971ee2a
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry.h
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+int solidigm_get_telemetry_log(int argc, char **argv, struct command *cmd, struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-telemetry/cod.c b/plugins/solidigm/solidigm-telemetry/cod.c
new file mode 100644
index 0000000..363822a
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/cod.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+#include "common.h"
+#include "cod.h"
+
+const char *oemDataMapDesc[] = {
+ "Media Read Count", //Uid 0x00
+ "Host Read count", //Uid 0x01
+ "Media Write Count", //Uid 0x02
+ "Host Write Count", //Uid 0x03
+ "Device Model", // 0x04
+ "Serial Number", // 0x05
+ "Firmware Revision", // 0x06
+ "Drive Status", // 0x07
+ "Minimum Temperature", // 0x08
+ "Maximum Temperature", // 0x09
+ "Power Loss Protection Status", // 0x0a
+ "Lifetime Unsafe Shutdown Count", // 0x0b
+ "Lifetime Power Cycle Count", // 0x0c
+ "Minimum Read Latency", // 0x0d
+ "Maximum Read Latency", // 0x0e
+ "Average Read Latency", // 0x0f
+ "Minimum Write Latency", // 0x10
+ "Maximum Write Latency", // 0x11
+ "Average Write Latency", // 0x12
+ "Grown Defects Count", // 0x13
+ "DQS Recovery Count", // 0x14
+ "Program Fail Count", // 0x15
+ "Erase Fail Count", // 0x16
+ "Defrag Writes in Progress Count", // 0x17
+ "Total Defrag Writes Count", // 0x18
+ "Max Die Offline Number", // 0x19
+ "Current Die Offline Number", // 0x1A
+ "XOR Enable Status", // 0x1B
+ "Media Life Used", // 0x1C
+ "Uncorrectable Error Count", // 0x1D
+ "Current Wear Range Delta", // 0x1E
+ "Read Errors Corrected by XOR", // 0x1F
+ "Background Data Refresh", // 0x20
+ "Pmic Vin History Data 1 Min", // 0x21
+ "Pmic Vin History Data 1 Max", // 0x22
+ "Pmic Vin History Data 1 Avg", // 0x23
+ "Pmic Vin History Data 2 Min", // 0x24
+ "Pmic Vin History Data 2 Max", // 0x25
+ "Pmic Vin History Data 2 Avg", // 0x26
+ "Pmic Vin History Data Total Readings", // 0x27
+ "All Time Current Max Wear Level", // 0x28
+ "Media Wear Remaining", // 0x29
+ "Total Non-Defrag Writes", // 0x2A
+ "Media Health Relocations" //Uid 0x2B = 43
+};
+
+static const char *getOemDataMapDescription(uint32_t id)
+{
+ if (id < ARRAY_SIZE(oemDataMapDesc))
+ return oemDataMapDesc[id];
+ return "unknown";
+}
+
+#define OEMSIGNATURE 0x504D4443
+
+#pragma pack(push, cod, 1)
+struct cod_header {
+ uint32_t versionMajor;
+ uint32_t versionMinor;
+ uint32_t Signature; //!Fixed signature value (0x504D4443) for identification and validation
+ uint32_t MapSizeInBytes; //!Total size of the map data structure in bytes
+ uint32_t EntryCount; //!Total number of entries in the entry list
+ uint8_t Reserved[12];
+};
+
+struct cod_item {
+ uint32_t DataFieldMapUid; //!The data field unique identifier value
+ uint32_t reserved1 : 8;
+ uint32_t dataFieldType : 8;
+ uint32_t issigned : 1;
+ uint32_t bigEndian : 1;
+ uint32_t dataInvalid : 1;
+ uint32_t reserved2 : 13;
+ uint32_t DataFieldSizeInBytes;
+ uint8_t Reserved1[4];
+ uint64_t DataFieldOffset;
+ uint8_t Reserved2[8];
+};
+
+struct cod_map {
+ struct cod_header header;
+ struct cod_item items[];
+};
+
+#pragma pack(pop, cod)
+
+void solidigm_telemetry_log_cod_parse(struct telemetry_log *tl)
+{
+ enum cod_field_type {
+ INTEGER,
+ FLOAT,
+ STRING,
+ TWO_BYTE_ASCII,
+ FOUR_BYTE_ASCII,
+
+ UNKNOWN = 0xFF,
+ };
+ struct json_object *telemetry_header = NULL;
+ struct json_object *COD_offset = NULL;
+ struct json_object *reason_id = NULL;
+
+ if (!json_object_object_get_ex(tl->root, "telemetryHeader", &telemetry_header))
+ return;
+ if (!json_object_object_get_ex(telemetry_header, "reasonIdentifier", &reason_id))
+ return;
+ if (!json_object_object_get_ex(reason_id, "oemDataMapOffset", &COD_offset))
+ return;
+
+ uint64_t offset = json_object_get_int(COD_offset);
+
+ if (!offset)
+ return;
+
+ if ((offset + sizeof(struct cod_header)) > tl->log_size) {
+ SOLIDIGM_LOG_WARNING("Warning: COD map header out of bounds.");
+ return;
+ }
+
+ const struct cod_map *data = (struct cod_map *) (((uint8_t *)tl->log) + offset);
+
+ uint32_t signature = be32_to_cpu(data->header.Signature);
+
+ if (signature != OEMSIGNATURE) {
+ SOLIDIGM_LOG_WARNING("Warning: Unsupported COD data signature %x!", signature);
+ return;
+ }
+ if ((offset + data->header.MapSizeInBytes) > tl->log_size) {
+ SOLIDIGM_LOG_WARNING("Warning: COD map data out of bounds.");
+ return;
+ }
+
+ struct json_object *cod = json_create_object();
+
+ json_object_object_add(tl->root, "cod", cod);
+
+ for (uint64_t i = 0; i < data->header.EntryCount; i++) {
+ if ((offset + sizeof(struct cod_header) + (i + 1) * sizeof(struct cod_item)) >
+ tl->log_size) {
+ SOLIDIGM_LOG_WARNING("Warning: COD data out of bounds at item %"PRIu64"!",
+ i);
+ return;
+ }
+ struct cod_item item = data->items[i];
+
+ if (item.DataFieldOffset + item.DataFieldOffset > tl->log_size)
+ continue;
+ if (item.dataInvalid)
+ continue;
+ uint8_t *val = ((uint8_t *)tl->log) + item.DataFieldOffset;
+ const char *key = getOemDataMapDescription(item.DataFieldMapUid);
+
+ switch (item.dataFieldType) {
+ case INTEGER:
+ if (item.issigned)
+ json_object_object_add(cod, key,
+ json_object_new_int64(le64_to_cpu(*(uint64_t *)val)));
+ else
+ json_object_add_value_uint64(cod, key, le64_to_cpu(*(uint64_t *)val));
+ break;
+ case FLOAT:
+ json_object_add_value_float(cod, key, *(float *)val);
+ break;
+ case STRING:
+ json_object_object_add(cod, key,
+ json_object_new_string_len((const char *)val, item.DataFieldSizeInBytes));
+ break;
+ case TWO_BYTE_ASCII:
+ json_object_object_add(cod, key,
+ json_object_new_string_len((const char *)val, 2));
+ break;
+ case FOUR_BYTE_ASCII:
+ json_object_object_add(cod, key,
+ json_object_new_string_len((const char *)val, 4));
+ break;
+ default:
+ SOLIDIGM_LOG_WARNING("Warning: Unknown COD field type (%d)", item.DataFieldMapUid);
+ break;
+ }
+ }
+}
diff --git a/plugins/solidigm/solidigm-telemetry/cod.h b/plugins/solidigm/solidigm-telemetry/cod.h
new file mode 100644
index 0000000..032ccdc
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/cod.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include "telemetry-log.h"
+void solidigm_telemetry_log_cod_parse(struct telemetry_log *tl);
diff --git a/plugins/solidigm/solidigm-telemetry/config.c b/plugins/solidigm/solidigm-telemetry/config.c
new file mode 100644
index 0000000..eceeede
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/config.c
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "config.h"
+
+// max 16 bit unsigned integer number 65535
+#define MAX_16BIT_NUM_AS_STRING_SIZE 6
+
+#define OBJ_NAME_PREFIX "UID_"
+#define NLOG_OBJ_PREFIX OBJ_NAME_PREFIX "NLOG_"
+
+static bool config_get_by_version(const struct json_object *obj, int version_major,
+ int version_minor, struct json_object **value)
+{
+ char str_key[MAX_16BIT_NUM_AS_STRING_SIZE];
+ char str_subkey[MAX_16BIT_NUM_AS_STRING_SIZE];
+
+ snprintf(str_key, sizeof(str_key), "%d", version_major);
+ snprintf(str_subkey, sizeof(str_subkey), "%d", version_minor);
+ struct json_object *major_obj = NULL;
+
+ if (!json_object_object_get_ex(obj, str_key, &major_obj))
+ return false;
+ if (!json_object_object_get_ex(major_obj, str_subkey, value))
+ return false;
+ return value != NULL;
+}
+
+bool solidigm_config_get_struct_by_token_version(const struct json_object *config, int token_id,
+ int version_major, int version_minor,
+ struct json_object **value)
+{
+ struct json_object *token = NULL;
+ char str_key[MAX_16BIT_NUM_AS_STRING_SIZE];
+
+ snprintf(str_key, sizeof(str_key), "%d", token_id);
+ if (!json_object_object_get_ex(config, str_key, &token))
+ return false;
+ if (!config_get_by_version(token, version_major, version_minor, value))
+ return false;
+ return value != NULL;
+}
+
+const char *solidigm_config_get_nlog_obj_name(const struct json_object *config, uint32_t token)
+{
+ struct json_object *nlog_names = NULL;
+ struct json_object *obj_name;
+ char hex_header[STR_HEX32_SIZE];
+ const char *name;
+
+ if (!json_object_object_get_ex(config, "TELEMETRY_OBJECT_UIDS", &nlog_names))
+ return NULL;
+ snprintf(hex_header, STR_HEX32_SIZE, "0x%08X", token);
+
+ if (!json_object_object_get_ex(nlog_names, hex_header, &obj_name))
+ return NULL;
+ name = json_object_get_string(obj_name);
+ if (strncmp(NLOG_OBJ_PREFIX, name, strlen(NLOG_OBJ_PREFIX)))
+ return NULL;
+
+ return &name[strlen(OBJ_NAME_PREFIX)];
+}
+
+struct json_object *solidigm_config_get_nlog_formats(const struct json_object *config)
+{
+ struct json_object *nlog_formats = NULL;
+
+ json_object_object_get_ex(config, "NLOG_FORMATS", &nlog_formats);
+ return nlog_formats;
+}
diff --git a/plugins/solidigm/solidigm-telemetry/config.h b/plugins/solidigm/solidigm-telemetry/config.h
new file mode 100644
index 0000000..4e56ba3
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/config.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+#include <stdbool.h>
+#include "util/json.h"
+
+#define STR_HEX32_SIZE sizeof("0x00000000")
+
+bool solidigm_config_get_struct_by_token_version(const struct json_object *obj,
+ int key, int subkey,
+ int subsubkey,
+ struct json_object **value);
+
+const char *solidigm_config_get_nlog_obj_name(const struct json_object *config, uint32_t token);
+struct json_object *solidigm_config_get_nlog_formats(const struct json_object *config);
+
diff --git a/plugins/solidigm/solidigm-telemetry/data-area.c b/plugins/solidigm/solidigm-telemetry/data-area.c
new file mode 100644
index 0000000..14d612b
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/data-area.c
@@ -0,0 +1,472 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include "common.h"
+#include "header.h"
+#include "cod.h"
+#include "data-area.h"
+#include "config.h"
+#include "nlog.h"
+#include <ctype.h>
+
+#define SIGNED_INT_PREFIX "int"
+#define BITS_IN_BYTE 8
+
+#define MAX_WARNING_SIZE 1024
+#define MAX_ARRAY_RANK 16
+
+static bool telemetry_log_get_value(const struct telemetry_log *tl,
+ uint64_t offset_bit, uint32_t size_bit,
+ bool is_signed, struct json_object **val_obj)
+{
+ uint32_t offset_bit_from_byte;
+ uint32_t additional_size_byte;
+ uint32_t offset_byte;
+ uint64_t val;
+
+ if (!size_bit) {
+ char err_msg[MAX_WARNING_SIZE];
+
+ snprintf(err_msg, MAX_WARNING_SIZE,
+ "Value with size_bit=0 not supported.");
+ *val_obj = json_object_new_string(err_msg);
+
+ return false;
+ }
+ additional_size_byte = (size_bit - 1) ? (size_bit - 1) / BITS_IN_BYTE : 0;
+ offset_byte = (uint32_t)offset_bit / BITS_IN_BYTE;
+
+ if (offset_byte > (tl->log_size - additional_size_byte)) {
+ char err_msg[MAX_WARNING_SIZE];
+
+ snprintf(err_msg, MAX_WARNING_SIZE,
+ "Value offset greater than binary size (%u > %zu).",
+ offset_byte, tl->log_size);
+ *val_obj = json_object_new_string(err_msg);
+
+ return false;
+ }
+
+ offset_bit_from_byte = (uint32_t) (offset_bit - ((uint64_t)offset_byte * BITS_IN_BYTE));
+
+ if ((size_bit + offset_bit_from_byte) > (sizeof(uint64_t) * BITS_IN_BYTE)) {
+ char err_msg[MAX_WARNING_SIZE];
+
+ snprintf(err_msg, MAX_WARNING_SIZE,
+ "Value crossing 64 bit, byte aligned boundary, not supported. size_bit=%u, offset_bit_from_byte=%u.",
+ size_bit, offset_bit_from_byte);
+ *val_obj = json_object_new_string(err_msg);
+
+ return false;
+ }
+
+ val = *(uint64_t *)(((char *)tl->log) + offset_byte);
+ val >>= offset_bit_from_byte;
+ if (size_bit < 64)
+ val &= (1ULL << size_bit) - 1;
+ if (is_signed) {
+ if (val >> (size_bit - 1))
+ val |= (0ULL - 1) << size_bit;
+ *val_obj = json_object_new_int64(val);
+ } else {
+ *val_obj = json_object_new_uint64(val);
+ }
+
+ return true;
+}
+
+static int telemetry_log_structure_parse(const struct telemetry_log *tl,
+ struct json_object *struct_def,
+ uint64_t parent_offset_bit,
+ struct json_object *output,
+ struct json_object *metadata)
+{
+ struct json_object *obj_arraySizeArray = NULL;
+ struct json_object *obj = NULL;
+ struct json_object *obj_memberList;
+ struct json_object *major_dimension = NULL;
+ struct json_object *sub_output;
+ bool is_enumeration = false;
+ bool has_member_list;
+ const char *type = "";
+ const char *name;
+ size_t array_rank;
+ uint64_t offset_bit;
+ uint32_t size_bit;
+ uint64_t linear_array_pos_bit;
+ uint32_t array_size_dimension[MAX_ARRAY_RANK];
+
+ if (!json_object_object_get_ex(struct_def, "name", &obj)) {
+ SOLIDIGM_LOG_WARNING("Warning: Structure definition missing property 'name': %s",
+ json_object_to_json_string(struct_def));
+ return -1;
+ }
+
+ name = json_object_get_string(obj);
+
+ if (metadata) {
+ json_object_get(obj);
+ json_object_object_add(metadata, "objName", obj);
+ }
+
+ if (json_object_object_get_ex(struct_def, "type", &obj))
+ type = json_object_get_string(obj);
+
+ if (!json_object_object_get_ex(struct_def, "offsetBit", &obj)) {
+ SOLIDIGM_LOG_WARNING(
+ "Warning: Structure definition missing property 'offsetBit': %s",
+ json_object_to_json_string(struct_def));
+ return -1;
+ }
+
+ offset_bit = json_object_get_uint64(obj);
+
+ if (!json_object_object_get_ex(struct_def, "sizeBit", &obj)) {
+ SOLIDIGM_LOG_WARNING(
+ "Warning: Structure definition missing property 'sizeBit': %s",
+ json_object_to_json_string(struct_def));
+ return -1;
+ }
+
+ size_bit = (uint32_t)json_object_get_uint64(obj);
+
+ if (json_object_object_get_ex(struct_def, "enum", &obj))
+ is_enumeration = json_object_get_boolean(obj);
+
+ has_member_list = json_object_object_get_ex(struct_def,
+ "memberList",
+ &obj_memberList);
+
+ if (!json_object_object_get_ex(struct_def, "arraySize",
+ &obj_arraySizeArray)) {
+ SOLIDIGM_LOG_WARNING(
+ "Warning: Structure definition missing property 'arraySize': %s",
+ json_object_to_json_string(struct_def));
+ return -1;
+ }
+
+ array_rank = json_object_array_length(obj_arraySizeArray);
+ if (!array_rank) {
+ SOLIDIGM_LOG_WARNING(
+ "Warning: Structure property 'arraySize' don't support flexible array: %s",
+ json_object_to_json_string(struct_def));
+ return -1;
+ }
+ if (array_rank > MAX_ARRAY_RANK) {
+ SOLIDIGM_LOG_WARNING(
+ "Warning: Structure property 'arraySize' don't support more than %d dimensions: %s",
+ MAX_ARRAY_RANK, json_object_to_json_string(struct_def));
+ return -1;
+ }
+
+ for (size_t i = 0; i < array_rank; i++) {
+ struct json_object *dimension = json_object_array_get_idx(obj_arraySizeArray, i);
+
+ array_size_dimension[i] = json_object_get_int(dimension);
+ major_dimension = dimension;
+ }
+ if (array_rank > 1) {
+ uint32_t linear_pos_per_index = array_size_dimension[0];
+ uint32_t prev_index_offset_bit = 0;
+ struct json_object *dimension_output;
+
+ for (unsigned int i = 1; i < (array_rank - 1); i++)
+ linear_pos_per_index *= array_size_dimension[i];
+
+ dimension_output = json_create_array();
+ if (json_object_get_type(output) == json_type_array)
+ json_object_array_add(output, dimension_output);
+ else
+ json_object_add_value_array(output, name, dimension_output);
+
+ /*
+ * Make sure major_dimension object will not be
+ * deleted from memory when deleted from array
+ */
+ json_object_get(major_dimension);
+ json_object_array_del_idx(obj_arraySizeArray, array_rank - 1, 1);
+
+ for (unsigned int i = 0 ; i < array_size_dimension[0]; i++) {
+ struct json_object *sub_array = json_create_array();
+ uint64_t offset;
+
+ offset = parent_offset_bit + prev_index_offset_bit;
+
+ json_object_array_add(dimension_output, sub_array);
+ telemetry_log_structure_parse(tl, struct_def,
+ offset, sub_array, NULL);
+ prev_index_offset_bit += linear_pos_per_index * size_bit;
+ }
+
+ json_object_array_put_idx(obj_arraySizeArray, array_rank - 1,
+ major_dimension);
+
+ return 0;
+ }
+
+ linear_array_pos_bit = 0;
+ sub_output = output;
+
+ if (array_size_dimension[0] > 1) {
+ sub_output = json_create_array();
+ if (json_object_get_type(output) == json_type_array)
+ json_object_array_add(output, sub_output);
+ else
+ json_object_add_value_array(output, name, sub_output);
+ }
+
+ for (uint32_t j = 0; j < array_size_dimension[0]; j++) {
+ if (is_enumeration || !has_member_list) {
+ bool is_signed = !strncmp(type, SIGNED_INT_PREFIX, sizeof(SIGNED_INT_PREFIX)-1);
+ struct json_object *val_obj;
+ uint64_t offset;
+
+ offset = parent_offset_bit + offset_bit + linear_array_pos_bit;
+ if (telemetry_log_get_value(tl, offset, size_bit, is_signed, &val_obj)) {
+ if (array_size_dimension[0] > 1)
+ json_object_array_put_idx(sub_output, j, val_obj);
+ else
+ json_object_object_add(sub_output, name, val_obj);
+ } else {
+ SOLIDIGM_LOG_WARNING(
+ "Warning: %s From property '%s', array index %u, structure definition: %s",
+ json_object_get_string(val_obj), name, j,
+ json_object_to_json_string(struct_def));
+ json_free_object(val_obj);
+ }
+ } else {
+ struct json_object *sub_sub_output = json_create_object();
+ int num_members;
+
+ if (array_size_dimension[0] > 1)
+ json_object_array_put_idx(sub_output, j, sub_sub_output);
+ else
+ json_object_add_value_object(sub_output, name, sub_sub_output);
+
+ num_members = json_object_array_length(obj_memberList);
+ for (int k = 0; k < num_members; k++) {
+ struct json_object *member = json_object_array_get_idx(obj_memberList, k);
+ uint64_t offset;
+
+ offset = parent_offset_bit + offset_bit + linear_array_pos_bit;
+ telemetry_log_structure_parse(tl, member, offset,
+ sub_sub_output, NULL);
+ }
+ }
+ linear_array_pos_bit += size_bit;
+ }
+ return 0;
+}
+
+static int telemetry_log_data_area_get_offset(const struct telemetry_log *tl,
+ enum nvme_telemetry_da da,
+ uint32_t *offset, uint32_t *size)
+{
+ uint32_t offset_blocks = 1;
+ uint32_t last_block = tl->log->dalb1;
+ uint32_t last;
+
+ switch (da) {
+ case NVME_TELEMETRY_DA_1:
+ offset_blocks = 1;
+ last_block = tl->log->dalb1;
+ break;
+ case NVME_TELEMETRY_DA_2:
+ offset_blocks = tl->log->dalb1 + 1;
+ last_block = tl->log->dalb2;
+ break;
+ case NVME_TELEMETRY_DA_3:
+ offset_blocks = tl->log->dalb2 + 1;
+ last_block = tl->log->dalb3;
+ break;
+ case NVME_TELEMETRY_DA_4:
+ offset_blocks = tl->log->dalb3 + 1;
+ last_block = tl->log->dalb4;
+ break;
+ default:
+ return -1;
+ }
+
+ *offset = offset_blocks * NVME_LOG_TELEM_BLOCK_SIZE;
+ last = (last_block + 1) * NVME_LOG_TELEM_BLOCK_SIZE;
+ *size = last - *offset;
+ if ((*offset > tl->log_size) || (last > tl->log_size) || (last <= *offset)) {
+ SOLIDIGM_LOG_WARNING("Warning: Data Area %d don't fit this Telemetry log.", da);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int telemetry_log_nlog_parse(const struct telemetry_log *tl, struct json_object *formats,
+ uint64_t nlog_file_offset, uint64_t nlog_size,
+ struct json_object *output, struct json_object *metadata)
+{
+ /* boundary check */
+ if (tl->log_size < (nlog_file_offset + nlog_size)) {
+ const char *name = "";
+ int media_bank = -1;
+ struct json_object *jobj;
+
+ if (json_object_object_get_ex(metadata, "objName", &jobj))
+ name = json_object_get_string(jobj);
+ if (json_object_object_get_ex(metadata, "mediaBankId", &jobj))
+ media_bank = json_object_get_int(jobj);
+ SOLIDIGM_LOG_WARNING("%s:%d do not fit this log dump.", name, media_bank);
+ return -1;
+ }
+ return solidigm_nlog_parse(((char *) tl->log) + nlog_file_offset,
+ nlog_size, formats, metadata, output);
+}
+
+struct toc_item {
+ uint32_t OffsetBytes;
+ uint32_t ContentSizeBytes;
+};
+
+struct data_area_header {
+ uint8_t versionMajor;
+ uint8_t versionMinor;
+ uint16_t TableOfContentsCount;
+ uint32_t DataAreaSize;
+ uint8_t Reserved[8];
+};
+
+struct table_of_contents {
+ struct data_area_header header;
+ struct toc_item items[];
+};
+
+struct telemetry_object_header {
+ uint16_t versionMajor;
+ uint16_t versionMinor;
+ uint32_t Token;
+ uint8_t CoreId;
+ uint8_t Reserved[3];
+};
+
+static void telemetry_log_data_area_toc_parse(const struct telemetry_log *tl,
+ enum nvme_telemetry_da da,
+ struct json_object *toc_array,
+ struct json_object *tele_obj_array)
+{
+
+ const struct telemetry_object_header *header;
+ const struct table_of_contents *toc;
+ char *payload;
+ uint32_t da_offset;
+ uint32_t da_size;
+ struct json_object *nlog_formats;
+
+ if (telemetry_log_data_area_get_offset(tl, da, &da_offset, &da_size))
+ return;
+
+ toc = (struct table_of_contents *)(((char *)tl->log) + da_offset);
+ payload = (char *) tl->log;
+ nlog_formats = solidigm_config_get_nlog_formats(tl->configuration);
+
+ for (int i = 0; i < toc->header.TableOfContentsCount; i++) {
+ struct json_object *structure_definition = NULL;
+ struct json_object *toc_item;
+ uint32_t obj_offset;
+ bool has_struct;
+ const char *nlog_name = NULL;
+ uint32_t header_offset = sizeof(const struct telemetry_object_header);
+
+ if ((char *)&toc->items[i] >
+ (((char *)toc) + da_size - sizeof(const struct toc_item))) {
+ SOLIDIGM_LOG_WARNING(
+ "Warning: Data Area %d, Table of Contents item %d crossed Data Area size.",
+ da, i);
+ return;
+ }
+
+ obj_offset = toc->items[i].OffsetBytes;
+ if ((obj_offset + sizeof(const struct telemetry_object_header)) > da_size) {
+ SOLIDIGM_LOG_WARNING(
+ "Warning: Data Area %d, item %d data, crossed Data Area size.", da, i);
+ continue;
+ }
+
+ toc_item = json_create_object();
+ json_object_array_add(toc_array, toc_item);
+ json_object_add_value_uint(toc_item, "dataArea", da);
+ json_object_add_value_uint(toc_item, "dataAreaIndex", i);
+ json_object_add_value_uint(toc_item, "dataAreaOffset", obj_offset);
+ json_object_add_value_uint(toc_item, "fileOffset", obj_offset + da_offset);
+ json_object_add_value_uint(toc_item, "size", toc->items[i].ContentSizeBytes);
+
+ header = (const struct telemetry_object_header *) (payload + da_offset + obj_offset);
+ json_object_add_value_uint(toc_item, "telemMajor", header->versionMajor);
+ json_object_add_value_uint(toc_item, "telemMinor", header->versionMinor);
+ json_object_add_value_uint(toc_item, "objectId", header->Token);
+ json_object_add_value_uint(toc_item, "mediaBankId", header->CoreId);
+
+ has_struct = solidigm_config_get_struct_by_token_version(tl->configuration,
+ header->Token,
+ header->versionMajor,
+ header->versionMinor,
+ &structure_definition);
+ if (!has_struct) {
+ if (!nlog_formats)
+ continue;
+ nlog_name = solidigm_config_get_nlog_obj_name(tl->configuration,
+ header->Token);
+ if (!nlog_name)
+ continue;
+ }
+ struct json_object *tele_obj_item = json_create_object();
+
+ json_object_array_add(tele_obj_array, tele_obj_item);
+ json_object_get(toc_item);
+ json_object_add_value_object(tele_obj_item, "metadata", toc_item);
+ struct json_object *parsed_struct = json_create_object();
+
+ json_object_add_value_object(tele_obj_item, "objectData", parsed_struct);
+ struct json_object *obj_hasTelemObjHdr = NULL;
+ uint64_t object_file_offset;
+
+ if (json_object_object_get_ex(structure_definition,
+ "hasTelemObjHdr",
+ &obj_hasTelemObjHdr)) {
+ bool hasHeader = json_object_get_boolean(obj_hasTelemObjHdr);
+
+ if (hasHeader)
+ header_offset = 0;
+ }
+ object_file_offset = ((uint64_t)da_offset) + obj_offset + header_offset;
+ if (has_struct) {
+ telemetry_log_structure_parse(tl, structure_definition,
+ BITS_IN_BYTE * object_file_offset,
+ parsed_struct, toc_item);
+ } else if (nlog_formats) {
+ json_object_object_add(toc_item, "objName",
+ json_object_new_string(nlog_name));
+ telemetry_log_nlog_parse(tl, nlog_formats, object_file_offset,
+ toc->items[i].ContentSizeBytes - header_offset,
+ parsed_struct, toc_item);
+ }
+ }
+}
+
+int solidigm_telemetry_log_data_areas_parse(struct telemetry_log *tl,
+ enum nvme_telemetry_da last_da)
+{
+ struct json_object *tele_obj_array = json_create_array();
+ struct json_object *toc_array = json_create_array();
+
+ solidigm_telemetry_log_header_parse(tl);
+ solidigm_telemetry_log_cod_parse(tl);
+ if (tl->configuration) {
+ json_object_add_value_array(tl->root, "tableOfContents", toc_array);
+ json_object_add_value_array(tl->root, "telemetryObjects", tele_obj_array);
+
+ for (enum nvme_telemetry_da da = NVME_TELEMETRY_DA_1; da <= last_da; da++)
+ telemetry_log_data_area_toc_parse(tl, da, toc_array, tele_obj_array);
+ }
+ return 0;
+}
diff --git a/plugins/solidigm/solidigm-telemetry/data-area.h b/plugins/solidigm/solidigm-telemetry/data-area.h
new file mode 100644
index 0000000..6b690d8
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/data-area.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+#include "telemetry-log.h"
+
+int solidigm_telemetry_log_data_areas_parse(struct telemetry_log *tl,
+ enum nvme_telemetry_da last_da);
diff --git a/plugins/solidigm/solidigm-telemetry/header.c b/plugins/solidigm/solidigm-telemetry/header.c
new file mode 100644
index 0000000..866ebff
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/header.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include "common.h"
+#include "header.h"
+
+#pragma pack(push, reason_indentifier, 1)
+struct reason_indentifier_1_0 {
+ uint16_t versionMajor;
+ uint16_t versionMinor;
+ uint32_t reasonCode; //! 0 denotes no issue. All other values denote a potential issue.
+ char DriveStatus[20]; //! Drive Status String (for example: "Healthy", "*BAD_CONTEXT_2020")
+ char FirmwareVersion[12]; //! Similar to IdentifyController.FR
+ char BootloaderVersion[12]; //! Bootloader version string
+ char SerialNumber[20]; //! Device serial number
+ uint8_t Reserved[56]; //! Reserved for future usage
+};
+static_assert(sizeof(const struct reason_indentifier_1_0) ==
+ MEMBER_SIZE(struct nvme_telemetry_log, rsnident),
+ "Size mismatch for reason_indentifier_1_0");
+
+struct reason_indentifier_1_1 {
+ uint16_t versionMajor;
+ uint16_t versionMinor;
+ uint32_t reasonCode; //! 0 denotes no issue. All other values denote a potential issue.
+ char DriveStatus[20]; //! Drive Status String (for example: "Healthy", "*BAD_CONTEXT_2020")
+ char FirmwareVersion[12]; //! Similar to IdentifyController.FR
+ char BootloaderVersion[12]; //! Bootloader version string
+ char SerialNumber[20]; //! Device serial number
+ uint64_t OemDataMapOffset; //! Customer Data Map Object Log Offset
+ uint8_t TelemetryMajorVersion; //! Shadow of version in TOC
+ uint8_t TelemetryMinorVersion; //! Shadow of version in TOC
+ uint8_t Reserved[46]; //! Reserved for future usage
+};
+static_assert(sizeof(const struct reason_indentifier_1_1) ==
+ MEMBER_SIZE(struct nvme_telemetry_log, rsnident),
+ "Size mismatch for reason_indentifier_1_1");
+
+struct reason_indentifier_1_2 {
+ uint16_t versionMajor;
+ uint16_t versionMinor;
+ uint32_t reasonCode; //! 0 denotes no issue. All other values denote a potential issue.
+ char DriveStatus[20]; //! Drive Status String (for example: "Healthy", "*BAD_CONTEXT_2020")
+ uint8_t Reserved1[24]; //! pad over Fields removed from version 1.1
+ char SerialNumber[20]; //! Device serial number
+ uint64_t OemDataMapOffset; //! Customer Data Map Object Log Offset
+ uint8_t TelemetryMajorVersion; //! Shadow of version in TOC
+ uint8_t TelemetryMinorVersion; //! Shadow of version in TOC
+ uint8_t ProductFamilyId;
+ uint8_t Reserved2[5]; //! Reserved for future usage
+ uint8_t DualPortReserved[40]; //! Reserved for dual port
+};
+static_assert(sizeof(const struct reason_indentifier_1_2) ==
+ MEMBER_SIZE(struct nvme_telemetry_log, rsnident),
+ "Size mismatch for reason_indentifier_1_2");
+#pragma pack(pop, reason_indentifier)
+
+static void telemetry_log_reason_id_parse1_0_ext(const struct telemetry_log *tl,
+ struct json_object *reason_id)
+{
+ const struct reason_indentifier_1_0 *ri;
+ struct json_object *reserved;
+
+ ri = (struct reason_indentifier_1_0 *) tl->log->rsnident;
+ json_object_object_add(reason_id, "firmwareVersion",
+ json_object_new_string_len(ri->FirmwareVersion,
+ sizeof(ri->FirmwareVersion)));
+ json_object_object_add(reason_id, "bootloaderVersion",
+ json_object_new_string_len(ri->BootloaderVersion,
+ sizeof(ri->BootloaderVersion)));
+ json_object_object_add(reason_id, "serialNumber",
+ json_object_new_string_len(ri->SerialNumber,
+ sizeof(ri->SerialNumber)));
+
+ reserved = json_create_array();
+ json_object_add_value_array(reason_id, "reserved", reserved);
+ for (int i = 0; i < sizeof(ri->Reserved); i++) {
+ struct json_object *val = json_object_new_int(ri->Reserved[i]);
+
+ json_object_array_add(reserved, val);
+ }
+}
+
+static void telemetry_log_reason_id_parse1_1_ext(const struct telemetry_log *tl,
+ struct json_object *reason_id)
+{
+ const struct reason_indentifier_1_1 *ri;
+ struct json_object *reserved;
+
+ ri = (struct reason_indentifier_1_1 *) tl->log->rsnident;
+ json_object_object_add(reason_id, "firmwareVersion",
+ json_object_new_string_len(ri->FirmwareVersion,
+ sizeof(ri->FirmwareVersion)));
+ json_object_object_add(reason_id, "bootloaderVersion",
+ json_object_new_string_len(ri->BootloaderVersion,
+ sizeof(ri->BootloaderVersion)));
+ json_object_object_add(reason_id, "serialNumber",
+ json_object_new_string_len(ri->SerialNumber,
+ sizeof(ri->SerialNumber)));
+ json_object_add_value_uint64(reason_id, "oemDataMapOffset",
+ le64_to_cpu(ri->OemDataMapOffset));
+ json_object_add_value_uint(reason_id, "telemetryMajorVersion",
+ le16_to_cpu(ri->TelemetryMajorVersion));
+ json_object_add_value_uint(reason_id, "telemetryMinorVersion",
+ le16_to_cpu(ri->TelemetryMinorVersion));
+
+ reserved = json_create_array();
+ json_object_add_value_array(reason_id, "reserved", reserved);
+ for (int i = 0; i < sizeof(ri->Reserved); i++) {
+ struct json_object *val = json_object_new_int(ri->Reserved[i]);
+
+ json_object_array_add(reserved, val);
+ }
+}
+
+static void telemetry_log_reason_id_parse1_2_ext(const struct telemetry_log *tl,
+ struct json_object *reason_id)
+{
+ const struct reason_indentifier_1_2 *ri;
+ struct json_object *dp_reserved;
+ struct json_object *reserved;
+
+ ri = (struct reason_indentifier_1_2 *) tl->log->rsnident;
+
+ json_object_object_add(reason_id, "serialNumber",
+ json_object_new_string_len(ri->SerialNumber,
+ sizeof(ri->SerialNumber)));
+ json_object_add_value_uint64(reason_id, "oemDataMapOffset",
+ le64_to_cpu(ri->OemDataMapOffset));
+ json_object_add_value_uint(reason_id, "telemetryMajorVersion",
+ le16_to_cpu(ri->TelemetryMajorVersion));
+ json_object_add_value_uint(reason_id, "telemetryMinorVersion",
+ le16_to_cpu(ri->TelemetryMinorVersion));
+ json_object_add_value_uint(reason_id, "productFamilyId", ri->ProductFamilyId);
+
+ reserved = json_create_array();
+ json_object_add_value_array(reason_id, "reserved2", reserved);
+ for (int i = 0; i < sizeof(ri->Reserved2); i++) {
+ struct json_object *val = json_object_new_int(ri->Reserved2[i]);
+
+ json_object_array_add(reserved, val);
+ }
+
+ dp_reserved = json_create_array();
+ json_object_add_value_array(reason_id, "dualPortReserved", dp_reserved);
+ for (int i = 0; i < sizeof(ri->DualPortReserved); i++) {
+ struct json_object *val = json_object_new_int(ri->DualPortReserved[i]);
+
+ json_object_array_add(dp_reserved, val);
+ }
+}
+
+static void solidigm_telemetry_log_reason_id_parse(const struct telemetry_log *tl, struct json_object *reason_id)
+{
+ const struct reason_indentifier_1_0 *ri1_0 =
+ (struct reason_indentifier_1_0 *) tl->log->rsnident;
+ uint16_t version_major = le16_to_cpu(ri1_0->versionMajor);
+ uint16_t version_minor = le16_to_cpu(ri1_0->versionMinor);
+
+ json_object_add_value_uint(reason_id, "versionMajor", version_major);
+ json_object_add_value_uint(reason_id, "versionMinor", version_minor);
+ json_object_add_value_uint(reason_id, "reasonCode", le32_to_cpu(ri1_0->reasonCode));
+ json_object_add_value_object(reason_id, "driveStatus",
+ json_object_new_string_len(ri1_0->DriveStatus,
+ sizeof(ri1_0->DriveStatus)));
+ if (version_major == 1) {
+ switch (version_minor) {
+ case 0:
+ telemetry_log_reason_id_parse1_0_ext(tl, reason_id);
+ break;
+ case 1:
+ telemetry_log_reason_id_parse1_1_ext(tl, reason_id);
+ break;
+ default:
+ telemetry_log_reason_id_parse1_2_ext(tl, reason_id);
+ break;
+ }
+ }
+}
+
+bool solidigm_telemetry_log_header_parse(const struct telemetry_log *tl)
+{
+ const struct nvme_telemetry_log *log;
+ struct json_object *ieee_oui_id;
+ struct json_object *reason_id;
+ struct json_object *header;
+
+ if (tl->log_size < sizeof(const struct nvme_telemetry_log)) {
+ SOLIDIGM_LOG_WARNING("Telemetry log too short.");
+ return false;
+ }
+
+ header = json_create_object();
+
+ json_object_object_add(tl->root, "telemetryHeader", header);
+ log = tl->log;
+
+ json_object_add_value_uint(header, "logIdentifier", log->lpi);
+ ieee_oui_id = json_create_array();
+
+ json_object_object_add(header, "ieeeOuiIdentifier", ieee_oui_id);
+ for (int i = 0; i < sizeof(log->ieee); i++) {
+ struct json_object *val = json_object_new_int(log->ieee[i]);
+
+ json_object_array_add(ieee_oui_id, val);
+ }
+ json_object_add_value_uint(header, "dataArea1LastBlock", log->dalb1);
+ json_object_add_value_uint(header, "dataArea2LastBlock", log->dalb2);
+ json_object_add_value_uint(header, "dataArea3LastBlock", log->dalb3);
+ json_object_add_value_uint(header, "hostInitiatedDataGeneration", log->hostdgn);
+ json_object_add_value_uint(header, "controllerInitiatedDataAvailable", log->ctrlavail);
+ json_object_add_value_uint(header, "controllerInitiatedDataGeneration", log->ctrldgn);
+
+ reason_id = json_create_object();
+ json_object_add_value_object(header, "reasonIdentifier", reason_id);
+ solidigm_telemetry_log_reason_id_parse(tl, reason_id);
+
+ return true;
+}
diff --git a/plugins/solidigm/solidigm-telemetry/header.h b/plugins/solidigm/solidigm-telemetry/header.h
new file mode 100644
index 0000000..027af55
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/header.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include "telemetry-log.h"
+bool solidigm_telemetry_log_header_parse(const struct telemetry_log *tl);
diff --git a/plugins/solidigm/solidigm-telemetry/meson.build b/plugins/solidigm/solidigm-telemetry/meson.build
new file mode 100644
index 0000000..96273b7
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/meson.build
@@ -0,0 +1,7 @@
+sources += [
+ 'plugins/solidigm/solidigm-telemetry/cod.c',
+ 'plugins/solidigm/solidigm-telemetry/header.c',
+ 'plugins/solidigm/solidigm-telemetry/config.c',
+ 'plugins/solidigm/solidigm-telemetry/data-area.c',
+ 'plugins/solidigm/solidigm-telemetry/nlog.c',
+]
diff --git a/plugins/solidigm/solidigm-telemetry/nlog.c b/plugins/solidigm/solidigm-telemetry/nlog.c
new file mode 100644
index 0000000..926772b
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/nlog.c
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include "nlog.h"
+#include "config.h"
+#include <string.h>
+#include <stdio.h>
+
+#include "ccan/ilog/ilog.h"
+
+#define LOG_ENTRY_HEADER_SIZE 1
+#define LOG_ENTRY_TIMESTAMP_SIZE 2
+#define LOG_ENTRY_NUM_ARGS_MAX 8
+#define LOG_ENTRY_MAX_SIZE (LOG_ENTRY_HEADER_SIZE + LOG_ENTRY_TIMESTAMP_SIZE + \
+ LOG_ENTRY_NUM_ARGS_MAX)
+#define NUM_ARGS_MASK ((1 << ((int)STATIC_ILOG_32(LOG_ENTRY_NUM_ARGS_MAX))) - 1)
+#define MAX_HEADER_MISMATCH_TRACK 10
+
+static int formats_find(struct json_object *formats, uint32_t val, struct json_object **format)
+{
+ char hex_header[STR_HEX32_SIZE];
+
+ snprintf(hex_header, STR_HEX32_SIZE, "0x%08X", val);
+ return json_object_object_get_ex(formats, hex_header, format);
+}
+
+static uint32_t nlog_get_pos(const uint32_t *nlog, const uint32_t nlog_size, int pos)
+{
+ return nlog[pos % nlog_size];
+}
+
+static uint32_t nlog_get_events(const uint32_t *nlog, const uint32_t nlog_size, int start_offset,
+ struct json_object *formats, struct json_object *events, uint32_t *tail_mismatches)
+{
+ uint32_t event_count = 0;
+ int last_bad_header_pos = nlog_size + 1; // invalid nlog offset
+ uint32_t tail_count = 0;
+
+ for (int i = nlog_size - start_offset - 1; i >= -start_offset; i--) {
+ struct json_object *format;
+ uint32_t header = nlog_get_pos(nlog, nlog_size, i);
+ uint32_t num_data;
+
+ if (header == 0 || !formats_find(formats, header, &format)) {
+ if (event_count > 0) {
+ //check if fould circular buffer tail
+ if (i != (last_bad_header_pos - 1)) {
+ if (tail_mismatches &&
+ (tail_count < MAX_HEADER_MISMATCH_TRACK))
+ tail_mismatches[tail_count] = header;
+ tail_count++;
+ }
+ last_bad_header_pos = i;
+ }
+ continue;
+ }
+ num_data = header & NUM_ARGS_MASK;
+ if (events) {
+ struct json_object *event = json_object_new_array();
+ struct json_object *param = json_object_new_array();
+ uint32_t val = nlog_get_pos(nlog, nlog_size, i - 1);
+
+ json_object_array_add(events, event);
+ json_object_array_add(event, json_object_new_int64(val));
+ val = nlog_get_pos(nlog, nlog_size, i - 2);
+ json_object_array_add(event, json_object_new_int64(val));
+ json_object_array_add(event, json_object_new_int64(header));
+ json_object_array_add(event, param);
+ for (uint32_t j = 0; j < num_data; j++) {
+ val = nlog_get_pos(nlog, nlog_size, i - 3 - j);
+ json_object_array_add(param, json_object_new_int64(val));
+ }
+ json_object_get(format);
+ json_object_array_add(event, format);
+ }
+ i -= 2 + num_data;
+ event_count++;
+ }
+ return tail_count;
+}
+
+int solidigm_nlog_parse(const char *buffer, uint64_t buff_size, struct json_object *formats,
+ struct json_object *metadata, struct json_object *output)
+{
+ uint32_t smaller_tail_count = UINT32_MAX;
+ int best_offset = 0;
+ uint32_t offset_tail_mismatches[LOG_ENTRY_MAX_SIZE][MAX_HEADER_MISMATCH_TRACK];
+ struct json_object *events = json_object_new_array();
+ const uint32_t *nlog = (uint32_t *)buffer;
+ const uint32_t nlog_size = buff_size / sizeof(uint32_t);
+
+ for (int i = 0; i < LOG_ENTRY_MAX_SIZE; i++) {
+ uint32_t tail_count = nlog_get_events(nlog, nlog_size, i, formats, NULL,
+ offset_tail_mismatches[i]);
+ if (tail_count < smaller_tail_count) {
+ best_offset = i;
+ smaller_tail_count = tail_count;
+ }
+ if (tail_count == 0)
+ break;
+ }
+ if (smaller_tail_count > 1) {
+ const char *name = "";
+ int media_bank = -1;
+ char str_mismatches[(STR_HEX32_SIZE + 1) * MAX_HEADER_MISMATCH_TRACK];
+ int pos = 0;
+ int show_mismatch_num = smaller_tail_count < MAX_HEADER_MISMATCH_TRACK ?
+ smaller_tail_count : MAX_HEADER_MISMATCH_TRACK;
+ struct json_object *jobj;
+
+ if (json_object_object_get_ex(metadata, "objName", &jobj))
+ name = json_object_get_string(jobj);
+ if (json_object_object_get_ex(metadata, "mediaBankId", &jobj))
+ media_bank = json_object_get_int(jobj);
+
+ for (int i = 0; i < show_mismatch_num; i++)
+ pos += snprintf(&str_mismatches[pos], STR_HEX32_SIZE + 1, "0x%08X ",
+ offset_tail_mismatches[best_offset][i]);
+
+ SOLIDIGM_LOG_WARNING("%s:%d with %d header mismatches ( %s). Configuration file may be missing format headers.",
+ name, media_bank, smaller_tail_count, str_mismatches);
+ }
+ nlog_get_events(nlog, nlog_size, best_offset, formats, events, NULL);
+
+ json_object_object_add(output, "events", events);
+ return 0;
+}
diff --git a/plugins/solidigm/solidigm-telemetry/nlog.h b/plugins/solidigm/solidigm-telemetry/nlog.h
new file mode 100644
index 0000000..f33aa45
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/nlog.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+#include "telemetry-log.h"
+
+int solidigm_nlog_parse(const char *buffer, uint64_t bufer_size,
+ struct json_object *formats, struct json_object *metadata,
+ struct json_object *output);
diff --git a/plugins/solidigm/solidigm-telemetry/telemetry-log.h b/plugins/solidigm/solidigm-telemetry/telemetry-log.h
new file mode 100644
index 0000000..e9eff73
--- /dev/null
+++ b/plugins/solidigm/solidigm-telemetry/telemetry-log.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright (c) 2022 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#ifndef _SOLIDIGM_TELEMETRY_LOG_H
+#define _SOLIDIGM_TELEMETRY_LOG_H
+
+#include "libnvme.h"
+#include "util/json.h"
+#include <assert.h>
+
+#if !defined __cplusplus
+#define static_assert _Static_assert
+#endif
+
+#define VA_ARGS(...), ##__VA_ARGS__
+#define SOLIDIGM_LOG_WARNING(format, ...) fprintf(stderr, format"\n" VA_ARGS(__VA_ARGS__))
+
+#define MEMBER_SIZE(type, member) sizeof(((type *)0)->member)
+
+struct telemetry_log {
+ struct nvme_telemetry_log *log;
+ size_t log_size;
+ struct json_object *root;
+ struct json_object *configuration;
+};
+
+#endif /* _SOLIDIGM_TELEMETRY_LOG_H */
diff --git a/plugins/solidigm/solidigm-temp-stats.c b/plugins/solidigm/solidigm-temp-stats.c
new file mode 100644
index 0000000..85a3c37
--- /dev/null
+++ b/plugins/solidigm/solidigm-temp-stats.c
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include <errno.h>
+
+#include "common.h"
+#include "nvme-print.h"
+#include "solidigm-util.h"
+
+#define SLDGM_TEMP_STATS_LID 0xC5
+
+struct temp_stats {
+ __le64 curr;
+ __le64 last_overtemp;
+ __le64 life_overtemp;
+ __le64 highest_temp;
+ __le64 lowest_temp;
+ __u8 rsvd[40];
+ __le64 max_operating_temp;
+ __le64 min_operating_temp;
+ __le64 est_offset;
+};
+
+static void show_temp_stats(struct temp_stats *stats)
+{
+ printf("Current temperature : %"PRIu64"\n", le64_to_cpu(stats->curr));
+ printf("Last critical overtemp flag : %"PRIu64"\n", le64_to_cpu(stats->last_overtemp));
+ printf("Life critical overtemp flag : %"PRIu64"\n", le64_to_cpu(stats->life_overtemp));
+ printf("Highest temperature : %"PRIu64"\n", le64_to_cpu(stats->highest_temp));
+ printf("Lowest temperature : %"PRIu64"\n", le64_to_cpu(stats->lowest_temp));
+ printf("Max operating temperature : %"PRIu64"\n", le64_to_cpu(stats->max_operating_temp));
+ printf("Min operating temperature : %"PRIu64"\n", le64_to_cpu(stats->min_operating_temp));
+ printf("Estimated offset : %"PRIu64"\n", le64_to_cpu(stats->est_offset));
+}
+
+int sldgm_get_temp_stats_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ unsigned char buffer[4096] = {0};
+ struct nvme_dev *dev;
+ __u8 uuid_idx;
+ int err;
+
+ const char *desc = "Get/show Temperature Statistics log.";
+ const char *raw = "dump output in binary format";
+ struct config {
+ bool raw_binary;
+ };
+
+ struct config cfg = {
+ .raw_binary = false,
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FLAG("raw-binary", 'b', &cfg.raw_binary, raw),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err)
+ return err;
+
+ uuid_idx = solidigm_get_vu_uuid_index(dev);
+
+ struct nvme_get_log_args args = {
+ .lpo = 0,
+ .result = NULL,
+ .log = buffer,
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .uuidx = uuid_idx,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .lid = SLDGM_TEMP_STATS_LID,
+ .len = sizeof(buffer),
+ .nsid = NVME_NSID_ALL,
+ .csi = NVME_CSI_NVM,
+ .lsi = NVME_LOG_LSI_NONE,
+ .lsp = NVME_LOG_LSP_NONE,
+ .rae = false,
+ .ot = false,
+ };
+
+ err = nvme_get_log(&args);
+ if (!err) {
+ uint64_t *guid = (uint64_t *)&buffer[4080];
+
+ if (guid[1] == 0xC7BB98B7D0324863 && guid[0] == 0xBB2C23990E9C722F) {
+ fprintf(stderr, "Error: Log page has 'OCP unsupported Requirements' GUID\n");
+ err = -EBADMSG;
+ goto closefd;
+ }
+ if (!cfg.raw_binary)
+ show_temp_stats((struct temp_stats *) buffer);
+ else
+ d_raw(buffer, sizeof(struct temp_stats));
+ } else if (err > 0) {
+ nvme_show_status(err);
+ }
+
+closefd:
+ /* Redundant close() to make static code analysis happy */
+ close(dev->direct.fd);
+ dev_close(dev);
+ return err;
+}
diff --git a/plugins/solidigm/solidigm-temp-stats.h b/plugins/solidigm/solidigm-temp-stats.h
new file mode 100644
index 0000000..58d5a86
--- /dev/null
+++ b/plugins/solidigm/solidigm-temp-stats.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+int sldgm_get_temp_stats_log(int argc, char **argv, struct command *cmd, struct plugin *plugin);
diff --git a/plugins/solidigm/solidigm-util.c b/plugins/solidigm/solidigm-util.c
new file mode 100644
index 0000000..0171a49
--- /dev/null
+++ b/plugins/solidigm/solidigm-util.c
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include "plugins/ocp/ocp-utils.h"
+#include "solidigm-util.h"
+
+__u8 solidigm_get_vu_uuid_index(struct nvme_dev *dev)
+{
+ int ocp_uuid_index = 0;
+
+ if (ocp_get_uuid_index(dev, &ocp_uuid_index) == 0)
+ if (ocp_uuid_index == 2)
+ return 1;
+
+ return 0;
+}
diff --git a/plugins/solidigm/solidigm-util.h b/plugins/solidigm/solidigm-util.h
new file mode 100644
index 0000000..fa5032f
--- /dev/null
+++ b/plugins/solidigm/solidigm-util.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2023 Solidigm.
+ *
+ * Author: leonardo.da.cunha@solidigm.com
+ */
+
+#include "nvme.h"
+
+#define DRIVER_MAX_TX_256K (256 * 1024)
+
+__u8 solidigm_get_vu_uuid_index(struct nvme_dev *dev);