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