summaryrefslogtreecommitdiffstats
path: root/plugins/toshiba/toshiba-nvme.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/toshiba/toshiba-nvme.c')
-rw-r--r--plugins/toshiba/toshiba-nvme.c573
1 files changed, 573 insertions, 0 deletions
diff --git a/plugins/toshiba/toshiba-nvme.c b/plugins/toshiba/toshiba-nvme.c
new file mode 100644
index 0000000..4927012
--- /dev/null
+++ b/plugins/toshiba/toshiba-nvme.c
@@ -0,0 +1,573 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include "nvme.h"
+#include "libnvme.h"
+#include "plugin.h"
+#include "linux/types.h"
+#include "nvme-print.h"
+
+#define CREATE_CMD
+#include "toshiba-nvme.h"
+
+static const __u32 OP_SCT_STATUS = 0xE0;
+static const __u32 OP_SCT_COMMAND_TRANSFER = 0xE0;
+static const __u32 OP_SCT_DATA_TRANSFER = 0xE1;
+
+static const __u32 DW10_SCT_STATUS_COMMAND;
+static const __u32 DW10_SCT_COMMAND_TRANSFER = 0x1;
+
+static const __u32 DW11_SCT_STATUS_COMMAND;
+static const __u32 DW11_SCT_COMMAND_TRANSFER;
+
+static const __u16 INTERNAL_LOG_ACTION_CODE = 0xFFFB;
+static const __u16 CURRENT_LOG_FUNCTION_CODE = 0x0001;
+static const __u16 SAVED_LOG_FUNCTION_CODE = 0x0002;
+
+/* A bitmask field for supported devices */
+enum {
+ MASK_0 = 1 << 0,
+ MASK_1 = 1 << 1,
+ /*
+ * Future devices can use the remaining 31 bits from this field
+ * and should use 1 << 2, 1 << 3, etc.
+ */
+ MASK_IGNORE = 0
+};
+
+/* Internal device codes */
+enum {
+ CODE_0 = 0x0D,
+ CODE_1 = 0x10
+};
+
+
+static int nvme_sct_op(int fd, __u32 opcode, __u32 cdw10, __u32 cdw11, void *data, __u32 data_len)
+{
+ void *metadata = NULL;
+ const __u32 cdw2 = 0;
+ const __u32 cdw3 = 0;
+ const __u32 cdw12 = 0;
+ const __u32 cdw13 = 0;
+ const __u32 cdw14 = 0;
+ const __u32 cdw15 = 0;
+ const __u32 timeout = 0;
+ const __u32 metadata_len = 0;
+ const __u32 namespace_id = 0x0;
+ const __u32 flags = 0;
+ const __u32 rsvd = 0;
+ __u32 result;
+
+ return nvme_admin_passthru(fd, opcode, flags, rsvd, namespace_id, cdw2, cdw3, cdw10, cdw11,
+ cdw12, cdw13, cdw14, cdw15, data_len, data, metadata_len,
+ metadata, timeout, &result);
+}
+
+static int nvme_get_sct_status(int fd, __u32 device_mask)
+{
+ int err;
+ void *data = NULL;
+ size_t data_len = 512;
+ unsigned char *status;
+ __u32 supported = 0;
+
+ if (posix_memalign(&data, getpagesize(), data_len))
+ return -ENOMEM;
+
+ memset(data, 0, data_len);
+ err = nvme_sct_op(fd, OP_SCT_STATUS, DW10_SCT_STATUS_COMMAND, DW11_SCT_STATUS_COMMAND, data, data_len);
+ if (err) {
+ fprintf(stderr, "%s: SCT status failed :%d\n", __func__, err);
+ goto end;
+ }
+
+ status = data;
+ if (status[0] != 1U) {
+ /* Eek, wrong version in status header */
+ fprintf(stderr, "%s: unexpected value in SCT status[0]:(%x)\n", __func__, status[0]);
+ err = -1;
+ errno = EINVAL;
+ goto end;
+ }
+
+ /* Check if device is supported */
+ if (device_mask != MASK_IGNORE) {
+ switch (status[1]) {
+ case CODE_0:
+ supported = (device_mask & MASK_0);
+ break;
+ case CODE_1:
+ supported = (device_mask & MASK_1);
+ break;
+ default:
+ break;
+ };
+
+ if (!supported) {
+ fprintf(stderr, "%s: command unsupported on this device: (0x%x)\n", __func__, status[1]);
+ err = -1;
+ errno = EINVAL;
+ goto end;
+ }
+ }
+end:
+ if (data)
+ free(data);
+ return err;
+}
+
+static int nvme_sct_command_transfer_log(int fd, bool current)
+{
+ int err;
+ void *data = NULL;
+ size_t data_len = 512;
+ __u16 function_code, action_code = INTERNAL_LOG_ACTION_CODE;
+
+ if (current)
+ function_code = CURRENT_LOG_FUNCTION_CODE;
+ else
+ function_code = SAVED_LOG_FUNCTION_CODE;
+
+ if (posix_memalign(&data, getpagesize(), data_len))
+ return -ENOMEM;
+
+ memset(data, 0, data_len);
+ memcpy(data, &action_code, sizeof(action_code));
+ memcpy(data + 2, &function_code, sizeof(function_code));
+
+ err = nvme_sct_op(fd, OP_SCT_COMMAND_TRANSFER, DW10_SCT_COMMAND_TRANSFER, DW11_SCT_COMMAND_TRANSFER, data, data_len);
+ free(data);
+ return err;
+}
+
+static int nvme_sct_data_transfer(int fd, void *data, size_t data_len, size_t offset)
+{
+ __u32 dw10, dw11, lba_count = (data_len) / 512;
+
+ if (lba_count) {
+ /*
+ * the count is a 0-based value, which seems to mean
+ * that it's actually last lba
+ */
+ --lba_count;
+ }
+
+ dw10 = (offset << 16) | lba_count;
+ dw11 = (offset >> 16);
+ return nvme_sct_op(fd, OP_SCT_DATA_TRANSFER, dw10, dw11, data, data_len);
+}
+
+static int d_raw_to_fd(const unsigned char *buf, unsigned int len, int fd)
+{
+ int written = 0;
+ int remaining = len;
+
+ while (remaining) {
+ written = write(fd, buf, remaining);
+ if (written < 0) {
+ remaining = written;
+ break;
+ } else if (written <= remaining) {
+ remaining -= written;
+ } else {
+ /* Unexpected overwrite */
+ break;
+ }
+ }
+
+ /* return 0 on success or remaining/error */
+ return remaining;
+}
+
+/* Display progress (incoming 0->1.0) */
+static void progress_runner(float progress)
+{
+ const size_t barWidth = 70;
+ size_t i, pos;
+
+ fprintf(stdout, "[");
+ pos = barWidth * progress;
+ for (i = 0; i < barWidth; ++i) {
+ if (i <= pos)
+ fprintf(stdout, "=");
+ else
+ fprintf(stdout, " ");
+ }
+
+ fprintf(stdout, "] %d %%\r", (int)(progress * 100.0));
+ fflush(stdout);
+}
+
+static int nvme_get_internal_log(int fd, const char *const filename, bool current)
+{
+ int err;
+ int o_fd = -1;
+ void *page_data = NULL;
+ const size_t page_sector_len = 32;
+ const size_t page_data_len = page_sector_len * 512; /* 32 sectors per page */
+ uint32_t *area1_last_page;
+ uint32_t *area2_last_page;
+ uint32_t *area3_last_page;
+ uint32_t log_sectors = 0;
+ size_t pages;
+ __u32 pages_chunk;
+ /*
+ * By trial and error it seems that the largest transfer chunk size
+ * is 128 * 32 = 4k sectors = 2MB
+ */
+ const __u32 max_pages = 128;
+ size_t i;
+ unsigned int j;
+ float progress = 0.0;
+
+ err = nvme_sct_command_transfer_log(fd, current);
+ if (err) {
+ fprintf(stderr, "%s: SCT command transfer failed\n", __func__);
+ goto end;
+ }
+
+ if (posix_memalign(&page_data, getpagesize(), max_pages * page_data_len)) {
+ err = ENOMEM;
+ goto end;
+ }
+ memset(page_data, 0, max_pages * page_data_len);
+
+ /* Read the header to get the last log page - offsets 8->11, 12->15, 16->19 */
+ err = nvme_sct_data_transfer(fd, page_data, page_data_len, 0);
+ if (err) {
+ fprintf(stderr, "%s: SCT data transfer failed, page 0\n", __func__);
+ goto end;
+ }
+
+ area1_last_page = (uint32_t *) (page_data + 8);
+ area2_last_page = (uint32_t *) (page_data + 12);
+ area3_last_page = (uint32_t *) (page_data + 16);
+
+ /* The number of total log sectors is the maximum + 1; */
+ if (*area1_last_page > log_sectors)
+ log_sectors = *area1_last_page;
+ if (*area2_last_page > log_sectors)
+ log_sectors = *area2_last_page;
+ if (*area3_last_page > log_sectors)
+ log_sectors = *area3_last_page;
+
+ ++log_sectors;
+ pages = log_sectors / page_sector_len;
+ if (filename == NULL) {
+ fprintf(stdout, "Page: %u of %zu\n", 0u, pages);
+ d(page_data, page_data_len, 16, 1);
+ } else {
+ progress_runner(progress);
+ o_fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (o_fd < 0) {
+ fprintf(stderr, "%s: couldn't output file %s\n", __func__, filename);
+ err = -EINVAL;
+ goto end;
+ }
+ err = d_raw_to_fd(page_data, page_data_len, o_fd);
+ if (err) {
+ fprintf(stderr, "%s: couldn't write all data to output file\n", __func__);
+ goto end;
+ }
+ }
+
+ /* Now read the rest */
+ for (i = 1; i < pages;) {
+ pages_chunk = max_pages;
+ if (pages_chunk + i >= pages)
+ pages_chunk = pages - i;
+
+ err = nvme_sct_data_transfer(fd, page_data,
+ pages_chunk * page_data_len,
+ i * page_sector_len);
+ if (err) {
+ fprintf(stderr, "%s: SCT data transfer command failed\n", __func__);
+ goto end;
+ }
+
+ progress = (float) (i) / (float) (pages);
+ progress_runner(progress);
+ if (filename == NULL) {
+ for (j = 0; j < pages_chunk; ++j) {
+ fprintf(stdout, "Page: %zu of %zu\n", i + j, pages);
+ d(page_data + (j * page_data_len), page_data_len, 16, 1);
+ }
+ } else {
+ progress_runner(progress);
+ err = d_raw_to_fd(page_data, pages_chunk * page_data_len, o_fd);
+ if (err) {
+ fprintf(stderr, "%s: couldn't write all data to output file\n",
+ __func__);
+ goto end;
+ }
+ }
+ i += pages_chunk;
+ }
+ progress = 1.0f;
+ progress_runner(progress);
+ fprintf(stdout, "\n");
+ err = nvme_get_sct_status(fd, MASK_IGNORE);
+ if (err) {
+ fprintf(stderr, "%s: bad SCT status\n", __func__);
+ goto end;
+ }
+end:
+ if (o_fd >= 0)
+ close(o_fd);
+ if (page_data)
+ free(page_data);
+ return err;
+}
+
+static int nvme_get_internal_log_file(int fd, const char *const filename, bool current)
+{
+ int err;
+
+ /* Check device supported */
+ err = nvme_get_sct_status(fd, MASK_0 | MASK_1);
+ if (!err)
+ err = nvme_get_internal_log(fd, filename, current);
+ return err;
+}
+
+enum LOG_PAGE_C0 {
+ ERROR_LOG_C0 = 0,
+ SMART_HEALTH_LOG_C0,
+ FIRMWARE_SLOT_INFO_C0,
+ COMMAND_EFFECTS_C0,
+ DEVICE_SELF_TEST_C0,
+ LOG_PAGE_DIRECTORY_C0,
+ SMART_ATTRIBUTES_C0,
+ NR_SMART_ITEMS_C0,
+};
+
+struct nvme_xdn_smart_log_c0 {
+ __u8 items[NR_SMART_ITEMS_C0];
+ __u8 resv[512 - NR_SMART_ITEMS_C0];
+};
+
+static void default_show_vendor_log_c0(struct nvme_dev *dev, __u32 nsid,
+ struct nvme_xdn_smart_log_c0 *smart)
+{
+ printf("Vendor Log Page Directory 0xC0 for NVME device:%s namespace-id:%x\n",
+ dev->name, nsid);
+ printf("Error Log : %u\n", smart->items[ERROR_LOG_C0]);
+ printf("SMART Health Log : %u\n", smart->items[SMART_HEALTH_LOG_C0]);
+ printf("Firmware Slot Info : %u\n", smart->items[FIRMWARE_SLOT_INFO_C0]);
+ printf("Command Effects : %u\n", smart->items[COMMAND_EFFECTS_C0]);
+ printf("Device Self Test : %u\n", smart->items[DEVICE_SELF_TEST_C0]);
+ printf("Log Page Directory : %u\n", smart->items[LOG_PAGE_DIRECTORY_C0]);
+ printf("SMART Attributes : %u\n", smart->items[SMART_ATTRIBUTES_C0]);
+}
+
+static int nvme_get_vendor_log(struct nvme_dev *dev, __u32 namespace_id,
+ int log_page, const char *const filename)
+{
+ int err;
+ void *log = NULL;
+ size_t log_len = 512;
+
+ if (posix_memalign(&log, getpagesize(), log_len)) {
+ err = ENOMEM;
+ goto end;
+ }
+
+ /* Check device supported */
+ err = nvme_get_sct_status(dev_fd(dev), MASK_0 | MASK_1);
+ if (err)
+ goto end;
+ err = nvme_get_nsid_log(dev_fd(dev), false, log_page, namespace_id,
+ log_len, log);
+ if (err) {
+ fprintf(stderr, "%s: couldn't get log 0x%x\n", __func__,
+ log_page);
+ goto end;
+ }
+ if (filename) {
+ int o_fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+
+ if (o_fd < 0) {
+ fprintf(stderr, "%s: couldn't output file %s\n",
+ __func__, filename);
+ err = -EINVAL;
+ goto end;
+ }
+ err = d_raw_to_fd(log, log_len, o_fd);
+ if (err) {
+ fprintf(stderr, "%s: couldn't write all data to output file %s\n",
+ __func__, filename);
+ /* Attempt following close */
+ }
+ if (close(o_fd)) {
+ err = errno;
+ goto end;
+ }
+ } else {
+ if (log_page == 0xc0)
+ default_show_vendor_log_c0(dev, namespace_id, log);
+ else
+ d(log, log_len, 16, 1);
+ }
+end:
+ if (log)
+ free(log);
+ return err;
+}
+
+static int vendor_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ char *desc = "Get extended SMART information and show it.";
+ const char *namespace = "(optional) desired namespace";
+ const char *output_file = "(optional) binary output filename";
+ const char *log = "(optional) log ID (0xC0, or 0xCA), default 0xCA";
+ struct nvme_dev *dev;
+ int err;
+
+ struct config {
+ __u32 namespace_id;
+ const char *output_file;
+ int log;
+ };
+
+ struct config cfg = {
+ .namespace_id = 0xffffffff,
+ .output_file = NULL,
+ .log = 0xca
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_UINT("namespace-id", 'n', &cfg.namespace_id, namespace),
+ OPT_FILE("output-file", 'o', &cfg.output_file, output_file),
+ OPT_UINT("log", 'l', &cfg.log, log),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err) {
+ fprintf(stderr, "%s: failed to parse arguments\n", __func__);
+ return -EINVAL;
+ }
+
+ if ((cfg.log != 0xC0) && (cfg.log != 0xCA)) {
+ fprintf(stderr, "%s: invalid log page 0x%x - should be 0xC0 or 0xCA\n", __func__, cfg.log);
+ err = -EINVAL;
+ goto end;
+ }
+
+ err = nvme_get_vendor_log(dev, cfg.namespace_id, cfg.log,
+ cfg.output_file);
+ if (err)
+ fprintf(stderr, "%s: couldn't get vendor log 0x%x\n", __func__, cfg.log);
+end:
+ if (err > 0)
+ nvme_show_status(err);
+ dev_close(dev);
+ return err;
+}
+
+static int internal_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
+{
+ char *desc = "Get internal status log and show it.";
+ const char *output_file = "(optional) binary output filename";
+ const char *prev_log = "(optional) use previous log. Otherwise uses current log.";
+ struct nvme_dev *dev;
+ int err;
+
+ struct config {
+ const char *output_file;
+ bool prev_log;
+ };
+
+ struct config cfg = {
+ .output_file = NULL,
+ .prev_log = false
+ };
+
+ OPT_ARGS(opts) = {
+ OPT_FILE("output-file", 'o', &cfg.output_file, output_file),
+ OPT_FLAG("prev-log", 'p', &cfg.prev_log, prev_log),
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err) {
+ fprintf(stderr, "%s: failed to parse arguments\n", __func__);
+ return -EINVAL;
+ }
+
+ if (cfg.prev_log)
+ printf("Getting previous log\n");
+ else
+ printf("Getting current log\n");
+
+ err = nvme_get_internal_log_file(dev_fd(dev), cfg.output_file,
+ !cfg.prev_log);
+ if (err < 0)
+ fprintf(stderr, "%s: couldn't get fw log\n", __func__);
+ if (err > 0)
+ nvme_show_status(err);
+
+ dev_close(dev);
+ return err;
+}
+
+static int clear_correctable_errors(int argc, char **argv, struct command *cmd,
+ struct plugin *plugin)
+{
+ char *desc = "Clear PCIe correctable error count.";
+ const __u32 namespace_id = 0xFFFFFFFF;
+ const __u32 feature_id = 0xCA;
+ const __u32 value = 1; /* Bit0 - reset clear PCIe correctable count */
+ const __u32 cdw12 = 0;
+ const bool save = false;
+ struct nvme_dev *dev;
+ __u32 result;
+ int err;
+
+ OPT_ARGS(opts) = {
+ OPT_END()
+ };
+
+ err = parse_and_open(&dev, argc, argv, desc, opts);
+ if (err) {
+ fprintf(stderr, "%s: failed to parse arguments\n", __func__);
+ return -EINVAL;
+ }
+
+ /* Check device supported */
+ err = nvme_get_sct_status(dev_fd(dev), MASK_0 | MASK_1);
+ if (err)
+ goto end;
+
+ struct nvme_set_features_args args = {
+ .args_size = sizeof(args),
+ .fd = dev_fd(dev),
+ .fid = feature_id,
+ .nsid = namespace_id,
+ .cdw11 = value,
+ .cdw12 = cdw12,
+ .save = save,
+ .uuidx = 0,
+ .cdw15 = 0,
+ .data_len = 0,
+ .data = NULL,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = &result,
+ };
+ err = nvme_set_features(&args);
+ if (err)
+ fprintf(stderr, "%s: couldn't clear PCIe correctable errors\n",
+ __func__);
+end:
+ if (err > 0)
+ nvme_show_status(err);
+ dev_close(dev);
+ return err;
+}