首页 理论教育LinuxPCI分配PCI设备使用的BAR寄存器

LinuxPCI分配PCI设备使用的BAR寄存器

【摘要】:Linux PCI采用这种方式,可以保证PCI设备BAR寄存器初始化是从上游PCI总线到下游PCI总线,而PCI桥Base、Limit寄存器的初始化是从下游PCI总线到上游PCI总线。目前Linux系统使用从上游总线到下游总线的方法初始化PCI设备的BAR寄存器。pbus_assign_resources_sorted函数负责分配“未初始化PCI设备的BAR寄存器”,该函数将对这些PCI设备的BAR寄存器进行写操作。

pci_subsys_init函数执行完毕后,Linux PCI将调用pcibios_assign_resources函数,设置PCI设备的BAR寄存器。pcibios_assign_resources函数首先处理PCI设备的ROM空间,并进行资源分配,之后调用pci_assign_unassigned_resources函数设置PCI设备的BAR寄存器。该函数在./drivers/pci/setup-bus.c文件中,如源代码14-37所示。

源代码14-37 pci_assign_unassigned_resources函数

该函数依次调用pci_bus_size_bridges、pci_bus_assign_resources和pci_enable_bridges函数,下文将分别讨论这些函数。而pci_bus_dump_resources函数的主要作用是将Linux系统分配的PCI设备的资源信息打印出来,本节对该函数不做介绍。

1.pci_bus_size_bridges函数

pci_bus_size_bridges函数的主要作用是修复和对界当前PCI总线树下的所有PCI设备(包括PCI桥)所使用的I/O和存储器地址空间。该函数的实现如源代码14-38所示。

源代码14-38 pci_bus_size_bridges函数

这段代码首先递归调用pci_bus_size_bridges函数,直到找到当前PCI总线树最底层的PCI桥,然后调用pci_bridge_check_ranges函数检查这个PCI桥所管理的地址空间是否支持I/O或者可预读的存储器空间,如果支持,则将当前PCI桥的pci_bus→self→resource参数的相应状态位置1。这段代码随后调用pbus_size_io和pbus_size_mem函数修复并对界当前PCI桥的I/O空间和存储器空间,并从低到高逐层递归调用pci_bus_size_bridges函数。

2.pci_bus_assign_resources函数

pci_bus_assign_resources函数在./drivers/pci/setup-bus.c文件中,如源代码14-39和源代码14-41所示。

源代码14-39 pci_bus_assign_resources函数片段1

该函数首先调用pbus_assign_resources_sorted函数遍历并初始化当前PCI总线上的所有PCI设备的BAR寄存器,包括PCI Agent设备和PCI桥,之后递归调用自身遍历当前PCI总线的所有下游总线,最后调用pci_setup_bridge函数初始化PCI桥的存储器和I/O Base、Limit寄存器。

在第14.3.2中曾经使用pci_scan_bridge函数,将PCI桥的Primary Bus Number、Seconda-ry Bus Number和Subordinate Bus Number寄存器初始化完毕,此时Linux PCI可以访问HOST主桥之下的所有PCI设备的配置空间,但是不能访问“未初始化的PCI设备”的BAR空间。PCI/PCIe总线规定使用ID寻址方式访问配置空间,而使用地址寻址方式访问存储器空间,因此处理器虽然不能访问BAR空间,但是依然能够访问PCI设备的配置空间。

值得注意的是这段代码中的一个细节问题。其中pbus_assign_resources_sorted函数在pci_bus_assign_resources函数递归调用之前执行,而pci_setup_bridge函数在递归调用之后执行。Linux PCI采用这种方式,可以保证PCI设备BAR寄存器初始化是从上游PCI总线到下游PCI总线,而PCI桥Base、Limit寄存器的初始化是从下游PCI总线到上游PCI总线。

这一细节对PCI设备的初始化非常重要,因为PCI桥所管理的地址空间是其下所有PCI设备使用地址空间的合集,因此PCI桥Base、Limit寄存器的初始化需要从下而上进行。而PCI设备的BAR寄存器的初始化的方向并没有严格规定,PCI规范并没有对此做具体的要求,在实现中只要保证系统软件在初始化PCI桥的Base、Limit寄存器之前,其下所有PCI设备的BAR寄存器已经完成初始化即可。目前Linux系统使用从上游总线到下游总线的方法初始化PCI设备的BAR寄存器。(www.chuimin.cn)

对于x86处理器系统,PCI设备的BAR空间已经被BIOS初始化,因此只要BIOS正确分配了PCI设备的BAR寄存器,Linux系统不执行pci_assign_unassigned_resources函数也没有什么关系。不过对于一些处理器系统,其Firmware并没有完全枚举PCI总线树上的PCI设备,此时必须调用pci_assign_unassigned_resources中的pci_bus_assign_resources[0]函数初始化“未初始化BAR空间”的PCI设备。

pbus_assign_resources_sorted函数负责分配“未初始化PCI设备的BAR寄存器”,该函数将对这些PCI设备的BAR寄存器进行写操作。该函数的实现如源代码14-40所示。

源代码14-40 pbus_assign_resources_sorted函数

这段代码首先调用pdev_sort_resources函数,该函数的实现过程较为简单,其主要作用是将“未初始化”的PCI设备使用的资源进行排序对齐,然后加入到head链表中,随后调用pci_assign_resource函数初始化这些PCI设备的BAR寄存器。

pci_assign_resource函数在./drivers/pci/setup-res.c文件中,该函数的实现逻辑较为简单,本节并不列出这段源代码。该函数两次调用了pci_bus_alloc_resource函数,第一次试图从上游总线的可预读存储器空间为当前PCI设备分配资源,第二次从“不可预读的存储器空间”分配资源。当资源分配成功后,pci_assign_resource→pci_update_resource函数将初始化PCI设备的BAR寄存器,这些代码并不复杂,本节将这些代码留给读者。

源代码14-41 pci_bus_assign_resources函数片段2

再次回到pci_bus_assign_resources函数,该函数开始递归调用自身,寻找当前PCI总线子树的最后一个PCI桥,之后调用pci_setup_bridge函数初始化这个PCI桥的Base、Limit寄存器。pci_setup_bridge函数的源代码在./drivers/pci/set-bus.c文件中,本节对此不作介绍。

当Linux PCI执行pci_setup_bridge函数初始化当前PCI桥之后,这个桥的上游设备和这个桥管理的PCI设备的BAR寄存器已经初始化完毕。因此pci_setup_bridge函数通过简单的计算,即可得出当前PCI桥Base、Limit寄存器的值,之后调用pci_write_config_dword函数将这个数据对PCI桥的这些寄存器更新即可。

至此,PCI总线树上的所有PCI设备的BAR寄存器,以及PCI桥的Base、Limit寄存器全部初始化完毕,从硬件的角度来看,PCI总线系统已经初始化完毕。

3.pci_enable_bridges函数

我们再次回到pci_assign_unassigned_resources函数,如源代码14-37所示,该函数将调用pci_enable_bridges函数,使能所有PCI桥设备。pci_enable_bridges函数的实现如源代码14-42所示。

源代码14-42 pci_enable_bridges函数

该函数的实现较为简单,分别调用pci_enable_device、pci_set_master函数启动当前PCI桥,之后递归调用pci_enable_bridges函数启动当前PCI桥下游的PCI桥。至此Linux PCI完成对当前PCI总线树的主要初始化工作。