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

本文介绍了EFI BIOS中PCI BUS驱动如何进行资源分配,包括PCI设备所需的IO、Memory、IRQ以及OpROM。通过PCI扫描和PCIBAR分析,详细讲解了PCI设备资源的需求与分配过程,涉及PCI BAR是否存在、计算长度以及OpRom的支持判断。

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

一说到资源,大家马上想到“利用”两个字。是的,没有利用价值的资源不是真正的资源。大到整个社会,小到个人,都在利用资源实现自己的想法。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做出来。这才是我自己真正的东西。)


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值