[转]PCIE 设备扫描的过程

本文详细解析了BIOS如何通过深度优先搜索算法扫描PCIe设备,介绍了PCI总线Bus号的分配原则及BIOS中PCI配置空间的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

注:本文为转载文章(简单明了说明了PCI总线bus号分配的原则)。

尊重原著版权,分享只为个人学习和交流目的,如有侵权请原作者联系后可随时删除,谢谢!

目前看到的原出处为http://blog.163.com/huang_bp/blog/static/1231198372012724103437743/,如有错误敬请指正!

PCIE 设备扫描的过程  

2012-08-24 22:34:37|  分类: Linux知识|举报|字号 订阅

初步了解完PCI总线标准之后,我们接下来正式开始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。如下图所示:


第二步:

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



第三步:

系统继续扫描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了。

如下图所示:


第四步:

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



第五步:

完成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了。如下图所示:



第六步:

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



了解了上面PCI设备扫描的大概流程,我们接下来看看Bios代码中具体是如何实现这些扫描的。

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



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配置空间的读写可以封装成下面的函数:

PCIE 设备扫描的过程  

2012-08-24 22:34:37|  分类: Linux知识|举报|字号 订阅

 初步了解完PCI总线标准之后,我们接下来正式开始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。如下图所示:


第二步:

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



第三步:

系统继续扫描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了。

如下图所示:


第四步:

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



第五步:

完成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了。如下图所示:



第六步:

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



了解了上面PCI设备扫描的大概流程,我们接下来看看Bios代码中具体是如何实现这些扫描的。

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



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配置空间的读写可以封装成下面的函数:


<think>嗯,用户想了解PCIe设备枚举过程中BDF编的存储位置和分配机制。首先,我得回忆一下PCIe的基本结构。根据引用[4],PCIe的拓扑结构由Bus、Device和Function组成,每个设备都有唯一的BDF标识。那BDF编是怎么分配的呢? 我记得在枚举过程中,系统会从Host Bridge开始扫描,逐级探测每个总线上的设备。当发现一个桥设备时,系统会分配新的总线,并继续扫描下级总线。比如引用[3]提到,当扫描到Bridge 3时,系统将Primary Bus设为1,Secondary设为2,并暂时设置Subordinate为0xFF。这时候BDF中的Bus就被分配了,而Device和Function则是根据设备在总线上的位置自动确定的。 接下来,BDF的存储位置可能是在设备的配置空间里。每个PCIe设备都有一个配置空间,其中包含了头寄存器,比如引用[4]提到的BDF由Bus、Device、Function组成,这些信息应该存储在配置空间的特定寄存器中。例如,Bus Number寄存器可能在配置空间的某个偏移地址处。 另外,引用[1]提到创建PCI_IO_DEVICE结构体时,会保存BDF信息。这说明操作系统在枚举过程中,会为每个设备创建数据结构,将BDF存储在其中,方便后续管理和访问。 还有,用户可能需要了解BDF的动态分配过程。系统在扫描时,遇到桥设备就会分配新的总线,然后递归扫描下级总线。这会导致BDF中的Bus是动态变化的,而Device和Function则是设备物理连接决定的。可能还需要解释配置空间的作用,比如访问方式是通过MMIO,像引用[2]提到的LAPIC寄存器地址,但PCIe配置空间访问方式可能不同,比如使用CONFIG_ADDRESS和CONFIG_DATA端口。 需要确认是否有更详细的寄存器说明,比如Bus Number寄存器的位置在配置空间的哪个部分。可能还需要提到Type 0和Type 1头类型的区别,桥设备是Type 1,普通设备是Type 0,它们的配置空间结构不同,影响BDF的存储位置。 总结下来,回答需要涵盖BDF的组成、分配机制(动态扫描和总线分配)、存储位置(配置空间寄存器)以及操作系统的数据结构保存这些信息。同时,要引用相关的资料来支持每个点,确保准确可靠。</think>### PCIe设备BDF编存储与分配机制分步解析 #### 一、BDF编的组成结构 根据PCIe协议规范,每个设备节点由三元组$BDF=(Bus, Device, Function)$唯一标识: - **Bus**:8位,支持256条总线 - **Device**:5位,每条总线支持32个设备 - **Function**:3位,每个设备支持8个功能[^4] #### 二、BDF编的存储位置 1. **设备配置空间寄存器**: - Bus存储在设备配置空间偏移量0x18的Primary Bus Number/Secondary Bus Number/Subordinate Bus Number寄存器中(针对桥设备) - Device和Function编由物理连接位置决定,不直接存储数值,通过配置空间访问时自动解码[^3] 2. **操作系统数据结构**: - 枚举过程中会创建PCI_IO_DEVICE结构体,其中包含完整的BDF信息[^1] - 例如Linux内核中的pci_dev结构体包含`unsigned int devfn`字段存储设备/功能编 #### 三、BDF分配机制 1. **深度优先搜索遍历**: - 从Host Bridge开始(默认Bus 0) - 发现桥设备时分配新Bus,递归扫描下级总线 - 示例分配过程: ``` Host Bridge (Bus 0) ├─ Bridge 1 (Bus 0→1) │ └─ Device (BDF=1:0.0) └─ Bridge 3 (Bus 0→2) └─ Device (BDF=2:0.0) ``` 2. **动态编规则**: - 总线扫描顺序递增分配 - Device由物理槽位决定(AD[31:16]信线编码) - Function通过配置空间Header Type寄存器识别[^4] 3. **桥设备寄存器配置**: - Primary Bus Number:上游总线 - Secondary Bus Number:分配给下游总线 - Subordinate Bus Number:下游最大总线(临时设为0xFF,最终确定后更新) #### 四、配置空间访问机制 通过PCIe的配置地址空间访问: $$ CFG_ADDR = 0xCF8 \quad CFG_DATA = 0xCFC $$ 地址格式: $$ ADDR = 0x80000000 | (bus << 16) | (dev << 11) | (func << 8) | reg_offset $$ #### 五、特殊设备处理 1. **多功能设备**:通过Header Type寄存器bit7判断,支持最多8个Function 2. **虚拟功能(SR-IOV)**:通过扩展配置空间分配虚拟BDF #### 六、枚举过程示例 ```c void pci_scan_bus(uint8_t bus) { for(int dev=0; dev<32; dev++){ uint16_t ven_id = pci_read(bus,dev,0,0x00); if(ven_id != 0xFFFF){ create_pci_device(bus, dev, 0); // 创建结构体 if(is_bridge(bus,dev,0)){ uint8_t new_bus = allocate_bus_number(); pci_write(bus,dev,0,0x19, new_bus); // 配置Secondary Bus pci_scan_bus(new_bus); // 递归扫描 } } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值