PCI设备初始化

访问PCI设备

我们知道CPU和网卡是通过PCI总线相连的,CPU可以直接访问系统内存(虚拟地址),也可以通过映射间接访问总线地址,那CPU怎么访问网卡的存储空间呢?

每个网卡都有自己的存储空间,这些空间的卡上地址(在网卡上的地址)本质上是局部的,所以都从0开始,它们不与总线直接相连,在把网卡插上总线并加电之初,从总线上还访问不到这些空间

系统初始化时扫描PCI总线上的各个PCI设备(包括网卡),为这些设备分配总线地址,并建立起其卡上地址和总线地址的映射,那映射是怎么建立起来的?

每个PCI设备上都有用来建立映射的配置寄存器组(配置空间),系统初始化时通过这组寄存器来为设备"配置"总线地址,那CPU怎么访问这组寄存器呢?这就又回到原点了

PCI标准规定配置寄存器组最大256 byte,其中开头64 byte是标准的(对每个PCI设备都一样),所有PCI设备的配置寄存器组都使用相同的地址(卡上地址或偏移量)
PCI设备初始化

系统在IO地址空间预留了八个字节(0xCF8~0xCFF),其中前四个字节做地址寄存器,后四个字节做数据寄存器,当CPU访问某个设备的某个配置寄存器时,首先通过I/O命令向地址寄存器写入目标地址(包括总线号、设备号、功能号、寄存器地址的综合地址),然后通过I/O命令读写数据寄存器

综合地址的结构如下图所示,其中寄存器地址的高6位用于寻址最大64 word(256 byte)的配置寄存器组:

PCI设备初始化
如下图所示,一个256B代表一个配置寄存器组,最多有2^16个配置寄存器组(最大有16MB)
PCI设备初始化
写入综合地址后,从0号总线开始,每个PCI桥将综合地址中的总线号和自己的总线号相比,若符合,根据设备号+功能号寻找设备;若不符合,将综合地址传递给下一级总线的PCI桥继续寻找,直到找到设备,最后根据8位寄存器地址找到配置寄存器,此时通过I/O命令读写数据寄存器就可以读写配置寄存器了

系统初始化时扫描PCI总线上的各个PCI设备(包括网卡),为这些设备分配总线地址,设备的每个BAR对应设备的一段存储空间,系统通过将总线地址写入BAR就建立了存储空间的卡上地址和总线地址的映射

设备驱动程序调用pci_ioremap_bar()将写入BAR的总线地址(保存在resource数组中)映射到系统内存的虚拟地址,之后CPU就可以通过虚拟地址访问PCI设备的存储空间,而不用再通过IO命令了

探测PCI设备

配置寄存器组中的Header Type为0表示普通PCI设备、为1表示PCI桥,PCI桥根据功能又分为Host-PCI桥、PCI-PCI桥、PCI-ISA桥、PCI-CardBus桥等

每个PCI总线都挂在一个PCI桥下,其中0号总线挂在Host-PCI桥下(CPU通过Host-PCI桥连到0号总线),所有总线组成一颗总线树

PCI设备初始化

直接探测

探测PCI设备有BIOS探测直接探测两种方式,两者都是从Host-PCI桥开始深度优先搜索总线树上的所有设备,我们以直接探测为例讨论

  • 申请IO地址空间0xCF8~0xCFF,探测Host-PCI桥
    PCI设备初始化
  • 深度优先搜索总线树,读取BAR,写入resource

PCI设备初始化

  • 深度优先搜索总线树,分配总线地址,写入BAR建立映射
    PCI设备初始化

深度优先搜索

PCI设备初始化
PCI设备初始化
参考资料
《Linux内核源代码情景分析》
《PCI Express 体系结构导读》
http://blog.sina.com.cn/s/articlelist_1685243084_3_1.html
http://blog.chinaaet.com/justlxy/p/5100057779