cpu 对外设的访问方式
ram-like 接口设备访问
我们在写程序时,对变量的赋值,取值操作,实际上做了对内存进行读写。通过地址就可以对内存进行读写操作。实际上对芯片上的其它外设IO操作也是一样。
int* a=0x100000;
int* b=0x200000;
*a = 1000;
*b= *a;
CPU 与外设的连接结构图如下所示,通过地址线传输想要访问的地址,数据线传输数据,读写信号线确定你的操作是读/写。
可以看到总线上所挂的设备(ram、flash、gpio),它们对信号线都是共享的,那么怎么选中想要访问的设备呢? 它们每一个设备都有各自的地址段,当地址信号线上的地址属于自己的范围时,cs(片选信号)就会选中芯片,然后进行访问。
关键在于:内存控制器,它会根据地址范围发出对应的片选信号,选中对应的设备。
类似于这样的接口 叫ram-like接口,所有的ram-like 接口设备,cpu 都可以直接通过地址读写。
下图是imx6ull 的片上外设控制器地址表(从0x0000_0000~ 0x8000_0000),可以看到不同的外设控制器各自有自己的地址段,0x8000_0000 是内存控制器。
无法直接用地址访问的设备
上图中的外设控制器都可以通过地址直接访问,但是它们管理的设备或其它设备是不能的。
例如我们在使用一个i2c设备时,需要读写i2c控制器,通过一定的协议才能完成对i2c设备的访问。i2c 协议
如emmc ,要像访问emmc 设备必须通过emmc 控制器,必须编写emmc控制器驱动。
这样的访问方式就会比较复杂,无法像内存一样轻松读写,因此pci 总线诞生。
pci 总线
PCI总线(Peripheral Component Interconnect,外部设备互联),挂在pci 总线下的设备,cpu可以通过地址直接访问,让访问外设变得像内存一样简单,那么它是怎么做到的呢?
如图pci 控制器(host birdge:主桥又称北桥)跟其它片上设备一样挂在内存控制器下,可以通过地址直接访问,pci/pcie 则由它来管理。
当cpu 想要访问某个pci 设备时,cpu 发出的地址到达pci控制器后,它会将地址做偏移处理,转换成pci_addr。同样每一个pci设备都拥有属于自己的一段pci_addr,它们确认控制器发出的pci_addr属于自己的范围内,就会与其进行交互。
pci_addr = cpu_addr + offset
pci 接口定义
pci 接口定义如图所示:红色为必需信号线,蓝色为可选信号线。
必需的引脚中分为以下几个部分
- 地址与数据
- AD[31:0] : 地址线与数据线是复用的。怎么控制它的功能是传输地址还是数据,使用 interface control 中的FRAME# 信号线。
- C/BE[3:0]# : 它也是一组复用信号线,当传输地址是它是命令 command;当传输数据时,它是字节使能 Byte enable,用于字节选择,可以进行单字节、字、双字访问;
- PAR : 奇偶校验信号,确保AD[31:00]和C/BE[3:0]#传递的正确性;
- 控制接口
- FRAME# : 低电平有效,FRAME# 拉低的第一个时钟为AD 地址周期,下一个时钟为数据周期。
- IRDY#: 信号由PCI主设备驱动,信号有效时表示PCI主设备数据已经ready。
- TRDY#: 信号由目标设备驱动,信号有效时表示目标设备数据已经ready
- STOP# : 停止信号线,目标设备请求主设备停止当前总线事务。
- DEVSEL#: 设备选择。
- IDSEL : ID并非身份ID,而是Initialization Device Select(初始化设备选择),在读写设备配置空间时用于选中某个设备。
- system
CLK : 时钟信号线。
RST#: 复位。
C/BE[3:0]# command 定义
在地址周期时,C/BE[3:0]# command 定义如下。
pci 设备的访问
pci 总线 与设备的连接结构如图,与前面一样许多设备都挂在同一条pci 总线上,它们对信号线是共享的,通过地址来对指定的设备访问。
问题1: 那这些设备怎么确认自己的地址段呢?在用地址访问之前需要对pci 设备做配置,也就是读写配置寄存器。
pci 配置空间访问——非桥设备(type0)
先看以下配置空间的结构:
每一条pci支总线最多可以拥有32个设备(实际上负载不了这么多),每一个设备都有8个功能,每一个功能有一块 256 Byte 的配置空间(所谓的配置空间即许多寄存器的集合)。
PCI设备可以简单地分为PCI Agent、PCI Bridge、Cardbus Bridge:
- PCI Agent:真正的PCI设备(比如网卡),是PCI树的最末端
- PCI Bridge:桥,用来扩展PCI设备,必定有一个Root Bridge,下面还可以有其他Bridge。
- Cardbus Bridge:用得不多,不介绍。
根据pci 设备类型的不同,pci 配置空间也有不同。pci配置空间一共有256 字节,其中64 字节为头部空间,配置过程就是头部寄存器的读写。
- device id 与 vendor id: 各占2 字节,cpu读取它们就可以知道厂商和设备。
- header type: 设备类型,1字节。普通设备它的值为0x00,桥设备为0x01。
- BAR: 普通设备在其中声明自己需要多大的空间,同样cpu 会把首地址写回 BAR。
pci 设备的简单配置过程: - 设备会在配置空间中声明自己需要多大的空间
- cpu获取到这个值,就会给它分配指定长度的空间,并将首地址写回pci 设备配置空间。
- 有了首地址后,该设备就有了自己的地址范围,首地址+长度= 地址范围;之后cpu就可以通过地址访问这个设备。
注意: cpu 的地址和pci 的地址是不一样的,pci控制器会对地址做转换。
问题2: pci 总线上有那么多设备,如何能访问到其中某一个设备?
- 通过IDSEL# 引脚选中设备,PCI 接口上有AD[31:0] 32条地址线,例如将AD31接到设备1的IDSEL、AD30——设备2 IDSEL、AD29——设备3 IDSEL,以此类推当选中某个设备时只要将对应的地址线拉低即可选中设备。(配置时地址线bit 31-11都是保留,可以与IDSEL相连)
问题3:如何访问设备上的某一个功能?配置空间中的哪一个寄存器? - 下图有定义AD[10:8] 表示功能序号,AD[7:2]表示寄存器序号。
pci 非桥设备配置过程:
- AD31——IDSEL# 拉低选中设备。
- FRAME# 拉低,第一个时钟发送地址 AD[10:8] 选择功能,AD[7:2] 选择寄存器地址。
- C/BE# 发送命令 1010 读取配置空间。
- TRDY#、IRDY# 双方设备准备就绪后开始传输数据。
- 读取device id与vendor id 得知设备信息,header type 得知设备类型(桥或非桥)。
- 非桥设备读取BAR 获取需要分配的空间大小。
- 分配一段空间,将首地址写回BAR。配置完成
- 以此类推配置bus0 下的每一个设备。
pci 配置空间访问——桥设备(type1)
0x00~0x0c 这一段寄存器,除了header type值不同外其它都与type 0相同,0x10 开始都不同。
BAR: 桥设备不需要多大的BAR空间,因为它只需要做转发工作。
primary bus: 上一级总线的总线号。
secondary bus: 自己的总线号。
subordlinate bus: 子总线中最大的总线号。
问题4:如何配置桥设备?
- 跟一般的PCI设备一样,通过IDSEL来选中它。
- 通过type 0配置命令读取配置空间,发现它是PCI桥设备,把分配的总线号写给它。
问题5:前面描述了如何配置 bus0 下的pci设备,那么如何配置bus1 下的pci设备呢?怎么通过桥转发到它的子设备。
- 主桥发出type 1的包。
- AD[23:16]:bus number 确定要访问哪个桥设备(哪一级总线)(桥的配置空间中有三个bus号分别是primary bus上级、secondary bus自己、subordlinate bus子级中最大)。
当 bus number>=secondary bus && bus number <=subordlinate bus 时它就知道需要自己做转发。 - 如果bus number = secondary bus时,需要访问的桥设备号与当前桥号相等时,那么需要访问的设备就挂在这一级的总线下,桥设备发出type 0命令,这一级的桥通过ADXX——IDSEL选中设备,根据function number选择哪个功能,register number 选择寄存器,就可以对某一级桥下的设备进行读写。
- bus number > secondary bus && bus number <=subordlinate bus ,如果要访问的总线号大于当前桥设备的总线号,并且小于等于自己桥设备中最大的总线号,那么要访问的设备就在自己子级桥下,因此该桥会发出type1 命令找到下一级桥,以此类推直到发生步骤③。
这个过程类似于路由,需要到达的设备属于自己时,就转发给设备,不属于自己,就转发给下一级总线。
- 桥:接收到
Configuration Command type 1
后,有2个选择- 对于跟它直连的PCI Agent
- 把
Configuration Command type 1
转换为Configuration Command type 0
- 使用IDSEL选择设备
- 把
- 如果还需要通过下一级的桥才能访问此设备
- 转发
Configuration Command type 1
- 转发
- 对于跟它直连的PCI Agent
PCIE 总线
PCIE 总线即PCI Express(快速的pci)。
在我们的映像中并行的数据线(例如pci 的AD[31:0])相对传输速率比较高,但是当真正高速后,信号线之间会产生干扰,这种情况下反而使用串行信号线速度更快。
所以PCIE 的数据线改为串行的差分信号线(一个方向上有两根信号线tx+、tx-)。
PCIE 的接口引脚如图:
PCIE_TXP/N、PCIE_RXP/N,构成了一组发送与接收的信号线,被称为一个 lane ,P为+、N为-。
PCIE_REFCLKP/N 时钟也是差分信号。
PCI接口的引脚时并行的,PCEe的是串行的,每个方向的数据使用2条差分信号线来传输,发送/接收两个方向就需要4条线,这被称为1个Lane:
PCIE 设备之间可以有多个lane:
- 两个PCIe设备之间有一个Link
- 一个Link中有1对或多对"发送/接收"引脚,每对"发送/接收"引脚被称为"Lane"
- 一个Lane:有发送、接收两个方向,每个方向用2条差分信号线,所以1个Lane有4条线
- 一个Link最多可以有32 Lane
PCIE 总线结构
PCIE总线 与PCI 总线的相同点:虽然PCIE 信号线发生改变,但是对CPU 来讲它与PCI 总线是一样的,CPU仍让是使用地址即可以访问到PCIE设备,所以软件驱动上PCI 与PCIE 设备驱动是兼容的。
PCIE 仍然需要像PCI 一样配置设备,分配设备所需要的地址段。
对比PCI 与PCIE 结构图,PCI 总线 CPU 对接的是Host Bridge(主桥)、PCIE 总线CPU 对接的是Root Complex,实际上Root Complex已经将桥封装在内部,所以它们是对于CPU 来说是相同的。只是在桥后的部分不同。
** PCIe 与PCI的不同:**
- 连接方式不同:在PCI系统里,多个PCI设备都链接到相同总线上。但是在PCIe系统里,是点对点传输,即一条PCIE 总线只能连接一个PCIE设备(不包括Root Complex、switch内部结构),要接多个PCIe设备,必须使用Switch进行扩展。
- 通信方式不同:PCI 使用并行通信,数据与地址在AD[31:0] 上并行传输;PCIe 使用串行通信,数据是有先后的一个Byte一个Byte发送接收,因此PCIe 采用数据包方式传输。
这是PCIE 总线详细的结构图,标注出了Root Complex、switch的内部结构。
Root Complex 由Host Bridge 和多个 PCI-to-PCI Bridge 组成。
Switch 由多个 PCI-to-PCI Bridge 组成,功能主要是扩展PCIE 能接更多的设备。
PCIE 设备之间数据包的传输
前面说到CPU 对于PCIE 设备仍然是使用PCI地址就可以访问到设备,那么它们需要像PCI 设备一样配置吗?
答案:是的
CPU 将如何配置它们?PCIE 的数据信号线变化,并且没有地址线,桥与PCIE 设备之间该以怎样的形式通信呢?
问题1:PCIE 设备之间如何通信(主要就是桥设备与普通设备),怎么传输地址、数据?
PCIE 总线数据线为串行信号线,属于字节流,只能一个一个Byte 发送,因此采用 数据包 方式传送。
PCIE 设备之间的数据通信,与网络协议栈非常相似,如图PCIE 协议栈一共有三层。
分别是Transaction层(事务层)、Data Link层(数据链路层)和 Physical层(物理层)。
事务层: 传输的是Transaction Layer Packet(TLP),其中包含 header 头部、data payload 要传输的数据内容、ECRC crc校验码,验证header 和data payload 传输正确性。头部中又包含了命令类型(什么方式的读/写)、地址,保证数据能发送到目的地
数据链路层: (发送回路)数据链路层在拿到 TLP 包后在两端加上序列号和LCRC,在发生传输错误时,拥有重传机制。(接收回路)接收到一个link 包,验证seq 与crc并传递给事务层。
物理层: (发送回路)物理层 拿到link 包,在两端加上开始信号和结束信号。(接收回路)接收到一个物理层的包,将两端的开始与结束信号拆分,传递给上层的数据链路层。
TLP 头部
TLP 的通用格式如下:
PCIe TLP头部决定 数据包的目标设备、目录地址、读写命令等等。
在 PCIE 的不同的读写情况下TLP 包的格式也有不同:
- 命令类型:你是读内存还是写内存?读IO还是写IO?读配置还是写配置?
在Header里面有定义。 - 地址:对于内存读写、IO读写,地址保存在Header里。
- Bus/Dev/Function/Regiser:对于配置读写,这些信息保存在Header里。
- 数据:对于内存读、IO读、配置读,先发出请求,再得到数据。
分为2个阶段:读请求报文、完成报文。 读请求报文,不含数据;完成报文,包含数据。
PCIE TLP头部完整格式:
Fmt 与type: 决定命令的类型,内存读/写、IO读/写、配置0读/写 或配置1读/写。参照表格
Bus Number: 要访问的总线号。(以下几个号都只适用于配置设备时的TLP头部)
Device Number: 要访问的设备号。
Function Number: 要访问的功能号。
Register Number: 要访问的寄存器号。
下图是PCIE 配置TLP头部:
PCIE 配置空间
PCIE总线 与PCI 一样,需要读取设备配置空间,获取到设备信息,分配地址并写入配置空间。
根据pcie设备类型不同,PCIE 配置空间分为两类:type0(非桥设备)和type1(桥设备)。
基本与PCI 设备配置空间相同,主要关注以下几个:
header type: 区分设备类型,非桥设备为0x00、桥设备为0x01。bit7 位决定PCIe 是多功能设备(一般endpoint才有多功能,桥设备一般为单功能),还是单功能设备。bit7=1 多功能,bit7=0 单功能。
Base Address: 需要分配的地址长度存放在BAR中,并且配置完会向BAR写入首地址。
Pirmary Bus Number: 上游总线号。
Secondary Bus Number: 自己的总线号。
Subordinate Bus Number: 下游总线号的最大数值。
配置PCIe 设备
配置桥设备
配置PCIe 设备首先需要配置桥设备,因为需要通过桥的转发才能到达endpoint。
例如配置与主桥相连的A桥:
根据Fmt 与type选择有两种类型的TLP 配置包:分别为type0和type1。它们的区别是当需要到达的设备与桥直连时(比如host 与A、A与C),该桥会发出type0的包,直接到达设备;当需要到达的设备在它的下游,并且不与他直连时(比如A 与最下端设备),该桥会发出type1的包,type1经过一层层桥的转发,到达与该设备直连的桥时,该桥会发出type0 的包,到达设备。
① A与主桥相连,所以主桥发出type0 的包,并且标明Device 、Function 和Register Number。
② 根据设备号(Root Complex 为封装好的硬件,所以设备号已经在硬件上固定了,switch 也是一样)、功能号和寄存器号,就可以找到想要读写的寄存器。
③ 读取配置空间header type,发现A 设备为桥,将其的总线号分别配置为 Pirmary Bus Number=0、Secondary Bus Number=1、Subordinate Bus Number=255(暂定为255,待扫描完下游所有的桥设备后,修改为下游最大桥设备号)。
这样一个桥设备就配置完成了。
注意:
- type0 需要到达的设备与桥直连,因此type0 的TLP 头部中应该不用注明Bus Number,因为这样没有意义。
- 一个PCIe 设备中最多可以有8个功能,每个功能拥有一个配置空间。
配置非桥设备
非桥设备如上图中的 Bus3 Dev0设备,这种设备一般被叫做endpoint。
配置Bus3 Dev0:
① Host Bridge 发出type1 的TLP 数据包,Fmt=010、type=00101,Bus Number=3,Device Number=0,Function Number=0,Register Number=xxx。
② type1 TLP 包经过桥A->C->D桥,最终桥D发出type0 数据包,到达Bus3 Dev0。
③ 读取Bus3 Dev0 Function0 的配置寄存器,发现为普通设备
④ 读取BAR,获得设备需要的内存大小。
⑤ 分配指定大小内存,并将首地址写入设备0 BAR。配置完成。
PCIe 系统完整配置过程示例
5.2.1 硬件拓扑结构
以下图中的设备的配置过程为例,给大家做示范。
5.2.2 配置过程演示
下文中BDF表示Bus,Device,Function,用这三个数值来表示设备。
- 软件设置Host/PCI Bridge的Secondary Bus Number为0,Subordinate Bus Number为255(先设置为最大,后面再改)。
- 从Bus 0开始扫描:先尝试读到BDF(0,0,0)设备的Vendor ID,如果不成功表示没有这个设备,就尝试下一个设备BDF(0,1,0)。一个桥下最多可以直接连接32个设备,所以会尝试32次:Device号从0到31。注意:在Host/PCI Bridge中,这些设备的Device号是硬件写死的。
- 步骤2读取BDF(0,0,0)设备(即使图中的A)时,发现它的Header Type是01h,表示它是一个桥、单功能设备
- 发现了设备A是一个桥,配置它:
- Primary Bus Number Register = 0:它的上游总线是Bus 0
- Secondary Bus Number Register = 1:从它发出的总线是Bus 1
- Subordinate Bus Number Register = 255:先设置为最大,后面再改
- 因为发现了桥A,执行"深度优先"的配置过程:先去枚举A下面的设备,再回来枚举跟A同级的B
- 软件读取BDF(1,0,0)设备(就是设备C)的Vendor ID,成功得到Vendor ID,表示这个设备存在。
- 它的Header Type是01h,表示这是一个桥、单功能设备。
- 配置桥C:
- Primary Bus Number Register = 1:它的上游总线是Bus 1
- Secondary Bus Number Register = 2:从它发出的总线是Bus 2
- Subordinate Bus Number Register = 255:先设置为最大,后面再改
- 继续从桥C执行"深度优先"的配置过程,枚举Bus 2下的设备,从BDF(2,0,0)开始
- 读取BDF(2,0,0)设备(就是设备D)的Vendor ID,成功得到Vendor ID,表示这个设备存在。
- 它的Header Type是01h,表示这是一个桥、单功能设备。
- 配置桥D:
- Primary Bus Number Register = 2:它的上游总线是Bus 2
- Secondary Bus Number Register = 3:从它发出的总线是Bus 3
- Subordinate Bus Number Register = 255:先设置为最大,后面再改
- 继续从桥D执行"深度优先"的配置过程,枚举Bus 2下的设备,从BDF(3,0,0)开始
- 读取BDF(3,0,0)设备的Vendor ID,成功得到Vendor ID,表示这个设备存在。
- 它的Header Type是80h,表示这是一个Endpoing、多功能设备。
- 软件枚举这个设备的所有8个功能,发现它有Function0、1
- 软件继续枚举Bus 3上其他设备(Device号1~31),没发现更多设备
- 现在已经扫描完桥D即Bus 3下的所有设备,它下面没有桥,所以桥D的Subordinate Bus Number等于3。扫描完Bus 3后,回退到上一级Bus 2,继续扫描其他设备,从BDF(2,1,0)开始,就是开始扫描设备E。
- 读取BDF(2,1,0)设备(就是设备E)的Vendor ID,成功得到Vendor ID,表示这个设备存在。
- 它的Header Type是01h,表示这是一个桥、单功能设备。
- 配置桥E:
- Primary Bus Number Register = 2:它的上游总线是Bus 2
- Secondary Bus Number Register = 4:从它发出的总线是Bus 4
- Subordinate Bus Number Register = 255:先设置为最大,后面再改
- 继续从桥D执行"深度优先"的配置过程,枚举Bus 4下的设备,从BDF(4,0,0)开始
- 读取BDF(4,0,0)设备的Vendor ID,成功得到Vendor ID,表示这个设备存在。
- 它的Header Type是00h,表示这是一个Endpoing、单功能设备。
- 软件继续枚举Bus 4上其他设备(Device号1~31),没发现更多设备
- 已经枚举完设备E即Bus 4下的所有设备了,更新设备E的Subordinate Bus Number为4。然后继续扫描设备E的同级设备:Bus=2,Device从2到31,发现Bus 2上没有这些设备。
- 软件更新设备C即Bus 2的桥,把它的Subordinate Bus Number设置为4。然后继续扫描设备C的同级设备:Bus=1,Device从1到31,发现Bus 1上没有这些设备。
- 软件更新设备A即Bus 1的桥,把它的Subordinate Bus Number设置为4。然后继续扫描设备A的同级设备:Bus=0,Device从1到31,发现Bus 0上的设备B。
- 配置桥B:
- Primary Bus Number Register = 0:它的上游总线是Bus 0
- Secondary Bus Number Register = 5:从它发出的总线是Bus 5
- Subordinate Bus Number Register = 255:先设置为最大,后面再改
- 再从桥B开始,执行"深度优先"的配置过程。
PCIe 路由方式
PCIe 的三种路由方式
数据传输时,最先要确定的是:怎么找到对方?
所谓"路由",就是怎么找到对方,PCIe协议中有三种路由方式:
- 配置读、配置写:使用基于ID的路由,就是使用<Bus, Device, Function>来寻找对方。配置成功后,每个PCIe设备都有自己的PCIe地址空间了。
- 内存读、内存写或者IO读、IO写:
发出报文给对方:使用基于地址的路由 。
对方返回数据或者返回状态时:使用基于ID的路由 。 - 各类消息,比如中断、广播等:使用隐式路由 。
不管是什么路由方式,我们最终关心的是TLP的格式。
地址路由
内存读、内存写或者IO读、IO写属于地址路由。
下图是内存读写与IO读写的TLP 头部格式:内存读写数据格式有64bit和32bit两种,IO读写32位bit。
Address:代表要读写设备的地址(pci地址)。
requester ID:请求读写的设备id,其中 <Bus, Device, Function>。
tag:表示一次pcie的通信。
pcie的每一次发送或读取信息都是通过TLP包,tag就表示TLP包的唯一序号。在设备发出包后会有一段缓存专门存放TLP包,如果对方回应这个TLP 那么设备会释放这段缓存,假如等待一段时间没有回应,设备将会重传这个TLP。就是通过tag判断。
想要了解PCIe 地址的路由方式首先要了解桥设备的配置空间(type1):
Memory Base 和Memory Limit:Base表示一段内存的首地址,Limit表示大小,合起来就描述了一段地址空间。描述下游设备所分配的所有地址段(一个桥下游设备所分配的地址应该是连续的,所以Base表示这些设备地址中最首的地址,Limit 表示它们的总长 )。
Prefetchable Memory Base、Limit:表示下游设备可预取的内存空间。
IO Base 和IO Limit:表示所有子设备所分配的IO 地址空间。
地址路由完成报文
下图里面的Requester ID、TAG,被称为"Transaction ID"。
主设备要给EndPoint的内存写数据,它发出"内存写报文",不需要对方回应。
主设备要读EndPoint的内存数据,它发出"内存读报文",需要对方回应。
主设备要给EndPoint的IO写数据,它发出"IO写报文",需要对方回应。
主设备要读EndPoint的IO数据,它发出"IO读报文",需要对方回应。
- PCIe设备要回应时,回应谁?给"Requester ID",使用基于ID的路由
- 发起PCIe传输的设备(主设备),对每次传输都分配一个独一的Tag,并且在硬件内部保存当前TLP。
- 接收到回应报文后,才会根据Tag清除掉内存中保存数据。
- 如果没接收到回应,或者失败了:会把硬件中保存的TLP重新发送出去。
回应的完成报文,可以含有数据,也可以不含数据,TLP头部格式如下:
地址路由寻址过程
场景①:最常见的场景。cpu 想要访问nedpoint1,cpu给他分配的空间是A-B,那么桥P-P2的Memory base和Limit描述的大小就是A-B(因为它的下游只有一个设备。如桥p-p1他的下游有多个设备分配了空间,分别是A-B和C-D,那么它的base和Limit 描述的这段空间就是A-D )。
- cpu 发出的指令首先到达RC,RC将cpu_addr 转换为 pci_addr,并发出TLP包。
- TLP 到达p-p1,对比自己base 和Limit 知道,这条消息是要访问自己的下游设备,于是往下一级总线转发。
- 同样p-p2 发现要访问的是自己的下游设备,往下转发;p-p3 发现不是便不转发。
- 最终到达EP1。当EP1 回应cpu 消息时使用的是ID路由,RC 发给EP1的TLP 中包含了requester ID,通过它就将回应的消息返回给RC,cpu最终从RC读取。
场景②:EP1 与EP2之间的通信
- EP2 发出含有EP1地址的TLP包,P-P3 桥发现自己不能处理,于是便往上一级总线转发。
- P-P2 发现自己可以处理,于是往自己的下一级转发,最终到达EP1。
场景③:EP2 通过DMA 直接访问内存(主存储器)
- EP2 发出含有主存储器 地址的TLP包,P-P3 桥发现自己不能处理,于是便往上一级总线转发。
- 同样P-P1 也往上一级转发,最终通过RC的地址转换(pci_addr to cpu_addr),到达主存储器。
隐式路由
消息报文的头部格式如下:
消息报文中头部的Type字段里低3位表示隐式路由方式:
- 000:路由到RC
- 001:使用地址路由(使用地址路由的消息不常见)
- 010:使用ID路由
- 011:来自RC的广播报文,底下的PCIe桥会向下游转发此消息
- 100:本地消息,消息到达目的设备后结束,不会再次转发
- 101:路由到RC,仅用于电源管理