UEFI—— 读取解析Bar寄存器的实现(函数GatherDeviceInfo 解析)

本文详细描述了GatherDeviceInfo函数如何通过读取PCI设备的Bar寄存器获取设备所需的地址空间大小,包括PciParseBar和BarExisted函数的作用,以及对Bar类型、长度和地址的处理过程。

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

GatherDeviceInfo有很多地方我还没有完全搞清楚,对于一些不影响当前理解的部分,就直接跳过了,日后有更进一步的了解在进行补充。
之所以将这个函数单独写出来,就是因为这个函数中包含了一直说的关于通过读取Bar获取设备所需地址空间的大小

函数流程

这个函数的形参比较简单

PCI_IO_DEVICE *
GatherDeviceInfo (
  IN PCI_IO_DEVICE  *Bridge,    //当前设备的所在的RootBridge
  IN PCI_TYPE00     *Pci,       //当前设备的Configuration Space Header 的内容
  IN UINT8          Bus,        //当前设备的Bus Number
  IN UINT8          Device,     //当前设备的Device Number
  IN UINT8          Func        //当前设备的Function Number
  )

接下来,根据现在已有的信息,为当前的设备创建 PCI_IO_DEVICE *PciIoDevice 结构体

  PciIoDevice = CreatePciIoDevice (
                  Bridge,
                  Pci,
                  Bus,
                  Device,
                  Func
                  );

这个函数不详细进行介绍了,主要就是对PCI_IO_DEVICE *PciIoDevice 结构体中的参数进行初始化,详细可以去看代码,在此简单说一下流程

  1. 初始化PCI_IO_DEVICE *PciIoDevice 的相关参数
  2. 判断当前设备PCI Express 设备
  3. 对当前设备进行安全认证
  4. 对于非RootBridge 的ARI设备进行设置
  5. 若当前平台支持 SR-IOV则设备进行相应设置

读取Bar寄存器 PciParseBar

根据现有的信息对当前的设备进行简单的初始化后, 就来到了最核心的一部分:对Bar寄存器进行读取

  //
  // Start to parse the bars
  //
  for (Offset = 0x10, BarIndex = 0; Offset <= 0x24 && BarIndex < PCI_MAX_BAR; BarIndex++) {
    Offset = PciParseBar (PciIoDevice, Offset, BarIndex);
  }

这个循环的起始是0x10,介绍Bar寄存器的时候就说过,Bar寄存器的起始位置就是0x10,并且在type 00 中有6组Bar寄存器,最后一组Bar寄存器的起始位置就是0x24,可见当前循环就是以第一个Bar寄存器为起始遍历所有的6个Bar寄存器
现在的关键就是分析 PciParseBar函数

在这里插入图片描述
PciParseBar的输入很简单,就是当前设备的PCI_IO_DEVICE *PciIoDevice,表示Bar寄存器的起始地址以及当前是第几个Bar寄存器

BarExisted

  Status = BarExisted (
             PciIoDevice,
             Offset,
             &Value,
             &OriginalValue
             );

详细对这个函数进行一下分析

EFI_STATUS
BarExisted (
  IN  PCI_IO_DEVICE  *PciIoDevice,
  IN  UINTN          Offset,
  OUT UINT32         *BarLengthValue,
  OUT UINT32         *OriginalBarValue
  )
{
  EFI_PCI_IO_PROTOCOL  *PciIo;
  UINT32               OriginalValue;
  UINT32               Value;
  EFI_TPL              OldTpl;

  PciIo = &PciIoDevice->PciIo;
  /// ...
}

这一部分对函数中需要使用的变量进行了初始化。

  //
  // Preserve the original value
  //
  PciIo->Pci.Read (PciIo, EfiPciIoWidthUint32, (UINT8)Offset, 1, &OriginalValue);

读取当前Bar寄存器中的内容,将读取的内容保存在 OriginalValue

  //
  // Raise TPL to high level to disable timer interrupt while the BAR is probed
  //
  OldTpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);

  PciIo->Pci.Write (PciIo, EfiPciIoWidthUint32, (UINT8)Offset, 1, &gAllOne);
  PciIo->Pci.Read (PciIo, EfiPciIoWidthUint32, (UINT8)Offset, 1, &Value);

向当前Bar寄存器中写入全1,然后进行读取,(这就是前面介绍过的向Bar寄存器写入1的具体实现)
读取结果保存在 Value

 //
  // Write back the original value
  //
  PciIo->Pci.Write (PciIo, EfiPciIoWidthUint32, (UINT8)Offset, 1, &OriginalValue);

  //
  // Restore TPL to its original level
  //
  gBS->RestoreTPL (OldTpl);

将原始值再写入Bar寄存器中

  //
  // Restore TPL to its original level
  //
  gBS->RestoreTPL (OldTpl);

  if (BarLengthValue != NULL) {
    *BarLengthValue = Value;
  }

  if (OriginalBarValue != NULL) {
    *OriginalBarValue = OriginalValue;
  }

  if (Value == 0) {
    return EFI_NOT_FOUND;
  } else {
    return EFI_SUCCESS;
  }
}

最后将读到的两个值分别返回
总结 BarExisted

  1. 读取当前Bar的原始值 OriginalValue
  2. 读取当前Bar写入全1之后的值 Value

解析Bar寄存器

通过BarExisted获取到当前Bar寄存器的原始值和全部写1之后的值,我们就可以根据 Bar寄存器中每一位的含义对当前设备的Bar寄存区进行对应的解析。
首先将部分信息保存至PCI_IO_DEVICE *PciIoDevice

  PciIoDevice->PciBar[BarIndex].BarTypeFixed = FALSE;
  PciIoDevice->PciBar[BarIndex].Offset       = (UINT8)Offset;

接下来的流程就是在代码中实现通过解析Bar寄存器获得当前设备所需的地址空间大小
前面说过 Value 中当前保存的就是当前Bar寄存器协写入全1 之后读取的结果

if ((Value & 0x01) != 0)

此时首先判断当前Bar寄存器的最后一位的情况
之前说明Bar寄存器的时候就说过 Bar寄存器中的最后一位就是用来区分映射的是IO空间还是memory空间(详细参考 UEFI——PCIe子系统(I) PCIe基础知识 )
在这里插入图片描述

所以这个位置就有两种情况:等于1表示是IO空间,等于0表示的是memory空间
按照代码的顺序首先来看bit 0 为1 表示IO空间的情况

    Mask = 0xfffffffc;

    if ((Value & 0xFFFF0000) != 0) {
      //
      // It is a IO32 bar
      //
      PciIoDevice->PciBar[BarIndex].BarType   = PciBarTypeIo32;
      PciIoDevice->PciBar[BarIndex].Length    = ((~(Value & Mask)) + 1);
      PciIoDevice->PciBar[BarIndex].Alignment = PciIoDevice->PciBar[BarIndex].Length - 1;
    } else {
      //
      // It is a IO16 bar
      //
      PciIoDevice->PciBar[BarIndex].BarType   = PciBarTypeIo16;
      PciIoDevice->PciBar[BarIndex].Length    = 0x0000FFFF & ((~(Value & Mask)) + 1);
      PciIoDevice->PciBar[BarIndex].Alignment = PciIoDevice->PciBar[BarIndex].Length - 1;
    }

这个else表示的情况是想要申请的空间是16bit的IO空间,但是这种情况现在应该已经不是标准的情况了,在PCIe Spe6.0中强调映射到IO空间的总是32bit的

在这里插入图片描述

所以我们就不需要关注else后面的内容,看if 后的内容就好
这个部分就是在当前设备的PciIoDevice结构体的对应的PciBar结构体中,将解析出来的结果进行存储

PciIoDevice->PciBar[BarIndex].BarType 表示当前Bar解析出来的类型
PciIoDevice->PciBar[BarIndex].Length 表示当前设备申请的空间的大小,这个大小是通过求补得到的
PciIoDevice->PciBar[BarIndex].Alignment 表示当前设备空间的对齐方式

Length表示的是当前需要的空间大小是多少,比如 2MB 但是这个大小在内存中的表示就是0到(2^11 -1)
这里的2^11 -1 就是当前保存的PciIoDevice->PciBar[BarIndex].Alignment

    if (PciIoDevice->PciBar[BarIndex].Length == 0) {
      PciIoDevice->PciBar[BarIndex].BarType = (PCI_BAR_TYPE)0;
    }

这部分是针对某些平台的特殊处理,显示为IO32但是实际的length为0 需要将其归类为 type =0

PciIoDevice->PciBar[BarIndex].BaseAddress = OriginalValue & Mask;

这个位置保存的数据具体作用暂时不明

bit 0 为0表示memory空间的情况

   Mask = 0xfffffff0;
   PciIoDevice->PciBar[BarIndex].BaseAddress = OriginalValue & Mask;

设定Mask,保留BaseAddress

switch (Value & 0x07)

前面在判断是否映射到IO空间的时候,Value是和0xO1与的,现在是和0x07。0x07 = 01111(B) 所以当前这个情况就是提取了最后三位的情况
前面曾经说过,当映射到memory 空间的时候可能存在32bit 和 64 bit两种情况

最后三位表示
00032bit
10064bit

如此 便可以得到两种case情况 :case 0x00 & case 0x04
case 0x00:

        if ((Value & 0x08) != 0) {
          PciIoDevice->PciBar[BarIndex].BarType = PciBarTypePMem32;
        } else {
          PciIoDevice->PciBar[BarIndex].BarType = PciBarTypeMem32;
        }

通过bit 3 来判断当前是否为可预期的,Bit3 设置为1 表示当前数据是可以预取的,设置为0表示不可以预取,将结果保存在PciIoDevice->PciBar[BarIndex].BarType中

		PciIoDevice->PciBar[BarIndex].Length = (~(Value & Mask)) + 1;
        if (PciIoDevice->PciBar[BarIndex].Length < (SIZE_4KB)) {
          //
          // Force minimum 4KByte alignment for Virtualization technology for Directed I/O
          //
          PciIoDevice->PciBar[BarIndex].Alignment = (SIZE_4KB - 1);
        } else {
          PciIoDevice->PciBar[BarIndex].Alignment = PciIoDevice->PciBar[BarIndex].Length - 1;
        }

获取当前的Length 保存在ciIoDevice->PciBar[BarIndex].Length 中,同时判断当前的对齐方式,如果当前的数据小于4K 那么采用4K 对齐,大于4K ,采取正常的对齐

case 0x04:

        if ((Value & 0x08) != 0) {
          PciIoDevice->PciBar[BarIndex].BarType = PciBarTypePMem64;
        } else {
          PciIoDevice->PciBar[BarIndex].BarType = PciBarTypeMem64;
        }

同样先判断是否为可预取的类型

        PciIoDevice->PciBar[BarIndex].Length    = Value & Mask;
        PciIoDevice->PciBar[BarIndex].Alignment = PciIoDevice->PciBar[BarIndex].Length - 1;

此时表示的是64bit的memory 空间申请,所以当前暂时将当前这个Bar寄存器的结果保存下来,这个Bar只能说明低32bit的情况。注意此时Length保存的并不是取反的结果,而是保存了当前这个寄存器哪些位置为1
针对64bit的情况,需要将下一个Bar作为扩展一起进行读取

        //
        // Increment the offset to point to next DWORD
        //
        Offset += 4;

因为接下来要继续处理下一个Bar寄存器的内容,因此在这里将Bar寄存器的Offset增加,表示至下一个Bar的起始位置

        Status = BarExisted (
                   PciIoDevice,
                   Offset,
                   &Value,
                   &OriginalValue
                   );

        if (EFI_ERROR (Status)) {
          //
          // the high 32 bit does not claim any BAR, we need to re-check the low 32 bit BAR again
          //
          if (PciIoDevice->PciBar[BarIndex].Length == 0) {
            //
            // some device implement MMIO bar with 0 length, need to treat it as no-bar
            //
            PciIoDevice->PciBar[BarIndex].BarType = PciBarTypeUnknown;
            return Offset + 4;
          }
        }

读取下一个Bar中的Value & OriginalValue,这个Bar表示的是高32bit的情况

        //
        // Fix the length to support some special 64 bit BAR
        //
        if (Value == 0) {
          DEBUG ((DEBUG_INFO, "[PciBus]BAR probing for upper 32bit of MEM64 BAR returns 0, change to 0xFFFFFFFF.\n"));
          Value = (UINT32)-1;
        } else {
          Value |= ((UINT32)(-1) << HighBitSet32 (Value));
        }

这个地方对当前读出来的Value进行了处理,一共就两种情况:

  1. 如果当前这个寄存器读取的所有值为0 表示,将当前的Value强制转化成全F
  2. 如果当前的Value不等于0,最高位为1之前的所有位都置为 1

举例说明 Value |= ((UINT32)(-1) << HighBitSet32 (Value));
假设当前的 Value = 0111 1000
此时 HighBitSet32 (Value) 等于 7 ,需要将(UINT32)(-1) 左移7位,得到 1000 0000
1000 0000 | Value = 1111 1000


 PciIoDevice->PciBar[BarIndex].BaseAddress |= LShiftU64 ((UINT64)OriginalValue, 32);

两个Bar寄存器的值进行组合,得到最终的BaseAddress的值

        PciIoDevice->PciBar[BarIndex].Length = PciIoDevice->PciBar[BarIndex].Length | LShiftU64 ((UINT64)Value, 32);
        PciIoDevice->PciBar[BarIndex].Length = (~(PciIoDevice->PciBar[BarIndex].Length)) + 1;

将两个Bar寄存器的值进行拼接,然后取反获得当前设备实际申请的空间长度Length
接下来还有一个default,是保留情况,避免出现其他意外 不做详细解释。最后返回的时候需要将Offset继续移到下一个带解析的Bar寄存器处
至此 针对Pcie设备的Bar寄存器的解析的代码实现全部完成

Parse the SR-IOV VF bars

  //
  // Parse the SR-IOV VF bars
  //
  if (PcdGetBool (PcdSrIovSupport) && (PciIoDevice->SrIovCapabilityOffset != 0)) {
    for (Offset = PciIoDevice->SrIovCapabilityOffset + EFI_PCIE_CAPABILITY_ID_SRIOV_BAR0, BarIndex = 0;
         Offset <= PciIoDevice->SrIovCapabilityOffset + EFI_PCIE_CAPABILITY_ID_SRIOV_BAR5;
         BarIndex++)
    {
      ASSERT (BarIndex < PCI_MAX_BAR);
      Offset = PciIovParseVfBar (PciIoDevice, Offset, BarIndex);
    }
  }

函数接下来对支持 SR-IOV的情况进行了处理,其核心函数 PciIovParseVfBar和前面详细解析的PciParseBar类似

return PciIoDevice

函数最终返回的就是创建好的PCI_IO_DEVICE *PciIoDevice 结构体。

在这个结构体中,除了初始化了基本的BDF等参数之外,!!!最重要的就是通过解析设备的Bar寄存器,收集了当前设备所需要的IO/Memory 空间的大小 PciBar[BarIndex].Length

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值