4.97内核nvme驱动通过多队列方式实现,nvme盘驱动路径:drivers/nvme/host,

drivers/nvme/target端是针对设备的驱动,不涉及。

和nvme盘操作直接相关的只有两个文件pci.c、core.c。

Nvme驱动入口:

Nvme盘是一个pcie设备,因此入口是pcie驱动probe函数:nvme_probe

Probe函数通过       queue_work(nvme_workq, &dev->reset_work);

调用nvme_reset_work函数

这个函数初始化nvme盘的admin和io队列(struct nvme_queue),同时初始化nvme盘的管理队列和请求队列对应的硬件队列描述结构blk_mq_tag_set,注意:这里的请求队列结构是struct request_queue,并不是nvme盘收发命令的admin和io队列,每个nvme逻辑盘只有一个请求队列,一个该请求队列对应多个nvme盘硬件io队列。nvme逻辑盘用struct nvme_ns结构表示,该结构包含通用盘设备结构:struct gendisk。

blk_mq_tag_set结构包含一个物理nvme盘硬件队列数、队列深度、io请求及处理等信息,该结构包含物理块设备所有描述信息,是块设备软件请求队列和硬件物理存储设备队列之间的纽带,建立了系统软件层面的io请求队列和物理存储设备硬件队列的映射关系。通过该结构文件系统读写操作发送的io请求最终到达物理存储设备。

创建完blk_mq_tag_set后,通过运行nvme_scan_work创建nvme_ns,nvme_ns包含struct gendisk接口,表示一个通用数据存储盘。同时用已经创建的blk_mq_tag_set创建一个io请求队列request_queue。

pci.c相关代码:

static void nvme_reset_work(struct work_struct *work)

{

struct nvme_dev *dev = container_of(work, struct nvme_dev, reset_work);

int result = -ENODEV;

if (WARN_ON(dev->ctrl.state == NVME_CTRL_RESETTING))

goto out;

/*

* If we're called to reset a live controller first shut it down before

* moving on.

*/

if (dev->ctrl.ctrl_config & NVME_CC_ENABLE)

nvme_dev_disable(dev, false);

if (!nvme_change_ctrl_state(&dev->ctrl, NVME_CTRL_RESETTING))

goto out;

result = nvme_pci_enable(dev);

if (result)

goto out;

//nvme盘队管理队列申请

result = nvme_configure_admin_queue(dev);

if (result)

goto out;

//nvme盘管理队列初始化

nvme_init_queue(dev->queues[0], 0);

//管理队列blk_mq_tag_set初始化

result = nvme_alloc_admin_tags(dev);

if (result)

goto out;

result = nvme_init_identify(&dev->ctrl);

if (result)

goto out;

result = nvme_setup_io_queues(dev);

if (result)

goto out;

/*

* A controller that can not execute IO typically requires user

* intervention to correct. For such degraded controllers, the driver

* should not submit commands the user did not request, so skip

* registering for asynchronous event notification on this condition.

*/

if (dev->online_queues > 1)

nvme_queue_async_events(&dev->ctrl);

mod_timer(&dev->watchdog_timer, round_jiffies(jiffies + HZ));

/*

* Keep the controller around but remove all namespaces if we don't have

* any working I/O queue.

*/

if (dev->online_queues < 2) {

dev_warn(dev->ctrl.device, "IO queues not created\n");

nvme_kill_queues(&dev->ctrl);

nvme_remove_namespaces(&dev->ctrl);

} else {

nvme_start_queues(&dev->ctrl);

//  初始化io请求队列对应blk_mq_tag_set

nvme_dev_add(dev);

}

if (!nvme_change_ctrl_state(&dev->ctrl, NVME_CTRL_LIVE)) {

dev_warn(dev->ctrl.device, "failed to mark controller live\n");

goto out;

}

if (dev->online_queues > 1)

nvme_queue_scan(&dev->ctrl); //运行nvme_scan_work创建nvme_ns

return;

out:

nvme_remove_dead_ctrl(dev, result);

}

static int nvme_dev_add(struct nvme_dev *dev)

{

if (!dev->ctrl.tagset) {

//针对nvme盘的请求处理函数接口,请求最终通过该接口和盘进行数据交互

dev->tagset.ops = &nvme_mq_ops;

dev->tagset.nr_hw_queues = dev->online_queues - 1;

dev->tagset.timeout = NVME_IO_TIMEOUT;

dev->tagset.numa_node = dev_to_node(dev->dev);

dev->tagset.queue_depth =

min_t(int, dev->q_depth, BLK_MQ_MAX_DEPTH) - 1;

dev->tagset.cmd_size = nvme_cmd_size(dev);

dev->tagset.flags = BLK_MQ_F_SHOULD_MERGE;

dev->tagset.driver_data = dev;

//不要被这个接口名误导,其实不是申请tag_set,只是进一步申请tag_set中的tags,并初始化,主要是建立硬件队列和CPU的映射,申请request空间。

if (blk_mq_alloc_tag_set(&dev->tagset))

return 0;

dev->ctrl.tagset = &dev->tagset;

} else {

blk_mq_update_nr_hw_queues(&dev->tagset, dev->online_queues - 1);

/* Free previously allocated queues that are no longer usable */

nvme_free_queues(dev, dev->online_queues);

}

return 0;

}

//请求队列相关操作接口

static struct blk_mq_ops nvme_mq_ops = {

.queue_rq         = nvme_queue_rq, //请求处理接口,该接口获取请求队列中的请求和物理盘进行数据交互

.complete         = nvme_complete_rq,

.init_hctx = nvme_init_hctx,

.init_request    = nvme_init_request,

.map_queues  = nvme_pci_map_queues,

.timeout  = nvme_timeout,

.poll           = nvme_poll,

};

core.c相关代码:

static void nvme_scan_work(struct work_struct *work)

{

struct nvme_ctrl *ctrl =

container_of(work, struct nvme_ctrl, scan_work);

struct nvme_id_ctrl *id;

unsigned nn;

if (ctrl->state != NVME_CTRL_LIVE)

return;

if (nvme_identify_ctrl(ctrl, &id))

return;

nn = le32_to_cpu(id->nn);

//以下两个接口最终都会调用nvme_alloc_ns创建nvme_ns

if (ctrl->vs >= NVME_VS(1, 1, 0) &&

!(ctrl->quirks & NVME_QUIRK_IDENTIFY_CNS)) {

if (!nvme_scan_ns_list(ctrl, nn))

goto done;

}

nvme_scan_ns_sequential(ctrl, nn);

done:

mutex_lock(&ctrl->namespaces_mutex);

list_sort(NULL, &ctrl->namespaces, ns_cmp);

mutex_unlock(&ctrl->namespaces_mutex);

kfree(id);

}

static void nvme_alloc_ns(struct nvme_ctrl *ctrl, unsigned nsid)

{

struct nvme_ns *ns;

struct gendisk *disk;

struct nvme_id_ns *id;

char disk_name[DISK_NAME_LEN];

int node = dev_to_node(ctrl->dev);

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

if (!ns)

return;

ns->instance = ida_simple_get(&ctrl->ns_ida, 1, 0, GFP_KERNEL);

if (ns->instance < 0)

goto out_free_ns;

//通过tagset创建逻辑块设备请求队列,并建立请求队列和物理队列映射关系

ns->queue = blk_mq_init_queue(ctrl->tagset);

if (IS_ERR(ns->queue))

goto out_release_instance;

queue_flag_set_unlocked(QUEUE_FLAG_NONROT, ns->queue);

ns->queue->queuedata = ns;

ns->ctrl = ctrl;

kref_init(&ns->kref);

ns->ns_id = nsid;

ns->lba_shift = 9; /* set to a default value for 512 until disk is validated */

blk_queue_logical_block_size(ns->queue, 1 << ns->lba_shift);

nvme_set_queue_limits(ctrl, ns->queue);

sprintf(disk_name, "nvme%dn%d", ctrl->instance, ns->instance);

if (nvme_revalidate_ns(ns, &id))

goto out_free_queue;

if (nvme_nvm_ns_supported(ns, id)) {

if (nvme_nvm_register(ns, disk_name, node,

&nvme_ns_attr_group)) {

dev_warn(ctrl->dev, "%s: LightNVM init failure\n",

__func__);

goto out_free_id;

}

} else {

disk = alloc_disk_node(0, node);

if (!disk)

goto out_free_id;

//设置盘控制操作接口,通过该操作可以直接向盘发送读写命令,不经过io块设备管理系统

disk->fops = &nvme_fops;

disk->private_data = ns;

disk->queue = ns->queue;

disk->flags = GENHD_FL_EXT_DEVT;

memcpy(disk->disk_name, disk_name, DISK_NAME_LEN);

ns->disk = disk;

__nvme_revalidate_disk(disk, id);

}

mutex_lock(&ctrl->namespaces_mutex);

list_add_tail(&ns->list, &ctrl->namespaces);

mutex_unlock(&ctrl->namespaces_mutex);

kref_get(&ctrl->kref);

kfree(id);

if (ns->ndev)

return;

device_add_disk(ctrl->device, ns->disk);

if (sysfs_create_group(&disk_to_dev(ns->disk)->kobj,

&nvme_ns_attr_group))

pr_warn("%s: failed to create sysfs group for identification\n",

ns->disk->disk_name);

return;

out_free_id:

kfree(id);

out_free_queue:

blk_cleanup_queue(ns->queue);

out_release_instance:

ida_simple_remove(&ctrl->ns_ida, ns->instance);

out_free_ns:

kfree(ns);

}

以上代码包含了nvme驱动最核心的实现部分,下面总结概括下nvme驱动架构组成

  1. 创建硬件相关描述tag_set(nvme盘硬件队列个数、深度、申请request空间,建立硬件队列、软件队列CPU核间的对应关系),该结构也包含request处理函数。
  2. 创建通用磁盘描述nvme_ns,该结构包含gendisk
  3. 创建io请求队列request_queue,用tag_set初始化队列,将nvme_ns的request_queue指针指向创建的io请求队列,初始化gendisk。

nvme盘读写数据的流程也可以分三个阶段:

1、发送将内存数据组建成请求request

2、将request提交到请求队列request_queue

3、调用tag_set中的queue_rq函数处理请求(请求下发到nvme盘)

nvme io命令prp参数使用算法(PAGESIZE不是host页大小,是盘设置的页大小):
1、只使用prp1 entry传输数据:datalen < PAGESIZE - prp1偏移 
2、使用prp1 entry和prp2 entry传数据: (PAGESIZE - prp1偏移) < datalen < ((PAGESIZE - prp1偏移) + PAGESIZE))
3、使用prp1 entry和prp2 list传数据:datalen > ((PAGESIZE - prp1偏移) + PAGESIZE))

如果我们要针对一个特定的盘开发驱动,只要实现以上三个部分即可。例如通过内存模拟一个虚拟磁盘对应驱动下载地址:

块设备驱动例子,通过读写内存模拟块设备驱动-Linux文档类资源-CSDN下载

nvme驱动架构分析1相关推荐

  1. 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】PowerPC + Linux2.6.25平台下的I2C驱动架构分析

    PowerPC + Linux2.6.25平台下的I2C驱动架构分析 Sailor_forever  sailing_9806#163.com (本原创文章发表于Sailor_forever 的个人b ...

  2. 【嵌入式Linux学习七步曲之第五篇 Linux内核及驱动编程】PowerPC + Linux2.6.25平台下的SPI驱动架构分析

    PowerPC + Linux2.6.25平台下的SPI驱动架构分析 Sailor_forever  sailing_9806#163.com (本原创文章发表于Sailor_forever 的个人b ...

  3. Linux网卡驱动(1)-网卡驱动架构分析

    1.Linux网络子系统 网络子系统采用分层的结构: 我们这里研究内核空间即可,在内核空间分成5层,分别是: 1.系统调用接口,它面向的客户是应用层序,为应用程序提供访问网络子系统的统一方法,比如说s ...

  4. Exynos4412 IIC总线驱动开发(一)—— IIC 基础概念及驱动架构分析

    关于Exynos4412 IIC 裸机开发请看 :Exynos4412 裸机开发 -- IIC总线 ,下面回顾下 IIC 基础概念 一.IIC 基础概念 IIC(Inter-Integrated Ci ...

  5. linux驱动架构变化,Linux网卡驱动架构分析

    一.网卡驱动架构 由上到下层次依次为:应用程序→系统调用接口→协议无关接口→网络协议栈→设备无关接口→设备驱动. 二.重要数据结构 1.Linux内核中每一个网卡由一个net_device结构来描述. ...

  6. Exynos4412 IIC总线驱动开发(一)—— IIC 基础概念及驱动架构分析 (iic驱动框架,i2c驱动框架)...

    转载于 : http://blog.csdn.net/zqixiao_09/article/details/50917655 关于Exynos4412 IIC 裸机开发请看 :Exynos4412 裸 ...

  7. linux tty结构体,linux tty驱动架构分析

    再看Linux tty驱动过程中发现linux的驱动构架中,面向对象的思想已经根深蒂固.就比如这串口驱动,代码中经常有一些貌似和串口无关的代码,比如,tty_register_driver等.但我们却 ...

  8. 字符设备驱动 架构分析

    Char Device Driver 相关数据结构: struct cdev {         struct kobject kobj;         struct module *owner; ...

  9. linux设备模型之tty驱动架构分析,linux设备模型之uart驱动架构分析

    五: uart_add_one_port()操作本文引用地址:http://www.eepw.com.cn/article/201610/305916.htm 在前面提到.在对uart设备文件过程中. ...

  10. NVMe驱动注释(持续更新)

    NVMe驱动注释 参考 源代码 阅读顺序 简单注释 初始化 nvme_core_init nvme_probe nvme_init_ctrl nvme_reset_work nvme_pci_enab ...

最新文章

  1. 2021甘肃高考艺考成绩查询,2021甘肃艺术统考/联考成绩查询时间及入口
  2. 大数据分析中国冬季重度雾霾的成因(三)
  3. 线性表、顺序表以及ArrayList、Iterable、Collection、List中重要的方法
  4. cassandra随机获取数据,Cassandra适合写入和少读,HBASE随机读取写入
  5. Kubernetes,多云和低代码数据科学:2020年最热门的数据管理趋势
  6. JavaFX自定义控件– Nest Thermostat第2部分
  7. Service Broker实现发布-订阅(Publish-Subscribe)框架
  8. TF-IDF 提取文本关键词
  9. 京东一面:高并发下,如何保证分布式唯一全局 ID 生成?
  10. shell截取字符串的方法
  11. 阿里春招Android面经
  12. Django评论系统
  13. 计算机工程工艺,中国计算机学会第十届计算机工程与工艺学术年会.pdf
  14. 微信生态圈的发展分析
  15. 使jets3t支持https
  16. 在SDLC中使用静态代码分析的最佳实践
  17. Microsoft Edge安装扩展插件
  18. Angular防抖指令——输入事件
  19. 《信息物理融合系统(CPS)设计、建模与仿真——基于 Ptolemy II 平台》——第3章 数据流 3.1同步数据流...
  20. KiCad V6使用记录

热门文章

  1. 激光打标机不能刻字的处理
  2. 使用Dism++安装系统(新手勿尝试)
  3. 中国象棋计算机比赛,2019年象棋软件大赛:四核弱机(56核)vs天命智棋(48核)...
  4. 哈里斯鹰优化算法(HHO)附代码
  5. 学科分类与代码_考研常识 | 2021硕士研究生学科门类、一级学科、二级学科、专业目录及代码查询...
  6. 惠普电脑u盘重装系统步骤_HP惠普电脑怎么用U盘装系统
  7. 微pe工具箱有linux版吗,微PE工具箱
  8. linux远程链接Windows桌面,linux远程桌面链接windows
  9. c语言程序设计输出函数,输出函数C语言程序设计.pdf
  10. WEB前端-CSS精灵技术