文章目录
前言
图片来自:https://zhuanlan.zhihu.com/p/463518877
一、配置空间header
参考:http://www.ssdfans.com/?p=8210
每个PCIe设备,有这么一段空间,Host软件可以读取它获得该设备的一些信息,也可以通过它来配置该设备,这段空间就叫做PCIe的配置空间。不同于每个设备的其它空间,PCIe设备的配置空间是协议规定好的,哪个地方放什么内容,都是有定义的。PCI或者PCI-X时代就有配置空间的概念。
整个配置空间就是一系列寄存器的集合,其中Type 0是Endpoint的配置,Type 1是Bridge(PCIe时代就是Switch)的配置,都由两部分组成:64 Bytes的Header+192Bytes的Capability结构,后者是设备告诉Host它的一些特殊功能。
进入PCIe时代,PCIe能耐更大,192 Bytes不足以罗列它的内容。为了保持后向兼容,整个配置空间由256 Bytes扩展成4KB,前面256 Bytes保持不变。
着重看一下Type 1的header:
Device ID: 只读;设备 ID。
Vendor ID: 只读;厂商 ID。如 Intel 的为 0X8086。
Status: 16 位状态寄存器。
Command: 16 位命令寄存器
Class Code: 此字段用于区分 PCI-E 设备属于什么类型设备,如网卡,显卡等
Revision ID: 只读;程序版本号
BIST: 自测
Header type: PCI-E 头类型寄存器
- 0bit~6bit: 0-EP;1-Switch; 2-CardBus
- 7bit: 0-单功能设备; 1-多功能设备
Lantency Timer: PCI-E 无意义
Cache line size: PCI-E 无意义
Base Address register: 扩展空间的基址寄存器
Expansion ROM Base Address: 扩展 ROM 地址
二、Base Address register(BAR)
2.1、BAR是干什么的
对Endpoint Configuration(Type 0),提供了最多6个BAR,而对Switch(Type 1)来说,只有2个。BAR是干什么用的?
每个PCIe设备,都有自己的内部空间,这部分空间如果开放给Host(软件或者CPU)访问,那么Host怎样才能往这部分空间写入数据,或者读数据呢?CPU只能直接访问Host内存(Memory)空间(或者IO空间,我们不考虑),不能对PCIe等外设直接操作。此时就需要Root Complex,RC。RC可以为CPU分忧。
具体解决办法是:CPU如果想访问某个设备的空间,由于它不能亲自跟那些PCIe外设打交道,因此叫RC去办。比如,如果CPU想读PCIe外设的数据,先叫RC通过TLP把数据从PCIe外设读到Host内存,然后CPU从Host内存读数据;如果CPU要往外设写数据,则先把数据在内存中准备好,然后叫RC通过TLP写入到PCIe设备。
2.2、具体实现过程
具体实现就是上电的时候,系统把PCIe设备开放的空间(系统软件可见)映射到内存空间,CPU要访问该PCIe设备空间,只需访问对应的内存空间。RC检查该内存地址,如果发现该内存空间地址是某个PCIe设备空间的映射,就会触发其产生TLP,去访问对应的PCIe设备,读取或者写入PCIe设备。
一个PCIe设备,可能有若干个内部空间(属性可能不一样,比如有些可预读,有些不可预读)需要映射到内存空间,设备出厂时,这些空间的大小和属性都写在Configuration BAR寄存器里面,然后上电后,系统软件读取这些BAR,分别为其分配对应的系统内存空间,并把相应的内存基地址写回到BAR。(BAR的地址其实是PCI总线域的地址,CPU访问的是存储器域的地址,CPU访问PCIe设备时,需要把总线域地址转换成存储器域的地址。)
BAR示例1——32bit内存地址空间请求
参考:https://zhuanlan.zhihu.com/p/463518877
下图展示了设置建立一个BAR的基础步骤,在本例中,要请求一个4KB大小的不可预取内存(NP-MMIO,non-prefetchable memory)。在图中,展示了BAR在配置过程中的三个节点:
-
在图中的(1)当中,我们可以看到BAR处于未初始化的状态。设备的设计者已经将低位bit固定为一个数值,来指示需要的memory的大小和类型,但是高位bit(可写可读的)则仍然是用X来表示,这代表它们的值还未知。系统软件将会首先把每个BAR都通过配置写操作来将可写入的bit写为全1(当然,被固定的低位bit不会受到配置写操作的影响)。在下图的(2)中展示的BAR就是处于第二阶段的样子,除了被固定的低位bit以外,所有的bit都被写为1。
写为全1这个操作是为了确定最低位的可写入的bit(least-significant writable bit)是哪一位,这个bit的位置指示了需要被请求的地址空间的大小。在本例中,最低位的可写入的bit为bit 12,因此这个BAR需要请求2的12次方(或者说是4KB)的地址空间。如果最低位的可写入的bit为bit 20,那么这个BAR就要请求2的20次方(1MB)的地址空间。 -
在软件将BARs中所有可写bit都写为1后,软件将从BAR0开始,依次读取每个BAR的数值,以此来确定各个BAR要请求的地址空间的大小和类型。下标中总结了本例中对BAR0进行配置读的结果。
-
这个过程中的最后一步就是系统软件为BAR 0分配一个地址范围,因为对于软件来说现在已经知道了BAR 0请求的地址空间的大小和类型。下图的(3)中展示了BAR处于第三阶段的样子,此时系统软件已经将一块地址区域的起始地址写入了BAR 0中。在本例中,这个起始地址为F900_0000h。
到这里为止,对BAR 0的配置就完成了。一旦软件启用了命令寄存器(Command register,偏移地址04h)中的内存地址译码(memory address decoding),那么这个设备就会接受所有地址在F900_0000h-F900_0FFFh(4KB大小)范围内的memory请求。
BAR示例2——64bit内存地址空间请求
在上一个例子中,我们看到了通过BAR 0来请求不可预取内存地址空间(NP-MMIO)。在当前的例子中,如图 4‑5所示,BAR 1和BAR 2被用来请求一块64MB的可预取内存地址空间。这里使用了两个连续相连的BAR是因为这个设备支持这个内存地址空间的请求使用64bit地址,这意味着如果需要的话,软件给它分配的地址空间可以超过4GB地址边界(并非必要)。由于地址可以是64bit位宽,因此必须将两个连续相连的BAR一起使用。