2014年10月20日 星期一

PCD (Platform Configuration Database) - Part 2 (PEI)

這篇將探討PCD背後的原理,PEI與DXE都分別提供PPI與Protocol給予其它Driver使用,雖然一般都是透過PcdLib來存取Dynamic類型的PCD,但實際上PcdLib是使用PPI或Protocol來達成存取PCD的目的。

AutoGen.h
typedef struct {
  DYNAMICEX_MAPPING  ExMapTable[PEI_EXMAPPING_TABLE_SIZE];
  UINT32             LocalTokenNumberTable[PEI_LOCAL_TOKEN_NUMBER_TABLE_SIZE];
  GUID               GuidTable[PEI_GUID_TABLE_SIZE];

  UINT8              StringTable[1]; /* _ */

  SIZE_INFO          SizeTable[PEI_SIZE_TABLE_SIZE];

  UINT8              SkuIdTable[PEI_SKUID_TABLE_SIZE];
  SKU_ID             SystemSkuId;
} PEI_PCD_DATABASE_INIT;

typedef struct {
  UINT64   PcdS3BootScriptTablePrivateDataPtr_a1aff049_fdeb_442a_b320_13ab4cb72bbc[1];

  UINT32   PcdFlashNvStorageFtwWorkingBase_a1aff049_fdeb_442a_b320_13ab4cb72bbc[1];
  UINT32   PcdFlashNvStorageFtwSpareBase_a1aff049_fdeb_442a_b320_13ab4cb72bbc[1];
  UINT32   PcdFlashNvStorageVariableBase_a1aff049_fdeb_442a_b320_13ab4cb72bbc[1];
} PEI_PCD_DATABASE_UNINIT;

typedef struct {
  PEI_PCD_DATABASE_INIT    Init;
  PEI_PCD_DATABASE_UNINIT  Uninit;
} PEI_PCD_DATABASE;

AutoGen.c
PEI_PCD_DATABASE_INIT gPEIPcdDbInit = {
  /* VPD */

  /* ExMapTable */
  {
    { 0U, 0U, 0U },
  },
  /* LocalTokenNumberTable */
  {
    offsetof(PEI_PCD_DATABASE, Uninit.PcdFlashNvStorageFtwWorkingBase_a1aff049_fdeb_442a_b320_13ab4cb72bbc) | PCD_TYPE_DATA | PCD_DATUM_TYPE_UINT32,
    offsetof(PEI_PCD_DATABASE, Uninit.PcdFlashNvStorageFtwSpareBase_a1aff049_fdeb_442a_b320_13ab4cb72bbc) | PCD_TYPE_DATA | PCD_DATUM_TYPE_UINT32,
    offsetof(PEI_PCD_DATABASE, Uninit.PcdFlashNvStorageVariableBase_a1aff049_fdeb_442a_b320_13ab4cb72bbc) | PCD_TYPE_DATA | PCD_DATUM_TYPE_UINT32,
    offsetof(PEI_PCD_DATABASE, Uninit.PcdS3BootScriptTablePrivateDataPtr_a1aff049_fdeb_442a_b320_13ab4cb72bbc) | PCD_DATUM_TYPE_UINT64 | PCD_TYPE_DATA,
  },
  /* GuidTable */
  {
    {0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
  },
 /* StringTable */
  { 0 }, /* _ */

  /* SizeTable */
  {
    0U, 0U, /* _ */
  },
  /* SkuIdTable */
  { 0U,  },
  0U
};

以上是在Compile過程中,Build Tool為PCD PEIM所生成的檔案。
可以注意到這邊只有少數幾組PCD,原因是PCD PEIM中不會包含只在DXE被使用的PCD。
而這個PEI_PCD_DATABASE,最後會透過HOB傳給PCD DXE。
建立HOB的過程可參考BuildPcdDatabase

整個Structure分為InitUninit的部分,兩個差別是在於,如果有定義好預設值的Dynamic PCD,其預設值會被放在Init裡面,但如果在DSC裡定義的值為0或NULL的話,會被視為無預設值,且會放置於Uninit中。

  /* LocalTokenNumberTable */
  {
    offsetof(PEI_PCD_DATABASE, Uninit.PcdFlashNvStorageFtwWorkingBase_a1aff049_fdeb_442a_b320_13ab4cb72bbc) | PCD_TYPE_DATA | PCD_DATUM_TYPE_UINT32,
    offsetof(PEI_PCD_DATABASE, Uninit.PcdFlashNvStorageFtwSpareBase_a1aff049_fdeb_442a_b320_13ab4cb72bbc) | PCD_TYPE_DATA | PCD_DATUM_TYPE_UINT32,
    offsetof(PEI_PCD_DATABASE, Uninit.PcdFlashNvStorageVariableBase_a1aff049_fdeb_442a_b320_13ab4cb72bbc) | PCD_TYPE_DATA | PCD_DATUM_TYPE_UINT32,
    offsetof(PEI_PCD_DATABASE, Uninit.PcdS3BootScriptTablePrivateDataPtr_a1aff049_fdeb_442a_b320_13ab4cb72bbc) | PCD_DATUM_TYPE_UINT64 | PCD_TYPE_DATA,
  },

PEI_PCD_DATABASE_INIT中的LocalTokenNumberTable會在Compile過程中,會決定好每個PCD中的Data在PEI_PCD_DATABASE中的Offset以及Data Type與PCD Type。

#define offsetof(s,m)  (UINT32) (UINTN) &(((s *)0)->m)
代入一個Structure的原型與其Member,會算出其Member的Offset為多少。

PCD Type Mask:
PCD_TYPE_DATA
PCD_TYPE_HII
PCD_TYPE_VPD
PCD_TYPE_SKU_ENABLED
PCD_TYPE_STRING

Datum Type:
PCD_DATUM_TYPE_POINTER
PCD_DATUM_TYPE_UINT8
PCD_DATUM_TYPE_UINT16
PCD_DATUM_TYPE_UINT32
PCD_DATUM_TYPE_UINT64

在上面的LocalTokenNumberTable中,共有四個PCD,如果去搜尋整套編譯過的Source Code,可以在某些AutoGen.h看到這些Dynamic PCD的Token Number,並且可以注意到第一個PCD的Token為1,那為何不是從0開始?0是給PCD_INVALID_TOKEN_NUMBER使用(Source Code中有註解),所以第一個PCD是從1開始,只是後面會將Token Number減1,再代入Table取得其相關資料。
#define _PCD_TOKEN_PcdFlashNvStorageFtwWorkingBase  1U
#define _PCD_GET_MODE_32_...  LibPcdGet32(_PCD_TOKEN_PcdFlashNvStorageFtwWorkingBase)
#define _PCD_SET_MODE_32_...(Value)  LibPcdSet32(_PCD_TOKEN_PcdFlashNvStorageFtwWorkingBase, (Value))

#define _PCD_TOKEN_PcdFlashNvStorageFtwSpareBase  2U
#define _PCD_GET_MODE_32_...  LibPcdGet32(_PCD_TOKEN_PcdFlashNvStorageFtwSpareBase)
#define _PCD_SET_MODE_32_...(Value)  LibPcdSet32(_PCD_TOKEN_PcdFlashNvStorageFtwSpareBase, (Value))

#define _PCD_TOKEN_PcdFlashNvStorageVariableBase  3U
#define _PCD_GET_MODE_32_...  LibPcdGet32(_PCD_TOKEN_PcdFlashNvStorageVariableBase)
#define _PCD_SET_MODE_32_...(Value)  LibPcdSet32(_PCD_TOKEN_PcdFlashNvStorageVariableBase, (Value))

#define _PCD_TOKEN_PcdS3BootScriptTablePrivateDataPtr  4U
#define _PCD_GET_MODE_64_...  LibPcdGet64(_PCD_TOKEN_PcdS3BootScriptTablePrivateDataPtr)
#define _PCD_SET_MODE_64_...(Value)  LibPcdSet64(_PCD_TOKEN_PcdS3BootScriptTablePrivateDataPtr, (Value))

在PPI或Protocol的底層會透過呼叫GetWorkerSetWorker來存取PCD。

GetWorker:


VOID *
GetWorker (
  IN UINTN               TokenNumber,
  IN UINTN               GetSize
  )

透過Token Number取得該PCD的LocalTokenNumber (Offset、PCD Type、Data Type)。
LocalTokenNumber = PeiPcdDb->Init.LocalTokenNumberTable[TokenNumber];

透過Mask單獨取得Offset。
Offset = LocalTokenNumber & PCD_DATABASE_OFFSET_MASK;

接下來會依照各種不同PCD Type做不同的處理:
PCD_TYPE_VPD:
傳回VPD (Vital Product Data)中特定的Offset的資料。
VPD_HEAD *VpdHead;
VpdHead = (VPD_HEAD *) ((UINT8 *)PeiPcdDb + Offset);
return (VOID *) (UINTN) (PcdGet32 (PcdVpdBaseAddress) + VpdHead->Offset);


PCD_TYPE_HII|PCD_TYPE_STRING:
PCD_TYPE_HII:
透過Offset取得Variable Head,裡面有著GUID與Name的Index。
VariableHead = (VARIABLE_HEAD *) ((UINT8 *)PeiPcdDb + Offset);

Guid = &(PeiPcdDb->Init.GuidTable[VariableHead->GuidTableIndex]);
Name = (UINT16*)&StringTable[VariableHead->StringIndex];


透過GetVariable取得Variable Data。
Status = GetHiiVariable (Guid, Name, &Data, &DataSize);

如有錯誤則去取得PCD設定的預設值(如:NOT_FOUND)。
if (Status == EFI_SUCCESS) {
  return (VOID *) ((UINT8 *) Data + VariableHead->Offset);
} else {
  //
  // Return the default value specified by Platform Integrator
  //
  if ((LocalTokenNumber & PCD_TYPE_ALL_SET) == (PCD_TYPE_HII|PCD_TYPE_STRING)) {
    return (VOID*)&StringTable[*(STRING_HEAD*)((UINT8*)PeiPcdDb + VariableHead->DefaultValueOffset)];
  } else {
    return (VOID *) ((UINT8 *) PeiPcdDb + VariableHead->DefaultValueOffset);
  }
}


PCD_TYPE_DATA:
直接透過Offset取得Data。
return (VOID *) ((UINT8 *)PeiPcdDb + Offset);

PCD_TYPE_STRING:
先取得該PCD的STRING_HEAD (StringTableIdx),裡面存放著對應StringTable的Offset。
StringTableIdx = * (STRING_HEAD*) ((UINT8 *) PeiPcdDb + Offset);
return (VOID *) (&StringTable[StringTableIdx]);


SetWorker:


EFI_STATUS
SetWorker (
  IN          UINTN               TokenNumber,
  IN          VOID                *Data,
  IN OUT      UINTN               *Size,
  IN          BOOLEAN             PtrType
  )

透過Token Number取得該PCD的LocalTokenNumber (Offset、PCD Type、Data Type)。
LocalTokenNumber = PeiPcdDb->Init.LocalTokenNumberTable[TokenNumber];

會先判斷Data Type是不是Pointer,通常像String之類的,會被歸類為Pointer。
要注意到在Build Tool會在PCD Database中,先宣告好固定長度的陣列來存放String等其它Data。
如果PtrType為True,而傳入的Size又大於原先預留好的最大值的話,就會造成問題,這邊就先擋掉並回傳MaxSize。
if (PtrType) {
  //
  // Get MaxSize first, then check new size with max buffer size.
  //
  GetPtrTypeSize (TokenNumber, &MaxSize, PeiPcdDb);
  if (*Size > MaxSize) {
    *Size = MaxSize;
    return EFI_INVALID_PARAMETER;
  }
} else {
  if (*Size != PeiPcdGetSize (TokenNumber + 1)) {
    return EFI_INVALID_PARAMETER;
  }
}


當某個PCD被修改時會去呼叫,為該PCD註冊Notify的Function。
可以透過PCD Library的LibPcdCallbackOnSet來進行註冊會比較方便。
if (TokenNumber + 1 < PEI_NEX_TOKEN_NUMBER + 1) {
  InvokeCallbackOnSet (0, NULL, TokenNumber + 1, Data, *Size);
}


透過Mask單獨取得Offset,並取得Data。
Offset = LocalTokenNumber & PCD_DATABASE_OFFSET_MASK;
InternalData    = (VOID *) ((UINT8 *) PeiPcdDb + Offset);


接下來會依照各種不同PCD Type做不同的處理:
PCD_TYPE_VPD:
PCD_TYPE_HII:
PCD_TYPE_HII|PCD_TYPE_STRING:
可能是上述幾個Type都不支援在PEI修改NVS,所以這邊就直接return回EFI_INVALID_PARAMETER。
尤其PCD_TYPE_HII實際上就是個Variable。
return EFI_INVALID_PARAMETER;

PCD_TYPE_STRING:
傳入的資料為一串Array,資料長度可能會變動,所以先更改Current Size。
SizeTable中有兩個欄位,一個是Max Size,一個是Current Size。
之後再透過CopyMem,將傳入的Data Copy到StringTable中。
if (SetPtrTypeSize (TokenNumber, Size, PeiPcdDb)) {
  StringTableIdx = *((STRING_HEAD *)InternalData);
  CopyMem (&PeiPcdDb->Init.StringTable[StringTableIdx], Data, *Size);
  return EFI_SUCCESS;
} else {
  return EFI_INVALID_PARAMETER;
}


PCD_TYPE_DATA:
PtrType的處理方式跟PCD_TYPE_STRING很像。
if (PtrType) {
  if (SetPtrTypeSize (TokenNumber, Size, PeiPcdDb)) {
    CopyMem (InternalData, Data, *Size);
    return EFI_SUCCESS;
  } else {
    return EFI_INVALID_PARAMETER;
  }
}


其餘基本型態只是透過轉型來處理。
switch (*Size) {
  case sizeof(UINT8):
    *((UINT8 *) InternalData) = *((UINT8 *) Data);
    return EFI_SUCCESS;

  case sizeof(UINT16):
    *((UINT16 *) InternalData) = *((UINT16 *) Data);
    return EFI_SUCCESS;

  case sizeof(UINT32):
    *((UINT32 *) InternalData) = *((UINT32 *) Data);
    return EFI_SUCCESS;

  case sizeof(UINT64):
    *((UINT64 *) InternalData) = *((UINT64 *) Data);
    return EFI_SUCCESS;

  default:
    ASSERT (FALSE);
    return EFI_NOT_FOUND;
}


沒有留言:

張貼留言