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还是继承了这种概念。
图 4‑1通用的Memory与IO的地址映射
图 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。
图 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时,它就会接收这个请求事务,因为它自己就是这个请求的目标设备。
图 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)的地址空间。
\2. 在软件将BARs中所有可写bit都写为1后,软件将从BAR0开始,依次读取每个BAR的数值,以此来确定各个BAR要请求的地址空间的大小和类型。表 4‑1中总结了本例中对BAR0进行配置读的结果。
\3. 这个过程中的最后一步就是系统软件为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请求。
表 4‑1对BAR 0写入全1后再读取BAR 0时的结果
图 4‑4设置建立32bit不可预取内存的BAR
标签:BAR,Express,MMIO,地址,PCI,IO,空间,Technology,设备 From: https://www.cnblogs.com/VerweileDoch/p/18183088