PCIE 如何获得BAR 空间的大小

27 篇文章 1 订阅
24 篇文章 7 订阅

首先关于PCIE最基础的东西,配置空间相关的东西。

配置空间的头部有两种类型,Type0与Type1,分别给非桥设备(Endpoint)和桥设备(Root和Switch端口中的P2P桥)来使用,配置头部的对比如下图所示,pcie由pci发展过来,pci的空间只有256个字节,PCIE扩展到4K的空间,所以根据BUS DEVICE FUNCTION,配置空间最大256 * 32 * 8预留16M的空间.

 1)Device ID Version ID首四个字节功能号和设备号没有什么可以说的,用来标识不同厂商的。

2) Header Type: BIT0到BIT6代表设备类型,0为EP设备,1为我们这节讲的桥身边,2为Card bus

3) Class code(offset 9~0xB)又分为三段信息:

1))sub-class(offset 0xB): 子类设备,若使用的是桥对应的是06

 

 2)) sub-class(offset A) 将设备更详细的划分,比如上个子设备是桥(06)则该字段进一步划分, offset 为 9 对应Programming_Interface

 4) Base Address Regsiter(offset 0x10~0x18),代表该设备在PCIE总线上面的寻址能力,起始地址是PCIE驱动配置的,大小是固定的,如图:

1) BAR0寄存器的大小怎么确定了, 举个例子,比如向offset 0x10寄存器写入全0xFFFFFFFF,然后再将寄存器的值读取回来若至为0xfff00000, 则代表这是一个大小为1M 32-bit的memory 空间寄存器。

2)若我们为PCIE设备分配是空间是[0xB0000000 ~ 0xBFFFFFFF], 则可以将BAR0的其实地址设置为0xB0000000, 可分配空间则需要减少1M, 下一个BAR的地址则为 0xB0000000+1M(特别说明一下,我们使用的这个地址虽然可以当成cpu直接访问MMIO地址,但是实际上对于PCIE来说这个是两码事情,CPU到访问我们设备地址是经过ATU单元转换的,因为我们一般设置为的是一一映射),这些配置好的地址只是用于cpu对于桥设备的一些特殊寄存器访问,对于桥下设备的地址响应没有关系,那桥响应IO 或者memory 空间在哪里设置了??

5) 两种请求,对于pci桥下游ep设备的范围在4个寄存器进行设置Prefetchable base upper 32 bits(offset 0x28) Prefetchable memory base(0x24) 这个两个寄存器设置prefetchable的memory的起始地址,如图:

Prefetchable base upper 32 bits: 为 p-mmio >> 32 & 0xffffffff = 0x2

Prefetchable memory base: p-mmio >> 16 & 0xfff0 = 0x4000

6) Primary Bus Number(Offset 0x18)寄存器用于记录PCI总线段的总线号网桥的主接口所连接的端口。

7) Secondary Bus Number(offset 0x20)寄存器用于记录PCI总线的总线号网桥的次接口所连接的网段。

8) Command 寄存器(offset 0x4),一般需要设置BIT1,让设备能够响应memory 空间

 

 

 

 

 对于nvme ssd 来说,BAR空间存放的都是NVMe 相关的寄存器.

设备在系统的PCI地址空间里申请一段来用,所申请的空间基址和大小保存在BAR寄存器里
BAR里的只是PCI域的地址空间,需要映射到IO地址空间里或者内存地址空间里之后软件才能使用。
映射到IO空间的话,用IO读写指令和函数去访问设备;映射到内存空间的话,首先得到的是物理地址,映射到虚拟地址后就可以像用指针那样访问。
IO BAR和MEM BAR分别是映射到IO空间和内存空间的BAR;BAR寄存器的0位指示要映射到哪,有的设备这位可以由用户设置,有的只读。
每个BAR具体干嘛是设备自己定义的,要看手册。 

在PCIE配置空间里,0x10开始后面有6个32位的BAR寄存器BAR寄存器中存储的数据是表示PCIE设备在PCIE地址空间中的基地址注意这里不是表示PCIE设备内存在CPU内存中的映射地址,关于这两者的关系以及两者如何转换后面会有介绍。

    1,BAR寄存器的数据格式,BAR寄存器表示的设备存储类型有memory space BAR和IO space BAR两种,

          对于memory space BAR,数组格式如下面所示

          31-----------------------------------4 |        3           |     2-1     |       0

          16-Byte aligned Base Adress   prefetchable    Type   always 0

           对于IO space BAR,数组格式如下所示

           31-----------------------------------2 |      1      |     0

           4-Byte Aligned Base Address    reserve   always 0

           对于memory space BAR的16-Byte aligned Base Address 这里的Base Address代表的是16个byte数据的地址

          对于IO space BAR的4-Byte aligned Base Address这里的BaseAddress代表的是4个byte数据的地址

    2,BAR寄存器数据的初始化

          BAR寄存器的数据是怎么初始化,由谁进行初始化的?因为初始化的数据是PCIE设备所在的总线域的地址空间,所以肯定不会是EP自己进行初始化,因为如果这样EP是不知道其他PCIE设备对应的总线地址空间的,所以可能会引起总线地址空间的冲突,所以BAR寄存器的初始化是由内核进行初始化的,在系统开机时,内核会遍历查找哥哥PCIE设备,然后为PCIE设备分配对应的总线地址空间。

    3,BAR寄存器存储的总线地址和应用程序内存地址的关系

          BAR寄存器存储的总线地址,应用程序是不能直接利用的,应用程序首先要做的就是读出BAR寄存器的值,然后用mmap函数建立应用程序内存空间和总线地址空间的映射关系。这样应用程序往PCIE设备内存读写数据的时候,直接利用PCIE设备映射到应用程序中的内存地址即可。但是应用程序的内存地址该由谁解析到PCIE设备对应的总线空间给EP呢,这个工作是由北桥或者是RC(root complex)来完成的,解析到总线地址空间之后,EP会把总线的地址空间解析成PCIE设备对应的设备内存地址.

bit0:表示设备寄存器是映射到memory(0)还是IO(1)空间。
bit1: reserved 0
bit2: 在base adress register for Memory 中0表示32位地址空间,1表示64位地址空间。
bit3:在memory BAR中用来表示该设备是否允许prefetch,1表示可以预取,0表示不可以预区。
bit4~31:用来表示设备需要占用的地址空间大小。

针对bit4~31,某些位为只读,且0来表示需要的地址空间大小,比如一个PCI设备需要占用1MB的地址空间,那么这个BAR就需要实现高12bit是可读写的,而20-4bit是只读且为0。地址空间大小的计算方法如下:

a.向BAR寄存器写全1

b.读回寄存器里面的值,然后clear 上图中特殊编码的值,(IO 中bit0,bit1, memory中bit0-3)。

c.对读回来的值取反,加一就得到了该设备需要占用的地址内存空间。

Bios可以为设备预留大于BAR要求的地址空间,而PCI规范中建议为需要的地址空间少于4KB的设备分配4KB的地址空间。映射到IO空间的设备每个BAR不能占用大于256byte,在X86系统中IO编址是16位的,此时BAR中的高16bit需要hardware to zero。构造完成以后需要为设备分配地址空间,此时就是把分配给设备的起始地址写入BAR寄存器即可。我们发现PCI这种地址自然地要求为设备分配的地址空间要跟BAR定义的大小对齐,因此PCI设备的映射空间始终是跟BAR的大小对齐的。

        如果某个设备的BAR没有被全部使用,则对应的BAR应被硬件全被设置为0,并且告知软件这些BAR是不可以操作的。对于被使用的BAR来说,其部分低比特位是不可以被软件操作的,只有其高比特位才可以被软件操作。而这些不可操作的低比特决定了当前BAR支持的操作类型和可申请的地址空间的大小。

        一旦BAR的值确定了(Have been programmed),其指定范围内的当前设备中的内部寄存器(或内部存储空间)就可以被访问了。当该设备确认某一个请求(Request)中的地址在自己的BAR的范围内,便会接受这请求.

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

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寄存器了。驱动中使用结构体来表示这些寄存器(结构体根据成员变量自动地址对齐)。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乾 乾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值