从cpu角度理解PCIe

举报
Herok 发表于 2019/01/22 17:14:39 2019/01/22
【摘要】 1 概述为什么需要写这篇文章,当我阅读《深入浅出SSD》这篇书籍中PCIe章节时发现,本书籍的侧重点是放在PCIe控制器和PCIe协议上,从CPU角度理解PCIe知识偏少,本文对下面几个知识点做出一些补充。1. CPU访问外设寄存器与内存编址方式;2. CPU如何访问PCIe配置空间;3. CPU能够通过寄存器访问配置空间...

1            概述

为什么需要写这篇文章,当我阅读《深入浅出SSD》这篇书籍中PCIe章节时发现,本书籍的侧重点是放在PCIe控制器和PCIe协议上,从CPU角度理解PCIe知识偏少,本文对下面几个知识点做出一些补充。

1.         CPU访问外设寄存器与内存编址方式;

2.         CPU如何访问PCIe配置空间;

3.         CPU能够通过寄存器访问配置空间,为什么还需要映射PCIe配置空间;

4.         如何扫描PCIe树并且为PCIe分配ID

5.         如何将pcie域地址映射到存储器域地址空间。

通过本篇文章将对问题123做出解答。

2            统一编址于独立编址

CPU编址是程序指令与物理地址线建立链接的方式,在CPU内部有专门的地址集合,编址过程是由CPU体系架构所决定的,参考示意图如 1所示(仅仅代表示意图,讲解一种逻辑结构,不代表实际电路)。CPU编址时就已经指定了0x8000_0000~0xFFFF_FFFF这个地址空间为连接到图中内存的地址线,内存如何连接到CPU需要当参考CPUdatasheet,当CPU程序指令对0x8000_0000这个物理地址地址发起访问时,等价于是在访问图中2G内存的首地址。

1.JPG

1

内存通过CPU地址总线来寻址定位,然后通过CPU数据总线读写数据。CPU的地址总线位数是CPU设计时确定,因此一款CPU所能寻址的地址范围是一定的,而内存是需要占用CPU的寻址空间的,内存与CPU采用总线直接连接。

IO指的是与CPU连接的各种外设,CPU访问各种外设有两种方式:一种是类似于访问内存的方式,即把外设的寄存器当成内存地址读写,从文可以以访问内存方式操作外设寄存器。这时,IO与内存统一编址,IO地址与内存地址在同一个地址空间下,这种编址方式叫做IO与内存统一编址。另外一种编址方式是IO地址与内存地址分开独立编址,这种编址方式叫做独立编址,此时,CPU访问外设寄存器需要通过CPU特定的指令去访问外设寄存器,而不能通过地址直接访问外设寄存器。常见的ARMPowerPcMIPS架构都是采用统一编址,X86架构采用独立编址。

3            访问PCIe配置空间256bytes

PCI总线规定访问配置空间总线事务,使用ID号进行寻址。PCI设备ID号由总线号(Bus Number)、设备号(Device Number)和功能号(Function Number)。其中总线号在HOST主桥遍历PCI总线树时确定,在一颗PCI总线树上,总线号由系统软件决定,通常与HOST主桥直接相连接的PCI总线编号为0,系统软件使用DFSDepth-First Search)算法扫描PCI总线树上的所有PCI总线,并依次编号。一条PCI总线的设备号由PCI设备的IDSEL信号与PCI总线地址线的连接关系确定,功能号与PCI设备的具体设计有关。一个PCIe系统最多有256Bus,每条Bus上最多可以挂在32个设备,每个PCIe设备最多有8个功能设备。

         XX处理器中的HOST主桥中,与PCIE设备配置相关的寄存器由CFG_ADDRCFG_DATA等组成。系统软件使用CFG_ADDRCFG_ADDR寄存器结构如 2所示)和CFG_DATA寄存器访问PCIe设备的配置空间,这些寄存器都是采取同一编址(所有内存寄存器都使用存储器映射方式进行寻址)。当处理器访问PCIe配置空间时,首先需要在CFG_ADD寄存器中设置这个PCIe设备对应的总线号、设备号、功能号和寄存器偏移,然后使能Enable位,之后当处理器对CFG_DATA读写访问时,HOST主桥将这个存储器读写访问转换成PCIe配置读写请求,并且发送到PCIe总线上。如果Enable位没有使能,那么CPU对寄存器的访问也就是一个普通IO的访问,而不能让HOST转换成总线请求访问,访问PCIe配置空间时按照PCIe总线标准配置TLP请求,CFG_DATA是读取的数据或者待写入的数据。

2.JPG

2

a)         31位:Enable位,为1时,对CFG_DATA读写才能转换成PCIe总线配置请求。

b)         30~24位:保留。

c)         23~16位:总线号,最多spacer.gif=256个。

d)         15~11位:设备号,最多spacer.gif=32个。

e)         10~8位:功能号,最多spacer.gif=8个。

f)          7~2位:寄存器偏移,最多访问寄存器spacer.gif=64个地址,这里一个地址是DW,那么能干访问的PCIe配置空间大小为64*4=256Byte,所以访问PCIe配置空间都是以4字节对齐访问的。

走到这里很多读者可能就会有这样的疑问,既然CPU能够直接通过寄存器访问配置空间,为啥还会出现配置空间在存储域地址的映射这一说法呢?下面给出详细解答。

访问PCIe配置空间寄存器的方法需要追溯到原始的PCI规范。为了发起PCI总线配置周期,IntelIntelPCIe龙头老大,最新的PCIe的规范总是它最先尝试的)实现的PCI规范使用IO空间的CF8hCFCh来分别作为索引和数据寄存器,这种方法可以访问所有PCI设备的255 bytes配置寄存器。Intel Chipsets目前仍然支持这种访PCI配置空间的方法。PCIe规范在PCI规范的基础上,将配置空间扩展到4K bytes,至于为什么扩展到4K,具体可以参考PCIe规范,这些配置CFG_ADDRCFG_DATA寄存器方法仍然可以访问所有PCIe设备配置空间的头255 bytes,但是该方法访问不了剩下的(255B~4K)配置空间。怎么办呢?Intel外一种PCIe配置空间访问方法。Intel Chipset通过将配置空间映射到内存地址空间,PCIe配置空间可以像对映射范围内的内存进行read/write来访问了。这种映射是由北桥芯片来完成的,但是不同芯片的映射方式也是不同的。目前我查看了ARM芯片的datasheet,确实是这样的方式。

PCIe规范为每个PCIe设备添加了更多的配置寄存器,空间为4K,尽管CFG_ADDRCFG_DATA寄存器方法仍然能够访问lower 255 bytes,但是必须提供另外一种方法来访问剩下的(255B~4Krange寄存器。Intel的解决方案是使用了预留256MB内存地址空间,对这段内存的任何访问都会发起PCIe 配置cycle。由于4K的配置空间是directly mapped to memory的,那么PCIe规范必须保证所有的PCIe设备的配置空间占用不同的内存地址,按照PCIe规范,支持最多256bus,每个Bus支持最多32PCIe devices,每个device支持最多8function,也就是说:占用内存的最大值为:256 * 32 * 8 * 4K = 256MB 3ARM Cortex-A9 datasheet内存地址分配局部图。被PCIe配置空间占用的256M内存空间会屏蔽掉DRAM使用该段内存区,这些地址都由CPU出厂时已经固化好了。

3.JPG

3

4            PCIe配置空间的内存映射对32bit系统的影响

由于PCIe配置空间占用了256M内存空间,而且该被占用空间对DRAM来说是不可用的,这意味着256M空间消失于系统内存,这在32bit系统中更为明显。比如,在32 bit winxp中(作者目前电脑还是用的XP系统,电脑用了七八年了),理论上可以访问到的内存是4G,如果4G空间都被DRAM给占用,由于PCIe的存在,被PCIe占用的那部分内存空间对OS来说是不可用的,莫名的消失了最多256M内存,其实还有其他外设寄存器需要映射到内存,如果是独立编址就不存在寄存器占用内存。所以在XP系统中实际能够访问DRAM空间最大值为3.2G64CPU寻址不存在这个情况,spacer.gif个地址目前来说应该用不完,这里读者需要注意的是CPU3264位寻址方式,同样操作系统也有3264位之分,在Linux系统中主要体现在库文件上。

有些CPU没有直接指定PCIe配置空间的地址范围,需要读取某个寄存器的值BaseAddr,这个值就说PCIe配置寄存器在内存区域映射的基地址。访问PCIe设备配置空间时候需要手动计算访问PCIe配置空间的地址。计算发放如下:

SIZE_PER_FUNC = 4K = 1000h

SIZE_PER_DEVICE = 4K * 8 = 8000h

SIZE_PER_BUS = 4K *8* 32 = 100000h

访问总线号为busNo,设备号为DevNo,功能号为funcNooffset寄存器的计算公式是:

Memory Address = BaseAddr+ busNo * SIZE_PER_BUS+ devNo * SIZE_PER_DEVICE+ funcNo * SIZE_PER_FUNC+ offset

访问PCIe配置空间就需要通过总线号、设备号、功能号、寄存器偏移进行转换成内存地址。转换函数如 2所示。

4.JPG

4

5            地址概述

上电扫描PCIe树和存储地址到PCIe地址的映射,本文可能会针对某一款芯片做出详细流程解答,读者可以只关注整个流程,具体映射机制和寄存器参考芯片datasheet。上篇文章已经了解到如何访问配置空间,前256Bytes可以通过寄存器方式访问,后面的256B~4k必须通过映射才能访问,映射无非就是把配置空间映射到存储地址空间,或者把PCIe设备空间映射到存储地址空间。下面开始讨论映射关系。

6            地址映射关系

PCIe在存储域地址空间分为三部分,PCIe控制器本身的寄存器、PCIe设备的配置空间、PCIe设备空间。寄存器和配置空间由处理器本身决定存储地址范围,本款处理器地址范围如图5所示,配置空间地址、寄存器地址、内存地址都已经确定。PCIe设备空间需要编程人员去配置OutboundInbound寄存器组,确定映射关系

1.JPG

5

OutboundPCIe控制器中扮演的角色是将存储地址翻译到PCIe域的PCIe地址,Inbound是将PCIe地址翻译成存储地址,图6是一个完整的RCEP模型地址翻译模型,图中的地址数字仅仅代表一种形态,具体地址应该是什么在后文中讲解。当cpu需要访问EP的内存空间时,首先应该将存储地址转换成PCIe地址,在根据TLP到达指定的EP,进而将PCIe地址转换成EP端的存储地址。

2.JPG

6

PCIe地址到存储地址之间的映射关系由三个寄存器决定(有两个寄存器组应该是32个寄存器)OB_SIZEOB_OFFSET_INDEXnOB_OFFSETn_HIn的范围是0~31。在PCIe控制器中是把PCIe地址等分成32regions (Regions 0 to 31),每个regions的大小是可以通过编程设置OB_SIZE寄存器确定大小,大小有1, 2, 4, or 8 MB,那么通过Outbound能够翻译的地址最大为8M*32=256M。存储域地址中有5位作为识别32regionsindexOB_SIZE的大小决定这5位在32位地址上的位置。当OB_SIZE等于0123时,index在存储地址中对应的位置是Bits[24:20], bits[25:21], bits[26:22], and bits[27:23],每个regions翻倍,是不是对应的地址应该按翻倍对齐呢,翻倍就是左移一位数据。OB_SIZE寄存器如图7所示。

3.JPG

7

OB_OFFSET_INDEXn寄存器结构如图8所示,n是上一段落提到的index的值。该寄存器第0位是地址翻译使能位,第31~20位是第nregions的基地址的31~20位,这里的取值取决regions的大小,当OB_SIZE 等于0123时,bits[31:20], bits[31:21], bits[31:22], and bits[31:23]位相应被使用。OB_OFFSETn_HI寄存器的值是64PCIe地址中第nregions的基地址的63~32位,在32PCIe地址中,该寄存器的值等于0

4.JPG

8

配置OutBound翻译的几个寄存器也做了详解,下面根据举例说明。图9中配置空间存储地址由CPU本身架构所决定,这部分的地址映射才芯片内部完成,不需要由编程人员配置。PCIe设备空间被分成了32等分。假设region大小是2MPCIe地址是64位,程序中需要对0x9D3A_1234存储地址做映射, 64PCIe地址被使用在region 9上,初始化OBOFFSET9_HI值为0x3344 5566, OB_OFFSET956Ex xxxxx的值这里不关心,看该寄存器结构就很清楚,第0位在地址翻译时候应该使能位1,这里仅仅用来讲解怎么做映射,不需要关心后面的Bits ,下面分析怎么翻译到PCIe地址:

1)         由于是regions大小2M,那么index应该取地址的bits [25:21],提取0x9D3A_1234存储地址的bits [25:21得到01001b,该值等于9,那么该地址应该启用regions 9 翻译。存储地址的bits[20:00]是用做翻译到PCIe地址的bits[20:00]位,该值也可以理解成reginos 9内的偏移值,值是0x001A 1234

2)         生成regions PCIe的基地址,该地址应pcie_base=OBOFFSET9_HI <<32 + OB_OFFSET9bits[31:21] = 0x 3344 5566 56E0 0000

3)         计算PCIe地址,pcie_addr = pcie_base + 存储地址bits[20:0] =0x3344 5566 56FA 1234

 

 

5.JPG

9

 

从上面存储地址到PCIe地址映射可以看到,通过cpu寻址可以直接访问到PCIe设备空间,最多可以访问PCIe设备空间大小为256M,具体Outbound能够访问的大小根据芯片而定,当CPUFPGA之间有大量数据交互时候也可以采用Inbound方式(Inbound地址翻译流程如图10所示,这里就不在翻译),将CPU的内存映射到FPGA的寻址空间(这里是站在CPU角度看的,从图6可以理解具体映射大小还由EP决定),FPGA可以采用DMA方式访问cpu的内存,并且速度很快。有些芯片厂商干脆采用同核异构方式将CPUFPGA集成在一起(有的将cpudsp集成在一起),两者之间采用AXI高速总线通讯。

6.JPG

10

7            扫描PCIe

扫描树的流程如下:

1)         建立存储地址到PCIe地址映射 (映射方式上面段落已经讲解了,固定的PCIe配置空间映射)

2)         分配PCIe总线号

3)         分配设备号

4)         访问配置空间 (这里有一个原则读者需要注意,对PCIe设备配置空间访问时,一定要确定总线号、设备号、功能号、寄存器,不然无法找到设备)

5)         读写BAR0确定PCIe设备1空间大小

6)         分配PCIe地址

7.1         分配总线号

扫描PCIe总线树时,需要对这些PCIe总线进行编号,即初始化PCIe桥(在本文一律指透明桥)的PrimarySecondarySubordinate Bus寄存器。在Linux内核中采用DFS算法对PCIe总线树进行遍历,DFS算法是按照深度优先的原则遍历PCIe树,局部代码如图11所示,这里可以跟踪pci_scan_bridge函数,函数采用DFS算法对总线进行编号(后期会讲解搜索树,比较常见hash表、红黑树)。

7.JPG

11

7.2         分配设备号

PCIe设备的IDSEL信号与PCIe总线的AD[31:0]信号的连接关系决定了该设备在这条总线上的设备号。在配置读写总线事务的地址周期中,AD[10:0]已经被信号已经被功能号和寄存器号使用,因此PCIe设备的IDSEL只能与AD[31:11]信号连接。上一篇文章中谈到CONFIG_ADDR寄存器中的Device Number字段一共有5位,最大能够表示32个设备,这里只有21位,显然在两者之间不能建立一一映射关系。一个PCIe总线号下最多可以挂在21PCIe设备,那么多个PCIe总线不就可以挂载32个设备了么。

7.3         访问配置空间

32PCIe地址空间中,PCIe设备通常将PCIe配置存放在E2PROM中,PCIe设备进行上电初始化时,将E2PROM中的信息读到PCIe设备的配置空间作为初始值,由硬件自动完成。BAR0空间存储了PCIe设备空间的大小,某些位被设置成不可预读,当BAR0全部写入1时,然后在读取BAR0值,从数据低位看有多少连续位没有改变。没有改变的数据位数记录的该PCIe设备空间的大小,假如有n位没有改变,那么设备空间大小应该是2n次方。第0位代表IO/Memory、第23位代表32/64位地址、第4位代表是否可预取,具体位定义格式可以直接参考内核PCIe总线代码,解析BAR函数如图12所示。

8.JPG

12

 

7.4         分配PCIe地址

系统软件根据根据设备空间大小建立存储地址PCIe设备地址空间的映射,给PCIe设备分配的PCIe基地址写入到BAR0,如果是64PCIe地址,那么BAR1是高32位地址。

8            结语

 写《从cpu角度理解PCIe》文章我参考了部分芯片的datasheet,并结合linux代码分析,本文仅仅起到分析流程的作用,具体映射机制和寄存器芯片参考相应芯片datasheet。如有错误,还望指正。

 


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。