讲解PCI & PCIe 的书有很多,我手上就拿了一本《PCI Express 体系结构导读》的书,据说这本书基本是翻译了外文,书上虽然内容比较全面,但是书那么厚,想达到快速掌握的目的还有一定难度;网上也有很多相关博客,但是普遍很浅,内容局限又支离破碎;这就是写这篇总结的目的,从我的理解出发,对PCI & PCIe 做一个总结。
内容聚焦于下面几个方面:
PCI是并行总线,是总线型拓扑结构,图中给出了一个比较复杂的PCI总线拓扑,借此来说明PCI总线的组成,初始化等部分
在描述PCI总线拓扑之前要先讲清楚图中的这几个域,其中有两个域是一定要区分清楚的,那就是存储器域与PCI总线域。域应该怎么理解呢?我理解为地址空间,PCI总线有其独立的地址空间,SOC也有其独立的地址空间,这两个地址空间不能搞混。
CPU所能访问的PCI总线地址一定在存储器域中有地址映射;
PCI设备能够访问的存储器域的地址也一定在PCI总线域中具有地址映射。
PCI总线拓扑主要由三部分构成:
HOST主桥
HOST主桥用来隔离处理器系统的存储器域与处理器系统的PCI总线域,管理PCI总线域,完成处理器与PCI设备间的数据交换。这也是我们为什么一上来就说要区分存储器域与PCI总线域了,因为HOST主桥的作用之一就是隔离这两个域。图中的拓扑带有两个HOST主桥,HOST主桥X与HOST主桥Y.
PCI总线
PCI总线由HOST主桥或者PCI桥管理,用来连接各类设备。图中给出了PCI总线0,1,2,3,4. HOST主桥出的总线编号号PCI总线0,在一棵PCI总线树中有多少个PCI桥(包含HOST主桥),就有多少条PCI总线。
PCI设备
在PCI总线中有三类设备:PCI主设备、PCI从设备、PCI桥设备。
一个PCI设备既可以是主设备也可以是从设备,但同一时刻只能有一种角色。
上述的描述讲了PCI总线的拓扑,拓扑中每一个部分的功能。那么接下来就会有疑问:我们(CPU)是如何访问PCI设备的?
知道了PCI设备与CPU如何交互,我们才能去写驱动代码,所以这个问题要搞清楚。
CPU访问PCI设备的配置空间使用的是ID寻址方式;CPU访问PCI设备的存储器和IO地址空间采用的是*地址寻址***方式
一下子出现了这么多名词,配置空间、ID寻址、存储器地址空间、IO地址空间,一个个讲清楚就知道PCI到底是如何访问的了。
CPU访问PCI设备是通过地址映射进行的,PCI设备内部的地址空间(PCI总线域的一部分)会通过一定方式映射到SOC能够访问到的一段地址空间中(存储器域的一部分),借助DMA来实现通信。这个地址映射关系总要有个地方记录吧;还有插入一个PCI设备,总要知道它的一些基本信息吧,比如厂家信息,版本信息…;插入的PCI设备是哪一种得有能区分的点吧。一旦遇到这种问题,大多数解决方案都是规定一个统一的格式,大家都按照这个格式来填写,而且要提供一种通用的方式能够获取到这个格式的数据,这种通用性必须能够屏蔽PCI设备间的差异。于是乎就有了配置空间这么个玩意。
PCI设备都有独立的配置空间,分为三种类型:
PCI Agent配置空间和PCI桥配置空间需要重点了解,Cardbus这个我也不知道是什么鬼。
绝大多数PCI设备将PCI配置信息存放在E2PROM中,PCI设备进行上电初始化,将E2PROM中的信息读到PCI设备的配置空间中作为初始值,这个操作由硬件完成。
PCI桥的配置空间在系统软件遍历PCI总线树时进行配置,系统软件不需要专门的驱动程序设置PCI桥的使用方法,PCI桥一般来讲是透明的。
PCI桥有两组BAR寄存器,如果PCI桥本身不存在私有寄存器,那么BAR寄存器可以不使用(透明桥),初始化为0.
PCI Bridge 的配置空间相比较 PCI Agent 的配置空间,多了 Bus Number 寄存器
PCI桥的配置空间中有三个 Bus Number 寄存器,这三个寄存器是需要软件在初始化PCI总线的时候填写的,也就是PCI总线树Bus号的初始化。
系统软件在遍历当前PCI总线树时,需要首先对这些PCI总线进行编号,即初始化PCI桥的Primary、Secondary、Subordinate Bus Number 寄存器。编号时使用深度优先算法。再次强调,在一棵PCI总线树中有多少个PCI桥(包含HOST主桥),就有多少条PCI总线。
在PCI总线拓扑图中,HOST主桥X直接出的PCI总线就是PCI Bus 0
HOST主桥扫描(这个扫描具体是怎样一种动作?)PCI总线0上的设备,系统软件首先忽略这条总线上的所有PCI Agent,
HOST主桥就发现了PCI桥,命名为X1, 把这个桥出的总线定为PCI Bus 1, 将PCI桥X1的Primary Bus Number 赋为0,
因为这个桥是接在PCI总线0上的,把Secondary Bus Number 寄存器赋值为1,因为它引出的PCI总线为Bus 1;
继续扫描PCI Bus 1,又发现了一个桥,命名为PCI桥X2, 把这个桥出的总线定为PCI Bus 2, 将PCI桥X2的Primary Bus Number为1,Secondary Bus Number为2;
继续扫描PCI Bus 2,发现了新桥,命名为PCI桥X3,将这个桥出的总线定为PCI Bus 3, 将PCI桥X3的Primary Bus Number设置为2,Secondary Bus Number设置为3;
继续扫描PCI Bus 3,没有发现PCI桥,也就是说PCI总线3下面不会有新的总线了,就把PCI桥X3的Subordinate Bus Number 赋值为3,并且回退到PCI Bus 2;
继续扫描PCI Bus 2,没有发现除PCI桥X3之外的桥,把PCI桥X2的Subordinate Bus Number 也赋值为3,并回退到PCI Bus 1;
继续扫描PCI Bus 1, 没有发现除PCI桥X2之外的桥,把PCI桥X1的Subordinate Bus Number 也赋值为3,并回退到PCI Bus 0;
继续扫描PCI Bus 0, 发现了新桥,命名为PCI桥X4,将PCI桥X4的出的总线定为PCI Bus 4, 将PCI桥X4的Primary Bus Number设置为0,Secondary Bus Number设置为4;
继续扫描PCI Bus 4,没有发现新桥,把PCI桥X4的Subordinate Bus Number 赋值为4, 并回退到PCI Bus 0;
继续扫描PCI Bus 0, 没有发现新桥,结束遍历,完成Bus号分配。
HOST主桥通过配置读写总线事务(这是总线事务的一种,什么是总线事务?这…)访问配置空间。
配置读写总线事务通过ID号进行寻址。ID号由总线号、设备号、功能号组成。
HOST主桥使用寄存器号来访问PCI设备配置空间的某个寄存器。
PCI总线有两类配置请求:
以x86处理器来讲,PCI控制器提供了CONFIG_ADDRESS寄存器和CONFIG_DATA寄存器,就是通过这两个寄存器来控制配置读写总线事务
CONFIG_ADDRESS寄存器与Type 01h配置请求的对应关系
CONFIG_ADDRESS寄存器与Type 00h配置请求的对应关系
在PCI总线中,只有PCI桥能够接收Type 01h 配置请求,Type 01h 配置请求不能直接发向最终的PCI Agent设备,而只能由PCI桥将其转换为Type 01h 请求继续发向其他PCI桥或者转换为Type 00h 配置请求发向PCI Agent 设备
在PCI总线拓扑中,加入要访问PCI设备01
HOST处理器访问PCI01的配置空间,发现PCI设备01与HOST主桥直接相连,
所以将直接使用Type 00h 配置请求访问该设备的配置空间
将CONFIG_ADDRESS 寄存器的Enabled位置1,
Bus Number号置为0,并对该寄存器的Device, Function, Register Number字段赋值
当HOST处理器对CONFIG_DATA寄存器访问时,
HOST主桥将存放在CONFIG_ADDRESS寄存器中的数值转换为Type 00h配置请求,
并发送到PCI总线0
PCI01设备接收到这个Type 00h配置请求,进行交互。
在PCI总线拓扑中,假如要访问PCI设备31
HOST处理器访问PCI设备31的配置空间,需要通过HOST主桥、PCI桥X1、X2和X3,最到达PCI31。
首先将CONFIG_ADDRESS的 Bus Number 置为3;
然后当HOST处理器对CONFIG_DATA寄存器进行读写访问时,HOST主桥将Type 01h 的配置请求发送到PCI总线0;
PCI Bus 0上的PCI桥X1接收配置请求。
PCI桥X1的Secondary Bus Number为1,Subordinate Bus Number为3, 1 < Bus Number <=3,
所以PCI桥X1接收来自PCI总线0的Type 01h配置请求,并将这个配置请求发送到PCI Bus 1;
PCI Bus 1上的PCI桥X2接收配置请求。
PCI桥X2的Secondary Bus Number为2,Subordinate Bus Number为3, 2 < Bus Number <= 3,
所以PCI桥X2接收来自PCI总线1的Type 01h配置请求, 并将这个配置请求发送到PCI Bus 2;
PCI Bus 2上的PCI桥X3接收配置请求。
PCI桥X3的Secondary Bus Number为3,Subordinate Bus Number为3,则要访问的设备就在这个桥下,
PCI桥X3将Type 01h的总线事务转换成Type 00h的总线事务,发送到PCI总线3, PCI31 接收到了请求,进行交互。
PCI设备的IDSEL信号与PCI总线的AD[31:0]信号的连接关系决定了该设备在这条PCI总线的设备号。
每一个PCI设备都使用独立的IDSEL信号,其中CONFIG_ADDRESS寄存器中的Device Number 字段共有5位,可以表示32个设备,而AD[31:11]只有21位,这意味着一条PCI总线上最多能接21个设备。
存储器域与PCI总线域的映射
PCI Agent 设备之间以及HOST 处理器和PCI Agent设备之间可以使用存储器读写和IO读写等总线事务进行数据传送送。大多数情况下, PCI桥不之间与PCI设备或者HOST主桥进行数据交换,仅仅是转发来自PCI Agent 或者 HOST 主桥的数据。
在PCI Agent 设备进行数据传送之前,系统软件需要初始化PCI Agent 设备的 BAR0 ~ BAR5寄存器,以及PCI桥的Base, Limit寄存器,系统软件使用DFS算法对PCI总线进行遍历时,完成这些寄存器的初始化,分配这些设备在PCI总线域的地址空间,然后PCI设备就可以使用PCI总线地址进行数据传输了。
PCI Agent 的BAR0 ~ 5, 以及 PCI bridge 的 Base 寄存器都是存的PCI总线地址,这些地址在处理器的存储器域有对应的映像,如果一个PCI设备的BAR空间在处理器的存储器域没有映像,处理器将不能访问PCI设备的BAR空间。
HOST主桥隔离了PCI总线域与存储器域。在PCI总线初始化时,会在CPU存储器域中建立PCI设备的存储器地址空间的映射,当处理器访问设备的地址空间时,首先访问该设备在存储器域中的地址空间,并且通过HOST主桥的地址空间转换为PCI总线域的地址空间,再使用PCI总线事务将数据发送到指定的PCI设备中
PCI设备访问存储器域的地址空间时通过DMA完成的。处理器需要将存储器域的地址空间反向映射到PCI总线地址空间。首先访问该储存器地址空间对应的PCI总线地址空间,通过HOST主桥将这个地址空间转换为存储器地址空间,再由DDR控制器对存储器进行读写访问。
X86处理器的HOST主桥中,存储器域的存储器地址与PCI总线域的地址相等。
存储器空间与IO空间???
PCI桥配置空间的图中,有一些Limit 和 Base 的寄存器,这些寄存器的作用就是记录该PCI桥所管理的PCI子树的存储器地址空间或者I/O地址空间的基地址和长度。
现在假设如下:
BAR寄存器初始化和PCI总线的Bus号分配是在同一个动作中完成的
软件遍历到PCI桥X3后,没有再探测到PCI Bus 3下面有PCI桥,这时候就为PCI Bus 3 下面的PCI Agent的BAR寄存器赋值(当然就是从0x7000-0000 ~ 0x7800-0000中分配)
PCI-Agent31.BAR0 = 0x7000-0000; PCI-Agent32.BAR0 = 0x7100-0000;
为PCI桥X3的Base, Limit 寄存器赋值 PCI-BridgeX3.Memory-Base = 0x7000-0000; PCI-BridgeX3.Memory-Limit = 0x200-0000;
回退到PCI Bus 2, 发现PCI设备21, PCI-Agent21.BAR0 = 0x7200-0000;
为PCI桥X2的Base, Limit 寄存器赋值 PCI-BridgeX2.Memory-Base = 0x7000-0000; PCI-BridgeX3.Memory-Limit = 0x300-0000;
回退到PCI Bus 1, 发现PCI设备11, PCI-Agent11.BAR0 = 0x7300-0000;
为PCI桥X1的Base, Limit 寄存器赋值 PCI-BridgeX1.Memory-Base = 0x7000-0000; PCI-BridgeX3.Memory-Limit = 0x400-0000;
回退到PCI Bus 0, 发现PCI桥X4, 进入PCI Bus 4, 没有再探测到PCI Bus 4 下面有PCI桥,这时候就为PCI Bus 4 下面的PCI Agent的BAR寄存器赋值
PCI-Agent41.BAR0 = 0x7400-0000; PCI-Agent42.BAR0 = 0x7500-0000;
为PCI桥X4的Base, Limit 寄存器赋值 PCI-BridgeX4.Memory-Base = 0x7400-0000; PCI-BridgeX4.Memory-Limit = 0x200-0000;
回退到PCI Bus 0, 没有再探测到PCI Bus 0下面有PCI桥, 这时候就为PCI Bus 4 下面的PCI Agent的BAR寄存器赋值
PCI-Agent01.BAR0 = 0x7600-0000;
遍历结束
地址译码
当一个存储器读写总线事务到达PCI总线时,在这条总线上所以的设备将进行地址译码,如果当前总线事务使用的地址在某个PCI设备的BAR空间中,该PCI设备将使能DVESEL#信号,认领总线事务。
Posted 传送方式与Non-Posted 传送方式
PCI设备读写主存储器
PCI设备与存储器直接进行数据交换的过程是通过DMA实现的。支持DMA传递的PCI设备可以在BAR空间中设置两个寄存器,分别保存目标地址和传送大小。
PCI设备进行DMA操作时,使用的目的地址是PCI总线域的物理地址,而不是存储器域的
中断机制
PCI提供了INTA#,INTB#,INTC#,INTD#信号向处理器发出中断请求,同时也提供了MSI机制向处理器提交中断请求
MSI中断机制
MSI中断机制采用存储器写总线事务向处理器系统提交中断请求,其实现机制是向HOST处理器指定的一个存储器地址写指定的数据。这个存储器地址一般是中断控制器规定的某段存储器地址范围,而且数据也是事先安排好的数据,通常含有中断向量号。
MSI在PCIe上已经成为了主流,PCIe设备必须支持MSI中断机制,PCI设备不一定都支持,而且在PCI设备上不常用。
PCI总线是并行总线,同一条总线上,所有外部设备共享总线带宽,PCIe总线使用高速差分总线,并采用端到端的连接方式,在一条PCIe链路的两端只能各连接一个设备,这两个设备互为数据发送端和数据接收端。PCIe总线在设计过程中使用了一些网络通信的技术。(现在感觉高速数据有很多类似的地方,基本都涉及serdies)