网站首页 > 技术文章 正文
前言:
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的命令来下发更高级命令下发得到的结果;
猜你喜欢
- 2025-05-21 关于 synchronized,这儿比你想知道的还要多
- 2025-05-21 STM32F4 HAL库学习笔记之串口通讯 2
- 2025-05-21 高并发服务器epoll接口、epoll Reactor(反应堆)模型详解
- 2025-05-21 写一个Nginx的模块没有那么难
- 2025-05-21 浅析weak指针的实现
- 2025-05-21 Objective-C的本质
- 2025-05-21 PhantomReference 和 WeakReference 究竟有何不同
- 2025-05-21 技术分享 | ROS里多机通信配置太繁琐?带你换个方式来操作
- 2025-05-21 为什么使用binder机制作为IPC通信方式
- 2025-05-21 Linux的epoll使用LT+非阻塞IO和ET+非阻塞IO有效率上的区别吗?
- 最近发表
- 标签列表
-
- cmd/c (64)
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- sqlset (64)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- chromepost (65)
- c++int转char (75)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- js数组插入 (83)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)