Bios工程師手邊事—PCI資源分配

一說到資源,大家馬上想到“利用”兩個字。是的,沒有利用價值的資源不是真正的資源。大到整個社會,小到個人,都在利用資源實現自己的想法。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做出來。這纔是我自己真正的東西。)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章