PCI设备初始化(一)
访问PCI设备
我们知道CPU和网卡是通过PCI总线相连的,CPU可以直接访问系统内存(虚拟地址),也可以通过映射间接访问总线地址,那CPU怎么访问网卡的存储空间呢?
每个网卡都有自己的存储空间,这些空间的卡上地址(在网卡上的地址)本质上是局部的,所以都从0开始,它们不与总线直接相连,在把网卡插上总线并加电之初,从总线上还访问不到这些空间
系统初始化时扫描PCI总线上的各个PCI设备(包括网卡),为这些设备分配总线地址,并建立起其卡上地址和总线地址的映射,那映射是怎么建立起来的?
每个PCI设备上都有用来建立映射的配置寄存器组(配置空间),系统初始化时通过这组寄存器来为设备”配置”总线地址,那CPU怎么访问这组寄存器呢?这就又回到原点了
PCI标准规定配置寄存器组最大256 byte,其中开头64 byte是标准的(对每个PCI设备都一样),所有PCI设备的配置寄存器组都使用相同的地址(卡上地址或偏移量)
系统在IO地址空间预留了八个字节(0xCF8~0xCFF),其中前四个字节做地址寄存器,后四个字节做数据寄存器,当CPU访问某个设备的某个配置寄存器时,首先通过I/O命令向地址寄存器写入目标地址(包括总线号、设备号、功能号、寄存器地址的综合地址),然后通过I/O命令读写数据寄存器
综合地址的结构如下所示,其中寄存器地址的高6位用于寻址最大64 word(256 byte)的配置寄存器组
写入综合地址后,从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设备有BIOS探测和直接探测两种方式,两者都是从Host-PCI桥开始深度优先搜索总线树上的所有设备,我们以直接探测为例讨论
- 申请IO地址空间0xCF8~0xCFF,探测Host-PCI桥
1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|
arch_initcall | pci_arch_init | pci_direct_probe | pci_check_type1 | pci_sanity_check |
- 深度优先搜索总线树,读取BAR,写入resource
1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|
subsys_initcall | pci_legacy_init | pcibios_scan_root | pci_scan_bus_parented | pci_scan_child_bus | pci_scan_slot |
pcibios_fixup_bus | |||||
pci_scan_bridge |
6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|
pci_scan_slot | pci_scan_single_device | pci_scan_device | pci_setup_device | pci_read_bases | __pci_read_base |
pcibios_fixup_bus | pci_read_bridge_bases | ||||
pci_scan_bridge | pci_scan_child_bus |
- 深度优先搜索总线树,分配总线地址,写入BAR建立映射
1 | 2 | 3 | 4 |
---|---|---|---|
subsys_initcall | pcibios_init | pcibios_resource_survey |
pcibios_allocate_bus_resources 为每个总线分配地址 |
pcibios_allocate_resources 为每个PCI设备分配地址 |
|||
fs_initcall | pcibios_assign_resources 为起始地址已改成0的区间分配地址 |
pci_assign_unassigned_resources | pci_bus_assign_resources |
4 | 5 | 6 | 7 |
---|---|---|---|
pcibios_allocate_bus_resources | pci_find_parent_resource | ||
request_resource | __request_resource | ||
pcibios_allocate_resources | alloc_resource | pci_find_parent_resource | |
request_resource | __request_resource | ||
pci_bus_assign_resources | pbus_assign_resources_sorted | pci_assign_resource | __pci_assign_resource |
7 | 8 | 9 | 10 |
---|---|---|---|
__pci_assign_resource | pci_bus_alloc_resource | allocate_resource | find_resource __request_resource |
pci_update_resource |
深度优先搜索
参考资料
《Linux内核源代码情景分析》
《PCI Express 体系结构导读》
http://blog.sina.com.cn/s/articlelist_1685243084_3_1.html
http://blog.chinaaet.com/justlxy/p/5100057779