PCIE扫描过程

本文深入解析了PCIE设备的扫描与初始化过程,详细介绍了BIOS如何利用深度优先搜索算法进行PCIe设备的查找与配置。从按下电源按钮开始,BIOS如何逐级扫描总线,设置桥接器参数,直至完成所有设备的扫描。
本文转载自 https://blog.youkuaiyun.com/Best_Ccc/article/details/78903642

1. PCIE扫描过程

从按下PC的电源按钮开始,BIOS就接管系统控制权开始工作,它会先进行一些内存和设备的初始化工作(当然,也包括PCI设备),由于商业上的原因,Phoenix等厂商的BIOS代码需要授权协议,下面以另外一款开源BIOS(openbios)为例,来剖析BIOS中,PCIe设备是如何被找到以及初始化的。

PCI设备的扫描是基于深度优先搜索算法(DFS:Depth First Search),也就是说,下级分支最多的PCI桥将最先完成其子设备的扫描。下面以图片来具体说明,BIOS是如何一步步完成PCI 设备扫描的。

第一步:PCI Host 主桥扫描Bus 0上的设备(在一个处理器系统中,一般将与HOST主桥直接相连的PCI总线被命名为PCI Bus 0),系统首先会忽略Bus 0上的D1,D2等不会挂接PCI桥的设备,主桥发现Bridge 1后,将Bridge1下面的PCI Bus定为 Bus 1,系统将初始化Bridge 1的配置空间,并将该桥的Primary Bus Number 和 Secondary Bus Number寄存器分别设置成0和1,以表明Bridge1的上游总线是0,下游总线是1,由于还无法确定Bridge1下挂载设备的具体情况,系统先暂时将Subordinate Bus Number设为0xFF。如图 1.1所示:

图 1.1  PCIE扫描第一步

第二步:系统开始扫描Bus 1,将会发现Bridge 2。系统将Bridge 2下面的PCI Bus定为Bus 2,并将该桥的Primary Bus Number 和 Secondary Bus Number寄存器分别设置成1和2,和上一步一样暂时把Bridge 2的Subordinate Bus Number设为0xFF。如图 1.2所示:

图 1.2 PCIE扫描第二步

第三步:

系统继续扫描Bus 2,将会发现Bridge 4。系统将Bridge 4下面的PCI Bus定为Bus 3,并将该桥的Primary Bus Number 和 Secondary Bus Number寄存器分别设置成2和3,此后

系统继续扫描后发现Bus 3下面已经没有任何Bridge了,意味着该PCI总线下已经没有任何挂载下游总线了,因此Bridge 4的Subordinate Bus Number的值已经可以确定为3了。

如图 1.3所示:

图 1.3 PCIE扫描第三步

第四步:

完成Bus 3的扫描后,系统返回到Bus 2继续扫描,发现Bus 2下面已经没有其他Bridge了。此时Bridge 2的Subordinate Bus Number的值也已经可以确定为3了。如图 1.4所示:

图 1.4 PCIE扫描第四步

第五步:

完成Bus 2的扫描后,系统返回到Bus1继续扫描,会发现Bridge 3,系统将Bridge 3下面的PCI Bus定为Bus 4。并将Bridge 4的Primary Bus Number 和 Secondary Bus Number寄存器分别设置成1和4,此后系统继续扫描后发现Bus 4下面已经没有任何Bridge了,意味着该PCI总线下已经没有挂载任何下游总线了,因此Bridge 3的Subordinate Bus Number的值已经可以确定为4了。如图 1.5所示:

图 1.5 PCIE扫描第五步

第六步:

完成Bus 4的扫描后,系统返回到Bus 1继续扫描, 发现Bus 1下面已经没有其他Bridge了。此时Bridge 1的Subordinate Bus Number的值已经可以确定为4,系统返回Bus 0继续扫描(Bus 0下如果有其他它Bridge,将重复上述的步骤进行扫描)。至此,本例中的整个PCI的设备扫描已经完成了。最终的设备和总线的扫描结果如图 1.6所示

图 1.6 PCIE扫描第六步

一般来说,可以通过两个寄存器来访问PCI的配置空间(寄存器CONFIG_ADDRESS与CONFIG_DATA),在x86体系下,这两个寄存器分别对应0xCF8和0xCFC端口,对配置空间的访问都是通过对这两个寄存器的读写来实现先。CONFIG_ADDRESS寄存器的具体位组成如图 1.7所示:

图 1.7 x86配置寄存器

Bus Number : 总线号(8 bit),范围0--255。

Device Number: 设备号(5 bit),范围0--31。

Function Number: 功能号(3 bit),范围0--7。

Register Number: 寄存器号(6 bit),范围0--63(配置空间一共256个字节,分割成64个4字节的寄存器,从0--63编号)。

每个PCI设备可根据上图所示的四个信息:Bus Number, Device Number, Function Number,Register Number来进行具体设备的定位并对其配置空间访问。当访问PCI设备的配置空间时,先根据以上格式设置CONFIG_ADDRESS寄存器,然后再读取CONFIG_DATA寄存器即可得到相应的配置空间寄存器的值。通过以上这些步骤,BIOS就完成了所有PCI设备的扫描,并且为每个设备分配好了系统资源。

### Linux PCIe设备扫描机制及实现 在Linux系统中,PCIe设备的扫描机制是通过一系列驱动程序和内核功能来完成的。以下是关于PCIe设备扫描机制的核心内容: #### 1. PCIe设备扫描的基本流程 当系统启动时,Linux内核会初始化PCI子系统,并执行PCIe设备的扫描操作。这一过程通常包括以下几个关键步骤[^3]: - **总线枚举**:内核通过遍历PCI配置空间,识别所有连接到系统的PCIe设备。 - **资源分配**:为每个识别到的设备分配必要的资源,如内存地址、中断号等。 - **驱动绑定**:将设备与相应的驱动程序绑定,以便后续的初始化和使用。 #### 2. 自定义扫描函数的作用 如果某些特定硬件需要特殊的初始化逻辑,可以实现自定义的扫描函数。例如,在SCSI子系统中,`shost->hostt->scan_start()` 和 `shost->hostt->scan_finished()` 可以被定义以支持驱动特定的扫描行为[^1]。尽管如此,最终的设备扫描仍然可能调用通用函数如`__scsi_scan_target()`,确保设备能够正确注册到系统中。 #### 3. 内核中的PCIe扫描实现 Linux内核中,PCIe设备的扫描主要依赖于以下核心函数: - **`pci_scan_bus()`**:用于扫描指定范围内的PCI总线,并检测所有设备和功能。 - **`pci_device_probe()`**:当设备被识别后,此函数会被调用来尝试将设备与合适的驱动程序绑定。 - **`pci_register_driver()`**:用于注册PCI驱动程序,使得驱动程序能够监听并响应新设备的插入事件。 以下是一个简单的代码示例,展示了如何注册一个PCI驱动程序: ```c static struct pci_driver my_pci_driver = { .name = "my_pci_driver", .id_table = my_pci_id_table, .probe = my_pci_probe, .remove = my_pci_remove, }; static int __init my_pci_init(void) { return pci_register_driver(&my_pci_driver); } static void __exit my_pci_exit(void) { pci_unregister_driver(&my_pci_driver); } module_init(my_pci_init); module_exit(my_pci_exit); ``` #### 4. 扫描机制中的问题解决 在实际应用中,可能会遇到以下常见问题及其解决方案: - **设备未被识别**:检查设备是否正确连接到主板,并验证其厂商ID和设备ID是否已包含在驱动程序的`id_table`中[^4]。 - **资源冲突**:确保设备所需的资源(如IRQ、I/O地址)没有与其他设备发生冲突。 - **驱动未加载**:确认相关驱动程序已正确编译并加载到内核中。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值