人的一生是一个不断认识自我,发展自我的过程。
认识PCIe设备的枚举过程需要以下知识:
- 拓扑结构
- 设备的表征及配置空间的访问
- BAR空间的含义和访问
其中第1/2点在总线结构与配置空间已经介绍过了,第3点在BAR空间和TLP也已经进行过详细的介绍,可以说是万事具备。接下来涉及的过程有以下几个:
- 根据深度优先搜索进行设备总线号的分配
- BAR空间的映射和简单访问测试
上面就是枚举过程中做的事情了。
一、基于深度优先搜索的总线号分配
1.1 原理阐述
每一个switch代表一级bus,挂在该switch下的所有设备(包括SW和EP)都属于这级bus。
SW配置空间中的总线号有Primary Bus、Secondary Bus和Subordinate Bus。含义如下
总线号 | 含义 |
---|---|
Primary Bus | 当前级别的总线号,从该设备往下的所有设备都属于primary bus |
Secondary Bus | 下一级总线号 |
Subordinate Bus | 从当前级别总线开始最远端的总线号 |
现在假设有一个tlp过来,首先瞅了瞅当前switch的subordinate bus number,发现比自己的bus number还小,那就很知趣的不往这级bus上路由了;如果发现subordinate bus比自己的大,她就知道我要去的地方在这一级bus上,然后就继续往secondary bus路由,继续重复上述过程。如果primary bus number等于tlp自己的bus number,那么这一级bus就是tlp的归宿了。上述这个tlp的路由过程由SW完成。
1.2 代码举例
init BUS[0]
push BUS[0] to bus_stack
while bus_stack != NULL:
bus = pop(bus_stack)
while find_next_sw(bus) is true:
child.prim_bus = bus.prim_bus
child.second_bus = bus.prim_bus+1
child.subord_bus = bus.second_bus
push(child)
// find an EP
update_subord_bus(bus_stack)
这个代码是刚敲的(风格似乎有点python的感觉),只是表示基于深度优先搜索的配置PCIe设备bus number的算法,其中的find_next_sw
负责找到当前sw下游的设备(如果发现设备0是EP,那么应该继续根据是否是多功能设备来搜索设备1,2,…,直到找到SW,或者没有)。如果发现SW,则继续入栈,否则(是EP)更新当前栈上所有设备的subordinate bus number为最后一个SW的primary bus number,然后栈顶弹出SW,进行下一个分支的深度搜索。
二、映射BAR空间
前面的枚举过程是在链路上通过发送配置读写请求来进行的,PCIe协议规定:访问SW配置要通过类型1的配置请求,访问EP配置要通过类型0的配置请求。
那么如果我们想要访问PCIe设备内部的mem空间呢?就要通过mem rd/wr请求。请求里面包含了需要访问的mem地址,PCIe网络根据这个地址把tlp路由到对应的设备上去处理。所以我们需要在系统枚举的阶段配置好各个设备的BAR空间范围,这样mem/io tlp就能根据地址是不是在BAR范围内来判断是不是应该访问该设备。
这里再次注意:这里的tlp中的地址和系统ram空间的地址没有必然联系,只是PCIe内部分配的地址(上一篇文章已经提到过这个点了)。
而BAR的配置地址是在configuration空间的,所以在枚举阶段我们通过发送cfg rd/wr tlp来配置BAR就行,具体关于BAR的介绍在大话PCIe:BAR空间和TLP中有说明了。
上一篇文章遗留了两个问题,到这里可以进行解答了。
一是关于配置空间及其地址的问题。
每个设备都有其配置空间,而且是固定地址0~4096 Byte,前面64字节是PCI兼容的。这个配置空间的地址和外部总线地址并没有什么关系。通过指定tlp中的bus number、device number、function number+偏移地址(这个就是写到tlp中的地址)就可以访问到对应设备的配置空间。
二是关于如何发起cfg tlp的问题。
上篇文章介绍了一个功能模块ATU,这是和总线对接的一个模块,除了地址转换之外,还根据配置产生tlp。一般是这样的,系统会为PCIe设备预留256MB空间,访问这256MB空间的时候就会触发产生一个mem request tlp,然后ATU根据配置(如果配置了cfg type)将其转换成cfg request,其中的target地址就写入了tlp中。然后PCIe网络就将这个tlp路由到对应地址的设备功能上了。举例如下:
系统ram空间地址基址为0x40000000,ATU配置source address为0x40000000,target address为0x00000000,ATU type = cfg 0,size=0x10000000(256MB),那么CPU读0x40000000地址的时候触发了一个mem read request,经过ATU之后产生了一个cfg type 0 rd的request;同样的,如果往0x40000000写一笔数据,就会触发mem write request,处理过程也是类似的。如果访问0x40100000,转换后的target地址就成了0x100000。
关于PCIe设备的枚举过程内容都在这里了。接下来重点介绍PCIe dma和MSI(-X)中断。
下篇预告: PCIe设备dma和MSI(-X)