2020年5月16日 星期六

EDK2 Core Module List

Core Module

這邊紀錄一下EDK2裡面4個phase的Core Module有哪些,大概看一下每個Entry裡面會幹嘛。
先複習一下Boot Flow

SecCore

## @file
#  SecCore module that implements the SEC phase.
#
#  This is the first module taking control of the platform upon power-on/reset.
#  It implements the first phase of the security phase. The entry point function is
#  _ModuleEntryPoint in PlatformSecLib. The entry point function will switch to
#  protected mode, setup flat memory model, enable temporary memory and
#  call into SecStartup().
##
[Defines]
  BASE_NAME                      = SecCore
  MODULE_TYPE                    = SEC
既然都說入口是_ModuleEntryPoint了,就來看一下吧(在SecEntry.nasm裡面)
;----------------------------------------------------------------------------
; Procedure:    _ModuleEntryPoint
; Input:        None
; Output:       None
; Destroys:     Assume all registers
; Description:
;   Transition to non-paged flat-model protected mode from a
;   hard-coded GDT that provides exactly two descriptors.
;   This is a bare bones transition to protected mode only
;   used for a while in PEI and possibly DXE.
;
;   After enabling protected mode, a far jump is executed to
;   transfer to PEI using the newly loaded GDT.
; Return:       None
;  MMX Usage:
;              MM0 = BIST State
;              MM5 = Save time-stamp counter value high32bit
;              MM6 = Save time-stamp counter value low32bit.
;----------------------------------------------------------------------------
global ASM_PFX(ModuleEntryPoint)
  1. BIST->Load GDT table->cr0&cr4 to protected mode->set up Selectors for 32 bit
  2. FSP info->FV header->Ext Fv header->FFS header->section header
  3. 還有一些其他東西,不過就先不看了,接下來就call進SecStartup。
/**
  Entry point to the C language phase of SEC. After the SEC assembly
  code has initialized some temporary memory and set up the stack,
  the control is transferred to this function.
**/
VOID
EFIAPI
SecStartup (
  IN UINT32                   SizeOfRam,
  IN UINT32                   TempRamBase,
  IN VOID                    *BootFirmwareVolume,
  IN PEI_CORE_ENTRY           PeiCore,
  IN UINT32                   BootLoaderStack,
  IN UINT32                   ApiIdx
  )
這邊的話看起來就是把IDT填好還有FSP初始化就進PEI了。

PeiCore

## @file
# PeiMain module is core module in PEI phase.
#
# It takes responsibilities of:
# 1) Initialize memory, PPI, image services etc, to establish PEIM runtime environment.
# 2) Dispatch PEIM from discovered FV.
# 3) Handoff control to DxeIpl to load DXE core and enter DXE phase.
##

[Defines]
  BASE_NAME                      = PeiCore
  MODULE_TYPE                    = PEI_CORE
  ENTRY_POINT                    = PeiCore
PEI的話上面敘述好像都說完了,主要應該是剛剛SEC還在Stack裡面玩,現在要把剛剛的東西shadow到memory裡面,然後再從memory裡面重新開始執行PeiCore,從新執行就是跑上面說的那3點了。

然後要切到Dxe的時候,在PeiCore裡面最後會執行一個DxeLoadCore
/**
   Main entry point to last PEIM.
   This function finds DXE Core in the firmware volume and transfer the control to
   DXE core.
**/
EFI_STATUS
EFIAPI
DxeLoadCore (
  IN CONST EFI_DXE_IPL_PPI *This,
  IN EFI_PEI_SERVICES      **PeiServices,
  IN EFI_PEI_HOB_POINTERS  HobList
  )
在這裡面就會去看BootMode是什麼,
  1. S3的話就把data要回來然後就SwitchStack回去
  2. 如果是Recovery的話就LoadRecoveryCapsule
  3. Flash Update的話就LoadCapsuleOnDisk
  4. Normal的話就從Fv裡面load DxeCore出來,然後把HobList和DxeCore的位置準備好給SwitchStack帶過去。

DxeCore

## @file
#  This is core module in DXE phase.
#  It provides an implementation of DXE Core that is compliant with DXE CIS.
##
[Defines]
  BASE_NAME                      = DxeCore
  MODULE_TYPE                    = DXE_CORE
  ENTRY_POINT                    = DxeMain
...
/**
  Main entry point to DXE Core.
**/
VOID
EFIAPI
DxeMain (
  IN  VOID *HobStart
  )
...
因為Memory好了,SystemTable, RuntimeServices, GCD Services都可以使用了,所以這邊有一部份是要把之前的東西移到Table裡,不過這邊最主要的還是Driver。

  // Initialize the DXE Dispatcher

前面會先看是不是Fv,然後存進gEfiFirmwareVolume2ProtocolGuid裡面,接下來Initialize要做什麼咧
  1. 把Fv的Section讀出來看他有沒有Depex
  2. 沒有Depex的話放進CoreProcessFvImageFile排序裡
  3. 有的話放進CoreAddToDriverList排序裡
  4. 還有一些是你有設定Apriori的話會再調整順序
  // Invoke the DXE Dispatcher
  1. LoadImage
  2. StartImage(supported->start)
  3. 調整List
  //
  // Transfer control to the BDS Architectural Protocol
  //
  gBds->Entry (gBds);
最後出現這行,看了DxeCore裡面沒這東西啊,才發現原來他是一個DXE_DRIVER阿,在所有Driver都裝好之後會去install gEfiBdsArchProtocolGuid procotol,這個protocol也就是上面gBds。
## @file
#  BdsDxe module is core driver for BDS phase.
#  When DxeCore dispatching all DXE driver, this module will produce architecture protocol
#  gEfiBdsArchProtocolGuid. After DxeCore finish dispatching, DxeCore will invoke Entry
#  interface of protocol gEfiBdsArchProtocolGuid, then BDS phase is entered.
##
[Defines]
  BASE_NAME                      = BdsDxe
  MODULE_TYPE                    = DXE_DRIVER
  ENTRY_POINT                    = BdsInitialize

BdsEntry

就是透過上面gBds切進BDS phase的.................
/**
  Service routine for BdsInstance->Entry(). Devices are connected, the
  consoles are initialized, and the boot options are tried.
**/
VOID
EFIAPI
BdsEntry (
  IN EFI_BDS_ARCH_PROTOCOL  *This
  )
看一下BootFlow,這邊簡單帶過
  1. BootOption
  2. ConIn
  3. BootManager
  4. signal gEfiEventReadyToBootGuid
  5. LoadImage
      REPORT_STATUS_CODE (EFI_PROGRESS_CODE, PcdGet32 (PcdProgressCodeOsLoaderLoad));
      Status = gBS->LoadImage (
                      TRUE,
                      gImageHandle,
                      FilePath,
                      FileBuffer,
                      FileSize,
                      &ImageHandle
                      );

:DONE

Ref

Beyond BIOS
EDK2-master

2020年5月13日 星期三

GPIO initialization

紀錄一下Intel的GPIO initialization,主要就是GpioTable填完以後的行為。
先提一下GpioTable的structure
typedef struct {
  GPIO_PAD           GpioPad;
  GPIO_CONFIG        GpioConfig;
GPIO_INIT_CONFIG;

typedef struct {
  /**
  Pad Mode
  Pad can be set as GPIO or one of its native functions.
  When in native mode setting Direction (except Inversion), OutputState,
  InterruptConfig, Host Software Pad Ownership and OutputStateLock are unnecessary.
  Refer to definition of GPIO_PAD_MODE.
  Refer to EDS for each native mode according to the pad.
  **/
  UINT32 PadMode            : 5;
  /**
  Host Software Pad Ownership
  Set pad to ACPI mode or GPIO Driver Mode.
  Refer to definition of GPIO_HOSTSW_OWN.
  **/
  UINT32 HostSoftPadOwn     : 2;
  /**
  GPIO Direction
  Can choose between In, In with inversion, Out, both In and Out, both In with inversion and out or disabling both.
  Refer to definition of GPIO_DIRECTION for supported settings.
  **/
  UINT32 Direction           : 6;
  /**
  Output State
  Set Pad output value.
  Refer to definition of GPIO_OUTPUT_STATE for supported settings.
  This setting takes place when output is enabled.
  **/
  UINT32 OutputState         : 2;
  /**
  GPIO Interrupt Configuration
  Set Pad to cause one of interrupts (IOxAPIC/SCI/SMI/NMI).
  This setting is applicable only if GPIO is in GpioMode with input enabled.
  Refer to definition of GPIO_INT_CONFIG for supported settings.
  **/
  UINT32 InterruptConfig     : 9;
  /**
  GPIO Power Configuration.
  This setting controls Pad Reset Configuration.
  Refer to definition of GPIO_RESET_CONFIG for supported settings.
  **/
  UINT32 PowerConfig        : 8;
  /**
  GPIO Electrical Configuration
  This setting controls pads termination.
  Refer to definition of GPIO_ELECTRICAL_CONFIG for supported settings.
  **/
  UINT32 ElectricalConfig   : 9;
  /**
  GPIO Lock Configuration
  This setting controls pads lock.
  Refer to definition of GPIO_LOCK_CONFIG for supported settings.
  **/
  UINT32 LockConfig         : 4;
  /**
  Additional GPIO configuration
  Refer to definition of GPIO_OTHER_CONFIG for supported settings.
  **/
  UINT32 OtherSettings      : 9;

  UINT32 RsvdBits           : 10;    ///< Reserved bits for future extension
GPIO_CONFIG;

  1. 在BoardInit的時候做一個Hob出來把GpioTable放進去
  2. 在PchInit的時候把Hob從memory裡面拿出來
  3. 接著就是一隻一隻腳拿出來看然後寫進memory裡面
  4. 有些腳是有特殊用途的會不參考你GpioTable裡面的設定
  5. 那會放進什麼位置是用算的,(PCH P2SB BAR) + (GPIO COMMUNITY) + PadCfgReg
  6. 之後接著會掃第二遍,主要是update有interrupt的
  7. update完之後還會掃第三遍....................   是要把interrupt的status清掉
PS.上面PadMode黃字的地方注意一下,Native的時候有些東西他也會寫進memory裡面可是不會去參考。

Sleep State Flow

先了解一下OSPM, _PTS, _WAK之間的flow。

Transitioning from the Working to the Sleeping State

  1. OSPM決定要進入sleeping state(S1, S2, S3, S4),這邊可以是你自己按的也可以是一些電源選項達成。
  2. OSPM會去call ACPI裡面的_TTS(Transition to State)看是要進到哪個sleeping state。
  3. 接著OSPM會去call device的_PRW(Power Resources to Wake)去確認有哪些device可以叫醒系統。
  4. OSPM要把所有device放進相對的sleeping state,可以wake的device就進到Dx state,反之進D3。
  5. OSPM call _PTS(Prepare to Sleep),_PTS在做什麼根據platform不同就會有所差異,主要就是傳遞現在要進哪個state,有些platform會在這邊把wake的enable起來還有把status清乾淨。
  6. OSPM會把其他processor的data存進memory。
  7. OSPM把waking vector放進FACS table裡。
  8. Clear WAK_STS in PM1a_STS(bit15),準備之後可以正常wake。
  9. 這時候才把local processor的data存進memory裡,如果是S1, S2, S3的話就會把caches清掉。
  10. OSPM會把GPE 或是其他可以wake的中斷enable起來。
  11. 如果是S4的話,OSPM會把S4BIOS_REQ寫進SMI_CMD port。
  12. 不是的話,OSPM會去填PM1a_CNT裡面的SLP_TYPx和SLP_EN。
  13. SLP_EN設起來以後就是HW的事情了。

Transitioning from the Working to the Soft Off State

  1. OSPM call _PTS傳遞即將進入S5。
  2. OSPM把所有的device關掉
  3. OSPM填SLP_TYP和SLP_EN設起來就進S5了。

接下來是wake起來後的flow

上面兩張圖是wake起來以後走的路線,現在總算知道S3起來以後會走到Waking Vector,Waking Vector是什麼呢?
Waking Vector是在系統要睡下去的時候,OSPM會寫進FACS(Firmware ACPI Control Structure)裡面的Byte Offset=12的位置。
--------------------------------------------------------------------------------------------------------------------
Field        | Byte Length | Byte Offset | Description                                                                          
--------------------------------------------------------------------------------------------------------------------
Firmware | 4                  | 12                | Before transitioning the system into a global sleeping 
Waking    |                     |                     | state, OSPM fills this field with the physical memory 
Vector      |                     |                     | address of an OS-specific wake function. If this is
                 |                    |                     |  non-zero, transfers control to the specified address.
---------------------------------------------------------------------------------------------------------------------
從code來看的話,當POST跑到最後一個PEIM也就是DxeLoadCore的時候
/**
   Main entry point to last PEIM.

   This function finds DXE Core in the firmware volume and transfer the control to
   DXE core.

**/
EFI_STATUS
EFIAPI
DxeLoadCore (
  IN CONST EFI_DXE_IPL_PPI *This,
  IN EFI_PEI_SERVICES      **PeiServices,
  IN EFI_PEI_HOB_POINTERS  HobList
  )
這裡面會去看BootMode是不是S3, Recovery Mode, Capsule Update,如果是S3的話就走S3的路,
最後會走到
      //
      // IA32 protected mode waking vector (Page disabled)
      //
      DEBUG ((DEBUG_INFO, "Transfer to 32bit OS waking vector - %x\r\n", (UINTN)Facs->XFirmwareWakingVector));
      SwitchStack (
        (SWITCH_STACK_ENTRY_POINT) (UINTN) Facs->XFirmwareWakingVector,
        NULL,
        NULL,
        (VOID *)(UINTN)TempStackTop
        );
這個SwitchStack的功能就是轉交控制權。
回到OS以後才要call _WAK(System Wake),其實就是做跟_PTS相反的事情,然後還要把剛剛睡下去的device叫醒,
  Case (43) { // interrupt 43
            Store(One, ISTS) // clear interrupt status register at device X
            // which is mapped via an operation region
            Notify (\_SB.DEVX0x2// wake event
            } 
Notify的第一個param是device,第二個是要notify什麼,這邊0x2是Device Wake. 
Device Wake : Used to notify that the device has signaled its wake event,and that OSPM needs to notify OSPM native device for the device.
This is only used for devices that support _PRW.
Notify完了以後,OS應該就會去把device和driver串起來了。

2020年5月6日 星期三

PCI Host Bridge Resource Allocation

Initialize Pci Host Bridge

Entry

PciHostBridgeDxe.inf
[Defines]
  BASE_NAME                      = PciHostBridgeDxe
  MODULE_TYPE                    = DXE_DRIVER
  ENTRY_POINT                    = InitializePciHostBridge
PciHostBridge.c
/**
  Entry point of this driver.
  @retval EFI_SUCCESS       Succeed.
  @retval EFI_DEVICE_ERROR  Fail to install PCI_ROOT_BRIDGE_IO protocol.
**/
EFI_STATUS
EFIAPI
InitializePciHostBridge (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  )

Step

  1. 因為要給Host Bridge適當的resources所以需要先掃出Host Bridge下面有哪些Root Bridge
      RootBridges = PciHostBridgeGetRootBridges (&RootBridgeCount);
      if ((RootBridges == NULL) || (RootBridgeCount == 0)) {
        return EFI_UNSUPPORTED;
      }
    這邊就是把所有的Bus下面的Dev和Fun找一遍,如果有找到Vid且Header Type = 1才會把現在的Bus視為RootBridge。
  2. 找到了以後就要確定IO,Memory和MemAbove4G的Resources
    1Ch : IO Base
    1Dh : IO Limit
    20h : Memory Base
    22h : Memory Limit
    24h : Prefetchable Memory Base
    26h : Prefetchable Memory Limit
    要記得的是這邊是找到Base和Size,Limit = Base + Size - 1。
    這邊配完resources以後,會做兩件事情:
    a. 若是IDE controller, legacy VGA controller, PCI bridge的話會先加對應的Attributes
    b. 再來看是不是multi function(Header Type bit7),是的話要重新配resources,否的話就直接開始Initialize Root Bridge了。

  3. 接下來步驟的前提是有抓到Vid且分配好resources。
    但其實initialize root bridge就是把剛剛找到的,分配好的位置放進structure裡面,然後再allocatepool出一塊memory把所有的root bridge照順序放進去方便之後使用(Step1 return回來的RootBridge),我是覺得這邊比較像準備工作。
  4. 做了這麼多事情以後,終於要initialize HostBridge了:
      //
      // Most systems in the world including complex servers have only one Host Bridge.
      //
      HostBridge = AllocateZeroPool (sizeof (PCI_HOST_BRIDGE_INSTANCE));
      HostBridge->Signature        = PCI_HOST_BRIDGE_SIGNATURE; //SIGNATURE_32 ('p', 'h', 'b', 'g')
      HostBridge->CanRestarted     = TRUE;
      InitializeListHead (&HostBridge->RootBridges);
    看起來就是做了一個LinkList的頭,接下來就要拿出剛剛的RootBridges裡面的data,一個一個做成Root Bridge Device Handle,且一個一個掛在Host Bridge後面。
  5. 接下來要做的事情是把剛剛Root Bridge的結構拿出來處理然後一個一個掛在Host Bridge後面
      //
      // Create Root Bridge Device Handle in this Host Bridge
      //
      for (Index = 0; Index < RootBridgeCount; Index++) {
        //
        // Create Root Bridge Handle Instance
        //
        RootBridge = CreateRootBridge (&RootBridges[Index]);
        ASSERT (RootBridge != NULL);
        if (RootBridge == NULL) {
          continue;
        } . . .
        //
        // Insert Root Bridge Handle Instance
        //
        InsertTailList (&HostBridge->RootBridges, &RootBridge->Link);
      }
    CreateRootBridge會做下面這些事情:
    a. 確認resources的Limit >= Base
    b. 接著把該丟的丟進去
      RootBridge = AllocateZeroPool (sizeof (PCI_ROOT_BRIDGE_INSTANCE));
      ASSERT (RootBridge != NULL);
      RootBridge->Signature = PCI_ROOT_BRIDGE_SIGNATURE;
      RootBridge->Supports = Bridge->Supports;
      RootBridge->Attributes = Bridge->Attributes;
      RootBridge->DmaAbove4G = Bridge->DmaAbove4G;
      RootBridge->NoExtendedConfigSpace = Bridge->NoExtendedConfigSpace;
      RootBridge->AllocationAttributes = Bridge->AllocationAttributes;
      RootBridge->DevicePath = DuplicateDevicePath (Bridge->DevicePath);
      RootBridge->DevicePathStr = DevicePathStr;
      RootBridge->ConfigBuffer = AllocatePool (
        TypeMax * sizeof (EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR) + sizeof (EFI_ACPI_END_TAG_DESCRIPTOR)
        );
      ASSERT (RootBridge->ConfigBuffer != NULL);
      InitializeListHead (&RootBridge->Maps);
      CopyMem (&RootBridge->Bus, &Bridge->Bussizeof (PCI_ROOT_BRIDGE_APERTURE));
      CopyMem (&RootBridge->Io, &Bridge->Iosizeof (PCI_ROOT_BRIDGE_APERTURE));
      CopyMem (&RootBridge->Mem, &Bridge->Memsizeof (PCI_ROOT_BRIDGE_APERTURE));
      CopyMem (&RootBridge->MemAbove4G, &Bridge->MemAbove4Gsizeof (PCI_ROOT_BRIDGE_APERTURE));
      CopyMem (&RootBridge->PMem, &Bridge->PMemsizeof (PCI_ROOT_BRIDGE_APERTURE));
      CopyMem (&RootBridge->PMemAbove4G, &Bridge->PMemAbove4Gsizeof (PCI_ROOT_BRIDGE_APERTURE));
    c. 有了這些資訊後,在Allocate一塊Io或Memory space出來,最後就是InsertTailList了。
    PS.這邊要注意的是step2拿到的Base是device address,需要處理過變成host address才能allocate出一塊memory。
  6. 把HostBridge和RootBridge全部都install到protocol裡面,之後把剛剛Allocate給instance的空間free掉就完成InitializePciHostBridge的部分了。
        Status = gBS->InstallMultipleProtocolInterfaces (
                        &HostBridge->Handle,
                        &gEfiPciHostBridgeResourceAllocationProtocolGuid, &HostBridge->ResAlloc,
                        NULL
                        ); . . .
        Status = gBS->InstallMultipleProtocolInterfaces (
                        &RootBridge->Handle,
                        &gEfiDevicePathProtocolGuidRootBridge->DevicePath,
                        &gEfiPciRootBridgeIoProtocolGuid, &RootBridge->RootBridgeIo,
                        NULL
                        );