本文说使用的代码版本是Linux3.16.3
代码位置drivers\block\nvme-core.c include/linux/nvme.h
NVMe driver的init过程
nvme的初始化过程主要跟两个函数有关,一个是nvme_init,另一个还nvme_probe,本文主要讲一下nvme_init和nvme_probe分别做了什么以及如何从nvme_init进入到nvme_probe。
一:nvme_init函数
以上是nvme_init函数的流程图
①init_waitqueue_head(&nvme_kthread_wait);
该函数的主要作用创建了一个等待队列,这个等待队列的主要作用是防止多个Control(dev)同时创建多个nvme_kthread,保证在系统存在多个controller的情况下,只创建一个nvme_kthread。这部分的功能在nvme_dev_start中会有体现,后面会讲到。
②nvme_workq = create_singlethread_workqueue("nvme");
创建一个工作队列,该工作队列主要是为reset服务的,在nvme_dev_start函数中我们可以看到该工作队列的的工作添加的工作项是nvme_reset_workfn。
③ register_hotcpu_notifier(&nvme_nb);
为cpu的热插拔所注册的notifier,当某个cpu被热插拔重新加入或离开系统调度后,nvme会对其绑定的IO队列进行相应的处理。(在nvme driver中,IO queue是绑定到cpu上的,每个cpu所对应的IO queue号是固定的,该CPU所对应的IO请求必须送到它对应的IO queue上,关于CPU和IO queue的绑定,在nvme_assign_io_queues中实现
)。
由于nvme是一个挂在PCIE总线上的block设备,所以nvme_init函数需要
注册一个块设备,并且
注册一个PCI的设备驱动。
注册一个块设备:
④register_blkdev(nvme_major,"nvme");
这里面
nvme_major = 0,所以Kernel会自动给该块设备分配一个主设备号。设备的name就是"nvme"
注册一个PCI的设备驱动
⑤pci_register_driver(&nvme_driver)
将要注册的
nvme_driver,pci_register_driver将会调用下面的一系列函数,对devier和device进行match,并将device和driver绑定,最终通过一个回调函数调用到nvme driver的nvme_probe函数
二:从nvme_init到nvme_probe
从nvme_init到nvme_probe是通过pci_register_driver实现的。
pci_register_driver
(&
nvme_driver
)//
↓
driver_register(&drv->driver)
↓
driver_find(drv->name, drv->bus) //locate driver on a bus by its name
bus_add_driver(drv) //Add a driver to the bus
↓
driver_attach(drv) //try to bind driver to devices
↓
bus_for_each_dev
(
drv
->bus, NULL,
drv
, __
driver_attach
)
//遍历bus上所挂接的所有设备
while ((dev = next_device(&i)) && !error)
error = fn(dev, data); // __driver_attach 是一个回调函数
__driver_attach (struct device *dev, void *data)
driver_probe_device
(
drv
,
dev
);
↓
really_probe(dev, drv);//
drv->probe(dev);
//这里就是调用的nvme_driver的nvme_probe函数
三:nvme_probe函数
①dev = kzalloc(sizeof(*dev), GFP_KERNEL);//声明并开辟nvme 的device,每一个nvme device都是一个PCI的设备
②dev->entry = kcalloc(num_possible_cpus(), sizeof(*dev->entry),
GFP_KERNEL); //根据cpu的个数开辟entry的空间,有多少个cpu就开辟多少个entry
③dev->queues = kcalloc(num_possible_cpus() + 1, sizeof(void *),
GFP_KERNEL);//开辟的queue的个数是CPU个数+1,+1是Admin queue
④dev->io_queue = alloc_percpu(unsigned short); //io_queue是一个percpu的变量
⑤ INIT_LIST_HEAD(&dev->namespaces); //初始化namespaces链表,该链表串接了一个control上的所有的namespaces
⑥ dev->reset_workfn = nvme_reset_failed_dev; //
INIT_WORK(&dev->reset_work, nvme_reset_workfn);//将nvme_reset_work添加到reset_work的工作队列中
⑦INIT_WORK(&dev->cpu_work, nvme_cpu_workfn);
//将nvme_rcpu_work添加到cpu_work的工作队列中,cpuwork的主要工作就是在cpu hotplug之后对cpu和ioqueue进行重新的分配
⑧ dev->pci_dev = pdev; //
pci_set_drvdata(pdev, dev);
result = nvme_set_instance(dev);//设置一个nvme的实例,分配内存资源,并分配了一个nvme_instance_ida,就是给nvme分配了一个ID
⑨nvme_setup_prp_pools(dev) //通过dma_pool_create给PRP分配空间,分配了两种PRP,一种是PAGESIZE的PRP,另一种是比较小的256的PRP,后者应用于I/Os between 4k and 128k
⑩kref_init(&dev->kref);//initialize object,计数器 +1
⑪nvme_dev_start(dev);//这个函数是一个实现的功能比较多,包括dev的映射,配置Admin queue和开辟I Oqueue空间以及IOqueue与cpu的绑定等
⑫nvme_dev_add(dev);//这个函数会读取controller的identifier和namespace identififer,并配置namespace disk,最终通过alloc disk和 add disk 将namespace添加到内核中