LJgibbs · 2022年02月16日

PCI Express Technology 3.0:地址空间与事务路由4.1-4.2节

关于前一章

前一章节对PCIe环境中的配置操作进行了介绍。这些介绍包括用来实现Function配置的寄存器的空间、一个Function是如何被发现的、配置事务是如何被生成并路由转发的、PCI兼容配置空间和PCIe扩展配置空间的不同点,以及软件是如何区分开EP和Bridge的。

关于本章

本章节将描述一个Function通过Base Address Registers(BARs,基地址寄存器)来请求地址空间(memory地址空间或IO地址空间)的目的和方法,以及软件需要怎样设置所有的Bridge内的Base/Limit寄存器来使得TLPs能从一个源端口被路由转发至正确的目的端口。另外,本章也将会讨论PCIe中TLP路由的一般概念,包括基于地址的路由、基于ID的路由和隐式路由(implicit routing)。

关于下一章

下一章的将对TLP(Transaction Layer Packet,事务层包)的内容进行详细描述。我们将描述它的用途、格式、TLP Type的定义以及其他一些相关字段的详细内容。

4.1 我需要一个地址(I Need An Address)

几乎所有的设备中都有能够让软件(或是其他潜在的设备)访问的内部寄存器或是存储空间位置。这些内部位置有可能用来控制设备的行为、报告设备的状态,或者可能用来保持住设备要进行处理的数据。这里先不管这些内部寄存器/存储的目的,我们这里更看重的一点是可以从设备外部来对它们进行访问。这意味着这些内部位置需要是可寻址的(addressable)。软件必须可以执行一个带有地址的读或者写操作,以此来访问目标设备内相应的内部位置。为了让这种方式可行,这些内部位置都需要被分配地址,并且所分配的地址也都必须是系统所支持的地址空间。

PCIe支持三个地址空间,与PCI中的三个地址空间完全相同:

n 配置空间(Configuration)
n 内存地址空间(Memory)
n IO地址空间(IO)

4.1.1 配置空间(Configuration Space)

如我们在Chapter 1中所讨论的,配置空间是由PCI引入的,软件通过配置空间就可以用一种标准化的方法来对设备的状态进行控制和检查。PCIe对PCI软件具有向后兼容性,所以PCIe中仍然支持配置空间,并且支持它的原因也和PCI一样,即用一种标准化的方法来对设备的状态进行控制和检查。更多关于配置空间的信息(目的、如何访问、大小、内容等等)请参阅Chapter 3。

尽管配置空间出现的意义是来放置和保持一些标准化的结构(PCI-defined Header、Capability Structure能力结构等等),但是PCIe设备也会经常的将一些设备特定(device-specific)的寄存器映射到设备自身的配置空间中。在这种情况下,映射到配置空间的设备特定寄存器常用来作为控制寄存器(control)、状态寄存器(status)或者指针寄存器(pointer),而不是用来存储数据。

4.1.2 内存和IO地址空间(Memory and IO Address Spaces)

4.1.2.1 整体说明(General)

在PC的早期阶段,IO设备的内部寄存器/存储是通过IO地址空间(IO Address,它是由intel定义的)来访问的。然而,由于IO地址空间的一些限制和不良影响(在这里我们暂不讨论),这种地址空间很快就失去了软件和硬件厂商的青睐。这使得IO设备的内部寄存器/存储被映射到了memory地址空间(Memory Address Space,内存地址空间),一般被称作内存映射IO或者简称MMIO(Memory-Mapped IO)。然而,因为早期的软件是使用IO地址空间来访问IO设备的内部寄存器/存储的,所以实际中常用的做法是将一套设备特定寄存器既映射到内存地址空间,也映射到IO地址空间。这使得新的软件可以使用内存地址空间,也就是通过MMIO,来对设备的内部位置进行访问。而传统(旧)的软件也依然可以运行,因为它依然可以通过IO地址空间来访问设备的内部寄存器。

对于更加新型的设备,如果它们不再依赖老旧的传统软件并且也不需要考虑对传统操作的兼容问题,那么它们一般只要将内部寄存器/存储映射到内存地址空间(MMIO)即可,而不需要请求IO地址空间来进行映射。实际上,PCIe协议中不鼓励使用IO地址空间,支持这种操作仅仅是因为一些传统遗留问题,并有可能在未来新版本的协议中被弃用。

如图 4‑1所示,图中展示了一种通用的内存和IO的映射。内存映射的大小是这个系统可使用地址范围(通常由CPU的可寻址范围决定)的一个函数。PCIe中IO映射的大小被限制为32bits(4GB),虽然其实很多使用Intel兼容(x86)处理器的计算机中仅有低16bit(64KB)被使用。PCIe可以支持的内存地址大小达到64bit。

图 4‑1给出的映射示例仅展示了EP所声明使用的MMIO和IO,但是这种能力并不是EP所独有的。它对于Switch和RC来说也是一种很普通的能力,Switch和RC内部也存在着可以通过MMIO和IO地址来进行访问的设备特定寄存器。

4.1.2.2 可预取的与不可预取的内存空间的对比(Prefetchable vs. Non-Prefetchable Memory Space)

图 4‑1展示了被PCIe设备声明的两种不同类型的MMIO:可预取MMIO(Prefetchable MMIO,P-MMIO)和不可预取MMIO(Non-Prefetchable MMIO,NP-MMIO)。了解这两种内存空间之间的区别是十分重要的。可预取空间有两个意义十分明确的属性:

n 读操作不存在副作用。(Reads do not have side effects)
n 允许写合并(Write merging is allowed)

将MMIO的一个区域定义为可预取的,这样就可以推测性的提前获取该区域中的数据,以预测Requester在不久的将来可能需要比当前实际请求更多的数据。之所以这种小型的Caching(Minor Caching)数据操作是安全的,是因为读取这些数据并不会改变目标设备的任何状态信息,也就是说读取某个位置并不会带来副作用。例如,如果一个Requester请求从一个地址中读取128byte数据,那么Completer可以在给出此次请求的128byte之后也预取出下一个128byte,而当下一个128byte被请求时数据早已经被Completer从内存空间中预取出来了,这样就提高了性能。然而如果Requester再也没有请求额外的数据,那么Completer就需要将预取的数据清除掉,释放自身的Buffer空间。如果读取数据的行为改变了对应地址上的数值(或者有其他的什么副作用),那么就将会无法恢复被清除的数据了。然而对于可预取空间来说,读取行为并没有任何副作用,因此它总是可以回退并且在稍后也能获得原始的数据,因为原始的数据一直都不变的存放在那里。

你也许会想知道什么样的内存空间会存在读取操作的副作用。举一个例子,一个内存映射的状态寄存器,它被设计成在读取时自动清除自己,这样可以减少程序员在读取这个状态后还需要额外步骤来清除这些比特的操作。

做这种可预取和不可预取的区分,对于PCI的意义要大于PCIe,因为PCI总线协议中的事务并没有包含传输量大小的信息。如果设备都在同一条总线上交换数据,那么没有传输量大小的信息也不是什么问题,因为在同一总线上会有实时的握手信号来指示Requester什么时候收到了足够的数据完成了事务。但是如果数据的传输需要跨过一个Bridge来到另一条总线,那么情况就不像刚才一样简单了,因为对于跨到不同总线的读操作来说,Bridge在收集另一条总线上的数据时需要去猜测传输数据总量。如果猜错了传输量的大小则会增加延时而降低性能,因此在这种情况下若拥有预取的权限则对提升性能会有不小的帮助。这就是为什么将内存空间指定为可预取的概念在PCI中很有好处。由于PCIe请求中包含了传输量大小的信息,所以可预取空间并不像以前在PCI中那样引人关注了,但是为了向后兼容性,PCIe还是继承了这种概念。
image.png
图 4‑1通用的Memory与IO的地址映射

4.2 基地址寄存器BARs(Base Address Registers)

4.2.1 整体说明(General)

一个系统中的每个设备对地址空间的数量和类型可能都有不同的要求。例如,一个设备可能有256byte大小的内部寄存器/存储需要通过IO地址空间来访问,而另一个设备中可能有16KB的内部寄存器/存储需要通过MMIO来访问。

基于PCI的设备不允许自己来决定哪些地址可以用来访问它们内部的位置,做这些决定是系统软件负责的工作(例如BIOS和操作系统内核)。因此设备必须为系统软件提供一个途径用来确定设备对地址空间的需求。一旦软件知道了设备对地址空间的需求是什么样的,并假设这个需求是可以被满足的,软件就会给对应的设备分配一段可用的地址范围和相应的地址空间类型(IO、NP-MMIO、P-MMIO)。

这些都是通过配置空间Header中的基地址寄存器BARs(Base Address Registers)来完成的。如图 4‑2所示,一个Type 0 Header拥有6个可用的BAR(每个大小为32bit),而一个Type 1 Header只拥有2个BAR。Type 1 Header是存在于所有Bridge设备中的,这意味着每个Switch端口和RC端口都会拥有1个Type 1 Header。Type 0 Header只存在于非Bridge设备中,例如EP。关于这里的一个例子可以参阅图 4‑3。
image.png
图 4‑2配置空间中的BARs

系统软件必须首先确定设备所需地址空间的大小和类型。设备的设计者知道设备中需要通过IO或者MMIO访问的内部寄存器/存储的总体大小。设备的设计者还知道当这些寄存器被访问时设备将会如何工作(例如读取操作是否有副作用)。这将决定设备所需要的是可预取MMIO(读取操作无副作用)还是不可预取MMIO(读取操作有副作用)。知道了这个信息之后,设备设计者将BARs的低位bit固定为某个值,以此来指示需要请求的地址空间的大小和类型。

BARs的高位bit是软件可进行写入的。一旦系统软件通过检查BARs的低位bit确定了设备所请求的地址空间的大小和类型,系统软件就会将分配给这个设备的地址范围的基地址写入BAR中。由于一个EP(使用Type 0 Header)拥有6个BARs,它最多可以请求6个不同的地址空间。然而,一般实际中请求6个不同的地址空间并不常见。绝大多数设备会请求1到3个不同的地址范围。

并不是所有的BARs都需要被实现。如果一个设备并不需要使用所有的BAR来映射自己的内部寄存器,那么多余的BARs将会被固定为全0,以此来通知软件这些BARs并没有被实现。

一旦BARs被编程写入(programmed),设备内的内部寄存器或者本地内存(local memory)就可以通过BARs中所写入的地址范围进行访问。任何时候,当设备发现一个请求事务的地址是映射到自己的一个BAR时,它就会接收这个请求事务,因为它自己就是这个请求的目标设备。
image.png
图 4‑3 PCIe设备中对Type 0、Type 1 Header的用法

4.2.2 BAR示例1——32bit内存地址空间请求

图 4‑4展示了设置建立一个BAR的基础步骤,在本例中,要请求一个4KB大小的不可预取内存(NP-MMIO,non-prefetchable memory)。在图中,展示了BAR在配置过程中的三个节点:

  1. 在图 4‑4中的(1),我们可以看到BAR处于未初始化的状态。设备的设计者已经将低位bit固定为一个数值,来指示需要的memory的大小和类型,但是高位bit(可写可读的)则仍然是用X来表示,这代表它们的值还未知。系统软件将会首先把每个BAR都通过配置写操作来将可写入的bit写为全1(当然,被固定的低位bit不会受到配置写操作的影响)。在图 4‑4的(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)的地址空间。

  1. 在软件将BARs中所有可写bit都写为1后,软件将从BAR0开始,依次读取每个BAR的数值,以此来确定各个BAR要请求的地址空间的大小和类型。表 4‑1中总结了本例中对BAR0进行配置读的结果。
  2. 这个过程中的最后一步就是系统软件为BAR 0分配一个地址范围,因为对于软件来说现在已经知道了BAR 0请求的地址空间的大小和类型。图 4‑4的(3)中展示了BAR处于第三阶段的样子,此时系统软件已经将一块地址区域的起始地址写入了BAR 0中。在本例中,这个起始地址为F900_0000h。

到这里为止,对BAR 0的配置就完成了。一旦软件启用了命令寄存器(Command register,偏移地址04h)中的内存地址译码(memory address decoding),那么这个设备就会接受所有地址在F900_0000h-F900_0FFFh(4KB大小)范围内的memory请求。
image.png
表 4‑1对BAR 0写入全1后再读取BAR 0时的结果
image.png
图 4‑4设置建立32bit不可预取内存的BAR

4.2.3 BAR示例2——64bit内存地址空间请求

在上一个例子中,我们看到了通过BAR 0来请求不可预取内存地址空间(NP-MMIO)。在当前的例子中,如图 4‑5所示,BAR 1和BAR 2被用来请求一块64MB的可预取内存地址空间。这里使用了两个连续相连的BAR是因为这个设备支持这个内存地址空间的请求使用64bit地址,这意味着如果需要的话,软件给它分配的地址空间可以超过4GB地址边界(并非必要)。由于地址可以是64bit位宽,因此必须将两个连续相连的BAR一起使用。

跟上面一样,将BAR在配置过程中分为3个节点:

  1. 在图 4‑5中的(1),我们可以看到一对BAR都处于未初始化的状态。设备的设计者已经将低位BAR(在本例中为BAR 1)中的低位bit固定为一个数值,来指示需要的memory的大小和类型,但是高位BAR(BAR 2)中的bit则都是可读可写的,没有被固定。系统软件的第一步就是把每个BAR都通过配置写操作来将可写入的bit写为全1。在图 4‑5的(2)中展示的BAR就是处于第二阶段的样子,除了BAR 1中被固定的低位bit以外,所有的bit都被写为1。
  2. 如上一个例子所述,系统软件已经评估过BAR 0。因此软件的下一步就是读取下一个BAR(BAR 1),并对其进行评估,以此来确定设备是否正在请求更多的地址空间。一旦BAR 1被读取,软件就会发现设备正在请求更多的地址空间,并且所请求的地址空间类型为可预取内存地址空间(P-MMIO),这个地址空间可以被分配为64bit地址范围的任何一个位置。由于这个地址空间请求支持64bit地址,那么下一个连续相连的BAR(本例中为BAR 2)就会被当做是BAR 1的高32bit。因此软件也会读取BAR 2的内容。然而,软件并不会对BAR 2进行像对BAR 1一样的低位bit的评估,因为软件仅仅是将BAR 2简单的当做BAR 1发起的64bit地址请求的高32bit。表 4‑2中总结了本例中对两个BAR都写入全1后再进行配置读的读取结果。
  3. 这个过程中的最后一步就是系统软件为这一对BAR分配一个地址范围,因为对于软件来说现在已经知道了这一对BAR请求的地址空间的大小和类型。图 4‑5的(3)中展示了这两个BAR处于第三阶段的样子,软件已经通过两次配置写操作将分配的地址区域的64bit起始地址写入BAR 1与BAR 2的组合体中。在本例中,高位BAR的bit 1(BAR pair的bit 33)被置为1,低位BAR的bit 30(也是BAR pair的bit 30)也被置为1,这表示其实地址为2_4000_0000h。两个BAR中所有其他的可写bit都已被清零。

到这里为止,BAR Pair(BAR 1和BAR 2)的配置就已经完成了。一旦软件启用了命令寄存器(Command register,偏移地址04h)中的内存地址译码,那么这个设备就会接受所有地址在2_4000_0000h-2_4300_0000h(64MB大小)这个范围内的memory请求。
image.png
图 4‑5设置建立64bit可预取内存的BAR
image.png
表 4‑2将两个BAR都写入为全1后的读取结果

4.2.4 BAR示例3——IO地址空间请求

让我们在前面两个例子的基础上继续,我们假设当前的Function还需要请求IO空间,如图 4‑6所示。在图中,进行请求的BAR(本例中为BAR 3)在配置过程中分为3个节点:

  1. 在图 4‑6中的(1),我们可以看到BAR 3处于未初始化的状态。系统软件此前已经将每一个BAR都写入了全1(写入BAR中可写的bit,不可写的不受影响),并且已经对BAR 0、BAR 1 BAR 2组成的BAR Pair进行了评估。现在软件要继续看看设备是否还需要通过BAR 3来请求更多的地址空间。图 4‑6中的状态(2)展示了BAR 3被写入全1后的样子。
  2. 软件现在将会读取BAR 3的内容,以此来评估请求地址空间的大小和类型。表 4‑3中总结了本次配置读的读取结果。
  3. 读取BAR 3的内容之后,软件就知道了这个地址空间请求是要请求256byte大小的IO地址空间,那么软件的最后一步操作就是要为设备分配一个IO地址空间范围,并将这个地址范围的起始地址写入BAR 3。图 4‑6中的状态(3)所展示的BAR 3就是完成了写入起始地址的样子。在我们的例子中,这个设备的起始地址为16KB,所以bit 14是被置为1的,也就是说基地址为4000h,所有的高位bit都是清0的。

到目前为止,对BAR 3的配置已经完成了。一旦软件启用了命令寄存器(Command register,偏移地址04h)中的IO地址译码,那么这个设备就会接受并响应所有地址在4000h-40FFh(256Byte大小)这个范围内的IO事务。
image.png
图 4‑6 IO BAR的设置建立
image.png
表 4‑3 IO BAR被写入全1后再读取的读取结果

4.2.5 所有的BARs都必须按顺序被评估

在讲解了前面的三个示例之后,我们可以很明显的发现,软件必须按顺序来对BARs进行评估,即BAR 0—BAR 1—BAR 2—BAR 3这样的顺序。

大多数时候,Function并不需要启用全部的6个BAR。即使是在我们上面举的例子中,也仅仅是使用了6个BAR中的4个。如果上面例子的基础上并不需要继续请求更多的地址空间,那么设备的设计者需要将BAR 4和BAR 5的所有bit都强制固定为全0。这样的话,即使软件也对BAR 4和BAR 5用配置写来写入全1,BAR 4和BAR 5的值(全0)也不会受到配置写的影响。在对BAR 3进行完评估(evaluate)之后,软件需要继续去评估BAR 4。一旦软件检测到BAR 4中没有bit被置为1(因为BAR 4的所有bit都被强制固定为0),软件就知道这个BAR并没有被使用,然后继续去评估下一个BAR(也就是BAR 5)。

即使软件发现了一个BAR并没有被使用,所有的BAR也都必须被评估。在PCI或者PCIe中,并没有规定说一定要以BAR 0来作为第一个用来请求地址空间的BAR。如果设备的设计者需要的话,他们也可以选择比如说BAR 4来作为第一个请求地址空间的BAR,并将BAR 0、BAR 1、BAR 2、BAR 3、BAR 5的所有bit都强制固定为0。这也再一次说明了软件必须按照顺序对Header中的每一个BAR都进行评估。

4.2.6 大小可变的BARs(Resizable BARs)

在PCIe协议的2.1版本中新加入的一个特性,支持改变BAR中所请求的地址空间的大小,这种特性是通过在扩展配置空间内定义一个新的能力结构(Capability Structure)来实现的。新的Capability Structure使得Function能够公布(advertise)它可以操作的地址空间的大小,然后让软件根据实际可用的系统资源来选用Function所公布的地址空间大小中的某一种。例如,如果一个Function理想情况下要拥有2GB的可预取内存地址空间(Prefetchable Memory Address Space),但是它也可以只使用1GB、512MB或者256MB的P-MMIO进行操作,假如说系统软件无法容纳更大的、大于256MB的地址空间请求,那么系统软件应该就只能使Function请求256MB的地址空间。

原文: Mindshare
译者: Michael ZZY
校对: Karl DGR

欢迎参与 《Mindshare PCI Express Technology 3.0 一书的中文翻译计划》
https://gitee.com/ljgibbs/chinese-translation-of-pci-express-technology

转载自:知乎
作者:LogicJitterGibbs

推荐阅读

更多招聘及面经请关注FPGA的逻辑
推荐阅读
关注数
10614
内容数
577
FPGA Logic 二三事
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息