PCI拓扑以及枚举流程

1 篇文章 0 订阅
1 篇文章 0 订阅

PCI拓扑

PCIe拓扑特征:图的顶部是一个CPU。这里要说明的一点是,CPU被认为是PCle层次结构的顶层。PCle只允许简单的树结构,这意味着不允许循环或其他复杂的拓扑结构。这样做是为了保持与PCI软件的向后兼容性,PCI软件使用一个简单的配置方案来跟踪拓扑,不支持复杂的环境。为了保持这种兼容性,软件必须能够与以前相同的方式生成配置周期,总线拓扑也必须与以前相同。因此,软件期望找到的所有配置寄存器仍然在那里,并且以它们始终具有的方式运行。

在上图PCIe系统中有几种设备类型,Root Complex、Switch、Bridge、Endpoint等,下面分别介绍其概念。

Root Complex:简称RC,CPU和PCle总线之间的接口,可能包含几个组件(处理器接口、DRAM接口等),甚至可能包含几个芯片。RC位于PCI倒立树拓扑的“根”,并代表CPU与系统的其余部分进行通信。但是,规范并没有仔细定义它,而是给出了必需和可选功能的列表。从广义上讲,RC可以理解为系统CPU和PCle拓扑之间的接口,PCle端口在配置空间中被标记为“根端口”。

Bridge:桥提供了与其他总线(如PCI或PCI- x,甚至是另一个PCle总线)的接口。如图中显示的桥接有时被称为“转发桥接”,它允许旧的PCI或PCIX卡插入新系统。相反的类型或“反向桥接”允许一个新的PCle卡插入一个旧的PCI系统。

Switch:提供扩展或聚合能力,并允许更多的设备连接到一个PCle端口。它们充当包路由器,根据地址或其他路由信息识别给定包需要走哪条路径。是一种PCIe转PCIe的桥。

Endpoint:处于PCIe总线系统拓扑结构中的最末端,一般作为总线操作的发起者(initiator,类似于PCI总线中的主机)或者终结者(Completers,类似于PCI总线中的从机)。显然,Endpoint只能接受来自上级拓扑的数据包或者向上级拓扑发送数据包。细分Endpoint类型的话,分为Lagacy PCIe Endpoint和Native PCIe Endpoint,Lagacy PCIe Endpoint是指那些原本准备设计为PCI-X总线接口的设备,但是却被改为PCIe接口的设备。而Native PCIe Endpoint则是标准的PCIe设备。其中,Lagacy PCIe Endpoint可以使用一些在Native PCIe Endpoint禁止使用的操作,如IO Space和Locked Request等。Native PCIe Endpoint则全部通过Memory Map来进行操作,因此,Native PCIe Endpoint也被称为Memory Mapped Devices(MMIO Devices)
————————————————
版权声明:本文为CSDN博主「咸鱼弟」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013253075/article/details/119045277

在这里插入图片描述
在这里插入图片描述
如上图,是一个高端服务器系统,系统内建其他组网接口,如FC,ETH,SAS/SATA等。“Intel Processor”包含许多组件,大多数现代CPU架构都是如此。这一个包括一个PCle端口访问图形,和2个DRAM通道,这意味着内存控制器和一些路由逻辑已经集成到CPU中。这些资源通常统称为“Uncore”逻辑,将它们与包中的几个CPU内核及其关联逻辑区分开来。Root Complex描述为CPU和PCle拓扑之间的接口,这意味着该部分必须位于CPU中。

枚举的第一个过程是rc端通过向bus上发起配置空间访问获取整个总线拓扑上的设备信息。

简单说来就是去遍历每个bus,以及bus下device的配置空间。如果读到有效的device ID值(非全F)则认为发现了可用的设备。
在这里插入图片描述

bdf number的分配

bus device function这3个number的分配是很有意思的事情。对于每个pcie设备来说,配置空间中并没有寄存器可以用来配置或者修改bdf number。那么这3个值在到底是怎么确定的呢。

经典的说法是,bdf number是由pcie总线的硬件拓扑来决定的。这个说法按照pcie的规范来说没有问题,但前提条件是必须先按照规范对整个总线进行枚举。在枚举之前,实际上每个设备的bdf number是不确定的。[注1]

function number的确定

function number是用于多function设备用于区分设备中具体访问的是哪个function的。那么对于大部分单function的设备来说,function number就固定为0;对于多个function的device或者支持虚拟function的device,function number也是由device内部来进行管理和区分的,不需要通过总线枚举来确定。

device和bus number的确定

为支持ID路由,每个PCIE设备(端点和交换开关)中都应设置有贮存设备总线号和设备号的寄存器,复位时,该寄存器清0,每当设备在它的原级链路上检测到一个Type0的 config transaction 事务包时,它就从该TLP头标中的第8~9字节“捕获”总线号和设备 号作为它自己的bus和device号,并贮存入上述总线号和设备号寄存器。所以说在接收至少一个 Type0配置访问事务包以前,设备不会响应除配置周期以外的任何事务。PCIE协 议中没有定义贮存总线号和设备号信息的配置空间单元,只是规定了它必须做这件事。

两个对接的PCIE设备,主设备是RC端,从设备是EP端,那么上电复位后,EP是如何获取自身的bus number的呢?

在这样的一个PCIE结构中,上电复位后,RC端发起一个对EP的配置访问事务,EP会解析配置请求中的bus number,并保存下来,作为自身的bus number号。之后就可以相应其他TLP事务了。也就是说,上电复位后,EP必须先接收到一个配置请求,然后才能响应其他TLP事务。

@【注1】,这里提一点,如果没有按照规范对设备进行枚举。对于rc直接接一个ep device的情况,在裸机的环境下实际上rc的driver可以为对端ep分配任意的bus和device number。因为pcie是点对点连接,第一笔Type0的 config transaction 事务的bus和device number无论是何值,都会被对端ep接收到,并保存下来作为自己的bus和device number。后续用这个bus和device number就可以访问对端的ep设备,直到ep设备复位。
————————————————
版权声明:本文为CSDN博主「Lenz’s law」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u010443710/article/details/108670922

PCIE枚举

PCIe设备在一个系统中是如何发现与访问的:
x86系统中PCIe体系架构
下图是一个PCIe的拓扑结构示例,
PCIe协议支持256个Bus, 每条Bus最多支持32个Device,每个Device最多支持8个Function,
所以由BDF(Bus,device,function)构成了每个PCIe设备节点的身份证号
在这里插入图片描述
Host对PCIe设备扫描是采用了深度优先算法,其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,
而且每个节点只能访问一次。我们一般称这个过程为PCIe设备枚举。
枚举过程中host通过配置读事物包来获取下游设备的信息,通过配置写事物包对下游设备进行设置。
第一步,PCI Host主桥扫描Bus 0上的设备(在一个处理器系统中,一般将Root complex中与Host Bridge相连接的PCI总线命名为PCI Bus 0),
系统首先会忽略Bus 0上的embedded EP等不会挂接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 3,并发现这是一个switch设备。系统将Bridge 3下面的PCI Bus定为Bus 2,并将该桥的Primary Bus Number 和 Secondary Bus Number寄存器分别设置成1和2,和上一步一样暂时把Bridge 3 的Subordinate Bus Number设为0xFF。
在这里插入图片描述
第三步,系统继续扫描Bus 2,将会发现Bridge 4。继续扫描,系统会发现Bridge下面挂载的NVMe SSD设备,系统将Bridge 4下面的PCI Bus定为Bus 3,并将该桥的Primary Bus Number 和 Secondary Bus Number寄存器分别设置成2和3,因为Bus3下面挂的是端点设备(叶子节点),下面不会再有下游总线了,因此Bridge 4的Subordinate Bus Number的值可以确定为3。
在这里插入图片描述
第四步,完成Bus 3的扫描后,系统返回到Bus 2继续扫描,会发现Bridge 5。继续扫描,系统会发现下面挂载的NIC设备,系统将Bridge 5下面的PCI Bus设置为Bus 4,并将该桥的Primary Bus Number 和 Secondary Bus Number寄存器分别设置成2和4,因为NIC同样是端点设备,Bridge 5的Subordinate Bus Number的值可以确定为4。
在这里插入图片描述
第五步,除了Bridge 4和Bridge 5以外,Bus2下面没有其他设备了,因此返回到Bridge 3,Bus 4是找到的挂载在这个Bridge下的最后一个bus号,因此将Bridge 3的Subordinate Bus Number设置为4。Bridge 3的下游设备都已经扫描完毕,继续向上返回到Bridge 1,同样将Bridge 1的Subordinate Bus Number设置为4。
在这里插入图片描述
第六步,系统返回到Bus0继续扫描,会发现Bridge 2,系统将Bridge 2下面的PCI Bus定为Bus 5。并将Bridge 2的Primary Bus Number 和 Secondary Bus Number寄存器分别设置成0和5, Graphics card也是端点设备,因此Bridge 2 的Subordinate Bus Number的值可以确定为5。

至此,挂在PCIe总线上的所有设备都被扫描到,枚举过程结束,Host通过这一过程获得了一个完整的PCIe设备拓扑结构。
在这里插入图片描述
在linux操作系统中,我们可以通过lspci –v -t命令来查询系统上电阶段扫描到的PCIe设备,执行结果会以一个树的形式列出系统中所有的pcie设备。如下图所示,其中黄色方框中的PCIe设备是北京忆芯科技公司(Bejing Starblaze Technology Co., LTD.)推出的STAR1000系列NVMe SSD主控芯片,图中显示的9d32是Starblaze在PCI-SIG组织的注册码,1000是设备系列号。
在这里插入图片描述
STAR1000设备的BDF也可以从上图中找出,其中bus是0x3C,device是0x00,function是0x0,BDF表示为3C:00.0,与之对应的上游端口是00:1d.0。

我们可以通过“lspci –xxx –s 3C:00.0”命令来列出该设备的PCIe详细信息(技术发烧友或数字控请关注该部分)。这些内容存储在PCIe配置空间,它们描述的是PCIe本身的特性。如下图所示(低位地址0x00在最左边),可以看到这是一个非易失性存储控制器,0x00起始地址是PCIe的Vendor ID和Device ID。Class code 0x010802表示这是一个NVMe存储设备。0x40是第一组capability的指针,如果你需要查看PCIe的特性,就需要从这个位置开始去查询,在每组特征的头字段都会给出下一组特性的起始地址。从0x40地址开始依次是power management,MSI中断,链路控制与状态,MSI-X中断等特性组。这儿特别列出了链路特征中的一个0x43字段,表示STAR1000设备是一个x4lane的链接,支持PCIe Gen3速率(8Gbps)。
在这里插入图片描述
当然也可以使用lspci –vvv –s 3C:00.0命令来查看设备特性,初学者看到下面的列表也就一目了然了。
在这里插入图片描述
Host在枚举设备的同时也会对设备进行配置,每个PCIe设备都会指定一段CPU memory访问空间,从上面的图中我们可以看到这个设备支持两段访问空间,一段的大小是1M byte,另一段的大小是256K byte,系统会分别指定它们的基地址。基地址配置完成以后,Host就可以通过地址来对PCIe memory空间进行访问了。

PCIe memory空间关联的是PCIe设备物理功能,对于STAR1000系列芯片而言,物理功能是NVMe,memory中存放的是NMVe的控制与状态信息,对于NMVe的控制以及工作状态的获取,都需要通过memory访问来实现。

下面以NVMe命令下发为例简单描述PCIe设备的memory访问。NVMe命令下发的基本操作是1)Host写doorbell寄存器,此时使用PCIe memory写请求。如下图所示,host发出一个memory write(MWr)请求,该请求经过switch到达要访问的NVMe SSD设备。
在这里插入图片描述
这个请求会被端点设备接收并执行2)NVMe读取命令操作。如下图所示,此时NVMe SSD作为请求者,发出一个memory read(MRd)请求,该请求经过Switch到达Host,Host作为完成者会返回一个完成事物包(CplD),将访问结果返回给NVMe SSD
在这里插入图片描述
这样,一个NVMe的命令下发过程就完成了。同样,NVMe的其他操作比如各种队列操作,命令与完成,数据传输都是通过PCIe memory访问的方式进行的,此处不再详述。

通过上面的描述,相信能够帮助大家了解PCIe的设备枚举和memory空间访问。以后会继续与大家探讨PCIe的其他内容,比如PCIe的协议分层,链路建立,功耗管理等等。目前PCIe协议还正在不断的快速演进中,2017年发布的PCIe Gen4标准,每条Serdes支持的速率已经达到16Gbps,Gen5也在加速制定中,其速率会再翻一倍达到32Gbps。Starblaze会紧跟技术的发展趋势,提供速率更高,性能更好更稳定的NVMe SSD系列产品。
————————————————
版权声明:本文为CSDN博主「wmzjzwlzs」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wmzjzwlzs/article/details/124018214

  • 9
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 EDKII 中,可以使用 UEFI 的标准 PCI 配置空间来枚举 PCI 设备。具体步骤如下: 1. 获取 PCI 根桥的 `EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL` 接口指针。 ```c EFI_STATUS Status; EFI_HANDLE* HandleBuffer; UINTN HandleCount; Status = gBS->LocateHandleBuffer( ByProtocol, &gEfiPciRootBridgeIoProtocolGuid, NULL, &HandleCount, &HandleBuffer ); if (EFI_ERROR(Status)) { // 错误处理 } EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL* PciRootBridgeIo; Status = gBS->HandleProtocol( HandleBuffer[0], &gEfiPciRootBridgeIoProtocolGuid, &PciRootBridgeIo ); if (EFI_ERROR(Status)) { // 错误处理 } ``` 2. 枚举 PCI 总线上的所有设备和函数。 ```c for (UINT8 Bus = 0; Bus <= 0xFF; Bus++) { for (UINT8 Device = 0; Device <= 0x1F; Device++) { for (UINT8 Function = 0; Function <= 0x07; Function++) { UINT64 Address = EFI_PCI_ADDRESS(Bus, Device, Function, 0); UINT16 VendorId; PciRootBridgeIo->Pci.Read(PciRootBridgeIo, EfiPciWidthUint16, Address + PCI_VENDOR_ID_OFFSET, 1, &VendorId); // 判断该设备是否存在 if (VendorId != 0xFFFF) { // TODO: 处理该设备 } } } } ``` 3. 对于存在的设备,可以根据需要读取和写入 PCI 配置空间的寄存器来获取设备的信息。 ```c UINT32 Register; UINT8 Buffer[4]; // 读取 Vendor ID 和 Device ID PciRootBridgeIo->Pci.Read(PciRootBridgeIo, EfiPciWidthUint32, Address, 1, &Register); UINT16 VendorId = (UINT16)(Register & 0xFFFF); UINT16 DeviceId = (UINT16)(Register >> 16); // 读取 BAR0 寄存器的值 PciRootBridgeIo->Pci.Read(PciRootBridgeIo, EfiPciWidthUint32, Address + PCI_BASE_ADDRESSREG_OFFSET(0), 1, &Register); if (Register & PCI_BASE_ADDRESS_MEM_TYPE_MASK == PCI_BASE_ADDRESS_MEM_TYPE_32) { PciRootBridgeIo->Mem.Read(PciRootBridgeIo, EfiPciWidthUint32, (UINT64)(Register & PCI_BASE_ADDRESS_MEM_MASK), 1, &Buffer); } else { PciRootBridgeIo->Io.Read(PciRootBridgeIo, EfiPciWidthUint32, (UINT64)(Register & PCI_BASE_ADDRESS_IO_MASK), 1, &Buffer); } ``` 需要注意的是,如果设备存在多个 BAR 寄存器,则需要根据相应的编程模型来读取和解释这些寄存器的值。此外,还需要考虑设备的中断、DMA 等资源的分配和管理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值