986 lines
26 KiB
C
986 lines
26 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (c) 2023 Solidigm.
|
|
*
|
|
* Authors: leonardo.da.cunha@solidigm.com
|
|
* shankaralingegowda.singonahalli@solidigm.com
|
|
* haro.panosyan@solidigm.com
|
|
*/
|
|
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <inttypes.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
|
|
#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;
|
|
};
|
|
|
|
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;
|
|
|
|
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)
|
|
return -errno;
|
|
|
|
bytes_remaining -= bytes_written;
|
|
buffer += bytes_written;
|
|
}
|
|
printf("Successfully wrote %s to %s\n", log->desc, file_path);
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
|
|
return err;
|
|
}
|
|
|
|
static int ilog_dump_telemetry(struct ilog *ilog, enum log_type ttype)
|
|
{
|
|
int err = 0;
|
|
enum nvme_telemetry_da da;
|
|
size_t mdts;
|
|
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;
|
|
mdts = ilog->id_ctrl.mdts;
|
|
|
|
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 = sldgm_dynamic_telemetry(dev_fd(ilog->dev), true, false, false, mdts,
|
|
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 = sldgm_dynamic_telemetry(dev_fd(ilog->dev), false, true, true, mdts,
|
|
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_attached_list;
|
|
struct nvme_ns_list ns_allocated_list;
|
|
__u32 j = 0;
|
|
|
|
struct log identify_base_list[] = {
|
|
{NVME_IDENTIFY_CNS_NS_ACTIVE_LIST, "Id Active Namespace ID list",
|
|
sizeof(ns_attached_list), (__u8 *) &ns_attached_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",
|
|
sizeof(ns_allocated_list), (__u8 *) &ns_allocated_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_NS_CTRL_LIST, "Id Namespace Id Controller List"},
|
|
};
|
|
|
|
struct log allocated = {NVME_IDENTIFY_CNS_ALLOCATED_NS, "Allocated Namespace Data",
|
|
NVME_IDENTIFY_DATA_SIZE, NULL};
|
|
|
|
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_attached_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_attached_list.ns[j]);
|
|
|
|
if (err == 0)
|
|
ilog->count++;
|
|
}
|
|
j++;
|
|
}
|
|
|
|
j = 0;
|
|
while (ns_allocated_list.ns[j]) {
|
|
int err = ilog_dump_identify_page(ilog, &allocated, ns_allocated_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;
|
|
size_t max_data_tx;
|
|
|
|
_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,
|
|
};
|
|
|
|
max_data_tx = (1 << ilog->id_ctrl.mdts) * NVME_LOG_PAGE_PDU_SIZE;
|
|
do {
|
|
err = nvme_get_log_page(dev_fd(ilog->dev), max_data_tx, &args);
|
|
max_data_tx /= 2;
|
|
} while (err == -EPERM && max_data_tx >= NVME_LOG_PAGE_PDU_SIZE);
|
|
|
|
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;
|
|
}
|