PCIe简介-配置空间

目录

1. 简介

1.1. 配置空间简介

1.2. Type 0设备的配置空间: 

1.2.1. 配置空间的结构

1.2.2. BAR(Base Address Register)

1.2.2. Type 1配置空间

 

 2. 配置空间的访问过程

3. 配置空间的分配

4. PCIe遍历过程

4.1. 消息路由

4.2. 配置空间访问流程

5.引用

1. 简介

1.1. 配置空间简介

        每个PCIe设备都有一段空间,Host软件可以读取它获得该设备的一些信息,也可以通过它来配置该设备,所以这段空间就被称为配置空间。PCie的配置空间是协议规定好的,每一个字段都是有定义的。

        配置空间在PCI/PCI-X时代就已经出现了,只不过那时的配置空间大小为256字节。整个配置空间就是一系列寄存器的集合,其中Type 0是Endpoint的配置,Type 1是Bridge(PCIe时代就是Switch)的配置,都由两部分组成:64 Bytes的Header+192Bytes的Capability结构,后者是设备告诉Host它所具有的功能。

         进入PCIe时代,配置空间的大小从256字节增加到4KB,但是64字节的可配置空间的头部大小没有改变。

 前面说到,PCIe有两种设备:Type0表示终端设备,Type1表示Switch,由于职责的不同,其配置空间的内容也不同。但是为了保持一致,方便管理,这两类设备的配置有很多相同的部分,比如配置空间的头部,如下图所示:

1.2. Type 0设备的配置空间: 

1.2.1. 配置空间的结构

 和公共字段相比,Type 0的配置空间多了一些字段:

  • Subsystem ID和Subsystem Vendor ID:顾名思义,用来帮助每个设备厂商标识更细粒度设备信息
  • Cardbus CIS Pointer Register:已经废弃,以前用于PC-Card
  • Expansion ROM Base Address Register:用于描述设备的ROM的地址,这里和我们的主要内容关系不大,就不过多展开了

1.2.2. BAR(Base Address Register)

除了上面这些字段,其中最重要的就是BAR了。在Type 0的配置空间中,BAR区域有24个字节,可以保存6个指针/地址,每一个都可以用来描述一个不同的内存空间或者IO空间的地址和范围。

为了描述不同类型的地址空间,这里的指针不是单纯的指针,而有着自己的结构,如下 [1] [3] :

其中:

  • 最低位Bit 0:是一个标志位,用于描述地址空间的类型,0表示内存空间,1表示IO空间
  • Memory Space中的Bit [2:1] - Type:用于描述内存空间的类型,00表示32位地址空间,10表示64位地址空间
  • Memory Space中的Bit 3 - Prefetchable:用于描述内存空间是否支持预取,0表示不支持,1表示支持。如果一段内存空间支持预取,它意味着读取时不会产生任何副作用,所以CPU可以随时将其预取到DRAM中。而如果预取被启用,在读取数据时,内存控制器也会先去DRAM查看是否有缓存。当然,这是一把双刃剑,如果数据本身不支持预取,那么除了可能导致数据不一致,多一次DRAM的查询还会导致速度下降。

另外也许你会觉得很奇怪,一个32位的空间,又是如何又表示地址又表示范围呢?这里其实和BAR的初始化过程有关。BAR的寄存器初始化主要有三步 [1](7.5.1.2.1 Base Address Registers):

  1. BIOS将全1的地址写入BAR寄存器,这样会导致BAR寄存器的值被重置,并被设备重新写入初始值。这个初始值是一个地址,表示如果将这个BAR寄存器指向的内存放在物理内存的最后,其地址为多少。比如,如果我们需要4KB的内存空间,那么这个地址就是0xFFFFF000,当然这里还需要加上最低几位表示类型的Flag。另外,如何这个空间不可用,那么返回全0。
  2. BIOS读取BAR寄存器的值,并去除掉最后几位Flag,然后将其取反并加1,求出其大小。比如0xFFFFF000,取反之后就是0x00000FFF,加1之后就是0x00001000,也就是4KB。
  3. BIOS接着进行真正的地址分配和映射,并将这个新的地址重新写入BAR。这个时候设备没有权利拒绝这个修改,并且也不能再对这个地址进行任何的更改了,不然系统可能会整个崩溃。

1.2.2. Type 1配置空间

接下来我们来看看Type 1设备,也就是Switch,的配置空间。它的配置空间和Type 0配置空间有着很大的不同。虽然我们可以看到它们大小是一样的,但是其中BAR空间和设备信息的字段变成了很多的地址信息,如下:

这些改动的原因是因为作为Switch,它并不需要也不会实现特定的功能,它的作用就是为PCIe的消息提供路由转发的机制,所以中间所有的字段几乎都变成了和路由转发相关的地址信息。

  • Primary Bus Number / Secondary Bus Number / Subordinate Bus Number:用于基于BDF的转发
  • Memory Base / Memory Limit:用于基于内存空间地址的转发
  • Prefetchable Memory Base (Upper) / Prefetchable Memory Limit (Upper):也是用于基于内存空间地址的转发,不过是Prefetchable的地址
  • IO Base / IO Limit:用于基于IO空间地址的转发

 

 2. 配置空间的访问过程

         由于CPUCPU只能直接访问Host内存(Memory)空间(或者IO空间,我们不考虑),不对PCIe等外设直接操作。因此需要Root Complex协助CPU对某个PCIe设备进行访问。比如,如果CPU想读取某个PCIe设备中的数据,那么RC通过TLP将数据从外设PCIe设备读取到为其分配的内存空间,然后CPU从内存中读取数据。如果CPU向某个外设PCIe设备发送数据,那么CPU先向为此设备分配的内存中写入数据,RC通过TLP写入到PCIe设备中。

上图例子中,最左边虚线的表示CPU要读Endpoint A的数据,RC则通过TLP(经历Switch)数据交互获得数据,并把它写入到系统内存中,然后CPU从内存中读取数据(紫色箭头所示),从而CPU间接完成对PCIe设备数据的读取。

具体实现就是上电的时候,系统把PCIe设备开放的空间(系统软件可见)映射到内存空间,CPU要访问该PCIe设备空间,只需访问对应的内存空间。RC检查该内存地址,如果发现该内存空间地址是某个PCIe设备空间的映射,就会触发其产生TLP,去访问对应的PCIe设备,读取或者写入PCIe设备。

一个PCIe设备,可能有若干个内部空间(属性可能不一样,比如有些可预读,有些不可预读)需要映射到内存空间,设备出厂时,这些空间的大小和属性都写在Configuration BAR寄存器里面,然后上电后,系统软件读取这些BAR,分别为其分配对应的系统内存空间,并把相应的内存基地址写回到BAR。(BAR的地址其实是PCI总线域的地址,CPU访问的是存储器域的地址,CPU访问PCIe设备时,需要把总线域地址转换成存储器域的地址。)

如上图例子,一个Native PCIe Endpoint,只支持Memory Map,它有两个不同属性的内部空间要开放给系统软件,因此,它可以分别映射到系统内存空间的两个地方;还有一个Legacy Endpoint,它既支持Memory Map,还支持IO Map,它也有两个不同属性的内部空间,分别映射到系统内存空间和IO空间。

3. 配置空间的分配

在系统启动时,BIOS会通过ACPI(Advanced Configuration and Power Interface)找到所有的PCIe设备,并为其分配配置空间,映射到物理地址空间中,然后通过ECAM(Enhanced Configuration Access Mechanism)转交给操作系统。我们通过acpidump对MCFG表进行导出,然后使用iasl就可以查看到ECAM的基址了:

# Dump MCFG table from ACPI as binary file: mcfg.dat
$ sudo acpidump -n MCFG -b

# Disassemble MCFG table
$ iasl ./mcfg.dat; cat mcfg.dsl
...
[000h 0000   4]                    Signature : "MCFG"    [Memory Mapped Configuration table]
...
[02Ch 0044   8]                 Base Address : 00000000E0000000
...

而为了方便访问,PCIe使用BDF来构造每个配置空间相对于ECAM的偏移。由于每个空间都是4096个字节,所以PCIe将BDF向左移位了12位,对其进行预留。其地址映射关系如下 :

 打个比方,如果某个设备的BDF是46:00.1,ECAM基址是0xE0000000,那么其配置空间起始地址就是:0xE0000000 + (0x46 << 20) | (0x00 << 15) | (0x01 << 12) = 0xE46001000。或者简单的记忆就是BDF的Hex后面跟三个0。我们这里也可以通过lspci/dev/mem进行直接的物理内存访问来验证。

$ lspci -s 46:00.1  -nn
46:00.1 Ethernet controller [0200]: Broadcom Inc. and subsidiaries NetXtreme BCM5720 Gigabit Ethernet PCIe [14e4:165f]

$ sudo hexdump -x --skip 0xe4601000 /dev/mem | head
e4601000    14e4    165f    0406    0010    0000    0200    0010    0080
...

这段内存的前面几个数字14e4165f就是这个设备的Vendor ID和Device ID,这和我们通过lspci看到的完全一致:[14e4:165f]

当然,每次这样进行计算和转换来查看原始的配置空间是非常麻烦的,所以我们可以通过setpci来直接访问:

$ setpci -s 46:00.1 00.w
14e4

$ setpci -s 46:00.1 02.w
165f

使用setpci的时候需要注意:无论是读取和写入,请务必按照目标字段的长度来进行输入,PCIe的内存地址的IO并不一定是内存的读取,而有可能被转换成PCIe的请求,如果长度不对,则很有可能出现错误

例子:

        (1)上电时,系统软件首先会读取PCIe设备的BAR0,得到数据:

        (2) 系统软件往该BAR0写入全1,得到

        BAR寄存器有些bit是只读的,是PCIe设备在出厂前就固定好的bit,写全1进去,如果值保持不变,就说明这些bit是厂家固化好的,这些固化好的bit提供了这块内部空间的一些信息:

低12没变,表明该设备空间大小是4KB(2的12次方),然后低4位表明了该存储空间的一些属性(IO映射还是内存映射,32bit地址还是64bit地址,能否预取?做过单片机的人可能知道,有些寄存器只要一读,数据就会清掉,因此,对这样的空间,是不能预读的,因为预读会改变原来的值),这些都是PCIe设备在出厂前都设置好的,提供给系统软件的信息。然后系统软件根据这些信息,在系统内存空间找到这样一块地方来映射这4KB的空间,把分配的基地址写入到BAR0:

从而最终完成了该PCIe空间的映射。一个PCIe设备可能有若干个内部空间需要开放出来,系统软件依次读取BAR1,BAR2。。。,直到BAR5,完成所有内部空间的映射。

整个过程如下:

前面说每个PCIe设备都有一个配置空间,其实这样说是不准确的,而是每个PCIe设备至少有一个配置空间。一个PCIe设备,它可能具有多个功能(function),比如既能当硬盘,还能当网卡。每个功能对应一个配置空间。

在一个PCIe拓扑结构里,一条总线下面可以挂几个设备,而每个设备可以具有几个功能,如下所示:

因此,在整个PCIe系统中,只要知道了Bus+Device+Function,就能找到对应的Function。寻址基本单元是功能(function),它的ID就由Bus+Device+Function组成 (BDF)。一个PCIe系统,可以最多有256条Bus,每条Bus上可以挂最多32个Device,而每个Device最多又能实现8个Function,而每个Function对应着4KB的配置空间。上电的时候,这些配置空间都是需要映射到Host的内存空间,因此,需要占用内存空间是:256*32*8*4KB =256MB。在这个动辄4GB、8GB内存的时代,256MB算不了什么。

4. PCIe遍历过程

说到路由转发,一个很奇怪的问题就出现了:为什么我们没有在配置空间中看到我们网络交换机中的那种复杂的路由表呢?这其实和PCIe如何进行ID和地址的分配有关,这个过程叫做PCIe的遍历(PCIe Enumeration)。我们这里就用下面这张图来说明一下这个过程是如何进行的:

整个PCIe的遍历过程其实是一个简单的DFS和线段树,拿上图的BDF来举例子,每一个Bridge中都保存着三个用于路由的关键信息:

  • Primary Bus Number(Pri):这个Bridge所在的Bus Number,也就是它的上游连接的Bus Number
  • Secondary Bus Number(Sec):这个Bridge所连接的下一个Bridge的Bus Number
  • Subordinate Bus Number(Sub):这个Bridge所连接的下游所有的Bus的最大的Bus Number

这些信息形成了一个递归的结构来帮助我们进行基于BDF进行路由,而PCIe的遍历就是来建立这个递归的结构。我们来看看上图中的遍历过程:

  1. 首先,我们从Root Complex的Host Bridge出发。Host Bridge略有不同,因为他的上游没有连接任何总线,所以没有Pri,它的下游连接的是Root Complex中的总线,也就是Bus 0,所以我们的遍历从Bus 0开始,此时Host Bridge中的Sec是0。另外,虽然此时Sub未知,但是为了安全,在向下遍历的过程中,我们会把Sub设置为最大值,也就是0xFF,这样即便是出错,我们也不会出现无法路由的情况。
  2. 然后,我们遍历到了Root Complex中的第一个Bridge,很明显它的Pri是0。由于桥接的原因,它的下游总线的Bus Number要保证唯一,于是我们加1,所以它的Sec是1,同样Sub还是改为0xFF。
  3. 然后,我们继续递归,到了第一个Switch的Upstream Bridge,因为它所连接的Bus Number是1,所以它的Pri是1,而同理,它的下游会连接到它的内部总线,于是需要把Bus Number再加1,变成2,于是Sec为2。
  4. 继续递归到第一个Downstream Bridge,同样它的下游的Bus Number需要加一,于是Pri为2,Sec为3。
  5. 继续递归,到了第一个Endpoint,它的下游没有任何设备,所以开始回溯。
  6. 回到了步骤4所在的Bridge,此时最大的Bus Number为3,所以Sub更新为3,最后Pri为3,Sec为3,Sub为3。
  7. 同理,第二个Bridge的Pri为3,Sec为4,Sub为4。
  8. 然后回到步骤3访问的Bridge,此时最大的Bus Number为4,所以Sub更新为4,最后Pri为1,Sec为2,Sub为4。
  9. 依次类推,直到所有的设备完成。

等所有的步骤结束,我们就会得到上面这张图中对应的分配了!类似的,内存的空间,IO的地址空间,Prefetchable的地址空间,都会进行类似的遍历和分配,然后把最后合并的区间保存在上游的配置空间中,这样大家应该就理解了为什么配置空间中只需要保存一个区间就可以进行路由了,而不需要保存复杂的路由表了。

另外,PCIe的热插拔其实就是靠在遍历过程中预留更多的Bus Number来实现的,这样就可以在不影响已有设备的情况下,插入新的设备了。

4.1. 消息路由

现在,有了上面的路由信息,我们就可以很轻松的来对PCIe的消息进行路由了!它其实就是一个非常简单的线段树,我们假设需要将一个消息从CPU发给BDF为04:00.0的设备,那么其路由过程如下:

  1. 首先,CPU会请求Host Bridge产生消息,然后由于Bus 4在第一个Bridge的Sec和Sub之间,Root Complex会将这个消息通过这个Bridge转发出去。
  2. 然后继续递归,这个消息将通过Bus 1传递给下游Switch中的连接上游总线的Bridge。而这个Switch会来检查它下游所有Bridge的配置,最后发现Bus 4在它的第二个下游Bridge的Sec和Sub之间,于是这个Switch会将这个消息通过这个Bridge转发出去。
  3. 消息经过Bus 4到达Function 04:00.0

4.2. 配置空间访问流程

为了总结,我们就从CPU出发,用对配置空间的读请求做一个例子,来对整体的流程来一个总结吧!

  1. 首先,CPU执行内存访问指令来读取虚拟内存中映射的,在ECAM中的,某个配置空间的内容。比如:mov ax, [0x10e8100000]
  2. 然后,这个读请求的地址经过MMU,查询页表得到物理内存的地址。假设,这个物理地址是BDF为 81:00.0 的设备的配置空间地址:0xe8100000
  3. 这个读请求会被发送给Memory Controller,Memory Controller检查这个地址之后,发现这个地址不属于DRAM,于是转发给对应的PCIE控制器,到Root Complex中。
  4. Root Complex的Host Bridge收到这个请求,发现这个请求属于设备的配置空间,于是将这个请求转换为一个配置空间的读请求(请求名称叫CfgR0,具体的结构后面会介绍),地址是BDF 81:00.0,Offset是0,长度是2个字节,并利用BDF开始路由。
  5. Root Complex根据所有连接到其上面的设备和桥的配置空间里的配置,将这个请求转发给对应的设备。如果是设备本身就检查Device Number和Function Number,如果是桥,就检查Secondary Bus Number和Subordinate Bus Number,然后进行递归的转发。
  6. 最后,请求到达设备。

数据返回的流程和请求的流程非常类似,只不过是从设备出发,返回给CPU,这里就不再赘述了。

5.引用

【1】PCIe(二) —— 配置空间 | Soul Orbit

【2】老男孩读PCIe之六:配置和地址空间

  • 22
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值