nvme 驱动详解 之1

按照老的套路,在分析一个driver时,我们首先看这个driver相关的kconfig及Makefile文件,察看相关的源代码文件.

在开始阅读一个driver,通常都是从module_init or syscall_init函数看起。

下面让我们开始nvme的旅程吧。

首先打开driver/block下的kconfig文件,其中定义了BLK_DEV_NVME config,如下。

config BLK_DEV_NVME

tristate"NVM Express block device"

dependson PCI

---help---

The NVM Express driver is for solid statedrives directly

connected to the PCI or PCI Express bus.  If you know you

don‘t have one of these, it is safe to answerN.

To compile this driver as a module, choose Mhere: the

module will be called nvme.

通过console,输入make menuconfig,搜索BLK_DEV_NEME得到如下依赖关系。

Symbol:BLK_DEV_NVME [=m]

| Type : tristate

| Prompt: NVM Express block device

|  Location:

|    -> Device Drivers

| (1)  -> Block devices (BLK_DEV [=y])

|  Defined at drivers/block/Kconfig:313

|  Depends on: BLK_DEV [=y] && PCI [=y]

可以看到nemv
依赖于BLK和PCI 。

打开driver/block/Makefile,搜索NVME,可以看到:

obj-$(CONFIG_BLK_DEV_NVME)    += nvme.o

nvme-y              := nvme-core.o nvme-scsi.o

关于和BLK相关的文件,打开block/Makefile:

obj-$(CONFIG_BLOCK):= bio.o elevator.o blk-core.o blk-tag.o blk-sysfs.o \

blk-flush.oblk-settings.o blk-ioc.o blk-map.o \

blk-exec.oblk-merge.o blk-softirq.o blk-timeout.o \

blk-iopoll.oblk-lib.o blk-mq.o blk-mq-tag.o \

blk-mq-sysfs.oblk-mq-cpu.o blk-mq-cpumap.o ioctl.o \

genhd.o scsi_ioctl.opartition-generic.o ioprio.o \

partitions/

哇塞,是不是很多?不要担心,NVME也只是用了BLOCK层的一些函数而已,不用把所用与BLOCK相关的文件都看了,除非你有精力去研究。

好了,到目前为止,我们知道了要看哪些文件了,nvme-core.cnvme-scsi.c是必须的,剩下的就是当我们的driver调用到block层哪些函数再去研究。

打开nvme-core,查看入口函数,module_init(nvme_init);

staticint __init nvme_init(void)

{

int result;

init_waitqueue_head(&nvme_kthread_wait);//创建等待队列

nvme_workq =create_singlethread_workqueue("nvme");//创建工作队列

if (!nvme_workq)

return -ENOMEM;

result = register_blkdev(nvme_major, "nvme");//注册块设备

if (result < 0)

goto kill_workq;

else if (result > 0)

nvme_major = result;

result = pci_register_driver(&nvme_driver);//注册pci driver

if (result)

goto unregister_blkdev;

return 0;

unregister_blkdev:

unregister_blkdev(nvme_major,"nvme");

kill_workq:

destroy_workqueue(nvme_workq);

return result;

}

注册pci driver后,会调用nvme_driver中的probe函数。发现开始总是美好的,函数是如此的简洁,不要高兴的太早,痛苦的经历正在逼近。

staticint nvme_probe(structpci_dev *pdev, const struct pci_device_id *id)

{

int node, result = -ENOMEM;

struct nvme_dev *dev;

node = dev_to_node(&pdev->dev);//获取node节点,与NUMA系统有关。

if (node == NUMA_NO_NODE)

set_dev_node(&pdev->dev,0);

dev = kzalloc_node(sizeof(*dev),GFP_KERNEL, node);

if (!dev)

return -ENOMEM;

dev->entry =kzalloc_node(num_possible_cpus() * sizeof(*dev->entry),//分配msix-entry

GFP_KERNEL,node);

if (!dev->entry)

goto free;

dev->queues =kzalloc_node((num_possible_cpus() + 1) * sizeof(void *),//分配queues 资源,

GFP_KERNEL,node);//这里之所以多1,是因为有admin-queues

if (!dev->queues)

goto free;

INIT_LIST_HEAD(&dev->namespaces);//初始化namespaces链表。

dev->reset_workfn =nvme_reset_failed_dev;

INIT_WORK(&dev->reset_work,nvme_reset_workfn);

dev->pci_dev = pci_dev_get(pdev);

pci_set_drvdata(pdev, dev);

result = nvme_set_instance(dev);//设置pci设备的句柄instance,代表该设备。

if (result)

goto put_pci;

result = nvme_setup_prp_pools(dev);//设置dma需要的prp内存池。

if (result)

goto release;

kref_init(&dev->kref);

result = nvme_dev_start(dev);//创建admin queue、
io queue 、request irq

if (result)

goto release_pools;

if (dev->online_queues > 1)

result = nvme_dev_add(dev);//初始化mq,并增加一个实际可用的nvme
dev,并且admin_queue可以发送cmd。

if (result)

goto shutdown;

scnprintf(dev->name,sizeof(dev->name), "nvme%d", dev->instance);

dev->miscdev.minor =MISC_DYNAMIC_MINOR;

dev->miscdev.parent =&pdev->dev;

dev->miscdev.name = dev->name;

dev->miscdev.fops =&nvme_dev_fops;

result =misc_register(&dev->miscdev);//注册一个misc设备

if (result)

goto remove;

nvme_set_irq_hints(dev);

dev->initialized = 1;

return 0;

remove:

nvme_dev_remove(dev);

nvme_dev_remove_admin(dev);

nvme_free_namespaces(dev);

shutdown:

nvme_dev_shutdown(dev);

release_pools:

nvme_free_queues(dev, 0);

nvme_release_prp_pools(dev);

release:

nvme_release_instance(dev);

put_pci:

pci_dev_put(dev->pci_dev);

free:

kfree(dev->queues);

kfree(dev->entry);

kfree(dev);

return result;

}

上面每一个主要功能的函数都简单了注释了一下,描述了做的哪些工作,下面具体看看那些函数怎么实现的。

staticint nvme_set_instance(structnvme_dev *dev)

{

int instance, error;

do {

if(!ida_pre_get(&nvme_instance_ida, GFP_KERNEL))

return -ENODEV;

spin_lock(&dev_list_lock);

error =ida_get_new(&nvme_instance_ida, &instance);

spin_unlock(&dev_list_lock);

} while (error == -EAGAIN);

if (error)

return -ENODEV;

dev->instance = instance;//该函数获得设备的instance,相当于该设备的id,代表着该设备。

return 0;

}

Nvme_setup_prp_pools用来创建dma时所用的内存池,prp_page_pool是虚拟内核地址,

staticint nvme_setup_prp_pools(structnvme_dev *dev)

{

struct device *dmadev =&dev->pci_dev->dev;

dev->prp_page_pool =dma_pool_create("prp list page", dmadev,

PAGE_SIZE,PAGE_SIZE, 0);

if (!dev->prp_page_pool)

return -ENOMEM;

/* Optimisation for I/Os between 4k and128k */

dev->prp_small_pool =dma_pool_create("prp list 256", dmadev,

256,256, 0);

if (!dev->prp_small_pool) {

dma_pool_destroy(dev->prp_page_pool);

return -ENOMEM;

}

return 0;

}

下面是一个重量级的函数之一,nvme_dev_start;

staticint nvme_dev_start(struct nvme_dev *dev)

{

int result;

bool start_thread = false;

result = nvme_dev_map(dev);

if (result)

return result;

result = nvme_configure_admin_queue(dev);//配置adminsubmit queue 和complete queue,64 depth

if (result)

goto unmap;

spin_lock(&dev_list_lock);

if (list_empty(&dev_list)&& IS_ERR_OR_NULL(nvme_thread)) {

start_thread = true;

nvme_thread = NULL;

}

list_add(&dev->node,&dev_list);

spin_unlock(&dev_list_lock);

if (start_thread) {

nvme_thread =
kthread_run(nvme_kthread, NULL,"nvme");

wake_up_all(&nvme_kthread_wait);

} else

wait_event_killable(nvme_kthread_wait,nvme_thread);

if (IS_ERR_OR_NULL(nvme_thread)) {

result = nvme_thread ?PTR_ERR(nvme_thread) : -EINTR;

goto disable;

}

nvme_init_queue(dev->queues[0], 0);//初始化queue,并online_queues++

result = nvme_alloc_admin_tags(dev);

if (result)

goto disable;

result = nvme_setup_io_queues(dev);

if (result)

goto free_tags;

nvme_set_irq_hints(dev);

return result;

free_tags:

nvme_dev_remove_admin(dev);

disable:

nvme_disable_queue(dev, 0);

nvme_dev_list_remove(dev);

unmap:

nvme_dev_unmap(dev);

return result;

}

首先看nvme_configure_admin_queue(dev) 这个函数。

staticint nvme_configure_admin_queue(struct nvme_dev *dev)

{

int result;

u32 aqa;

u64 cap =readq(&dev->bar->cap);//读cap寄存器

struct nvme_queue *nvmeq;

unsigned page_shift = PAGE_SHIFT;

unsigned dev_page_min =NVME_CAP_MPSMIN(cap) + 12;

unsigned dev_page_max =NVME_CAP_MPSMAX(cap) + 12;

if (page_shift < dev_page_min) {

dev_err(&dev->pci_dev->dev,

"Minimumdevice page size (%u) too large for "

"host(%u)\n", 1 << dev_page_min,

1 <<page_shift);

return -ENODEV;

}

if (page_shift > dev_page_max) {

dev_info(&dev->pci_dev->dev,

"Devicemaximum page size (%u) smaller than "

"host(%u); enabling work-around\n",

1 <<dev_page_max, 1 << page_shift);

page_shift = dev_page_max;

}

result = nvme_disable_ctrl(dev, cap);//disablecontroller

if (result < 0)

return result;

nvmeq = dev->queues[0];

if (!nvmeq) {

nvmeq = nvme_alloc_queue(dev, 0,NVME_AQ_DEPTH);//如果nvmeq==null,就创建nvmeq

if (!nvmeq)

return -ENOMEM;

}

aqa = nvmeq->q_depth - 1;

aqa |= aqa << 16;

dev->page_size = 1 <<page_shift;

dev->ctrl_config = 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;

writel(aqa, &dev->bar->aqa);

writeq(nvmeq->sq_dma_addr,&dev->bar->asq);

writeq(nvmeq->cq_dma_addr,&dev->bar->acq);
//该语句是创建nvmeq的submit queue和complete
queue

result = nvme_enable_ctrl(dev, cap);

if (result)

goto free_nvmeq;

nvmeq->cq_vector = 0;

result = queue_request_irq(dev, nvmeq,nvmeq->irqname);//注册中断

if (result)

goto free_nvmeq;

return result;

free_nvmeq:

nvme_free_queues(dev, 0);

return result;

}

下面看一下在nvme_alloc_queue函数中作了什么。

staticstruct nvme_queue *nvme_alloc_queue(structnvme_dev *dev, int qid,

intdepth)

{

struct device *dmadev =&dev->pci_dev->dev;

struct nvme_queue *nvmeq =kzalloc(sizeof(*nvmeq), GFP_KERNEL);

if (!nvmeq)

return NULL;

nvmeq->cqes =dma_zalloc_coherent(dmadev, CQ_SIZE(depth),

&nvmeq->cq_dma_addr, GFP_KERNEL);
//分配complete queue cmds空间,深度为depth个。

if (!nvmeq->cqes)

goto free_nvmeq;

nvmeq->sq_cmds =dma_alloc_coherent(dmadev, SQ_SIZE(depth),

&nvmeq->sq_dma_addr,GFP_KERNEL);//分配submit queue中cmds空间,深度为depth个。

if (!nvmeq->sq_cmds)

goto free_cqdma;

nvmeq->q_dmadev = dmadev;

nvmeq->dev = dev;

snprintf(nvmeq->irqname,sizeof(nvmeq->irqname), "nvme%dq%d",

dev->instance,qid);//设置nvmeq的irqname

spin_lock_init(&nvmeq->q_lock);

nvmeq->cq_head = 0;

nvmeq->cq_phase = 1;

nvmeq->q_db = &dev->dbs[qid *2 * dev->db_stride];

nvmeq->q_depth = depth;

nvmeq->qid = qid;

dev->queue_count++;

dev->queues[qid] = nvmeq;//将分配的nvmeq保存在dev->queues[qid]位置

return nvmeq;//返回得到的nvmeq

free_cqdma:

dma_free_coherent(dmadev,CQ_SIZE(depth), (void *)nvmeq->cqes,

nvmeq->cq_dma_addr);

free_nvmeq:

kfree(nvmeq);

return NULL;

}

到此,我们完成了admin queue的complete queue和submit queue的创建和中断的注册。下面一句是nvme_kthread 守护进程的创建,这个我们稍候再讲。我们先看一下下面的函数。

staticvoid nvme_init_queue(structnvme_queue *nvmeq, u16 qid)

{

struct nvme_dev *dev = nvmeq->dev;

spin_lock_irq(&nvmeq->q_lock);

nvmeq->sq_tail = 0;//完成一些nvmeq的初始化工作

nvmeq->cq_head = 0;

nvmeq->cq_phase = 1;

nvmeq->q_db = &dev->dbs[qid *2 * dev->db_stride];

memset((void *)nvmeq->cqes, 0,CQ_SIZE(nvmeq->q_depth));

dev->online_queues++;//将dev->online_queues++,代表online_queues增加1

spin_unlock_irq(&nvmeq->q_lock);

}

下面的函数时nvme使用mq的核心。

staticint nvme_alloc_admin_tags(structnvme_dev *dev)

{

if (!dev->admin_q) {//初始化admin_q为null,故进入if分支

dev->admin_tagset.ops =&nvme_mq_admin_ops;//初始化blk_mq_tag_set结构体,nvme_mq_admin_ops在run
request会用到

dev->admin_tagset.nr_hw_queues= 1;//hardware queue个数为1

dev->admin_tagset.queue_depth= NVME_AQ_DEPTH - 1;

dev->admin_tagset.timeout= ADMIN_TIMEOUT;

dev->admin_tagset.numa_node= dev_to_node(&dev->pci_dev->dev);

dev->admin_tagset.cmd_size= sizeof(struct nvme_cmd_info);

dev->admin_tagset.driver_data= dev;

if (blk_mq_alloc_tag_set(&dev->admin_tagset))//分配一个tag set与一个或多个request
queues关联。

return -ENOMEM;

dev->admin_q =blk_mq_init_queue(&dev->admin_tagset);

if (IS_ERR(dev->admin_q)){

blk_mq_free_tag_set(&dev->admin_tagset);

return -ENOMEM;

}

if (!blk_get_queue(dev->admin_q)){

nvme_dev_remove_admin(dev);

return -ENODEV;

}

} else

blk_mq_unfreeze_queue(dev->admin_q);

return 0;

}

下面依次介绍blk_mq中相关的函数。

/*

* Alloc a tag set to be associated with one ormore request queues.

* May fail with EINVAL for various errorconditions. May adjust the

* requested depth down, if if it too large. Inthat case, the set

* value will be stored in set->queue_depth.

*/

int blk_mq_alloc_tag_set(structblk_mq_tag_set *set)

{

BUILD_BUG_ON(BLK_MQ_MAX_DEPTH > 1<< BLK_MQ_UNIQUE_TAG_BITS);

if (!set->nr_hw_queues)

return -EINVAL;

if (!set->queue_depth)

return -EINVAL;

if (set->queue_depth <set->reserved_tags + BLK_MQ_TAG_MIN)

return -EINVAL;

if (!set->nr_hw_queues ||!set->ops->queue_rq || !set->ops->map_queue)

return -EINVAL;

if (set->queue_depth >BLK_MQ_MAX_DEPTH) {

pr_info("blk-mq: reducedtag depth to %u\n",

BLK_MQ_MAX_DEPTH);

set->queue_depth =BLK_MQ_MAX_DEPTH;

}

/*

* If a crashdump is active, then we arepotentially in a very

* memory constrained environment. Limit us to1 queue and

* 64 tags to prevent using too much memory.

*/

if (is_kdump_kernel()) {

set->nr_hw_queues = 1;

set->queue_depth =min(64U, set->queue_depth);

}

set->tags =kmalloc_node(set->nr_hw_queues *  
//在这里给tags分配与nr_hw_queues个空间

sizeof(struct blk_mq_tags *),

GFP_KERNEL, set->numa_node);

if (!set->tags)

return -ENOMEM;

if (blk_mq_alloc_rq_maps(set))

goto enomem;

mutex_init(&set->tag_list_lock);

INIT_LIST_HEAD(&set->tag_list);

return 0;

enomem:

kfree(set->tags);

set->tags = NULL;

return -ENOMEM;

}

/*

* Allocate the request maps associated withthis tag_set. Note that this

* may reduce the depth asked for, if memory istight. set->queue_depth

* will be updated to reflect the allocateddepth.

*/

staticint blk_mq_alloc_rq_maps(structblk_mq_tag_set *set)

{

unsigned int depth;

int err;

depth = set->queue_depth;

do {

err =__blk_mq_alloc_rq_maps(set);//如果成功,则跳出,否则,将queue_depth减半,创建

if (!err)

break;

set->queue_depth >>=1;

if (set->queue_depth <set->reserved_tags + BLK_MQ_TAG_MIN) {

err = -ENOMEM;

break;

}

} while (set->queue_depth);

if (!set->queue_depth || err) {

pr_err("blk-mq: failedto allocate request map\n");

return -ENOMEM;

}

if (depth != set->queue_depth)

pr_info("blk-mq: reducedtag depth (%u -> %u)\n",

depth,set->queue_depth);

return 0;

}

未完待续。。。。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2025-01-05 03:19:34

nvme 驱动详解 之1的相关文章

使用VS2010编译MongoDB C++驱动详解

最近为了解决IM消息记录的高速度写入.多文档类型支持的需求,决定使用MongoDB来解决. 考虑到MongoDB对VS版本要求较高,与我现有的VS版本不兼容,在leveldb.ssdb.redis.hbase等NoSQL中转了一圈,最后还是选择了MongoDB,应了那句话:没有最好的,只有最合适的. MongoDB由于使用了C++的新特性,官方建议使用VS2013来编译,最低要求VS2010. MongoDB C++驱动编译过程较为复杂,官方也没有提供编译好的驱动包,网上的资料编译版本都比较老了

Linux的i2c驱动详解

目录(?)[-] 简介 架构 设备注册 I2C关键数据结构和详细注册流程 关键数据结构 详细注册流程 使用I2C子系统资源函数操作I2C设备 Gpio模拟i2c总线的通用传输算法 总结 理清i2c中的个结构体关系 i2c驱动的编写建议 1 简介 I2C 总线仅仅使用 SCL . SDA 两根信号线就实现了设备之间的数据交互,极大地简化对硬件资源和 PCB 板布线空间的占用.因此, I2C 总线被非常广泛地应用在 EEPROM .实时钟.小型 LCD 等设备与 CPU 的接口中. Linux I2

Linux USB 鼠标输入驱动详解

平台:mini2440 内核:linux 2.6.32.2 USB设备插入时,内核会读取设备信息,接着就把id_table里的信息与读取到的信息做比较,看是否匹配,如果匹配,就调用probe函数.USB设备拔出时会调用disconnect函数.URB在USB设备驱动程序中用来描述与USB设备通信时用到的基本载体和核心数据结构. URB(usb request block)处理流程: ①USB设备驱动程序创建并初始化一个访问特定USB设备特定端点的urb并提交给USB core. ②USB cor

16.Linux-LCD驱动(详解)

在上一节LCD层次分析中,得出写个LCD驱动入口函数,需要以下4步: 1) 分配一个fb_info结构体: framebuffer_alloc(); 2) 设置fb_info 3) 设置硬件相关的操作 4) 使能LCD,并注册fb_info: register_framebuffer() 本节需要用到的函数: void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);

MTK平台tp驱动详解

MTK平台tp驱动详解 本博文将讲解基于goodix9157触控芯片的tp驱动程序.这里有对应的驱动程序. 初始化 static int __init tpd_driver_init(void) { GTP_INFO("MediaTek gt91xx touch panel driver init\n"); #if defined(TPD_I2C_NUMBER) i2c_register_board_info(TPD_I2C_NUMBER, &i2c_tpd, 1); #els

13.Linux键盘按键驱动 (详解)

版权声明:本文为博主原创文章,未经博主允许不得转载. 在上一节分析输入子系统内的intput_handler软件处理部分后,接下来我们开始写input_dev驱动 本节目标: 实现键盘驱动,让开发板的4个按键代表键盘中的L.S.空格键.回车键 1.先来介绍以下几个结构体使用和函数,下面代码中会用到 1)input_dev驱动设备结构体中常用成员如下: struct input_dev { void *private; const char *name; //设备名字 const char *ph

si4745 FM-AM-SW 音量控制芯片 驱动详解

在论坛上看到有人发这个dsp 芯片,仔细看了下,发现功能正合我意,网上能找到的资料(源码)不多 软件环境:linux4.1.36  arm-linux-gcc 4.3.2 实现功能:自动搜台,上一台, 下一台, 音量大小控制,保存设置到文件,断电开机后自动恢复,之前收音状态. 首先是接线 2440 开发板,mitsumi 车机收音芯片 si4745 ,这里加了一个 PAM8403 ,基本可以算是一个产品了. 先把 si4745 焊接在万能板上,加上排针,方便连线. 这里i2c , 接在 came

virtio前端驱动详解

2016-11-08 前段时间大致整理了下virtIO后端驱动的工作模式以及原理,今天就从前端驱动的角度描述下目前Linux内核代码中的virtIO驱动是如何配合后端进行工作的. 注:本节代码参考Linux 内核3.11.1代码 virtIO驱动从架构上来讲可以分为两部分,一个是其作为PCI设备本身的驱动,此驱动需要提供一些基本的操作PCI设备本身的函数比如PCI设备的探测.删除.配置空间的设置和寄存器空间的读写等.而另一个就是其virtIO设备本身实现的功能驱动例如网络驱动.块设备驱动.con

很好的linux下GPIO驱动详解文章

原文地址  http://blog.csdn.net/llxmedici/article/details/6282372 打算跟着友善之臂的<mini2440 linux移植开发指南>来做个LED驱动,虽然LED的原理简单得不能再简单了,但是要把kernel中针对于s3c24**的GPIO的一些数据结构,还有函数搞清楚也不是那么轻松的事,所以本文主要简单地说明下LED驱动中的相关数据结构以及函数/宏的定义,并对驱动加以验证 ***********************************