学习Linux NVMe Driver之前,我们得先了解一下Driver在Linux架构中的位置,如下图. NVMe driver在Block Layer之下,负责与NVMe设备交互。
为了紧跟时代的大趋势,现在的NVMe driver已经很强大了,也可以支持NVMe over Fabric相关设备,如下图。不过,我们这里的学习目前还主要以NVMe over PCIe为主。
小编这里学习的NVMe driver内容是Linux-4.10.6中的相关代码,先看一下NVMe driver的文件目录:包括两个子文件夹: host和target,还有两个文件: Kconfig和Makefile.
关于如何分析driver的内容时,古猫一开始也是不得其法,后得到大神的指点,说:"在分析一个driver时,最好先看这个driver相关的kconfig及Makefile文件,了解其文件架构,再阅读相关的source code". 此时,已经是柳暗花明咯~
Kconfig 文件的作用是:
1.控制make menuconfig时, 出现的配置选项;
2.根据用户配置界面的选择,将配置结果保存在.config 配置文件(该文件将提供给Makefile使用,用以决定要编译的内核组件以及如何编译)
先看一下nvme/host/kconfig的内容(NVMeOF相关内容已省略,后续不再注明):
config NVME_CORE
tristate
config BLK_DEV_NVME
tristate "NVM Express block device"
depends on PCI && BLOCK
select NVME_CORE
config BLK_DEV_NVME_SCSI
bool "SCSI emulation for NVMe device nodes"
depends on NVME_CORE
接着,再看一下nvme/host/Makefile的内容:
obj-$(CONFIG_NVME_CORE) += nvme-core.o
obj-$(CONFIG_BLK_DEV_NVME) += nvme.o
nvme-core-y := core.o
nvme-core-$(CONFIG_BLK_DEV_NVME_SCSI) += scsi.o
nvme-y += pci.o
从Kconfig和Makefile来看,了解NVMe over PCIe相关的知识点,我们主要关注三个文件就好:core.c, pci.c, scsi.c.
那我们就先从core.c开始学习,打开core.c文件,找到程序入口module_init(nvme_core_init),
int __init nvme_core_init(void)
{
int result;
//1. 注册字符设备"nvme"
result = __register_chrdev(nvme_char_major, 0, NVME_MINORS, "nvme", &nvme_dev_fops);
if (result < 0)
return result;
else if (result > 0)
nvme_char_major = result;
//2. 新建一个nvme class,拥有者(Owner)是为THIS_MODULE
nvme_class = class_create(THIS_MODULE, "nvme");
//如果有Error发生,删除字符设备nvme
if (IS_ERR(nvme_class)) {
result = PTR_ERR(nvme_class);
goto unregister_chrdev;
}
return 0;
unregister_chrdev:
__unregister_chrdev(nvme_char_major, 0, NVME_MINORS, "nvme");
return result;
}
从上面的code来看,nvme_core_init主要做了两件事:
-
调用__register_chrdev函数,注册一个名为"nvme"的字符设备.
-
调用class_create函数,动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加到内核中。创建的逻辑类位于/sys/class/.
在注册字符设备时,涉及到了设备号的知识点:
一个字符设备或者块设备都有一个主设备号(Major)和次设备号(Minor)。主设备号用来表示一个特定的驱动程序,次设备号用来表示使用该驱动程序的各个设备。比如,我们在Linux系统上挂了两块NVMe SSD. 那么主设备号就可以自动分配一个数字(比如8),次设备号分别为1和2.
例如,在32位机子中,设备号共32位,高12位表示主设备号,低20位表示次设备号。
主设备号12位 次设备号20位
有了上面已经注册的字符设备,我们就可以通过open,ioctrl,release接口对其进行操作了。nvme字符设备的文件操作结构体"nvme_dev_fops"定义如下:
static const struct file_operations nvme_dev_fops = {
.owner = THIS_MODULE,
// open()用来打开一个设备,在该函数中可以对设备进行初始化。
.open = nvme_dev_open,
// release()用来释放open()函数中申请的资源。
.release = nvme_dev_release,
// nvme_dev_ioctl()提供一种执行设备特定命令的方法。
.unlocked_ioctl = nvme_dev_ioctl,
.compat_ioctl = nvme_dev_ioctl,
/* 这里的ioctl有两个unlocked_ioctl和compat_ioctl,如果这两个同时存在的话,优先调用unlocked_ioctl,对于compat_ioctl只有打来了CONFIG_COMPAT 才会调用compat_ioctl。obj-$(CONFIG_COMPAT) += compat.o compat_ioctl.o */
};
先查看一下nvme_dev_open的定义:
static int nvme_dev_open(struct inode *inode, struct file *file)
{
struct nvme_ctrl *ctrl;
int instance = iminor(inode); // 从Incode中获取次设备号。
int ret = -ENODEV; // 默认尚未分配到具体设备。
// spin lock是一种死等的锁机制。当发生访问资源冲突的时候,可以有两个选择:
一个是死等,一个是挂起当前进程,调度其他进程执行。
spin_lock(&dev_list_lock);
list_for_each_entry(ctrl, &nvme_ctrl_list, node) {
if (ctrl->instance != instance)
continue;
if (!ctrl->admin_q) {
ret = -EWOULDBLOCK;
break;
}
if (!kref_get_unless_zero(&ctrl->kref))
break;
file->private_data = ctrl;
ret = 0;
break;
}
spin_unlock(&dev_list_lock);//解锁
return ret;
}
所有nvme设备都会加入nvme_ctrl_list, 这里调用list_for_each_entry遍历nvme_ctrl_list找到与instance次设备号对应的nvme设备,接着检查ctrl->admin_q和ctrl->kref 不能为null,最后找到的nvme设备放到file->private_data区域。
再来看看nvme_dev_release的定义:
static int nvme_dev_release(struct inode *inode, struct file *file)
{
nvme_put_ctrl(file->private_data);
return 0;
}
nvme_dev_release比较简单,就是用来释放open()函数中申请的资源。
最后来看看nvme_dev_ioctl的定义:
static long nvme_dev_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct nvme_ctrl *ctrl = file->private_data;
void __user *argp = (void __user *)arg;
switch (cmd) {
case NVME_IOCTL_ADMIN_CMD:
return nvme_user_cmd(ctrl, NULL, argp);
case NVME_IOCTL_IO_CMD:
return nvme_dev_user_cmd(ctrl, argp);
case NVME_IOCTL_RESET:
dev_warn(ctrl->device, "resetting controller\n");
return ctrl->ops->reset_ctrl(ctrl);
case NVME_IOCTL_SUBSYS_RESET:
return nvme_reset_subsystem(ctrl);
case NVME_IOCTL_RESCAN:
nvme_queue_scan(ctrl);
return 0;
default:
return -ENOTTY; //错误的ioctrl命令
}
}
从上面的code中,我们可以发现在nvme_dev_ioctl 中总共实现了五种Command:
NVME_IOCTL_ADMIN_CMD和NVME_IOCTL_IO_CMD是NVMe Spec定义的两种命令:Admin、IO命令,具体定义请参考之前的文章介绍: NVMe系列专题之二:队列(Queue)管理. 有一点需要注意的是,NVME_IOCTL_IO_CMD不支持NVMe设备有多个namespace的情况。
NVME_IOCTL_RESET和NVME_IOCTL_SUBSYS_RESET 命令则是通过nvme的ops直接写register来Reset Ioctrl和Ioctrl_subsys。
前者调用的是reset_ctrl, 后者调用的是reg_write32,
static inline int nvme_reset_subsystem(struct nvme_ctrl *ctrl)
{
if (!ctrl->subsystem)
return -ENOTTY;
return ctrl->ops->reg_write32(ctrl, NVME_REG_NSSR, 0x4E564D65);
}
这个nvme的ops名字叫做"nvme_ctrl_ops", 这个ops在nvme初始化中调用nvme_probe()时赋值为"nvme_pci_ctrl_ops".
nvme初始化以及nvme_probe相关内容在后续文章中再展开解析。
看一下nvme_pci_ctrl_ops的定义:
static const struct nvme_ctrl_ops nvme_pci_ctrl_ops = {
.name = "pcie",
.module = THIS_MODULE,
.reg_read32 = nvme_pci_reg_read32,
.reg_write32 = nvme_pci_reg_write32,
.reg_read64 = nvme_pci_reg_read64,
.reset_ctrl = nvme_pci_reset_ctrl,
.free_ctrl = nvme_pci_free_ctrl,
.submit_async_event = nvme_pci_submit_async_event,
};
看到这里,我们可以发现reset Ioctrl调用的是nvme_pci_reset_ctrl函数,而reset Ioctrl_subsys调用的是nvme_pci_reg_write32函数。
一步一步剖析,先看nvme_pci_reset_ctrl的定义:
static int nvme_pci_reset_ctrl(struct nvme_ctrl *ctrl)
{
struct nvme_dev *dev = to_nvme_dev(ctrl);
int ret = nvme_reset(dev);
if (!ret)
flush_work(&dev->reset_work);
return ret;
}
从上面的定义中,可以看到,通过调用to_nvme_dev获取nvme_ctrl的地址并赋值于nvme_dev. 接着调用nvme_reset执行reset。
再深一步,看nvme_reset的定义:
static int nvme_reset(struct nvme_dev *dev)
{
if (!dev->ctrl.admin_q || blk_queue_dying(dev->ctrl.admin_q))
return -ENODEV;
if (work_busy(&dev->reset_work))
return -ENODEV;
if (!queue_work(nvme_workq, &dev->reset_work))
return -EBUSY;
return 0;
}
从上面的定义中,我们知道,如果当前work 是busy的话,则reset失败,也就是reset的时候nvme controller 必须处于空闲状态,如果处于空闲状态,则调用dev->reset_work 来reset nvme controller,reset_work同样是在nvme初始化中调用nvme_probe()时赋值,赋值的结果是"nvme_reset_work".这个函数比较长,这里就不展开咯~
回过头来,我再看看reset Ioctrl_subsys调用的nvme_pci_reg_write32函数是如何定义的:
static int nvme_pci_reg_write32(struct nvme_ctrl *ctrl, u32 off, u32 val)
{
writel(val, to_nvme_dev(ctrl)->bar + off);
return 0;
}
结合nvme_reset_subsystem的定义(如下),我们发现Ioctrl_subsys的reset就是直接往bar寄存器里面写0x4E564D65。
static inline int nvme_reset_subsystem(struct nvme_ctrl *ctrl)
{
if (!ctrl->subsystem)
return -ENOTTY;
return ctrl->ops->reg_write32(ctrl, NVME_REG_NSSR, 0x4E564D65);
}