Linux PCI驱动到底都干了些什么?(二)

紧接着前文:linux pci驱动到底都干了些什么?(一)
我们在浅谈linux pci设备驱动(一)中(以下简称 浅谈(一) )介绍了pci的配置寄存器组,而linux pci初始化就是使用了这些寄存器来进行的。后面我们会举个例子来说明linux pci设备驱动的主要工作内容(不是全部内容),这里只做文字性的介绍而不会涉及具体代码的分析,因为要分析代码的话,基本就是对 linux内核源代码情景分析(下册)第八章的解读,读者若想分析代码,可以参考该书的内容,我们这里就不去深入分析这些代码了。
linux pci设备驱动代码必须扫描系统中所有的pci总线,寻找系统中所有的pci设备(包括pci-pci桥设备)。系统中的每条pci总线都有个编号number,根pci总线的编号为0。系统当前存在的所有根总线(因为可能存在不止一个host/pci桥,那么就可能存在多条根总线) 都通过其pci_bus结构体中的node成员链接成一个全局的根总线链表,其表头由struct list_head类型的全局变量pci_root_buses来描述,我们在/linux-2.4.18/linux/drivers/pci/pci.c的38行可以看到如下定义:
list_head(pci_root_buses);
而根总线下面的所有下级总线则都通过其pci_bus结构体中的node成员链接到其父总线的children链表中。这样,通过这两种pci总线链表,linux内核就将所有的pci_bus结构体以一种倒置树的方式组织起来。
另外,每个pci设备都由一个pci_dev结构体表示,每个pci_dev结构体都同时连入两个队列,一方面通过其成员global_list挂入一个总的pci_dev结构队列(队列头是pci_devices);同时又通过成员bus_list挂入其所在总线的pci_dev结构队列devices(队列头是pci_bus.devices,即该pci设备所在的pci总线的devices队列),并且使指针bus(指pci_dev结构体里的bus成员)指向代表着其所在总线的pci_bus结构。如果具体的设备是pci-pci桥,则还要使其指针subordinate指向代表着另一条pci总线的pci_bus结构。同样我们在/linux-2.4.18/linux/drivers/pci/pci.c的39行可以看到如下定义:
list_head(pci_devices);
对于pci设备链表,我们可以通过图1来理解。
注:该图摘自《linux设备驱动开发详解》 第21章 pci设备驱动。
图1 linux pci设备链表
而对于我们在浅谈(一)中贴出的图1的pci系统结构示意图,linux内核中对应的数据结构如这里的图2所示。
图2 linux内核pci数据结构
linux pci初始化代码从pci总线0开始扫描,它通过读取vendor id和device id来试图发现每一个插槽上的设备。如果发现了一个pci-pci桥,则创建一个pci_bus数据结构并且连入到由pci_root_buses指向的pci_bus和pci_dev数据结构组成的树中。pci初始化代码通过设备类代码0x060400来判断一个pci设备是否是pci-pci桥。然后,linux核心开始构造这个桥设备另一端的pci总线和其上的设备。如果还发现了桥设备,就以同样的步骤来进行构建。这个处理过程称之为深度优先算法。pci-pci桥横跨在两条总线之间,寄存器pci_primary_bus和pci_secondary_bus的内容就说明了其上下两端的总线号,其中pci_secondary_bus就是该pci-pci桥所连接和控制的总线,而pci_subordinate_bus则说明自此以下、在以此为根的子树中最大的总线号是什么。
我们可以在/linux-2.4.18/linux/include/linux/pci.h看到如下定义:
112:/*headertype1(pci-to-pcibridges)*/113:#definepci_primary_bus0x18/*primarybusnumber*/114:#definepci_secondary_bus0x19/*secondarybusnumber*/115: #define pci_subordinate_bus 0x1a /* highest bus number behind the bridge */
由于在枚举阶段做的是深度优先扫描,所以子树中的总线号总是连续递增的。当cpu往i/o寄存器0xcf8中写入一个综合地址以后,从0号总线开始,每个pci-pci桥会把综合地址中的总线号与自身的总线号相比,如果相符就用逻辑设备号在本总线上寻访目标设备;否则就进一步把这个总线号与pci_subordinate_bus中的内容相比,如果目标总线号落在当前子树范围中,就把综合地址传递给其下的各个次层pci-pci桥,要不然就不予理睬。这样,最终就会找到目标设备。当然,这个过程只是在pci设备的配置阶段需要这样做,一旦配置完成,cpu就直接通过有关的总线地址访问目标设备了。
pci-pci桥要想正确传递对pci i/o,pci memory或pci configuration地址空间的读和写请求,必须知道下列信息:
(1)primary bus number(主总线号)
该pci-pci桥所处的pci总线称为主总线。
(2)secondary bus number(子总线号)
该pci-pci桥所连接的pci总线称为子总线/次总线号。
(3)subordinate bus number
pci总线的下属pci总线的总线编号最大值。有点绕,看后面的分析就明白了。
pci i/o 和 pci memory 窗口
pci桥的配置寄存器与一般的pci设备不同。一般pci设备可以有6个地址区间,外加一个rom区间,代表着设备上实际存在的存储器或寄存器区间。而pci桥,则本身并不一定有存储器或寄存器区间,但是却有三个用于地址过滤的区间。每个地址过滤区间决定了一个地址窗口,从cpu一侧发出的地址,如果落在pci桥的某个窗口内,就可以穿过pci桥而到达其所连接的总线上。此外,pci桥的命令寄存器中还有”memory access enable”和”i/o access enable ”的两个控制位,当这两个控制位为0时,这些窗口就全都关上了。在未完成对pci总线的初始化之前,还没有为pci设备上的各个区间分配合适的总线地址时,正是因为这两个控制位为0,才不会对cpu一侧造成干扰。例如, 对于浅谈(一)的 pci系统示意图 ,仅当读和写请求中的pci i/o或pci memory地址属于scsi或ethernet设备时,pci-pci桥才将这些总线上的请求从pci总线0传递到pci总线1。这种过滤机制可以避免地址在系统中没必要的繁衍。为了做到这点,每个pci-pci桥必须正确地被设置好它所负责的pci i/o或pci memory的起始地址和大小。当一个读或写请求地址落在其负责的范围之内,这个请求将被映射到次级的pci总线上。系统中的pci-pci桥一旦设置完毕,如果linux中的设备驱动程序存取的pci i/o和pci memory地址落在在这些窗口之内,那么这些pci-pci桥就是透明的。这是个很重要的特性,使得linux pci设备驱动程序开发者的工作容易些。
问题是配置一个pci-pci桥的时候,并不知道这个pci-pci桥的subordinate bus number。那么就不知道该pci桥下面是否还有其他的pci-pci桥。即使你知道,也不清楚如何对它们赋值。解决方法是利用上述的深度扫描算法来扫描每个总线。每当发现pci-pci桥就对它进行赋值。当发现一个pci-pci桥时,可以确定它的secondary bus number。然后我们暂时先将其subordinate bus number赋值为0xff。紧接着,开始扫描该pci-pci桥的downstream桥。这个过程看起来有点复杂,下面的例子将给出清晰的解释:
图3 配置pci系统 第一步
pci-pci桥编号--第一步
以图3的拓扑结构为例,扫描时首先发现的桥是bridge1。bridge 1的downstream pci总线号码被赋值1。自然该桥的secondary bus number也是1。其subordinate bus number暂时赋值为0xff。上述赋值的含义是所有类型1的含有pci总线1或更高(<255)的号码的pci配置地址将被bridge 1传递到pci总线1上。如果pci总线号是1,bridge 1 还负责将配置地址的类型转换成类型0(对于这里说的类型0和类型1,请参考浅谈(一))。否则,就不做转换。上述动作就是开始扫描总线1时linux pci初始化代码所完成的对总线0的配置工作。
图4 配置pci系统 第二步
pci-pci桥编号--第二步
由于linux pci设备驱动使用深度优先算法进行扫描,所以初始化代码开始扫描总线1。从而bridge 2被发现。因为在bridge 2下面发现不再有pci-pci桥,所以bridge 2的subordinate bus number是2,等于它的secondary bus number。图4显示了在这个时刻总线和pci-pci桥的赋值情况。
图5 配置pci系统 第三步
pci-pci桥编号--第三步
linux pci设备驱动代码从总线2的扫描中回来接着进行扫描总线1,发现bridge 3。它的primary bus number被赋值为1,secondary bus number为3。因为总线3上还发现了pci-pci桥,所以bridge 3的subordinate bus number暂时赋值0xff。图5显示了这个时刻系统配置的状态。到目前为止,含有总线号1,2,3的类型1的pci配置都可以正确地传送到相应的总线上。
图6 配置pci系统 第四步
pci-pci桥编号--第四步
现在linux开始扫描pci总线3,bridge 3的downstream。pci总线3上有另外一个pci-pci桥,bridge 4。因此bridge 4的primary bus number的值为3,secondary bus number为4。由于bridge 4下面没有别的桥设备,所以bridge 4的subordinate bus number为4。然后回到pci-pci bridge 3。这时就将bridge 3的subordinate bus number从0xff改为4,表示总线4是从bridge 3往下走的最远的pci-pci桥。最后,linux pci设备驱动代码将4以同样的道理赋值给bridge 1的subordinate bus number。图6反映了系统最后的状态。
注:浅谈linux pci设备驱动(二)暂时的整体结构就是这样了,后续可能还会有些细节上的修补和添加。在此强烈推荐想学linux pci设备驱动的朋友结合《linux内核源代码情景分析下册》第八章和《linux设备驱动开发详解》第21章 来学习。感谢您关注本文。

京东使用运输机器人——“小红帽”,运输货物,为智慧仓储开辟了新方向
高通授权金模式会改变吗?韩国重罚是否能改变
2019年的IC产业该何去何从?
车载类RE/CE测试的小技巧
LED切片分析(金相显微镜)失效分析
Linux PCI驱动到底都干了些什么?(二)
工业平板电脑介绍
三星Galaxy S8尚未发布 然而拆解图已经曝光
diy压屏机对位方法有哪些
CPU参数、主板设置里的内存频率有啥区别
数字射频存储器模块的电路设计
延时器的接线方法有哪些
磁浮子液位计的常见故障分析与处理
高集成度的模拟前端信号调理电路图
dht11的工作原理_dht11接线图
2017Q1全球IC设计排名 博通超越高通位列第一
高精度肥料养分速测仪的特点是什么
美国都抄袭的共享单车,日本表示否认,现被摩拜征服!
大数据公司被调查的后面隐藏着什么
Linux文件系统的特点及缓存知识