您的位置:首页 > 移动开发 > IOS开发

Bios工程师手边事—PCI资源分配

2016-03-28 22:15 507 查看
一说到资源,大家马上想到“利用”两个字。是的,没有利用价值的资源不是真正的资源。大到整个社会,小到个人,都在利用资源实现自己的想法。PCI设备也不例外,想让PCI设备工作,PCI设备驱动一定要有资源可以利用,但是这个资源从何而来?下面就来介绍一下EFI下最为重要的一个驱动:PCIBUS驱动。

在此注明一下,IRQ也是PCI资源重要的一种。但是其并不是PCIBUS驱动所设,之前我也有写过一篇INTERRUPT的文章讲述这一点,所以此篇文章将此忽略。

PCIBUS驱动要为PCI设备分配的资源有哪些呢?换言之,一个PCI设备插到系统中,它需要什么东西才可以正常工作?IO,Memory,IRQ,OpROM。IRQ是CPU查找设备请求访问的触发机制,IO和MEMORY是CPU访问设备所用到的映射机制,OpROM是PCI设备厂商方便用户使用其设备所提供的一个便利机制。想要搞懂如何分配这些资源,首先PCIBUS要找到相应的设备,我们来看一下PCIBUS找设备的过程。

1 PCI扫描

在PLTRST#过后,所有PCI设备均处于初始状态。包括PCI BUS,也只有Host Bridge掌控的BUS0处于正常状态,其余的PCI桥都处于“杂乱无章”的状态。如果想让PCI BUS能够正常工作,能够让CPU发出的PCI访问信息正确路由至PCI设备处,我们必须扫描PCI BUS,为各个PCI 桥分配上正确的Primary Bus,Secondary Bus和SubOrdinate Bus。

EFI BIOS采用深度优先的算法来扫描PCI Bus,具体可查看UDK2014 PCIBUS驱动的PciScanBus函数,这是一个递规的函数。从BUS0开始,见到有扫描的PCI设备为PCI桥的时候,便为该PCI桥分配Secondary Bus,然后立即转入该Secondary Bus的扫描。除了设置PCI Bus Number,PCI BUS驱动还会根据PCI桥下挂的设备资源来设置PCI桥的2个BASE ADRESSRegister以及MEMORY BASE,Limit和IO Base,Limit。

在扫描的过程中,PCI BUS驱动会检查PCI Agent(我也不知道Agent怎么翻译)。如果其支持内存,IO和OpROM,则PCI BUS会计算其大小,然后为其分配基址。下面来看一下,PCI BUS如何获取PCI设备是否支持MEMORY,IO,OpROM的,以及如何获取其大小的。

2PCIBar

一个PCI Agent,有6个PCIBar,在其配置空间的0ffset 0x10至0x24处,每个Bar占4个字节。PCI Agent需不需要Memory和IO,需要多少,是由PCI Agent本身决定的,所以我们要通过访问这些BAR来确定这些资源。

2.1 是否需要MEMORY和IO

在PCIBUS Gather PCI Agent信息的时候,会调用BarExisted()函数来确定某个BAR是否支持MEMORY和IO。我们来看一个函数体:

EFI_STATUS

BarExisted(

IN PCI_IO_DEVICE *PciIoDevice, //指向该PCI Agent结构体

IN UINTN Offset, //这个是BAR的offset,0x10到0x24

OUT UINT32 *BarLengthValue, //用来接收BAR需要的资源长度,这个最重要

OUT UINT32 *OriginalBarValue //PCI AGENT原始值

)

{

EFI_PCI_IO_PROTOCOL *PciIo;

UINT32 OriginalValue;

UINT32 Value;

EFI_TPL OldTpl;

PciIo = &PciIoDevice->PciIo;

// 保存原始的BAR值

PciIo->Pci.Read (PciIo,EfiPciIoWidthUint32, (UINT8) Offset, 1, &OriginalValue);

// 提升本任务优先级别,TPL_HIGH_LEVEL为最高级别

OldTpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);

//写全1,再读回值,对于有关长度的BIT,写1无效。

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

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

PciIo->Pci.Write (PciIo,EfiPciIoWidthUint32, (UINT8) Offset, 1, &OriginalValue);

// 恢复任务优先级

gBS->RestoreTPL (OldTpl);

if (BarLengthValue != NULL) {

*BarLengthValue = Value;

}

if (OriginalBarValue != NULL) {

*OriginalBarValue = OriginalValue;

}

//返回值为0,说明此BAR不支持MEMORY或IO,否则是支持的

if (Value == 0) {

return EFI_NOT_FOUND;

} else {

return EFI_SUCCESS;

}

}

如果此BAR根本不支持MEMORY或IO,再去取补计算这个BAR的LENGTH是没意义的。

2.2 计算BAR长度

计算BAR长度,就是对2.1中读到的BarLengthValue参数求补操作。

看一下下面的程序段,Value就是BarLengthValue。

该BAR请求IO的情况:

Mask = 0xfffffffc;

if ((Value & 0xFFFF0000) != 0) { //操,难道还有为0的情况?

PciIoDevice->PciBar[BarIndex].BarType = PciBarTypeIo32;

PciIoDevice->PciBar[BarIndex].Length = ((~(Value & Mask)) + 1);//该BAR真正的长度

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;

如果BIT0=0,那么该BAR是请求MEMORY的BAR,如下图所示,可以看出其需求:



Mask =0xfffffff0;

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

switch (Value & 0x07) {

case 0x00: //BIT2,1=00B,需求32位

if ((Value & 0x08) != 0) {

PciIoDevice->PciBar[BarIndex].BarType= PciBarTypePMem32;

} else {

PciIoDevice->PciBar[BarIndex].BarType = PciBarTypeMem32;

}

PciIoDevice->PciBar[BarIndex].Length = (~(Value & Mask)) + 1; //和IO没啥区别

if (PciIoDevice->PciBar[BarIndex].Length< (SIZE_4KB)) {

PciIoDevice->PciBar[BarIndex].Alignment = (SIZE_4KB - 1);

} else {

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

}

break;

case 0x04: //BIT2,1=10B,需求64位,需要两个BAR

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;

Offset += 4;

Status = BarExisted (

PciIoDevice,

Offset,

&Value,

&OriginalValue

);

if (EFI_ERROR (Status)) {

if(PciIoDevice->PciBar[BarIndex].Length == 0) {

PciIoDevice->PciBar[BarIndex].BarType = PciBarTypeUnknown;

}

return Offset + 4;

}

Value |= ((UINT32)(-1) <<HighBitSet32 (Value));

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

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

PciIoDevice->PciBar[BarIndex].Length =(~(PciIoDevice->PciBar[BarIndex].Length)) + 1;//64位MEMORY,长度和32位算法一样

if(PciIoDevice->PciBar[BarIndex].Length < (SIZE_4KB)) {

PciIoDevice->PciBar[BarIndex].Alignment = (SIZE_4KB - 1);

} else {

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

}

break;

//其它情况,PCI还未定义

default:

PciIoDevice->PciBar[BarIndex].BarType = PciBarTypeUnknown;

PciIoDevice->PciBar[BarIndex].Length = (~(Value & Mask)) + 1;

if(PciIoDevice->PciBar[BarIndex].Length < (SIZE_4KB)) {

//

// Force minimum 4KByte alignment forVirtualization technology for Directed I/O

//

PciIoDevice->PciBar[BarIndex].Alignment= (SIZE_4KB - 1);

} else {

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

}

break;

}

这些BAR和长度算出后,紧接着PCIBUS就会提交给HOST Bridge该设备的资源请求,HOST Bridge进行分配。

3 OpRom

3.1 判断是否支持OpRom

如果设备上有一颗I2C的ROM,那么该PCI Agent是支持OpROM的。那如何确定是否支持呢?我们来看一下UDK2014的一段代码:

EFI_STATUS

GetOpRomInfo(

IN OUT PCI_IO_DEVICE *PciIoDevice

)

{

UINT8 RomBarIndex;

UINT32 AllOnes;

UINT64 Address;

EFI_STATUS Status;

UINT8 Bus;

UINT8 Device;

UINT8 Function;

EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL*PciRootBridgeIo;

Bus = PciIoDevice->BusNumber;

Device = PciIoDevice->DeviceNumber;

Function = PciIoDevice->FunctionNumber;

PciRootBridgeIo =PciIoDevice->PciRootBridgeIo;

//PCI Agent的ROMBar在配置空间的0x30处,而PciBridge的RomBar在0x38处,我们先假设其为PCI Agent

RomBarIndex = PCI_EXPANSION_ROM_BASE;

if (IS_PCI_BRIDGE (&PciIoDevice->Pci)){ //若PCI设备为桥设备,则将Index改为0x38

RomBarIndex= PCI_BRIDGE_ROMBAR;

}

//和PCIBar没什么区别,也要往寄存器里写全1

AllOnes = 0xfffffffe;

Address = EFI_PCI_ADDRESS (Bus, Device,Function, RomBarIndex);

Status = PciRootBridgeIo->Pci.Write (

PciRootBridgeIo,

EfiPciWidthUint32,

Address,

1,

&AllOnes

);

if (EFI_ERROR (Status)) {

return EFI_NOT_FOUND;

}

Status = PciRootBridgeIo->Pci.Read(

PciRootBridgeIo,

EfiPciWidthUint32,

Address,

1,

&AllOnes

);

if (EFI_ERROR (Status)) {

return EFI_NOT_FOUND;

}

//BIT0指示是否允许OpROM的读取,BIT1到BIT10指示其大小

AllOnes &= 0xFFFFF800;

if ((AllOnes == 0) || (AllOnes ==0xFFFFF800)) {

return EFI_NOT_FOUND;

}

PciIoDevice->RomSize = (UINT64)((~AllOnes) + 1); //也相当于取补,和PCIBAR一样

return EFI_SUCCESS;

}

当该RomSize确定后,会在LoadOpRomImage()中找寻OpROM,并将OpRom下载至相应内存处。该种方法在读之前,需要进行RomDecode,就是将Expantion ROM base address的BIT0置1,那么PCI Agent就会映射EEPROM的东西至该BASE Address处,我们就可以通过读内存的方式读取ROMSize个字节。

3.2 另一种找寻ROM的方法

OpROM并不总是放在EEPROM中,现在BIOS的作法是将其直接配在BIOS中,这样可以节省一颗ROM芯片,并且省去了RomDecode的步骤。我使用的是INSYDE代码,不敢贴代码。现在就把实现流程说一下吧。

Step1,将OpROM编译成一个RAW型SECTION,并生成一个FILE,放入任一FV中。

Step2,将OpROM的VID,DID,以及FILE GUID放入一个固定的数组中。

Step3,安装一个事件,并使用RegisterProtocolNotify(),在gEfiBdsArchProtocolGuid安装时,偷梁换柱,重定向BDS入口函数至自己的一个函数XXX上。

Step4,BDS进入时,先执行XXX,重定向gEfiPciPlatformProtocolGuid的实例。

Step5,PCI BUS扫描时,在RegisterPciDevice()函数中,优先CHECK STEP2的OpROM,如果VID和DID相同,则将STEP1中的OPROM解析至内存中。

(唉,每次写点东西,查阅INSYDE代码的时候,老是觉得自己是偷窃。希望早日把国产平台的BIOS做出来。这才是我自己真正的东西。)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: