UEFI——PCIe子系统(IV) PCIe设备扫描之资源分配

前言

前述UEFI——PCIe子系统(II) HostBridge&RootBridge初始化中曾经已经说过,关于PCIe子系统的构建,主要可以分成两个部分:

  1. HostBridge初始化构建相应的结构体对其进行描述
  2. PCIe设备的扫描与资源分配

关于 HostBridge&RootBridge初始化的过程 ,前面的文章已经详细介绍过。而介绍PCIe设备的扫描与资源分配函数PciEnumerator 时又分析过,该函数又分成了两个主要的部分

  1. 对设备进行扫描进行信息收集并且分配bus number 的函数PciHostBridgeEnumerator
  2. 对设备进行资源分配的函数 PciHostBridgeResourceAllocator
    Bus number的分配和信息的收集 UEFI——PCIe子系统(III) PCIe设备扫描之BusNumber分配 这篇文章已经详细的介绍过了,那么整个PCIe子系统构建中就剩下最后一个部分:资源的分配。PciEnumerator 中实现资源分配的函数如下
    在这里插入图片描述

这个函数实现的就是对当前PCIe子系统中所需要的资源进行分配,这一过程仍旧可以分成两个部分

  1. 计算所需资源并且向Hostbridge申请资源
  2. 申请资源得到基地址后对设备bar进行写入

在看下面的代码之前,需要回顾在之前的PciHostBridgeEnumerator 函数中,已经通过读取BAR将当前所有设备所需要的空间大小保存在设备对应的结构体PCI_IO_DEVICE当中,结构体中 PCI_BAR PciBar[PCI_MAX_BAR]; 就保存了设备所需的空间、类型、大小等情况(详细可以参考读取Bar空间的具体操作 UEFI—— 读取解析Bar寄存器的实现(函数GatherDeviceInfo 解析) )
但是,之前的枚举过程中虽然得到了所需要的资源大小,但并没有为对应的设备分配相应的资源,接下来就根据之前获得的信息进行相应的资源的分配

计算所需资源并且申请

在这里插入图片描述

函数中计算所需资源和申请资源是在一个while循环中进行,直到完全申请完成,循环才会退出。在开始计算所需要的空间之前,首先初始化了名为IoPool Mem32Pool PMem32Pool Mem64Pool PMem64Pool类型 为PCI_RESOURCE_NODE的五个变量,这五个变量作为资源双向链表链表的头,表示了当前这个HostBridge下五种不同类型的资源的总的需求情况。
所需资源的计算过程也是以root bridge为单位来进行的,遍历所有的root bridge 之后就能够得到当前Host Bridge下的总的资源需求。故使用while循环来遍历所有的root bridge,如下所示
在这里插入图片描述

当获取到当前Host Bridge下的某一个 Root Bridge之后,首先又创建了五个类型为PCI_RESOURCE_NOD的变量,这五个变量作为资源链表的头,用来保存当前root bridge下五种不同类型资源的所需情况。
在这里插入图片描述

注意,此时初始化的链表头中已经填充了一些基本的参数。比如,链表头节点指向的是RootBridgeDev结构体,表示当前节点是对RootBridgeDev这个设备的资源情况的描述;还有,根据不同资源类型对节点中node type 、 Bar等都进行了相应的初始化。但是此时头节点的Length这一参数还没有进行初始化,因为此时资源树尚未完全构建完成,还不能得知最终所需的各类型的资源是多少。
接下是对存在Option Rom的情况进行的一步特殊处理:
在这里插入图片描述

如果当前RootBridge下有设备存在OptionRom,找到所需资源最大的OptionRom,记录下所需要的资源MaxOptionRomSize,然后根据OptionRom所需要的资源类型、大小等向对应的资源链表中添加相应的所需资源节点(GetResourceFromDevice 即向root bridge资源树中增加相应的节点,详细后面介绍
以上对OptionRom的处理是一种特殊情况,处理完这个部分后,就使用遍历&递归的方式对当前root bridge下的所有设备的资源情况进行处理,实现的函数为
在这里插入图片描述

CreateResourceMap

CreateResourceMap函数是一个关键函数,资源链表的构建就是这个函数来完成的。这个函数与之前用来分配bus number的函数具有类似性,都是利用递归函数来实现对于设备的遍历完成资源的收集工作。
首先看传入参数
在这里插入图片描述

函数的入参共五个

Bridge : 表示当前待处理的Bridge设备,传入的可能是普通Bridge 也可能是root bridge 。
IoNode&Mem32Node&PMem32Node&Mem64Node&PMem64Node:这个五个参数表示当前设备所需的五种类型资源的链表的头,遍历完成之后,这五个资源链表也应该构建完成了

接下来开始对当前root bridge下的子设备进行遍历并且开始统计需要的资源

在这里插入图片描述

可见,核心函数为 GetResourceFromDevice,(注意:前面OptionRom的处理位置也用到了这个函数) ^75vivn

GetResourceFromDevice

函数前面的注释其实已经说的很清楚:

通过扫描Bar数组创建这个设备相应的resource node,如果上游的桥不支持当前的设备,则不会为这个设备创建任何的resource node

这个函数其实比较简单,核心思想就是遍历当前设备的Bar数组,针对bar数组中存储的对不同类型的资源需求创建相应的Node并插入到相应的链表中。
在这里插入图片描述

这里面需要注意的就是 InsertResourceNode 函数

InsertResourceNode

在这里插入图片描述

这个函数原理简单,就是向链表中插入相应的节点,但是需要注意的是,资源节点插入之后,还有一个排序的操作,设备资源链表中的资源节点是按照所需资源的大小降序排列的

对当前设备的Bar数组进行处理之后,需要根据当前子设备是否为Bridge分情况进行处理:
若当前子设备为PCI bridge :
则需要为当前的桥设备创建对应的资源链表头,和前面为root bridge创建俩表头类似,也需要对部分参数进行相应的初始化
在这里插入图片描述

创建完成之后,对这个桥进行递归,再次调用 CreateResourceMap ,以这个桥为起始,向下深度遍历其下设备的资源情况并创建相应的resource node。
在这里插入图片描述

当对Bridge下的设备全部遍历完成之后,如果其下的设备存在对相应类型资源的申请,则将此桥下对应类型的资源链表插入上一级设备的资源链表中,对每个类型的链表都执行一次 判断 - > 插入 的操作,代码如下图
在这里插入图片描述

若当前子设备不是PCIe Bridge
则不需要执行上述的递归操作,直接向下执行,继续收集其他子设备的资源信息,即继续执行while循环[当下层的桥设备已经全部扫描完毕,资源链表插入完成之后,也继续向下执行这段代码] (这段是一个典型的递归操作,搞不清楚的找个图对着走一遍就清楚是怎么操作的了)

当前root 设备下的所有子设备全部扫描完成之后,若当前需要预留部分资源,则执行相应的资源预留操作。(资源预留在很多平台都会有,但是目前我还没有详细深入的研究过)
在这里插入图片描述

接下来是对当前root deivce下的资源节进行降级操作,这个操作是为了防止有一些设备需要PMEM64等类型的资源,但是平台不支持,那么就需要进行操作将设备所需的资源降级成当前平台支持的资源类型(不详细介绍了)

函数最后,需要计算当前bridge所需要的总资源,并将需要的总的资源情况填入资源链表的头中
在这里插入图片描述
计算所需资源的时候,需要关注的只有一点:计算的时候是以记录的资源对齐要求来进行资源预留的。
(关于资源的预留和资源对齐,有兴趣的可以详细看 CalculateResourceAperture 函数 当前只是将PCIe设备的资源分配思路就不详细展开了)
至此就得到了一个bridge下所需的各种类型资源的情况,当传入root bridge,函数执行完成就能够得到当前这个root bridge所需要的各种资源的情况了。这样,root bridge下所有的设备需要的资源情况都以与PCIe设备相似的层级保存在了map中,形成了类似 resource tree的结构,只不过这个tree 中每一层资源的请求节点是按照所需资源降序排列

构建ACPI resource node

接下来就是根据创建好的resource tree 构建ACPI resource node
在这里插入图片描述

ConstructAcpiResourceRequestor函数的核心就是根据传入的资源树的情况创建与之相对应的EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR,每种类型都需要创建相应的一个DESCRIPTOR,DESCRIPTOR link 的最后还需要添加上EFI_ACPI_END_TAG_DESCRIPTOR 然后将其返回值作为AcpiConfig EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR 没有详细的研究是什么构成的 有待后续研究)。后续的资源申请就是以AcpiConfig的形式提交的

插入resource nodes

PciHostBridgeResourceAllocator 函数的最开始,曾创建了名为IoPool Mem32Pool PMem32Pool Mem64Pool PMem64Pool的资源双向链表链表的头,表示了当前这个HostBridge下五种不同类型的资源的总的需求情况。 至此 一个root bridge的资源枚举完成,就需要将这个root bridge的资源合并到host bridge 的资源map中,也就是插入IoPool Mem32Pool PMem32Pool Mem64Pool PMem64Pool 对应的链表中。
在这里插入图片描述

如果所有的root bridge全部扫描完成,最终获得的就是包含了全部resource node的四个不同类型的resource tree

提交资源请求

所谓提交资源请求,就是将刚刚计算得到的 AcpiConfig,保存在对应的root bridge的PCI_ROOT_BRIDGE_INSTANCE 结构体中
(PCI_ROOT_BRIDGE_INSTANCE 这种类型的结构体是能够和host bridge进行交互的,详细的对应关系 UEFI——PCIe子系统(II) HostBridge&RootBridge初始化 有详细的说明
在这里插入图片描述

SubmitResources函数的原型在PciHostBridge.c文件中可以看到,函数看起来很长但是实际上核心部分就这一段
在这里插入图片描述

这一段函数就是将传入的AcpiConfig中的节点一次遍历,然后根据传入的节点的情况设置 RootBridge->ResAllocNode的各个属性,其中

RootBridge->ResAllocNode[Type].Length 表示当前root bridge下需要的资源长度是多少
RootBridge->ResAllocNode[Type].Alignment 表示当前root bridge下的对齐要求
RootBridge->ResAllocNode[Type].Status 表示当前root bridge已经完成了所需资源的提交

经过这个函数当前root bridge需要的资源情况就被记录在RootBridge->ResAllocNode中了
(大的while循环中下面一部分是针对之前申请资源如果出现异常的处理,大致就是free掉之前申请的所有东西恢复到最初的状态 就不详细分析了)

分配资源并写入基地址

上述部分已经完成了所需资源情况的收集,并且将所需要的资源提交到了RootBridge->ResAllocNode中,接下来就需要做的主要分为两个部分

  1. 根据提交的情况为每一个root bridge进行资源的分配
  2. 逐级向下将对应的地址填入到设备的Bar寄存器中

资源分配

在这里插入图片描述

这个函数就是实现了资源分配的函数,详细的操作需要在NotifyPhase函数中来看
在这里插入图片描述

如果详细看代码就会发现NotifyPhase函数在整个PCIe系统的构建中是一个经常出现的函数,这个函数的第二个入参每次各有不同,对应函数中就会根据这个入参执行不同的操作。此处,这个函数就开始执行资源分配的操作,当传入的参数是 EfiPciHostBridgeAllocateResources的时候,函数具体内容如下
在这里插入图片描述

下面对这个函数进行简单的分析,只关注主要的相关代码

// PciHostBridge.c

    case EfiPciHostBridgeAllocateResources:
      ReturnStatus = EFI_SUCCESS;

      //
      // Make sure the resource for all root bridges has been submitted.
      //
      for (Link = GetFirstNode (&HostBridge->RootBridges)
           ; !IsNull (&HostBridge->RootBridges, Link)
           ; Link = GetNextNode (&HostBridge->RootBridges, Link)
           )
      {
        RootBridge = ROOT_BRIDGE_FROM_LINK (Link);
        if (!RootBridge->ResourceSubmitted) {
          return EFI_NOT_READY;
        }
      }

      DEBUG ((DEBUG_INFO, "PciHostBridge: NotifyPhase (AllocateResources)\n"));

首先遍历了当前Hostbridge 下的 所有root bridge,以确保当前所有的root bridge都已经将所需要的资源情况提交完成(同一个Host bridge 下的所有root bridge共享同一个资源池,所以需要所有的root bridge提交完成由Host bridge 对资源进行统一分配避免发生冲突的情况)

            //
            // Allocate the resource node with max alignment at first
            //
            MaxAlignment = 0;
            Index        = TypeMax;
            for (Index2 = TypeIo; Index2 < TypeBus; Index2++) {
              if (ResNodeHandled[Index2]) {
                continue;
              }

              if (MaxAlignment <= RootBridge->ResAllocNode[Index2].Alignment) {
                MaxAlignment = RootBridge->ResAllocNode[Index2].Alignment;
                Index        = Index2;
              }
            }

接下来对当前rootbridge的对齐要求进行遍历,选择最大的Alignment,后面分配以MaxAlignment对齐

            ASSERT (Index < TypeMax);
            ResNodeHandled[Index] = TRUE;
            Alignment             = RootBridge->ResAllocNode[Index].Alignment;
            BitsOfAlignment       = LowBitSet64 (Alignment + 1);
            BaseAddress           = MAX_UINT64;

将需要的参数先进行临时初始化

            //
            // RESTRICTION: To simplify the situation, we require the alignment of
            // Translation must be larger than any BAR alignment in the same root
            // bridge, so that resource allocation alignment can be applied to
            // both device address and host address.
            //
            Translation = GetTranslationByResourceType (RootBridge, Index);

此处是根据所需要的资源类型从root bridge中获取地址转换时需要的偏移量
接下来就是根据资源需求的类型从相应的GCD空间中分配相应的地址

case TypeIo:
//
// Base and Limit in PCI_ROOT_BRIDGE_APERTURE are device address.
// For AllocateResource is manipulating GCD resource, we need to use
// host address here.
//
    BaseAddress = AllocateResource (
                  FALSE,
                  RootBridge->ResAllocNode[Index].Length,
                  MIN (15, BitsOfAlignment),
                  TO_HOST_ADDRESS (
                  ALIGN_VALUE (RootBridge->Io.Base, Alignment + 1),
                  RootBridge->Io.Translation
                  ),
                  TO_HOST_ADDRESS (RootBridge->Io.Limit,
                                 RootBridge->Io.Translation)
                                );
       break;

因为AllocateResource函数涉及从GCD空间请求内存,这部分后续在深入了解。现在大致看这个函数的传入参数

UINT64
AllocateResource (
  BOOLEAN  Mmio,
  UINT64   Length,
  UINTN    BitsOfAlignment,
  UINT64   BaseAddress,
  UINT64   Limit
  )

传入参数中 BaseAddress / Limit 都要求是CPU域的地址,而 RootBridge->Io.Base RootBridge->Io.Limit中保存的均为PCIe域的地址 ,因此在向DCG域请求资源的时候需要将其转化成CPU域的地址进行分配(在前面HostBridge&RootBridge初始化的时候曾经说过,平台在初始化root bridge的时候就已经将对应的RootBridge->Io.Base RootBridge->Io.Limit 分配完成,只不过当时举例的平台在初始化的时候就固定了每一个root bridge的资源大小,直接将转换过后的结果保存在root bridge中了。
通过 AllocateResource 就可以获取到CPU域的为相应root bridge分配的 BaseAddress

if (BaseAddress != MAX_UINT64) {
     RootBridge->ResAllocNode[Index].Base   = BaseAddress;
     RootBridge->ResAllocNode[Index].Status = ResAllocated;

获取到对应的BaseAddress 后,将其写入 RootBridge->ResAllocNode[Index].Base
至此为每一个root bridge进行的基地址分配已经完成,分配完成的基地址也已经被写入PCI_ROOT_BRIDGE_INSTANCE RootBridge 结构体中

逐级填写Bar

前面已经完成了HostBridge对RootBridge的资源分配,接下来要做的就是根据在此基础上为每一个device分配相应的基地址,这部分内容相对简单。
整个过程还是在一个while循环中进行的,因为依旧需要对每一个root bridge执行相同的操作。循环中,首先通过GetProposedResources函数获取分配的资源情况
在这里插入图片描述

函数原型在 PciHostBridge.c 文件中
函数中需要注意的只有一个位置
在这里插入图片描述

获取到的地址信息是经过了转换的,此时传递过来的信息就是PCIe域中的信息了
在这里插入图片描述

接下来从得到的AcpiConfig中获取到相应的 IoBase & Mem32Base & PMem32Base & Mem64Base & PMem64Base

在这里插入图片描述

接下来在之前创建好的resource pool中找到当前root bridge对应的 resource 链表,从root bridge 开始按照层级分配地址并编辑Bar寄存器(ProgramBar)。
ProgramResource如下所示
在这里插入图片描述
可以看到这个函数也是一个递归函数,当当前设备为bridge的时候,进行深度递归,同时将下一个设备可以使用的地址(Base + Node->Offset)作为参数进行传递。

以上整个资源分配、bar寄存器的编辑过程就全部完成,与前面的介绍一起构成了整个PCIe子系统的构建过程。

总结

整个PCIe子系统的构建过程中,整体可以分成三个大部分

  1. HostBridge&RootBridge初始化
  2. Bus Number的枚举
  3. 资源的收集与分配
    在这个过程中,反复应用到的一个方法就是深度递归,BusNumber的枚举 收集所需要的资源 向Bar中写入base address都利用了这个递归方法。

还有一点需要注意的是,在提交资源、分配资源的过程中,发生了PCIe域地址和CPU域地址的转化,以为PCIe子系统是从GCD空间(后续介绍 属于UEFI中的内存管理方式)中申请的资源,GCD是CPU管理全局资源的一种方式,故在进行分配的时候需要将PCIe域的地址转换成CPU域的在向PCIe设备中写入的时候同样需要将地址转换回来

至此,PCIe子系统的大致构建过程全部介绍完成,上述仅为大致的流程分析,其中还有许多细节部分没有涉及,例如资源的预留、hotplug的设计等都没有详细的介绍,后续如果详细的接触或者遇到相应的问题再继续深入了解。

  • 26
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
东南亚位于我国倡导推进的“一带一路”海陆交汇地带,作为当今全球发展最为迅速的地区之一,近年来区域内生产总值实现了显著且稳定的增长。根据东盟主要经济体公布的最新数据,印度尼西亚2023年国内生产总值(GDP)增长5.05%;越南2023年经济增长5.05%;马来西亚2023年经济增速为3.7%;泰国2023年经济增长1.9%;新加坡2023年经济增长1.1%;柬埔寨2023年经济增速预计为5.6%。 东盟国家在“一带一路”沿线国家中的总体GDP经济规模、贸易总额与国外直接投资均为最大,因此有着举足轻重的地位和作用。当前,东盟与中国已互相成为双方最大的交易伙伴。中国-东盟贸易总额已从2013年的443亿元增长至 2023年合计超逾6.4万亿元,占中国外贸总值的15.4%。在过去20余年中,东盟国家不断在全球多变的格局里面临挑战并寻求机遇。2023东盟国家主要经济体受到国内消费、国外投资、货币政策、旅游业复苏、和大宗商品出口价企稳等方面的提振,经济显现出稳步增长态势和强韧性的潜能。 本调研报告旨在深度挖掘东南亚市场的增长潜力与发展机会,分析东南亚市场竞争态势、销售模式、客户偏好、整体市场营商环境,为国内企业出海开展业务提供客观参考意见。 本文核心内容: 市场空间:全球行业市场空间、东南亚市场发展空间。 竞争态势:全球份额,东南亚市场企业份额。 销售模式:东南亚市场销售模式、本地代理商 客户情况:东南亚本地客户及偏好分析 营商环境:东南亚营商环境分析 本文纳入的企业包括国外及印尼本土企业,以及相关上下游企业等,部分名单 QYResearch是全球知名的大型咨询公司,行业涵盖各高科技行业产业链细分市场,横跨如半导体产业链(半导体设备及零部件、半导体材料、集成电路、制造、封测、分立器件、传感器、光电器件)、光伏产业链(设备、硅料/硅片、电池片、组件、辅料支架、逆变器、电站终端)、新能源汽车产业链(动力电池及材料、电驱电控、汽车半导体/电子、整车、充电桩)、通信产业链(通信系统设备、终端设备、电子元器件、射频前端、光模块、4G/5G/6G、宽带、IoT、数字经济、AI)、先进材料产业链(金属材料、高分子材料、陶瓷材料、纳米材料等)、机械制造产业链(数控机床、工程机械、电气机械、3C自动化、工业机器人、激光、工控、无人机)、食品药品、医疗器械、农业等。邮箱:market@qyresearch.com

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值