NVMe驱动解析-关键的BAR空间

PCIe的Header空间和BAR空间是PCIe得以广泛使用的关键特性。Header空间是PCIe设备的通有属性,所有的PCIe Spec功能和规范都在这里实现;BAR空间则是设备差异化的具体体现,BAR空间的定义决定了这个设备是网卡,SSD还是虚拟设备。

这篇文章将着重介绍NVMe的BAR空间和驱动的实现。

BAR空间的来历

PCIe Header空间中有几个寄存器专门用来存储BAR空间地址,一个PCIe设备有6个32位BAR寄存器,下图是NVMe协议规定的BAR空间设置。NVMe设备只使用BAR0和BAR1,两者表示了一个64位地址,同时,BAR0的前4bit定义了地址的属性。

BAR空间的地址是谁设置的呢?BAR空间是Host根据PCIe 设备的反馈结果设置的。首先,当Host准备配置BAR空间时,会先根据这个BAR寄存器的偏移量(相对于PCIe Header)向PCIe设备发送写请求(写这个BAR寄存器,所有位写1),PCIe设备收到请求后,并不会按照Host的要求全部写1,而是写入自己要使用的BAR空间大小。然后Host再读这个寄存器的时候,就知道PCIe设备需要多少空间了。当Host进行PCIe设备的初始化时,按照这个方法知道了系统中所有PCIe设备需要的BAR空间后,就可以分配PCIe地址空间给每一个设备了。

需要注意的是,BAR空间的数据实际存储在PCIe设备上,也就是说BAR空间只是Host这边给PCIe设备分配的地址资源,并不占用Host的内存资源。当读写BAR空间时,都需要通过PCIe接口(通过PCI TLP消息)进行实际的数据传输。

NVMe SSD 如何使用BAR空间

前面已经提到,BAR空间是PCIe设备体现自己功能的地方。NVMe协议在BAR空间上定义了一系列的寄存器。有了这些寄存器,Host就可以与NVMe SSD的Controller进行交互了。如下图,可以看到有Controller 状态,设置等寄存器,管理命令(Admin Command)配置寄存器和NVMe队列寄存器(Doorbell用来通知Controller I/O Request操作信息)


NVMe 驱动如何使用BAR空间

根据前面的介绍,通过BAR空间可以设置Controller,读取Controller状态等。因为BAR空间并不是Host的内存,NVMe驱动使用BAR空间前,必须将BAR空间映射到虚拟地址空间,使用内核提供的ioremap函数可以做到这一点。前面关于设备注册的文章提到在设备加载时,就会调用

dev->bar = ioremap(pci_resource_start(pdev, 0), 8192);

此后,驱动就可以通过dev->bar加上寄存器的偏移地址(如Controller Status的偏移地址是0x1C),使用writel,readl这类函数访问Controller寄存器了。驱动中使用结构体来表示这些寄存器(结构体根据成员变量自动地址对齐)。

521 struct nvme_dev {
522         struct list_head node;
523         struct nvme_queue **queues;
524         u32 __iomem *dbs;
525         struct pci_dev *pci_dev;
526         struct dma_pool *prp_page_pool;
527         struct dma_pool *prp_small_pool;
528         int instance;
529         int queue_count;
530         int db_stride;
531         u32 ctrl_config;
532         struct msix_entry *entry;
533         struct nvme_bar __iomem *bar;
534         struct list_head namespaces;
535         struct kref kref;
536         struct miscdevice miscdev;
537         char name[12];
538         char serial[20];
539         char model[40];
540         char firmware_rev[8];
541         u32 max_hw_sectors;
542         u32 stripe_size;
543         u16 oncs;
544 };

 24 struct nvme_bar {
 25         __u64                   cap;    /* Controller Capabilities */
 26         __u32                   vs;     /* Version */
 27         __u32                   intms;  /* Interrupt Mask Set */
 28         __u32                   intmc;  /* Interrupt Mask Clear */
 29         __u32                   cc;     /* Controller Configuration */
 30         __u32                   rsvd1;  /* Reserved */
 31         __u32                   csts;   /* Controller Status */
 32         __u32                   rsvd2;  /* Reserved */
 33         __u32                   aqa;    /* Admin Queue Attributes */
 34         __u64                   asq;    /* Admin SQ Base Address */
 35         __u64                   acq;    /* Admin CQ Base Address */
 36 };

下面是加载设备时使能NVMe Controller的例子:

dev->ctrl_config = NVME_CC_ENABLE | NVME_CC_CSS_NVM;
.......
writel(dev->ctrl_config, &dev->bar->cc);

后续文章中会有更多关于BAR空间的例子。总之,BAR空间是Host和PCIe设备进行信息交互的重要介质,了解BAR空间的定义对理解设备的运行有重要作用。

张元元是Memblaze SSD事业部应用工程师,研究方向涉及PCIe SSD在VSAN、Docker等环境中的应用及优化。对于服务器虚拟化、NVMe驱动的实现、Linux内核及容器技术有深入的研究。本系列文章为张元元对于NVMe驱动及相关技术的全面解读,更多张元元的文章请关注他的微信公众号:yuan_memblaze


相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页