// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2023 Solidigm. * * Authors: leonardo.da.cunha@solidigm.com * shankaralingegowda.singonahalli@solidigm.com */ #include #include #include #include #include #include #include #include "common.h" #include "nvme.h" #include "libnvme.h" #include "plugin.h" #include "nvme-print.h" #include "solidigm-util.h" #define DWORD_SIZE 4 #define LOG_FILE_PERMISSION 0644 enum log_type { NLOG = 0, EVENTLOG = 1, ASSERTLOG = 2, HIT, CIT, ALL }; #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 { char *out_dir; char *type; bool verbose; }; struct ilog { struct nvme_dev *dev; struct config *cfg; int count; struct nvme_id_ctrl id_ctrl; enum nvme_telemetry_da max_da; __u32 max_tx; }; 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 ilog_dump_assert_logs(struct ilog *ilog) { __u8 buf[INTERNAL_LOG_MAX_BYTE_TRANSFER]; __u8 head_buf[INTERNAL_LOG_MAX_BYTE_TRANSFER]; char file_path[PATH_MAX] = {0}; char file_name[] = "AssertLog.bin"; struct assert_dump_header *ad = (struct assert_dump_header *) head_buf; struct nvme_passthru_cmd cmd = { .opcode = 0xd2, .nsid = NVME_NSID_ALL, .addr = (unsigned long)(void *)head_buf, .cdw12 = ASSERTLOG, .cdw13 = 0, }; int output, err; err = read_header(&cmd, dev_fd(ilog->dev)); if (err) return err; snprintf(file_path, sizeof(file_path), "%.*s/%s", (int) (sizeof(file_path) - sizeof(file_name) - 1), ilog->cfg->out_dir, file_name); output = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, LOG_FILE_PERMISSION); 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 (ilog->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(ilog->dev), false); if (err) { close(output); return err; } } close(output); printf("Successfully wrote Assert to %s\n", file_path); return err; } static int ilog_dump_event_logs(struct ilog *ilog) { __u8 buf[INTERNAL_LOG_MAX_BYTE_TRANSFER]; __u8 head_buf[INTERNAL_LOG_MAX_BYTE_TRANSFER]; char file_path[PATH_MAX] = {0}; struct event_dump_header *ehdr = (struct event_dump_header *) head_buf; struct nvme_passthru_cmd cmd = { .opcode = 0xd2, .nsid = NVME_NSID_ALL, .addr = (unsigned long)(void *)head_buf, .cdw12 = EVENTLOG, .cdw13 = 0, }; int output; int core_num, err; err = read_header(&cmd, dev_fd(ilog->dev)); if (err) return err; snprintf(file_path, sizeof(file_path) - 1, "%s/EventLog.bin", ilog->cfg->out_dir); output = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, LOG_FILE_PERMISSION); 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 (ilog->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 (ilog->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(ilog->dev), false); if (err) { close(output); return err; } } close(output); printf("Successfully wrote Events 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 ilog_dump_nlogs(struct ilog *ilog, int core) { int err = 0; __u32 count, core_num; __u8 buf[INTERNAL_LOG_MAX_BYTE_TRANSFER]; char file_path[PATH_MAX] = {0}; struct nlog_dump_header_common *nlog_header = (struct nlog_dump_header_common *)buf; struct nvme_passthru_cmd cmd = { .opcode = 0xd2, .nsid = NVME_NSID_ALL, .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(ilog->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) - 1, "%s/NLog.bin", ilog->cfg->out_dir); output = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, LOG_FILE_PERMISSION); 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 (ilog->cfg->verbose) print_nlog_header(buf); cmd.cdw13 = 0x400; err = cmd_dump_repeat(&cmd, nlog_header->nlogbytesize / 4, output, dev_fd(ilog->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 Nlog to %s\n", file_path); } return err; } int ensure_dir(const char *parent_dir_name, const char *name) { char file_path[PATH_MAX] = {0}; struct stat sb; snprintf(file_path, sizeof(file_path) - 1, "%s/%s", parent_dir_name, name); if (!(stat(file_path, &sb) == 0 && S_ISDIR(sb.st_mode))) { if (mkdir(file_path, 777) != 0) { perror(file_path); return -errno; } } return 0; } struct log { __u8 id; const char *desc; size_t buffer_size; __u8 *buffer; }; static int log_save(struct log *log, const char *parent_dir_name, const char *subdir_name, const char *file_name, __u8 *buffer, size_t buf_size) { _cleanup_fd_ int output = -1; char file_path[PATH_MAX] = {0}; size_t bytes_remaining = 0; int err = 0; ensure_dir(parent_dir_name, subdir_name); snprintf(file_path, sizeof(file_path) - 1, "%s/%s/%s", parent_dir_name, subdir_name, file_name); output = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, LOG_FILE_PERMISSION); if (output < 0) return -errno; bytes_remaining = buf_size; while (bytes_remaining) { ssize_t bytes_written = write(output, buffer, bytes_remaining); if (bytes_written < 0) { err = -errno; goto log_save_close_output; } bytes_remaining -= bytes_written; buffer += bytes_written; } printf("Successfully wrote %s to %s\n", log->desc, file_path); log_save_close_output: close(output); return err; } static int ilog_dump_identify_page(struct ilog *ilog, struct log *cns, __u32 nsid) { __u8 data[NVME_IDENTIFY_DATA_SIZE]; __u8 *buff = cns->buffer ? cns->buffer : data; char filename[sizeof( "cntid_XXXXX_cns_XXX_nsid_XXXXXXXXXX_nvmsetid_XXXXX_csi_XXX.bin")] = {0}; int err = nvme_identify_cns_nsid(dev_fd(ilog->dev), cns->id, nsid, buff); if (err) return err; snprintf(filename, sizeof(filename) - 1, "cntid_0_cns_%d_nsid_%d_nvmsetid_0_csi_0.bin", cns->id, nsid); return log_save(cns, ilog->cfg->out_dir, "identify", filename, buff, sizeof(data)); } static int ilog_ensure_dump_id_ctrl(struct ilog *ilog) { static bool first = true; static int err; struct log idctrl = {NVME_IDENTIFY_CNS_CTRL, "Id Controller Data", sizeof(ilog->id_ctrl), (__u8 *) &ilog->id_ctrl}; if (!first) return err; first = false; err = ilog_dump_identify_page(ilog, &idctrl, 0); if (err) return err; ilog->count++; if (ilog->id_ctrl.lpa & 0x8) ilog->max_da = NVME_TELEMETRY_DA_3; if (ilog->id_ctrl.lpa & 0x40) ilog->max_da = NVME_TELEMETRY_DA_4; /* assuming CAP.MPSMIN is zero minimum Memory Page Size is at least 4096 bytes */ ilog->max_tx = (1 << ilog->id_ctrl.mdts) * NVME_LOG_PAGE_PDU_SIZE; if (ilog->max_tx > DRIVER_MAX_TX_256K) ilog->max_tx = DRIVER_MAX_TX_256K; return err; } static int ilog_dump_telemetry(struct ilog *ilog, enum log_type ttype) { int err = 0; enum nvme_telemetry_da da; size_t max_data_tx; const char *file_name; struct nvme_feat_host_behavior prev = {0}; bool host_behavior_changed = false; struct log log = {0}; err = ilog_ensure_dump_id_ctrl(ilog); if (err) return err; da = ilog->max_da; max_data_tx = ilog->max_tx; if (da == 4) { __u32 result; int err = nvme_get_features_host_behavior(dev_fd(ilog->dev), 0, &prev, &result); if (!err && !prev.etdas) { struct nvme_feat_host_behavior da4_enable = prev; da4_enable.etdas = 1; nvme_set_features_host_behavior(dev_fd(ilog->dev), 0, &da4_enable); host_behavior_changed = true; } } switch (ttype) { case HIT: file_name = "lid_0x07_lsp_0x01_lsi_0x0000.bin"; log.desc = "Host Initiated Telemetry"; err = nvme_get_telemetry_log(dev_fd(ilog->dev), true, false, false, max_data_tx, da, (struct nvme_telemetry_log **) &log.buffer, &log.buffer_size); break; case CIT: file_name = "lid_0x08_lsp_0x00_lsi_0x0000.bin"; log.desc = "Controller Initiated Telemetry"; err = nvme_get_telemetry_log(dev_fd(ilog->dev), false, true, true, max_data_tx, da, (struct nvme_telemetry_log **) &log.buffer, &log.buffer_size); break; default: return -EINVAL; } if (host_behavior_changed) nvme_set_features_host_behavior(dev_fd(ilog->dev), 0, &prev); if (err) return err; err = log_save(&log, ilog->cfg->out_dir, "log_pages", file_name, log.buffer, log.buffer_size); return err; } static int ilog_dump_identify_pages(struct ilog *ilog) { struct nvme_ns_list ns_list; __u32 j = 0; struct log identify_base_list[] = { {NVME_IDENTIFY_CNS_NS_ACTIVE_LIST, "Id Active Namespace ID list", sizeof(ns_list), (__u8 *) &ns_list}, {NVME_IDENTIFY_CNS_NVMSET_LIST, "Id NVM Set List"}, {NVME_IDENTIFY_CNS_CSI_CTRL, "Id I/O Command Set specific"}, {NVME_IDENTIFY_CNS_ALLOCATED_NS_LIST, "Id Allocated Namespace ID list"}, {NVME_IDENTIFY_CNS_CTRL_LIST, "Id Controller List"} }; struct log identify_ns_required_list[] = { {NVME_IDENTIFY_CNS_NS, "Id Namespace data"}, {NVME_IDENTIFY_CNS_NS_DESC_LIST, "Id Namespace Id Descriptor list"}, {NVME_IDENTIFY_CNS_CSI_NS, "Id Namespace ID I/O Command Set specific"}, {NVME_IDENTIFY_CNS_CSI_INDEPENDENT_ID_NS, "I/O Command Set Independent Identify Namespace Data"}, {NVME_IDENTIFY_CNS_ALLOCATED_NS, "Id Namespace data "}, {NVME_IDENTIFY_CNS_NS_CTRL_LIST, "Id Namespace Id Controller List"}, }; ilog_ensure_dump_id_ctrl(ilog); for (int i = 0; i < ARRAY_SIZE(identify_base_list); i++) { int err = ilog_dump_identify_page(ilog, &identify_base_list[i], 0); if (err == 0) ilog->count++; } while (ns_list.ns[j]) { for (int i = 0; i < ARRAY_SIZE(identify_ns_required_list); i++) { int err = ilog_dump_identify_page(ilog, &identify_ns_required_list[i], ns_list.ns[j]); if (err == 0) ilog->count++; } j++; } return 0; } static int ilog_dump_log_page(struct ilog *ilog, struct log *lp, __u32 nsid) { __u8 *buff = lp->buffer; char filename[sizeof("lid_0xXX_lsp_0xXX_lsi_0xXXXX.bin")] = {0}; int err; if (!lp->buffer_size) return -EINVAL; if (!buff) { buff = nvme_alloc(lp->buffer_size); if (!buff) return -ENOMEM; } err = nvme_get_nsid_log(dev_fd(ilog->dev), 0, lp->id, 0, lp->buffer_size, buff); if (err) return err; snprintf(filename, sizeof(filename), "lid_0x%02x_lsp_0x00_lsi_0x0000.bin", lp->id); return log_save(lp, ilog->cfg->out_dir, "log_pages", filename, buff, lp->buffer_size); } static int ilog_dump_no_lsp_log_pages(struct ilog *ilog) { struct lba_status_info { __u32 lslplen; __u32 nlslne; __u32 estulb; __u16 rsvd; __u16 lsgc; } lba_status = {}; __u64 num_entries = 0; struct log log_page_dependent_list[] = { {NVME_LOG_LID_LBA_STATUS}, {NVME_LOG_LID_ENDURANCE_GRP_EVT}, }; struct log log_page_base_list[] = { {NVME_LOG_LID_SUPPORTED_LOG_PAGES, NULL, sizeof(struct nvme_supported_log_pages)}, {NVME_LOG_LID_ERROR, NULL, (ilog->id_ctrl.elpe + 1) * sizeof(struct nvme_error_log_page)}, {NVME_LOG_LID_SMART, NULL, sizeof(struct nvme_smart_log)}, {NVME_LOG_LID_FW_SLOT, NULL, sizeof(struct nvme_firmware_slot)}, {NVME_LOG_LID_CHANGED_NS, NULL, sizeof(struct nvme_ns_list)}, {NVME_LOG_LID_CMD_EFFECTS, NULL, sizeof(struct nvme_cmd_effects_log)}, {NVME_LOG_LID_DEVICE_SELF_TEST, NULL, sizeof(struct nvme_self_test_log)}, {NVME_LOG_LID_LBA_STATUS, NULL, sizeof(lba_status), (__u8 *) &lba_status}, {NVME_LOG_LID_ENDURANCE_GRP_EVT, NULL, sizeof(num_entries), (__u8 *) &num_entries}, {NVME_LOG_LID_FID_SUPPORTED_EFFECTS, NULL, sizeof(struct nvme_fid_supported_effects_log)}, {NVME_LOG_LID_MI_CMD_SUPPORTED_EFFECTS, NULL, sizeof(struct nvme_mi_cmd_supported_effects_log)}, {NVME_LOG_LID_CMD_AND_FEAT_LOCKDOWN, NULL, 512}, {NVME_LOG_LID_PHY_RX_EOM, NULL, 512}, {NVME_LOG_LID_SANITIZE, NULL, sizeof(struct nvme_sanitize_log_page)}, {0xC0, "OCP or VU SMART / Health Information Extended", 512}, {0xC1, "OCP Error Recovery or VU Latency Reads", 512}, {0xC2, "OCP Firmware Activation History or VU Latency Writes", 4096}, {0xC3, "OCP Latency Monitor", 512}, {0xC4, "OCP Device Capabilities or VU Endurance Manager Statistics", 4096}, {0xC5, "OCP Unsupported Requirements or VU Tempeture Statistics", 4096}, {0xC7, "OCP TCG Configuration", 512}, {0xCA, "SMART Attributes", 512}, {0xd5, "Tempeture Statistics", 512}, {0xfe, "Latency Outlier", 8192}, }; for (int i = 0; i < ARRAY_SIZE(log_page_base_list); i++) { log_page_base_list[i].desc = log_page_base_list[i].desc ? log_page_base_list[i].desc : nvme_log_to_string(log_page_base_list[i].id); if (!ilog_dump_log_page(ilog, &log_page_base_list[i], 0)) ilog->count++; } /* if needed, patch logs based on retrieved log size */ if (lba_status.lslplen > sizeof(lba_status)) log_page_dependent_list[0].buffer_size = lba_status.lslplen; if (num_entries) log_page_dependent_list[1].buffer_size = sizeof(num_entries) + (num_entries * sizeof(__u16)); for (int i = 0; i < ARRAY_SIZE(log_page_dependent_list); i++) { log_page_dependent_list[i].desc = log_page_dependent_list[i].desc ? log_page_dependent_list[i].desc : nvme_log_to_string(log_page_dependent_list[i].id); ilog_dump_log_page(ilog, &log_page_dependent_list[i], 0); } return 0; } static int ilog_dump_pel(struct ilog *ilog) { struct log lp = { NVME_LOG_LID_PERSISTENT_EVENT, nvme_log_to_string(NVME_LOG_LID_PERSISTENT_EVENT) }; void *pevent_log_full; int err; struct nvme_get_log_args args; _cleanup_free_ struct nvme_persistent_event_log *pevent = NULL; _cleanup_huge_ struct nvme_mem_huge mh = {0}; err = nvme_get_log_persistent_event(dev_fd(ilog->dev), NVME_PEVENT_LOG_RELEASE_CTX, sizeof(*pevent), pevent); if (err) return err; pevent = nvme_alloc(sizeof(*pevent)); if (!pevent) return -ENOMEM; err = nvme_get_log_persistent_event(dev_fd(ilog->dev), NVME_PEVENT_LOG_EST_CTX_AND_READ, sizeof(*pevent), pevent); if (err) return err; lp.buffer_size = le64_to_cpu(pevent->tll); pevent_log_full = nvme_alloc_huge(lp.buffer_size, &mh); if (!pevent_log_full) return -ENOMEM; err = nvme_get_log_persistent_event(dev_fd(ilog->dev), NVME_PEVENT_LOG_READ, lp.buffer_size, pevent_log_full); args = (struct nvme_get_log_args) { .lpo = 0, .result = NULL, .log = pevent_log_full, .args_size = sizeof(args), .fd = dev_fd(ilog->dev), .timeout = NVME_DEFAULT_IOCTL_TIMEOUT, .lid = NVME_LOG_LID_PERSISTENT_EVENT, .len = lp.buffer_size, .nsid = NVME_NSID_ALL, .csi = NVME_CSI_NVM, .lsi = NVME_LOG_LSI_NONE, .lsp = NVME_PEVENT_LOG_READ, .uuidx = NVME_UUID_NONE, .rae = false, .ot = false, }; err = nvme_get_log_page(dev_fd(ilog->dev), ilog->max_tx, &args); if (err) return err; err = log_save(&lp, ilog->cfg->out_dir, "log_pages", "lid_0x0d_lsp_0x00_lsi_0x0000.bin", pevent_log_full, lp.buffer_size); nvme_get_log_persistent_event(dev_fd(ilog->dev), NVME_PEVENT_LOG_RELEASE_CTX, sizeof(*pevent), pevent); return err; } int solidigm_get_internal_log(int argc, char **argv, struct command *command, struct plugin *plugin) { char sn_prefix[sizeof(((struct nvme_id_ctrl *)0)->sn)+1]; char date_str[sizeof("-YYYYMMDDHHMMSS")]; char full_folder[PATH_MAX] = {0}; char unique_folder[sizeof(sn_prefix)+sizeof(date_str)-1] = {0}; char *initial_folder; char zip_name[PATH_MAX] = {0}; char *output_path; struct ilog ilog = {0}; int err; _cleanup_nvme_dev_ struct nvme_dev *dev = NULL; enum log_type log_type = ALL; char type_ALL[] = "ALL"; time_t current_time; DIR *dir; const char *desc = "Get Debug Firmware Logs and save them."; const char *type = "Log type; Defaults to ALL."; const char *out_dir = "Output directory; defaults to current working directory."; const char *verbose = "To print out verbose info."; struct config cfg = { .out_dir = ".", .type = type_ALL, }; OPT_ARGS(opts) = { OPT_STRING("type", 't', "ALL|CIT|HIT|NLOG|ASSERT|EVENT", &cfg.type, type), OPT_STRING("dir-name", 'd', "DIRECTORY", &cfg.out_dir, out_dir), OPT_FLAG("verbose", 'v', &cfg.verbose, verbose), OPT_END() }; err = parse_and_open(&dev, argc, argv, desc, opts); if (err) return err; ilog.dev = dev; ilog.cfg = &cfg; for (char *p = cfg.type; *p; ++p) *p = toupper(*p); if (!strcmp(cfg.type, "ALL")) log_type = ALL; else if (!strcmp(cfg.type, "HIT")) log_type = HIT; else if (!strcmp(cfg.type, "CIT")) log_type = CIT; else if (!strcmp(cfg.type, "NLOG")) log_type = NLOG; else if (!strcmp(cfg.type, "ASSERT")) log_type = ASSERTLOG; else if (!strcmp(cfg.type, "EVENT")) log_type = EVENTLOG; else { fprintf(stderr, "Invalid log type: %s\n", cfg.type); return -EINVAL; } dir = opendir(cfg.out_dir); if (dir) closedir(dir); else { perror(cfg.out_dir); return -errno; } initial_folder = cfg.out_dir; err = get_serial_number(sn_prefix, dev_fd(dev)); if (err) return err; current_time = time(NULL); strftime(date_str, sizeof(date_str), "-%Y%m%d%H%M%S", localtime(¤t_time)); snprintf(unique_folder, sizeof(unique_folder), "%s%s", sn_prefix, date_str); snprintf(full_folder, sizeof(full_folder) - 1, "%s/%s", cfg.out_dir, unique_folder); if (mkdir(full_folder, 0755) != 0) { perror("mkdir"); return -errno; } cfg.out_dir = full_folder; output_path = full_folder; /* Retrieve first logs that records actions to retrieve other logs */ if (log_type == ALL || log_type == HIT) { err = ilog_dump_telemetry(&ilog, HIT); if (err == 0) ilog.count++; else if (err < 0) perror("Error retrieving Host Initiated Telemetry"); } if (log_type == ALL || log_type == NLOG) { err = ilog_dump_nlogs(&ilog, -1); if (err == 0) ilog.count++; else if (err < 0) perror("Error retrieving Nlog"); } if (log_type == ALL || log_type == CIT) { err = ilog_dump_telemetry(&ilog, CIT); if (err == 0) ilog.count++; else if (err < 0) perror("Error retrieving Controller Initiated Telemetry"); } if (log_type == ALL || log_type == ASSERTLOG) { err = ilog_dump_assert_logs(&ilog); if (err == 0) ilog.count++; else if (err < 0) perror("Error retrieving Assert log"); } if (log_type == ALL || log_type == EVENTLOG) { err = ilog_dump_event_logs(&ilog); if (err == 0) ilog.count++; else if (err < 0) perror("Error retrieving Event log"); } if (log_type == ALL) { err = ilog_dump_identify_pages(&ilog); if (err < 0) perror("Error retrieving Identify pages"); err = ilog_dump_pel(&ilog); if (err < 0) perror("Error retrieving Persistent Event Log page"); err = ilog_dump_no_lsp_log_pages(&ilog); if (err < 0) perror("Error retrieving no LSP Log pages"); } if (ilog.count > 0) { int ret_cmd; char *cmd; char *quiet = cfg.verbose ? "" : " -q"; snprintf(zip_name, sizeof(zip_name) - 1, "%s.zip", unique_folder); if (asprintf(&cmd, "cd \"%s\" && zip -MM -r \"../%s\" ./* %s", cfg.out_dir, zip_name, quiet) < 0) { err = errno; perror("Can't allocate string for zip command"); goto out; } printf("Compressing logs to %s\n", zip_name); ret_cmd = system(cmd); if (ret_cmd) perror(cmd); else { output_path = zip_name; free(cmd); if (asprintf(&cmd, "rm -rf %s", cfg.out_dir) < 0) { err = errno; perror("Can't allocate string for cleanup"); goto out; } if (system(cmd) != 0) perror("Failed removing logs folder"); } free(cmd); } out: if (ilog.count == 0) { if (err > 0) nvme_show_status(err); } else if ((ilog.count > 1) || cfg.verbose) printf("Total: %d log files in %s/%s\n", ilog.count, initial_folder, output_path); return err; }