【好文推荐】
驱动的加载
驱动加载实际就是module的加载,而module加载时会对整个module进行初始化,nvme驱动的module初始化函数为nvme_init(),如下:
static struct pci_driver nvme_driver = {
.name = "nvme",
.id_table = nvme_id_table,
.probe = nvme_probe,
.remove = nvme_remove,
.shutdown = nvme_shutdown,
.driver = {
.pm = &nvme_dev_pm_ops,
},
.err_handler = &nvme_err_handler,
};
static int __init nvme_init(void)
{
int result;
/* 初始化等待队列nvme_kthread_wait,此等待队列用于创建nvme_kthread(只允许单进程创建nvme_kthread) */
init_waitqueue_head(&nvme_kthread_wait);
/* 创建一个workqueue叫nvme */
nvme_workq = create_singlethread_workqueue("nvme");
if (!nvme_workq)
return -ENOMEM;
/* 在内核中注册新的一类块设备驱动,名字叫nvme,注意这里只是注册,表示kernel支持了nvme类的块设备,返回一个major,之后所有的nvme设备的major都是此值 */
result = register_blkdev(nvme_major, "nvme");
if (result < 0)
goto kill_workq;
else if (result > 0)
nvme_major = result;
/* 注册一些通知信息 */
nvme_nb.notifier_call = &nvme_cpu_notify;
result = register_hotcpu_notifier(&nvme_nb);
if (result)
goto unregister_blkdev;
/* 注册pci nvme驱动 */
result = pci_register_driver(&nvme_driver);
if (result)
goto unregister_hotcpu;
return 0;
unregister_hotcpu:
unregister_hotcpu_notifier(&nvme_nb);
unregister_blkdev:
unregister_blkdev(nvme_major, "nvme");
kill_workq:
destroy_workqueue(nvme_workq);
return result;
}
这里面其实最重要的就是做了两件事,一件事是register_blkdev,注册nvme这类块设备,返回一个major,另一件事是注册了nvme_driver,注册了nvme_driver后,当有nvme设备插入后系统后,系统会自动调用nvme_driver->nvme_probe去初始化这个nvme设备.这时候可能会有疑问,系统是如何知道插入的设备是nvme设备的呢,注意看struct pci_driver nvme_driver这个结构体,里面有一个nvme_id_table,其内容如下:
/* Move to pci_ids.h later */
#define PCI_CLASS_STORAGE_EXPRESS 0x010802
static const struct pci_device_id nvme_id_table[] = {
{ PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_EXPRESS, 0xffffff) },
{ 0, }
};
再看看PCI_DEVICE_CLASS宏是如何定义的
#define PCI_DEVICE_CLASS(dev_class,dev_class_mask) \
.class = (dev_class), .class_mask = (dev_class_mask), \
.vendor = PCI_ANY_ID, .device = PCI_ANY_ID, \
.subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID
也就是当pci class为PCI_CLASS_STORAGE_EXPRESS时,就表示是nvme设备,并且这个是写在设备里的,当设备插入host时,pci driver(并不是nvme driver)回去读取这个值,然后判断它需要哪个驱动去做处理.
nvme数据结构
现在假设nvme.ko已经加载完了(注册了nvme类块设备,并且注册了nvme driver),这时候如果有nvme盘插入pcie插槽,pci会自动识别到,并交给nvme driver去处理,而nvme driver就是调用nvme_probe去处理这个新加入的设备.
在说nvme_probe之前,先说一下nvme设备的数据结构,首先,内核使用一个nvme_dev结构体来描述一个nvme设备, 一个nvme设备对应一个nvme_dev,nvme_dev如下:
/* nvme设备描述符,描述一个nvme设备 */
struct nvme_dev {
struct list_head node;
/* 设备的queue,一个nvme设备至少有2个queue,一个admin queue,一个io queue,实际情况一般都是一个admin queue,多个io queue,并且io queue会与CPU做绑定 */
struct nvme_queue __rcu **queues;
/* unsigned short的数组,每个CPU占一个,主要用于存放CPU上绑定的io queue的qid,一个CPU绑定一个queues,一个queues绑定到1到多个CPU上 */
unsigned short __percpu *io_queue;
/* ((void __iomem *)dev->bar) + 4096 */
u32 __iomem *dbs;
/* 此nvme设备对应的pci dev */
struct pci_dev *pci_dev;
/* dma池,主要是以4k为大小的dma块,用于dma分配 */
struct dma_pool *prp_page_pool;
/* 也是dma池,但是不是以4k为大小的,是小于4k时使用 */
struct dma_pool *prp_small_pool;
/* 实例的id,第一个加入的nvme dev,它的instance为0,第二个加入的nvme,instance为1,也用于做/dev/nvme%d的显示,%d实际就是instance的数值 */
int instance;
/* queue的数量, 等于admin queue + io queue */
unsigned queue_count;
/* 在线可以使用的queue数量,跟online cpu有关 */
unsigned online_queues;
/* 最大的queue id */
unsigned max_qid;
/* nvme queue支持的最大cmd数量,为((bar->cap) & 0xffff)或者1024的最小值 */
int q_depth;
/* 1 << (((bar->cap) >> 32) & 0xf),应该是每个io queue占用的bar空间 */
u32 db_stride;
/* 初始化设置的值
* dev->ctrl_config = NVME_CC_ENABLE | NVME_CC_CSS_NVM;
* dev->ctrl_config |= (PAGE_SHIFT - 12) << NVME_CC_MPS_SHIFT;
* dev->ctrl_config |= NVME_CC_ARB_RR | NVME_CC_SHN_NONE;
* dev->ctrl_config |= NVME_CC_IOSQES | NVME_CC_IOCQES;
*/
u32 ctrl_config;
/* msix中断所使用的entry,指针表示会使用多个msix中断,使用的中断的个数与io queue对等,多少个io queue就会申请多少个中断
* 并且让每个io queue的中断尽量分到不同的CPU上运行
*/
struct msix_entry *entry;
/* bar的映射地址,默认是映射8192,当io queue过多时,有可能会大于8192 */
struct nvme_bar __iomem *bar;
/* 其实就是块设备,一张nvme卡有可能会有多个块设备 */
struct list_head namespaces;
/* 对应的在/sys下的结构 */
struct kref kref;
/* 对应的字符设备,用于ioctl操作 */
struct miscdevice miscdev;
/* 2个work,暂时还不知道什么用 */
work_func_t reset_workfn;
struct work_struct reset_work;
struct work_struct cpu_work;
/* 这个nvme设备的名字,为nvme%d */
char name[12];
/* SN号 */
char serial[20];
char model[40];
char firmware_rev[8];
/* 这些值都是从nvme盘上获取 */
u32 max_hw_sectors;
u32 stripe_size;
u16 oncs;
u16 abort_limit;
u8 vwc;
u8 initialized;
};
在nvme_dev结构中,最最重要的数据就是nvme_queue,struct nvme_queue用来表示一个nvme的queue,每一个nvme_queue会申请自己的中断,也有自己的中断处理函数,也就是每个nvme_queue在驱动层面是完全独立的.nvme_queue有两种,一种是admin queue,一种是io queue,这两种queue都用struct nvme_queue来描述,而这两种queue的区别如下:
- admin queue: 用于发送控制命令的queue,所有非io命令都会通过此queue发送给nvme设备,一个nvme设备只有一个admin queue,在nvme_dev中,使用queues[0]来描述.
- io queue: 用于发送io命令的queue,所有io命令都是通过此queue发送给nvme设备,简单来说读/写操作都是通过io queue发送给nvme设备的,一个nvme设备有一个或多个io queue,每个io queue的中断会绑定到不同的一个或多个CPU上.在nvme_dev中,使用queues[1~N]来描述.
以上说的io命令和非io命令都是nvme命令,比如快层下发一个写request,nvme驱动就会根据此request构造出一个写命令,将这个写命令放入某个io queue中,当controller完成了这个写命令后,会通过此io queue的中断返回完成信息,驱动再将此完成信息返回给块层.明白了两种队列的作用,我们看看具体的数据结构struct nvme_queue
/* nvme的命令队列,其中包括sq和cq。一个nvme设备至少包含两个命令队列
* 一个是控制命令队列,一个是IO命令队列
*/
struct nvme_queue {
struct rcu_head r_head;
struct device *q_dmadev;
/* 所属的nvme_dev */
struct nvme_dev *dev;
/* 中断名字,名字格式为nvme%dq%d,在proc/interrupts可以查看到 */
char irqname[24]; /* nvme4294967295-65535\0 */
/* queue的锁,当操作nvme_queue时,需要占用此锁 */
spinlock_t q_lock;
/* sq的虚拟地址空间,主机需要发给设备的命令就存在这里面 */
struct nvme_command *sq_cmds;
/* cq的虚拟地址空间,设备返回的命令就存在这里面 */
volatile struct nvme_completion *cqes;
/* 实际就是sq_cmds的dma地址 */
dma_addr_t sq_dma_addr;
/* cq的dma地址,实际就是cqes对应的dma地址,用于dma传输 */
dma_addr_t cq_dma_addr;
/* 等待队列,当sq满时,进程会加到此等待队列,等待有空闲的cmd区域 */
wait_queue_head_t sq_full;
/* wait queue的一个entry,主要是当cmdinfo满时,会将它放入sq_full,而sq_full最后会通过它,唤醒nvme_thread */
wait_queue_t sq_cong_wait;
struct bio_list sq_cong;
/* iod是读写请求的封装,可以看成是一个bio的封装,此链表有可能为空,比如admin queue就为空 */
struct list_head iod_bio;
/* 当前sq_tail位置,是nvme设备上的一个寄存器,告知设备最新的发送命令存在哪,存在于bar空间中 */
u32 __iomem *q_db;
/* cq和sq最大能够存放的command数量 */
u16 q_depth;
/* 如果是admin queue,那么为0,之后的io queue按分配顺序依次增加,主要用于获取对应的irq entry,因为所有的queue的irq entry是一个数组 */
u16 cq_vector;
/* 当完成命令时会更新,当sq_head == sq_tail时表示cmd queue为空 */
u16 sq_head;
/* 当有新的命令存放到sq时,sq_tail++,如果sq_tail == q_depth,那么sq_tail会被重新设置为0,并且cq_phase翻转
* 实际上就是一个环
*/
u16 sq