优秀的编程知识分享平台

网站首页 > 技术文章 正文

NVME CLI源码之:error-log源码学习

nanyue 2025-05-21 15:30:35 技术文章 5 ℃

前言:

Nvme cli命令之error-log:根据nvme协议,error-log用于记录命令完成后的错误信息。error-log可以记录1-64个error,这个需要看identify中elpe字段定义的大小。

error log page定义为64bit大小,各字段值所在bit如下表:

error-log的协议entry info各字段的值

源码入口:

我们从源码中的nvme-builtin.h文件可以看到所有的nvme command,在29行找到error-logg命令,以及它指向的函数get_error_log。


ENTRY("error-log", "Retrieve Error Log, show it", get_error_log)

erro-log函数解析

函数中起始部分主要定义多个参数和其默认值:


static int get_error_log(int argc, char **argv, struct command *cmd, struct plugin *plugin)
{
    const char *desc = "Retrieve specified number of "\
        "error log entries from a given device "\
        "in either decoded format (default) or binary.";
    const char *log_entries = "number of entries to retrieve";
    const char *raw_binary = "dump in binary format";
    struct nvme_id_ctrl ctrl;
    int err, fmt, fd;
    struct config {
        __u32 log_entries;
        int   raw_binary;
        char *output_format;
    };
    struct config cfg = {
        .log_entries  = 64,
        .output_format = "normal",
    };
    const struct argconfig_commandline_options command_line_options[] = {
        {"log-entries",   'e', "NUM", CFG_POSITIVE, &cfg.log_entries,   required_argument, log_entries},
        {"raw-binary",    'b', "",    CFG_NONE,     &cfg.raw_binary,    no_argument,       raw_binary},
        {"output-format", 'o', "FMT", CFG_STRING,   &cfg.output_format, required_argument, output_format },
        {NULL}
    };


log-entries是返回多少个error数目,像下图所示:


entries数目不同返回不同个数error-log

raw-binary是指返回格式为原始数据,output-format返回格式,默认值是normal。从代码中可以看到当output-format和raw-binary同时指定时,会以raw-binary为返回格式:


   fmt = validate_output_format(cfg.output_format);
    if (fmt < 0) {
        err = fmt;
        goto close_fd;
    }
    if (cfg.raw_binary)
        fmt = BINARY;

其后在parse_and_open做命令行的参数解析和open nvme设备的操作:

    fd = parse_and_open(argc, argv, desc, command_line_options, &cfg, sizeof(cfg));
    if (fd < 0)
        return fd;

函数接着对nvme设备做identify信息查询,获取elpe值,也就是文章开头指出的那个,定义了error log的个数:

    err = nvme_identify_ctrl(fd, &ctrl);
    if (err < 0)
        perror("identify controller");
    else if (err) {
        fprintf(stderr, "could not identify controller\n");
        err = ENODEV;
    } else {
        struct nvme_error_log_page *err_log;
        cfg.log_entries = min(cfg.log_entries, ctrl.elpe + 1);
        err_log = calloc(cfg.log_entries, sizeof(struct nvme_error_log_page));
        if (!err_log) {
            fprintf(stderr, "could not alloc buffer for error log\n");
            err = ENOMEM;
            goto close_fd;
        }

这么做是确定具体需要查询多少个error log:

cfg.log_entries = min(cfg.log_entries, ctrl.elpe + 1);

以上将处理完成的参数传入到nvme_error_log函数;


int nvme_error_log(int fd, int entries, struct nvme_error_log_page *err_log)
{
    return nvme_get_log(fd, NVME_NSID_ALL, NVME_LOG_ERROR, false,
            entries * sizeof(*err_log), err_log);
}
```
代码中NVME_NSID_ALL=0xffffffff,NVME_LOG_ERROR=0x01,data_len=entries * sizeof(*err_log),其中error_log结构体如下,sizeof算出长度为512bit(64位对齐);
```C
struct nvme_error_log_page {
    __u64   error_count;
    __u16   sqid;
    __u16   cmdid;
    __u16   status_field;
    __u16   parm_error_location;
    __u64   lba;
    __u32   nsid;
    __u8    vs;
    __u8    resv[3];
    __u64   cs;
    __u8    resv2[24];
}

在nvme_get_log函数中,ptr参数为data,data由上传入为error-log的结构体,offset初始值为0,代码以4k大小做切割,循环查error-log,所以当entries * sizeof(*err_log)的值大于4096时,会去循环查询;


int nvme_get_log(int fd, __u32 nsid, __u8 log_id, bool rae, __u32 data_len, void *data)
{
    void *ptr = data;
    __u32 offset = 0, xfer_len = data_len;
    int ret;
    /*
     * 4k is the smallest possible transfer unit, so by
     * restricting ourselves for 4k transfers we avoid having
     * to check the MDTS value of the controller.
     */
    do {
        xfer_len = data_len - offset;
        if (xfer_len > 4096)
            xfer_len = 4096;




        ret = nvme_get_log13(fd, nsid, log_id, NVME_NO_LOG_LSP,
                     offset, 0, rae, xfer_len, ptr);
        if (ret)
            return ret;
        offset += xfer_len;
        ptr += xfer_len;
    } while (offset < data_len);
    return 0;
}

以entries=1为例,xfer_len=512,nsid=0xffffffff,log_id=0x01, NVME_NO_LOG_LSP=0,rae=false;


int nvme_get_log13(int fd, __u32 nsid, __u8 log_id, __u8 lsp, __u64 lpo,
                 __u16 lsi, bool rae, __u32 data_len, void *data)
{
    struct nvme_admin_cmd cmd = {
        .opcode     = nvme_admin_get_log_page,
        .nsid       = nsid,
        .addr       = (__u64)(uintptr_t) data,
        .data_len   = data_len,
    };
    __u32 numd = (data_len >> 2) - 1;
    __u16 numdu = numd >> 16, numdl = numd & 0xffff;
    cmd.cdw10 = log_id | (numdl << 16) | (rae ? 1 << 15 : 0);
    if (lsp)
        cmd.cdw10 |= lsp << 8;
    cmd.cdw11 = numdu | (lsi << 16);
    cmd.cdw12 = lpo;
    cmd.cdw13 = (lpo >> 32);
    return nvme_submit_admin_passthru(fd, &cmd);
}

cmd结构体包含8个参数,opcode=0x02,nsid=0xffffffff,addr=data为error_log结构体,data_len=512;cdw10-13结合协议文档来看;

CDW10的处理:

根据协议中CDW10的定义,CDW10是为32bit的值,其中0-7bit代表log_id,8-11bit代表lsp,12-14作为保留,15bit代表RAE,16-31bit代表NUMDL;

所以在代码中能看到以下处理,NUMDL左移16位,RAE左移15位,LSP左移8位,cdw10等于各参数相加的值;

    cmd.cdw10 = log_id | (numdl << 16) | (rae ? 1 << 15 : 0);
    if (lsp)
        cmd.cdw10 |= lsp << 8;

协议定义numdl为用户传入的data_len的dword低16位返回,numdu为高16位,CDW11协议定义0-15bit为NUMDU,numdu和numdl为DWORD,即4字节,协议定义为0 base的值,所以numd = (data_len >> 2) - 1;所以有以下处理,numdu为numd的高16位,往右移16位,numdl为numd的低16位,与0xffff相与:


    __u32 numd = (data_len >> 2) - 1;
    __u16 numdu = numd >> 16, numdl = numd & 0xffff;

协议中定义cdw12和cdw13为log offset的dword 高低位,cdw12等于lpo,cdw13先右移2位再右移16位;

    cmd.cdw12 = lpo;
    cmd.cdw13 = (lpo >> 32);

最终我们构造出admin-passthru的命令来代替error-log命令下发:

用admin-passthru替代error-log下发查询

error-log查询结果

可以看出通过对源码的解读,可以用admin-passthru的命令来下发更高级命令下发得到的结果;

Tags:

最近发表
标签列表