diff options
Diffstat (limited to '')
-rw-r--r-- | plugins/toshiba/toshiba-nvme.c | 573 | ||||
-rw-r--r-- | plugins/toshiba/toshiba-nvme.h | 21 |
2 files changed, 594 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; +} diff --git a/plugins/toshiba/toshiba-nvme.h b/plugins/toshiba/toshiba-nvme.h new file mode 100644 index 0000000..6208f5d --- /dev/null +++ b/plugins/toshiba/toshiba-nvme.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#undef CMD_INC_FILE +#define CMD_INC_FILE plugins/toshiba/toshiba-nvme + +#if !defined(TOSHIBA_NVME) || defined(CMD_HEADER_MULTI_READ) +#define TOSHIBA_NVME + +#include "cmd.h" +#include "plugin.h" + +PLUGIN(NAME("toshiba", "Toshiba NVME plugin", NVME_VERSION), + COMMAND_LIST( + ENTRY("vs-smart-add-log", "Extended SMART information", vendor_log) + ENTRY("vs-internal-log", "Get Internal Log", internal_log) + ENTRY("clear-pcie-correctable-errors", "Clear PCIe correctable error count", clear_correctable_errors) + ) +); + +#endif + +#include "define_cmd.h" |