一、概述
为什么需要写这篇文章,当我阅读《深入浅出SSD》这篇书籍中PCIe章节时发现,本书籍的侧重点是放在PCIe控制器和PCIe协议上,从CPU角度理解PCIe知识偏少,本文对下面几个知识点做出一些补充。
- CPU访问外设寄存器与内存编址方式;
- CPU如何访问PCIe配置空间;
- CPU能够通过寄存器访问配置空间,为什么还需要映射PCIe配置空间;
- 如何扫描PCIe树并且为PCIe分配ID;
- 如何将pcie域地址映射到存储器域地址空间。
通过本篇文章将对问题1、2、3做出解答。
二、统一编址于独立编址
CPU编址是程序指令与物理地址线建立链接的方式,在CPU内部有专门的地址集合,编址过程是由CPU体系架构所决定的,参考示意图如图 1所示(仅仅代表示意图,讲解一种逻辑结构,不代表实际电路)。CPU编址时就已经指定了0x8000_0000~0xFFFF_FFFF这个地址空间为连接到图中内存的地址线,内存如何连接到CPU需要当参考CPU的datasheet,当CPU程序指令对0x8000_0000这个物理地址地址发起访问时,等价于是在访问图中2G内存的首地址。
图1
内存通过CPU地址总线来寻址定位,然后通过CPU数据总线读写数据。CPU的地址总线位数是CPU设计时确定,因此一款CPU所能寻址的地址范围是一定的,而内存是需要占用CPU的寻址空间的,内存与CPU采用总线直接连接。
IO指的是与CPU连接的各种外设,CPU访问各种外设有两种方式:一种是类似于访问内存的方式,即把外设的寄存器当成内存地址读写,从文可以以访问内存方式操作外设寄存器。这时,IO与内存统一编址,IO地址与内存地址在同一个地址空间下,这种编址方式叫做IO与内存统一编址。另外一种编址方式是IO地址与内存地址分开独立编址,这种编址方式叫做独立编址,此时,CPU访问外设寄存器需要通过CPU特定的指令去访问外设寄存器,而不能通过地址直接访问外设寄存器。常见的ARM、PowerPc、MIPS架构都是采用统一编址,X86架构采用独立编址。
三、访问PCIe配置空间256bytes
PCI总线规定访问配置空间总线事务,使用ID号进行寻址。PCI设备ID号由总线号(Bus Number)、设备号(Device Number)和功能号(Function Number)。其中总线号在HOST主桥遍历PCI总线树时确定,在一颗PCI总线树上,总线号由系统软件决定,通常与HOST主桥直接相连接的PCI总线编号为0,系统软件使用DFS(Depth-First Search)算法扫描PCI总线树上的所有PCI总线,并依次编号。一条PCI总线的设备号由PCI设备的IDSEL信号与PCI总线地址线的连接关系确定,功能号与PCI设备的具体设计有关。一个PCIe系统最多有256条Bus,每条Bus上最多可以挂在32个设备,每个PCIe设备最多有8个功能设备。
在XX处理器中的HOST主桥中,与PCIE设备配置相关的寄存器由CFG_ADDR、CFG_DATA等组成。系统软件使用CFG_ADDR(CFG_ADDR寄存器结构如图 2所示)和CFG_DATA寄存器访问PCIe设备的配置空间,这些寄存器都是采取同一编址(所有内存寄存器都使用存储器映射方式进行寻址)。当处理器访问PCIe配置空间时,首先需要在CFG_ADD寄存器中设置这个PCIe设备对应的总线号、设备号、功能号和寄存器偏移,然后使能Enable位,之后当处理器对CFG_DATA读写访问时,HOST主桥将这个存储器读写访问转换成PCIe配置读写请求,并且发送到PCIe总线上。如果Enable位没有使能,那么CPU对寄存器的访问也就是一个普通IO的访问,而不能让HOST转换成总线请求访问,访问PCIe配置空间时按照PCIe总线标准配置TLP请求,CFG_DATA是读取的数据或者待写入的数据。
图2
- 31位:Enable位,为1时,对CFG_DATA读写才能转换成PCIe总线配置请求。
- 30~24位:保留。
- 23~16位:总线号,最多=256个。
- 15~11位:设备号,最多=32个。
- 10~8位:功能号,最多=8个。
- 7~2位:寄存器偏移,最多访问寄存器=64个地址,这里一个地址是DW,那么能干访问的PCIe配置空间大小为64*4=256Byte,所以访问PCIe配置空间都是以4字节对齐访问的。
走到这里很多读者可能就会有这样的疑问,既然CPU能够直接通过寄存器访问配置空间,为啥还会出现配置空间在存储域地址的映射这一说法呢?下面给出详细解答。
访问PCIe配置空间寄存器的方法需要追溯到原始的PCI规范。为了发起PCI总线配置周期,Intel(Intel是PCIe龙头老大,最新的PCIe的规范总是它最先尝试的)实现的PCI规范使用IO空间的CF8h和CFCh来分别作为索引和数据寄存器,这种方法可以访问所有PCI设备的255 bytes配置寄存器。Intel Chipsets目前仍然支持这种访PCI配置空间的方法。PCIe规范在PCI规范的基础上,将配置空间扩展到4K bytes,至于为什么扩展到4K,具体可以参考PCIe规范,这些配置CFG_ADDR和CFG_DATA寄存器方法仍然可以访问所有PCIe设备配置空间的头255 bytes,但是该方法访问不了剩下的(255B~4K)配置空间。怎么办呢?Intel外一种PCIe配置空间访问方法。Intel Chipset通过将配置空间映射到内存地址空间,PCIe配置空间可以像对映射范围内的内存进行read/write来访问了。这种映射是由北桥芯片来完成的,但是不同芯片的映射方式也是不同的。目前我查看了ARM芯片的datasheet,确实是这样的方式。
PCIe规范为每个PCIe设备添加了更多的配置寄存器,空间为4K,尽管CFG_ADDR和CFG_DATA寄存器方法仍然能够访问lower 255 bytes,但是必须提供另外一种方法来访问剩下的(255B~4K)range寄存器。Intel的解决方案是使用了预留256MB内存地址空间,对这段内存的任何访问都会发起PCIe 配置cycle。由于4K的配置空间是directly mapped to memory的,那么PCIe规范必须保证所有的PCIe设备的配置空间占用不同的内存地址,按照PCIe规范,支持最多256个bus,每个Bus支持最多32个PCIe devices,每个device支持最多8个function,也就是说:占用内存的最大值为:256 * 32 * 8 * 4K = 256MB。图 3是ARM Cortex-A9 datasheet内存地址分配局部图。被PCIe配置空间占用的256M内存空间会屏蔽掉DRAM使用该段内存区,这些地址都由CPU出厂时已经固化好了。
图3
四、PCIe配置空间的内存映射对32bit系统的影响
由于PCIe配置空间占用了256M内存空间,而且该被占用空间对DRAM来说是不可用的,这意味着256M空间消失于系统内存,这在32bit系统中更为明显。比如,在32 bit winxp中(作者目前电脑还是用的XP系统,电脑用了七八年了),理论上可以访问到的内存是4G,如果4G空间都被DRAM给占用,由于PCIe的存在,被PCIe占用的那部分内存空间对OS来说是不可用的,莫名的消失了最多256M内存,其实还有其他外设寄存器需要映射到内存,如果是独立编址就不存在寄存器占用内存。所以在XP系统中实际能够访问DRAM空间最大值为3.2G。64位CPU寻址不存在这个情况,个地址目前来说应该用不完,这里读者需要注意的是CPU有32和64位寻址方式,同样操作系统也有32和64位之分,在Linux系统中主要体现在库文件上。
有些CPU没有直接指定PCIe配置空间的地址范围,需要读取某个寄存器的值BaseAddr,这个值就说PCIe配置寄存器在内存区域映射的基地址。访问PCIe设备配置空间时候需要手动计算访问PCIe配置空间的地址。计算发放如下:
SIZE_PER_FUNC = 4K = 1000h
SIZE_PER_DEVICE = 4K * 8 = 8000h
SIZE_PER_BUS = 4K *8* 32 = 100000h
访问总线号为busNo,设备号为DevNo,功能号为funcNo的offset寄存器的计算公式是:
Memory Address = BaseAddr+ busNo * SIZE_PER_BUS+ devNo * SIZE_PER_DEVICE+ funcNo * SIZE_PER_FUNC+ offset
访问PCIe配置空间就需要通过总线号、设备号、功能号、寄存器偏移进行转换成内存地址。转换函数如图 2所示。
图 4
问题4和5在下篇文章中讲解,介于作者实力有限。如有错误,望读者给出宝贵的意见。