ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

PCI 总线学习笔记(二)

PCI 总线学习笔记(二) PCI 总线学习系列皆参考自技术大牛博客 PCIe 扫盲系列博文连载目录篇书籍王齐老师的《PCI Express 体系结构导读》下面的文章中加入了自己的一些理解和实际使用中遇到的一些场景供日后查询和回忆使用1、PCI 总线的组成下图是在《PCIExpress体系结构导读》一书中截取主要为了方便理解各个组成的细节1.1、HOST 主桥HOST 主桥的主要功能是隔离处理器系统的存储器域与PCI 总线域 管理 PCI 总线域 并完成处理器与 PCI 设备间的数据交换。处理器与 PCI 设备间的数据交换主要是由 “处理器访问 PCI 设备的地址空间” 和 “PCI 设备使用 DMA 机制访问主存储器” 这两部分组成要注意 Cache 一致性问题。在一个处理器系统中 每一个 HOST 主桥都管理了一棵 PCI 总线树在同一棵 PCI 总线树上的所有 PCI 设备属于同一个 PCI 总线域。1.2、PCI 总线在一棵 PCI 总线树中可能具有多条 PCI 总线 而具有血缘关系的 PCI 总线组成一棵 PCI 总线树。例如 PCI 总线树 x由 PCI 总线 x0 和 x1 组成。1.3、PCI 设备PCI 总线中主要有三类设备PCI 主设备、 PCI 从设备和桥设备。一个 PCI 设备既可以是主设备也可以是从设备但是同一时刻只能是主或者从PCI 总线规范将 PCI 主从设备统称为 PCI Agent 设备。PCI 从设备只能被动地接收来自 HOST 主桥或者其他 PCI 设备的读写请求而 PCI 主设备可以通过总线仲裁获得 PCI 总线的使用权主动地向其他 PCI 设备或者主存储器发起存储器读写请求。PCI 总线规范中有一个特殊的 PCI 设备即桥设备。桥设备的主要作用是管理下游的 PCI 总线并转发上下游之间的总线事务。桥设备包括 PCI 桥、PCI-to-(E)ISA 桥和 PCI-to-Cardbus 桥。PCI 桥的引入使 PCI 总线极具扩展性也极大地增加了 PCI 总线的复杂度。PCI 总线的电气特性决定了在一条 PCI 总线上挂接的负载有限当 PCI 总线需要连接多个 PCI 设备时需要使用 PCI 桥进行总线扩展扩展出的 PCI 总线可以连接其他 PCI 设备包括 PCI 桥。通过 PCI 桥进行扩展有限制一颗 PCI 总线树上最多挂载包括 PCI 桥在内的 256 个 PCI 设备。2、存储器域与 PCI 总线域HOST 主桥的主要作用就是隔离存储器域和 PCI 总线域实现存储器域和 PCI 总线域相互转换。存储器域指的是 CPU 能直接访问到的空间在 x86 架构中南桥和北桥都能被 CPU 直接访问都是属于存储器域。在 SOC 架构中DDR 控制器PCI 控制器、USB 控制器等这些外围设备也能被 CPU 直接访问也属于存储器域。PCI 总线域只有 PCI Agent、PCI 桥和 HOST 主桥能直接访问CPU 要想访问 PCI 总线域需要通过 HOST 主桥完成存储器域到 PCI 域的地址转换PCI 设备要想访问存储器域也要通过 HOST 主桥将 PCI 域地址转成存储器域地址。深入理解这两个域的区别是理解 HOST 主桥的关键所在。在一个处理器系统中存储器域、PCI 总线域与 HOST 主桥的关系如图 2‑1 所示。上图所示的处理器系统由一个 CPU一个 DRAM 控制器和两个 HOST 主桥组成。在这个处理器系统中包含存储器域和 PCI 总线域地址空间暂不关心 DRAM 域和 CPU 域把这二者当成 存储器域 即可。其中 HOST 主桥 x 和 HOST 主桥 y 分别管理 PCI 总线 x 域与 PCI 总线 y 域。PCI 设备访问存储器域时也需要通过 HOST 主桥并由 HOST 主桥进行 PCI 总线域到存储器域的地址转换CPU 访问 PCI 设备时同样需要通过 HOST 主桥进行存储器域到 PCI 总线域的地址转换。3、地址空间PCI 体系结构中一共支持三种地址空间Memory Address Space、I/O Address Space 和 Configuration Address Space。这三者相互独立。3.1、配置空间由于 PCI 支持设备即插即用所以 PCI 设备不占用固定的内存地址空间或 I/O 地址空间而是通过配置空间来实现地址映射的。系统加电时BIOS 检测 PCI 总线确定所有连接在 PCI 总线上的设备以及它们的配置要求并进行系统配置。所以所有的 PCI 设备必须实现配置空间从而能够实现参数的自动配置实现真正的即插即用。PCI 的配置空间是 256 字节其中 64 字节是标准配置空间 header 后面的 192 字节是 Capability 结构 展示 PCI 设备所能提供的能力。为了兼容 PCIPCIe 的配置空间前 256 字节与 PCI 保持一致256~4096 字节是 PCIe 扩展配置空间包含 PCIe 的扩展能力如 AER。每个 PCI 设备都有独立的配置空间HOST 主桥通过配置读写总线事务Configuration Write访问这段空间。PCI 总线规定了三种类型的 PCI 配置空间分别是 PCI Agent 设备使用的配置空间PCI 桥使用的配置空间和 Cardbus 桥片使用的配置空间。本节重点介绍 PCI Agent 和 PCI 桥使用的配置空间而并不介绍 Cardbus 桥片使用的配置空间。前 64 个字节的配置空间称为配置头类型一般有两种Type0 主要是针对 PCI 的 PCI Agent 设备Type1 主要是针对 PCI bridge 设备。这里简单介绍下两种配置头的概念配置头中比较重要的寄存器后面使用单独一小节讲解。Status状态寄存器报告 INTx 中断状态、一些错误状态等Command包括一些状态位、使能位等Header Type0x0Type 0普通设备0x1Type 1桥设备0x2Card bus这一位保留有些地方把他用作 card bus 位class code设备分类信息 表示 pci 设备属于哪一种类别如网卡存储卡显卡等Interrupt Pin只读寄存器定义了中断边带信号引脚号详见 PCI 总线学习笔记三Interrupt Line读写寄存器定义了中断向量号详见 PCI 总线学习笔记三Cache Line Size老的功能现已不用Latency Timer老的功能现已不用Base Address Register0~5该组寄存器简称为 BAR 寄存器BAR 寄存器保存 PCI 设备使用的地址空间的基地址注意该基地址是指 PCI 总线域地址并不是 CPU 域地址。其中每一个设备最多可以有 6 个基地址空间但多数设备不会使用这么多组地址空间我们编写 PCI 设备驱动、访问 PCI 设备的寄存器时访问的都是这个地址Expansion ROM base addressBIOS 阶段想使用一个 PCIe 设备但是原始的 BIOS 不包含这个设备的驱动程序这个时候就从这个 PCI 设备 Expansion ROM 指定的地址中获取设备驱动程序代码并加载到内存中执行。ROM 所需空间大小计算方法与 BAR 寄存器相同下面是 Type1 类型就是桥的配置空间Primary Bus NumberPri这个 Bridge 所在的 Bus Number也就是它的上游连接的 Bus NumberSecondary Bus NumberSec这个 Bridge 所连接的下一个 Bridge 的 Bus NumberSubordinate Bus NumberSub这个 Bridge 所连接的下游所有的 Bus 的最大的 Bus NumberBase Address 0/1这两个寄存器是可选的。如果在 PCI 桥中不存在私有寄存器那么可以不使用这组寄存器设置 BAR 空间。在大多数 PCI 桥中都不存在私有寄存器操作系统也不需要为 PCI 桥提供专门的驱动程序这也是这类桥被称为透明桥的原因I/O Limit/Base在 PCI 桥管理的 PCI 子树中包含许多 PCI 设备而这些 PCI 设备可能会使用 I/O 地址空间。PCI 桥使用这两个寄存器存放 PCI 子树中所有设备使用的 I/O 地址空间集合的基地址和结束地址Memory Limit/Base在 PCI 桥管理的 PCI 子树中有许多 PCI 设备这些 PCI 设备可能会使用存储器地址空间。这两个寄存器存放所有这些 PCI 设备使用的存储器地址空间集合的基地址和结束地址PCI 桥规定这个空间的大小至少为1MB、地址 1MB 对齐Prefetchable Memory Limit/Base在 PCI 桥管理的 PCI 子树中有许多 PCI 设备如果这些 PCI 设备支持预读则需要从 PCI 桥的可预读空间中获取地址空间。PCI 桥的这两个寄存器存放这些 PCI 设备使用的可预取存储器空间的基地址和结束地址。如果 PCI 桥不支持预读则其下支持预读的 PCI 设备需要从 Memory Base 寄存器为基地址的存储器空间中获取地址空间。如果 PCI 桥支持预读其下的 PCI 设备需要根据情况决定使用可预读空间还是不可预读空间。PCI 总线建议 PCI 设备支持预读但是支持预读的 PCI 设备并不多见3.2、I/O 空间传统的一些 PCI 设备才会使用到这部分空间现代设备几乎不会使用这块空间。通常 PCI 设备驱动中会根据当前 PCI 设备配置空间中的 BAR 寄存器选择是否要使用这部分空间。3.3、Memory 空间PCI 设备具备哪些可以提供给外部或内部使用的地址空间。Memroy 空间通常用于数据传输。我们在编写 PCI 设备驱动时需要访问 PCI 设备寄存器通常就在这个空间中。4、配置空间第三小节中提到的配置空间只是粗略的讲解因为实际的 PCI 总线驱动、设备驱动开发中会经常使用到配置空间所以这里单独一章节讲解配置空间的一些细节。4.1、Base Address Register系统中的每个 PCI 设备中对地址空间的大小和访问方式可能有不同的需求例如一个设备可能有 256 字节的内部寄存器/存储可能通过 IO 地址空间访问而另一个设备可能有 16KB 的内部寄存器/存储可能通过 Memory 地址空间访问。哪些地址应该使用哪种方式IO或Memory来访问它们的内部空间这是系统软件即BIOS和OS内核的工作。因此设备必须为系统软件提供一种方法来确定设备的地址空间需求。这种需求就是是通过配置空间头部中的 Base Address register BAR实现的。一旦系统软件知道设备在地址空间方面的需求系统软件将分配一个适当类型IO、Memory、Prefetch Memory 等的可用地址范围给该设备。简单来说我们如果需要访问 PCI 设备空间不管是 Memory 空间还是 IO 空间都需要先将该空间的地址写到 BAR 寄存器中PCI 总线驱动中实现然后 CPU 才能去访问。这个地址一般由芯片手册提供。在 Type0 类型PCI Agent的配置头中有 6 个 32bit 的 BAR 寄存器Type1 类型PCI Bridge有 2 个 32bit 的 BAR 寄存器。每一个 BAR 地址对应一个地址空间这里指的是 PCI 总线域地址空间。bit0表示设备寄存器是映射到 memory0还是IO1)空间bit1reserved 0bit2在 base adress register for Memory 中 0 表示 32 位地址空间1 表示 64 位地址空间bit3在 base adress register for Memory 中用来表示该设备是否允许prefetch1 表示可以预取0 表示不可以预区bit4~31用来表示设备需要占用的地址空间大小也表示设备空间的起始地址Bits 3-0 are read-onlyBAR 地址寄存器负责 PCI 设备内部空间的映射 有了这个映射CPU 可以做到对 PCI 设备空间的访问。但是要注意的是BAR 寄存器里面的是 PCI 总线域的地址CPU 不能直接使用CPU 使用的是存储器域的地址。对于不同的 PCI 控制器来说这两个地址的映射方式不同例如有些 PCI 控制器具有 InBound、OutBound 寄存器的可以去配置 PCI 总线域地址和存储器域地址的映射关系通常情况会下配置成一一映射。例如 rk3568 板卡就支持通过 InBound、OutBound 寄存器组进行地址转换。例如 CPU 想去读 PCI 设备的数据则需要去访问 PCI 设备 BAR 空间对应的地址。因为不能直接访问所以先将 BAR 空间地址读出然后对这个地址去做转换转换操作在 PCI 总线驱动中实现将 PCI 总线域地址转换成存储器域地址后CPU 就可以使用转换后的地址直接去访问 PCI 设备的数据了。另外也许你会觉得很奇怪一个 32 位的空间又是如何又表示地址又表示范围呢这里其实和 BAR 的初始化过程有关BAR 的寄存器初始化主要有两步系统软件将全 F 的地址写入 BAR 寄存器这样会导致 BAR 寄存器的值被重置重置后的值就表示当前设备所需地址空间的大小。计算空间所需大小步骤如下软件读取重置后的 BAR 寄存器的值并去除掉最后几位 Flag bar_mask 操作强制把最后几位 Flag 置 0 然后将其取反并加 1求出其大小。例如读出重置后的值是 0xFFFFF000这里包含了最后几位的 Flag取反之后就是0x00000FFF加 1 之后就是 0x00001000也就是 4KB注意软件最终分配的 BAR aperture 大小通常是 2 的幂BAR 基地址必须自然对齐在内存中起始地址是它自身大小的整数倍This design implies that all address spaces used are a power of two in size and are naturallyaligneduint32_tget_pci_bar_size(structpci_dev*dev,intbar_offset){uint32_toriginal_bar_value;uint32_tsize_value;uint32_tbar_size;uint32_tbar_mask0xFFFFFFF0;// Read original BAR valuepci_read_config_dword(dev,bar_offset,original_bar_value);// Write 0xFFFFFFFF to BARpci_write_config_dword(dev,bar_offset,0xFFFFFFFF);// Read the size valuepci_read_config_dword(dev,bar_offset,size_value);// Restore original BAR valuepci_write_config_dword(dev,bar_offset,original_bar_value);// Calculate the BAR sizebar_size~(size_valuebar_mask)1;pr_info(BAR size: %u bytes\n,bar_size);returnbar_size;}软件接着进行真正的地址分配和映射根据上一步计算出的所需空间大小分配相应的地址空间并将这个新的地址空间的起始地址重新写入 BAR4.2、Base Limit Register我们在访问 PCI 设备空间时使用的是内存读写总线事务Memory Read/Write。在该总线事务从 HOST 主桥向下传递的过程中PCI 桥设备是如何判断该总线事务是否属于自己或者自己的分支下的设备的呢。这实际上是通过 Type1 型配置空间 Header 也就是 PCI 桥配置空间中的 Base 和 Limit 寄存器来实现的。Base 和 Limit 寄存器分别确定了其所有分支下设备的地址的起始和结束地址Base 表示基址Limit 表示结束地址。根据请求类型的不同分别对应不同的 BaseLimit 组合Prefetchable Memory SpaceNon - Prefetchable Memory SpaceIO Space当 HOST 主桥发出一个内存读写总线事务时该事务中会包含要访问的设备空间的地址、地址类型等信息当 PCI 桥收到这个事务后会将事务中的地址与地址类型和 PCI 桥中相应的 BaseLimit 寄存器对比如果要访问的地址在 BaseLimit 寄存器的范围内PCI 桥则转发事务否则丢弃该事务。换句话说如果 PCI 桥中的 BaseLimit 寄存器配置错误CPU 是无法访问到 PCI 桥下对应的设备PCI 总线驱动初始化的过程中会去配置 BaseLimit 寄存器。4.3、Pri、Sec、Sub Bus Number Register我们在访问 PCI 设备的配置空间时使用的是配置读写总线事务Configuration Read/Write。在该总线事务从 HOST 主桥向下传递的过程中PCI 桥设备是如何判断该总线事务是否属于自己或者自己的分支下的设备的呢这实际上是通过 Type1 型配置空间 Header 也就是 PCI 桥配置空间中的 Primary 、Secondary 、Subordinate 寄存器来实现的。Primary Bus NumberPri这个 Bridge 所在的 Bus Number也就是它的上游连接的 Bus NumberSecondary Bus NumberSec这个 Bridge 所连接的下一个 Bridge 的 Bus NumberSubordinate Bus NumberSub这个 Bridge 所连接的下游所有的 Bus 的最大的 Bus Number因此一个 PCI 桥能够管理的 PCI 总线号在 Secondary Bus Number~Subordinate Bus Number 之间。这两个寄存器的值由系统软件遍历 PCI 总线树时设置。当 HOST 主桥发出一个配置读写总线事务时该事务中会包含要访问的设备的 bus 号等信息当 PCI 桥收到这个事务后会将事务中的 bus 号和 PCI 桥中的 Pri、Sec、Sub Bus Number 寄存器对比如果要访问的地址在 SecSub Bus Number 寄存器的范围内PCI 桥则转发事务否则丢弃该事务。换句话说如果 PCI 桥中的 Pri、Sec、Sub Bus Number 寄存器配置错误CPU 是无法访问到 PCI 桥下对应的设备的配置空间的4.4、Basic Capability40h-FFhPCI 的配置空间是 256 字节其中 64 字节是标准配置空间 header也就是我们 3.1 章节所讲的后面的 192 字节是 Capability 结构主要用于扩展设备的功能和特性信息。简单来说Basic Capability 这段空间用来描述当前 PCI 设备支持哪些功能例如 是否支持 MSI 中断、电源管理、是否支持热插拔等。操作系统通过扫描设备的配置空间能够读取到 Capability 链表中的每个项从而知道设备支持的功能Basic Capability 这段空间可以被看做是一个由 PCI Capability Item 组成的一个链表。链表的头指针就存在了 PCI 标准配置空间 header 的 0x34 的一字节的寄存器里。有了这个指针就可以知道链表的第一个 Capability Item 存在于 [40h-FFh] 区间的哪个位置。链表的组织结构如下Each defined capability must have a SIG assigned ID code. These codes are assigned and handled much like the Class Codes. Refer to Appendix H for a list of currently defined Capabilities. Each Capability must define the detailed register map for that capability. These registers must immediately follow the pointer to the next capability——《PCI Local Bus Specification Revision 3.0》每个 Capability Iteam 的大小长度范围不都是一样的但所有 Capability Iteam 都定义了一个标准化的 Capability Header。例如PCI 规范中对 Capability Iteam 的类型做了严格定义都给出了 Capability 的 ID 号这个 Capability ID 就存放在这个 Header 里。下图是标准的 PCI Capabilities 中 Capability ID 对应的功能。Header 里另一个重要的内容就是下一个 Capability Iteam 的偏移量以便形成链表。在 PCI Capability List 的所有存放指针的寄存器都是 8 位的因为 8 位足够在 256 字节范围内定位了。系统软件一般会通过遍历 PCI Capability Item 组成的链表通过 Capability 中的 next capability pointer 找到下一个 capability 偏移最终找到自己想要的 Capability ID 对应的 Capability Item5、配置空间的访问5.1、BDF 的概念每个 PCI 设备在系统总线上都有自己的标识符这个标识符就是 BDFBusDeviceFunctionPCI 的配置软件应当有能力识别整个 PCI 总线系统的拓扑逻辑以及其中的每一条总线Bus每一个设备Device和每一项功能Function。也就是说通过 BDF 可以唯一确定一个 PCI 设备。而我们访问设备的配置空间时使用的就是 BDF。5.2、I/O 方式访问x86 处理器定义了两个 I/O 端口寄存器 分别为 CONFIG_ADDRESS 和 CONFIG_DATA 寄存器 其地址为 0xCF8 和 0xCFC。 x86 处理器使用这两个 I/O 端口访问 PCI 设备的配置空间。PCI 总线规范也以这两个寄存器为例 说明处理器如何访问 PCI 设备的配置空间。 其中 CONFIG_ADDRESS 寄存器存放 PCI 设备的 IDBDF号 而 CONFIG_DATA 寄存器存放进行配置读写的数据。CONFIG_ADDRESS 寄存器与 PowerPC 处理器中的 CFG_ADDR 寄存器的使用方法类似 而 CONFIG_DATA 寄存器与 PowerPC 处理器中的 CFG_DATA 寄存器的使用方法类似。 CONFIG_ADDRESS 寄存器的结构下图所示CONFIG_ADDRESS 寄存器的各个字段和位的说明如下所示。Enable 位 第31 位。 该位为1 时 对 CONFIG_DATA 寄存器进行读写时将引发 PCI 总线的配置周期Bus Number 字段 第23 16 位 记录 PCI 设备的总线号Device Number 字段 第15 11 位 记录 PCI 设备的设备号Function Number 字段 第10 8 位 记录 PCI 设备的功能号Register Number 字段 第7 2 位 记录 PCI 设备的寄存器号bit 10 为只读寄存器读取时返回为 0从上图中也可看到 register number 只有 8 bit所以只能访问 256 Byte 的标准 PCI 配置空间无法访问 PCIe 扩展配置空间。而对于扩展配置空间的访问通常依赖 ECAM 方式当 x86 处理器对 CONFIG_DATA 寄存器进行 I/O 读写访问 且 CONFIG_ADDR 寄存器的 Enable 位为1 时 HOST 主桥将这个 I/O 读写访问转换为 PCI配置读写总线事务 然后发送到 PCI 总线上 PCI 总线根据保存在 CONFIG_ADDR 寄存器中的 ID 号 将 PCI 配置读写请求发送到指定 PCI 设备的指定配置寄存器中。5.3、ECAM 方式访问ECAM 方式实际上到了 PCIe 协议上才被广泛使用。但是这里也简单讲讲它的原理作为拓展。ECAM 全称为 Enhanced Configuration Access Mechanism意思为增强的配置访问方式。ECAM 是一个将配置空间映射到 memory 空间的规则。硬件根据 ECAM 的方式将某个 Memory 空间映射给 PCIe 配置空间CPU 访问对应的 memory 空间即可以操作 PCIe 配置空间。x86 平台上也支持 ECAM 方式访问 PCIe 配置空间。因为通过 I/O 端口方式访问 PCIe 配置空间有大小的限制只能访问 256 个字节下图是 “组装访问配置空间地址 address” 的示例实际上每一家厂商的组装方式都有可能不同由上图可知ECAM 方式其实也是通过 BDF 方式去访问具体设备的。具体的访问方式简单来说就是ECAM 基地址 由 BDF 等组装而成的 address在某个平台上要想通过 ECAM 方式访问配置空间大致需要经过以下几个步骤的设置确定平台相关的 ECAM 在存储器域的基地址物理地址根据 bus、dev、func、reg 这四个参数来组装成 address具体的组装方式由具体平台而定确定要访问的存储器域地址ECAM Base address调用 ioremap 将要访问的存储器域物理地址映射成虚拟地址使用通过要访问的字节数来确定调用何种接口进行访问比如单字节、双字节还是四字节以 x86 系统为例其他系统的原理其实都差不多在支持 ACPI 规范 x86 系统上 操作系统通过读 MCFG 表获得系统中全部设备 PCIe 配置空间的基地址ECAM Base 。操作系统在引导时枚举 PCIe 设备从而知道每一个 PCIe 设备的 Bus, Device, Function 号 。 有了 BDF 这三个编号加上从MCFG 中得到的基地址就能够计算出给定设备的 PCIe 配置空间在物理内存空间的地址就可以通过访问内存的方式去访问配置空间。对于 X86 主机使用如下命令sudocat/proc/iomem|grepMMCONFIG若支持 ECAM则会出现类似以下字段f8000000 - fbffffff:PCI MMCONFIG 0000[bus 00-3f]其中 0xf8000000 就是该主机下 PCI ECAM的基地址。加上 PCI_ECAM_ADDRESS(bus, device, func, reg)就得到了指定 PCI 设备的物理地址可以使用此地址直接访问其配置空间。对于 ARM 主机sudocat/proc/iomem|grepECAM若支持 ECAM则会出现以下字段40000000-4fffffff:PCI ECAM其中 0x40000000 就是该主机下 PCI ECAM 的基地址。加上PCI_ECAM_ADDRESS(bus, device, func, reg)就得到了指定 PCI 设备的物理地址可以使用此地址直接访问其配置空间。5.4、寄存器方式访问这种访问方式相对特殊但在实际开发中非常常见特别是在处理 Root ComplexRC相关功能时。以 RK3568 为例SoC 通常通过特定的寄存器由芯片手册提供来配置 RC 的访问窗口开发者可以通过这些寄存器访问 PCIe RC 的配置空间进而设置 Primary Bus Number 等寄存器。Primary Bus Number 寄存器即指定当前 RC 所管理的起始总线号。这项配置至关重要它直接决定了该 RC 所能覆盖的总线范围。一旦该值配置错误将导致该 RC 下所有设备的 BDFBus/Device/Function地址映射异常从而引发配置空间访问失败设备无法正确识别或初始化等问题。因为 PCIe 设备的访问路径是严格依赖于正确的总线号路由机制的。6、实战6.1、PCI 总线域与存储器域地址转换在 Linux 代码中的体现uboot 实现了两个函数pci_hose_bus_to_physPCI 总线域地址转换成 CPU 存储器域地址pci_hose_phys_to_busCPU 存储器域地址转换成 PCI 总线域地址/* * uboot/drivers/pci/pci_common.c */phys_addr_tpci_hose_bus_to_phys(structpci_controller*hose,pci_addr_taddr,unsignedlongflags);pci_addr_tpci_hose_phys_to_bus(structpci_controller*hose,phys_addr_taddr,unsignedlongflags);关于两个函数的详细实现这里就不展开讲了。大概的原理如下我们要知道不管是 PCI 总线域地址还是 CPU 存储器域地址他都是一段连续的地址空间。所以我们利用这一点找到一个 offset 偏移值地址转换的方式就变成了CPU 域地址 PCI 总线域地址 offset这个 offset 的值通常和 PCI 控制器的硬件设计有关每个板卡都可能不同我们在适配 PCI 总线驱动时找到正确的 offset 值即可。Linux 中的pci_add_resource_offset接口在注册 PCI 控制器地址资源时就会将上面提到的 offset 注册进内核中。voidpci_add_resource_offset(structlist_head*resources,structresource*res,resource_size_toffset);Linux 实现的pcibios_bus_to_resource、pcibios_resource_to_bus两个接口就是通过上面注册的 offset完成地址转换。/* PCI 总线域地址转换成 CPU 存储器域地址 */voidpcibios_bus_to_resource(structpci_bus*bus,structresource*res,structpci_bus_region*region);/* CPU 存储器域地址转换成 PCI 总线域地址 */voidpcibios_resource_to_bus(structpci_bus*bus,structpci_bus_region*region,structresource*res);6.2、设备树中关于 PCI 控制器地址空间的解析以 rk3568 设备树为例pcie2x1: pciefe260000{compatiblerockchip,rk3568-pcie,snps,dw-pcie;#address-cells 3;#size-cells 2;bus-range0x0 0xf;......linux,pci-domain0;phy-namespcie-phy;ranges0x00000800 0x0 0xf4000000 0x0 0xf4000000 0x0 0x100000 0x81000000 0x0 0xf4100000 0x0 0xf4100000 0x0 0x100000 0x82000000 0x0 0xf4200000 0x0 0xf4200000 0x0 0x1e00000 0xc3000000 0x3 0x00000000 0x3 0x00000000 0x0 0x40000000;reg0x3 0xc0000000 0x0 0x400000,0x0 0xfe260000 0x0 0x10000;reg-namespcie-dbi,pcie-apb;......};bus-range 属性针对的是就是当前控制器下的 bus 总线号资源linux,pci-domain 属性一般如果存在该属性该属性为当前 PCIe 控制器索引ranges 属性表示资源配置信息7个元素为一组第一个元素表示地址属性具体每一位的含义详见Device Tree Usage 的 PCI Address Translation 章节第二个和第三个为 PCI 域地址空间第四个和第五个为 CPU 域地址空间第六个和第七个为 SIZE表示设备访问 CPU 域的地址时将转化到 PCI 域的地址大小翻译一下就是PCIe 总线 0xf4000000 地址映射到 CPU 的 0xf4000000 地址配置空间大小为 0x100000PCIe 总线 0xf4100000 地址映射到 CPU 的 0xf4100000 地址IO 空间大小为 0x100000PCIe 总线 0xf4200000 地址映射到 CPU 的 0xf4200000 地址Mem 空间大小为 0x1e00000PCI e总线 0x300000000 地址映射到 CPU 的 0x300000000 地址Mem64 空间大小为 0x40000000
返回列表