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
结构体中的参数进行初始化,详细可以去看代码,在此简单说一下流程
- 初始化
PCI_IO_DEVICE *PciIoDevice
的相关参数- 判断当前设备PCI Express 设备
- 对当前设备进行安全认证
- 对于非RootBridge 的ARI设备进行设置
- 若当前平台支持 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
:
- 读取当前Bar的原始值
OriginalValue
- 读取当前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两种情况
最后三位 | 表示 |
---|---|
000 | 32bit |
100 | 64bit |
如此 便可以得到两种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进行了处理,一共就两种情况:
- 如果当前这个寄存器读取的所有值为0 表示,将当前的Value强制转化成全F
- 如果当前的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
。