지금까지 우리가 조사한 모든 PCD 유형은 모듈에 대해 로컬이다. PcdsFixedAtBuild와 PcdsFeatureFlag들은 간단한 정의이며, PcdsPatchableInModule은 단지 지역 변수일 뿐이다.
이런 PCD들만을 이용해서는 모듈 간의 정보를 전달할 수 없다. 만약, 완전한 UEFI Firmware 이미지에 이러한 기능이 있으면 매우 유용할 것이라 생각된다. 이 문제를 해결하는 데 도움이 될 수 있는 다른 종류의 PCD가 있다.
이 클래스를 PcdsDynamic이라고 부른다. 여기서 잠깐 생각해보자.
우리가 이런 PCD 값을 어디에 저장하더라? 이 데이터들은 플랫폼(모든 모듈)에 속하나, 하나의 특정 모듈에 국한되어 저장되지는 않는다. 따라서 모든 모듈이 액세스할 수 있는 알려진 인터페이스가 있는 플랫폼에 공통 데이터베이스가 필요하다. 또한, DEC/INF/DSC 파일에서 모든 동적 PCD 설정을 읽어 이 데이터베이스의 초기 값을 구성해야 한다.
typedef struct {
GUID Signature; // PcdDataBaseGuid.
UINT32 BuildVersion;
UINT32 Length; // Length of DEFAULT SKU PCD DB
SKU_ID SystemSkuId; // Current SkuId value.
UINT32 LengthForAllSkus; // Length of all SKU PCD DB
UINT32 UninitDataBaseSize; // Total size for PCD those default value with 0.
TABLE_OFFSET LocalTokenNumberTableOffset;
TABLE_OFFSET ExMapTableOffset;
TABLE_OFFSET GuidTableOffset;
TABLE_OFFSET StringTableOffset;
TABLE_OFFSET SizeTableOffset;
TABLE_OFFSET SkuIdTableOffset;
TABLE_OFFSET PcdNameTableOffset;
UINT16 LocalTokenCount; // LOCAL_TOKEN_NUMBER for all.
UINT16 ExTokenCount; // EX_TOKEN_NUMBER for DynamicEx.
UINT16 GuidTableCount; // The Number of Guid in GuidTable.
UINT8 Pad[6]; // Pad bytes to satisfy the alignment.
} PCD_DATABASE_INIT;
hexdump의 출력값을 보면, 이 값으로 어떻게 출력을 시작하는 지 그 방법을 알 수 있다. PCD 데이터베이스 코드는 이 값을 확인하여 데이터가 실제로 PCD 데이터베이스 파일인지 확인한다.
Build version
UINT32 BuildVersion;
PCD 데이터베이스의 형식은 EDKII 개발을 통해 변경될 수 있다. 만약 변경된다면, BuildVersion 값을 증가시켜야 한다. 현재 버전은 7이다. 물론 PEI PCD DB 모듈, DXE PCD DB 모듈 그리고 빌드 도구들은 모두 이 부분에서 동기화가 일어나야 한다. DB 모듈 코드는 이것이 올바르지 확인하는 작업을 한다.
/// Please make sure the PCD Serivce PEIM Version is consistent with// the version of the generated PEIM PCD Database by build tool.//#definePCD_SERVICE_PEIM_VERSION7//// PCD_PEI_SERVICE_DRIVER_VERSION is defined in Autogen.h.//#if (PCD_SERVICE_PEIM_VERSION!=PCD_PEI_SERVICE_DRIVER_VERSION)#error"Please make sure the version of PCD PEIM Service and the generated PCD PEI Database match."#endif
//
// Please make sure the PCD Serivce DXE Version is consistent with
// the version of the generated DXE PCD Database by build tool.
//
#define PCD_SERVICE_DXE_VERSION 7
//
// PCD_DXE_SERVICE_DRIVER_VERSION is defined in Autogen.h.
//
#if (PCD_SERVICE_DXE_VERSION != PCD_DXE_SERVICE_DRIVER_VERSION)
#error "Please make sure the version of PCD DXE Service and the generated PCD DXE Database match."
#endif
버전 변경에 대한 예를 보고 싶다면 다음 링크의 커밋을 확인하면 된다. 버전 6에서 7로의 업데이트이다.
그러나 실제로 PEI/DXE DB 모듈에서 PCD로 작동할 때, 우리가 직접 값을 할당할 수 있기 때문에, 모든 PCD가 실제 메모리에 존재해야 한다. 또한, UninitDataBaseSize 필드를 이용하면 이를 위해 얼마나 많은 공간을 준비해야 하는지도 알 수 있다.
pcd_type : 직관적으로 말하면, PCD의 유형이다. PCD에는 여러 클래스들이 존재할 수 있다. 이에 대해서는 나중에 언급하도록 하겠다. PCD는 DATA, HII 혹은 VPD가 될 수 있다.
pcd_datum_type : PCD에 존재하는 데이터 유형은 UINT8, UINT16, UINT32, UINT64, VOID*이다.
offset : 데이터베이스 이미지에서 이 PCD의 초기 데이터에 대한 offset
LocalToken은 암묵적으로 1부터 숫자를 매긴다. 그리고 일부 펌웨어 모듈이 PCD와 함께 동작하려할 때, "야, 나 있잖아.. LocalToken №xxx에서 PCD를 가져오거나 설정하고 싶다."라고 말하는 데이터베이스와 통신한다.
PCD 데이터베이스는 이 LocalToken을 읽은 다음 pcd_type 및 pcd_datum_type에 따라 오프셋 필드에서 실제 데이터를 해석한다.
넘버링하는 것은 PEI 및 DXE 데이터베이스에 대해 순차적이프라는 점에 유의해야 한다.
즉, PEI 데이터베이스의 LocalToken에는 숫자 1 .. (PEI_DB.LocalTokenCount)가 있는 것으로 간주되고 DXE 데이터베이스의 LocalToken에는 (PEI_DB.LocalTokenCount + 1) .. (PEI_DB.LocalTokenCount + 1 + DXE_DB.LocalTokenCount)가 있는 것으로 간주된다.
LocalToken No.로 PCD는 쉽게 참조할 수 있다. 하지만, 이를 위해서는 실제 매핑된 구조(PCD <--> LocalToken)를 알아야 한다. 플래시 이미지의 일부와 동시에 모듈 및 PCD 데이터베이스를 구축할 때도 마찬가지이다(이 경우 OVMF).
그러나 때로는, 플래시 이미지가 이미 구축되어 있고 일부 이미지 PCD를 사용하는 별도의 모듈을 작성해야하는 경우가 있다. 이때, EDKII는 [PcdsDynamicEx] 방식을 제공한다. 이 방식은 GUID+ExTokenNumber의 조합으로 dynamic PCD를 참조한다.
해당 방법으로 dynamic PCD를 참조할 경우 ExTokenNumber는 UINT32 값이며, 두 값은 모든 PCD에 대하여 고유한 값으로 지정되어야 한다. PCD 데이터베이스는 GUID+ExTokenNumber의 요청을 수신하면, 우선 GUID가 데이터베이스에 존재하는지 확인한다.
PCD 데이터베이스 안에는 완전(totally) GuidTableCount GUID가 존재하며, 데이터베이스 파일의 GuidTableOffset에서 시작하여 배치된다. 요청된 GUID가 데이터베이스의 일부 GUID와 동일한 경우, 이 GUID의 인덱스를 기억한다. 그 다음, 코드는 ExMapTableOffset 아래에 있는 ExMapTable의 기록을 확인한다.
완전 ExTokenCount의 기록과 각 기록형식은 다음과 같다.
typedefstruct { UINT32 ExTokenNumber; UINT16 TokenNumber; // local token index in the LocalTokenNumberTable UINT16 ExGuidIndex; // GUID index in the GuidTable} DYNAMICEX_MAPPING;
일부 기록에는 요청된 ExTokenNumber가 있고, 요청된 GUID에 해당하는 ExGuidIndex가 있는데, 우리는 우리의 LocalToken 숫자를 찾은 셈이다. 이제 나머지는 [PcdDynamic]의 경우와 동일하게 진행된다. 그리고 ExTokenCount는 항상 LocalTokenCount보다 작거나 같다. (ExTokenCount ≤ LocalTokenCount)
데이터베이스 코드가 PCD 값을 반환해야 하는 경우, 오프셋 이외의 PCD 자체의 크기를 알아야 한다. 단순한 데이터 유형인 경우에는 크기가 고정된다. 예를 들어, PCD의 pcd_datum_type의 UINT64의 경우 PCD 데이터의 크기는 8바이트이다.
하지만, PCD 데이터는 구조 또는 문자열일 수 있다. 이 경우, StringTable에 대한 인덱스로 해석되는 LocalToken의 오프셋은 StringTableOffset 아래에 나와있다.
이 테이블은 UINT32(데이터에 대한 실제 오프셋)으로 구성된다. 이 경우 데이터의 크기는 SizeTable에 기록되며 이는 SizeTableOffset 아래에 위치하고 다음과 같은 요소로 구성된다.
struct { UINT16 MaxSize; UINT16 CurrentSize;}
다시 한 번 말하지만, 숫자는 암묵적인 방식을 따라 붙인다. 대상 LocalToken의 경우 처음부터 LocalTokenNumberTable을 검사하고, 대상 LocalToken 이전에 몇 개의 문자열/포인터 PCD가 있었는지 계산하여야하며 이 값은 SizeTable 기록의 인덱스가 된다.
AutoGen files
이전 섹션을 기반으로, 데이터베이스 내용을 파싱하는 것이 쉽지 않았다는 것을 알 수 있었다. 이를 위해 EDKII는 AutoGen 파일에서 도움을 제공한다. 예를 들어, 이 내용은 PEI PCD DB AutoGen 파일에 작성된다.
해당 파일의 코드를 읽어보면, PEI_PCD_DATABASE_INIT / PEI_PCD_DATABASE_UNINIT 구조 내부에 주석이 있는 PCD의 내용을 찾을 수 있다. 보다시피 모든 코드가 빌드에 포함되어있지 않으므로, 단지 이것은 개발자를 위한 도움말이라고 생각하면 된다.
AutoGen 파일의 정보는 꽤나 유용하지만, 검색하기가 다소 어렵다. 이를 대비해 PCD_TABLE_parser 유틸리티를 사용할 수 있다.
Clone & Build
$ git clone https://github.com/Kostr/PCD_TABLE_parser.git ~/PCD_TABLE_parser
$ cd ~/PCD_TABLE_parser
$ make
cc -c -o main.o main.c
cc -c -o guids.o guids.c
cc -c -o utils.o utils.c
gcc main.o guids.o utils.o -o parse_pcd_db
--help 명령어를 사용하면, 도움말을 해결할 수 있다.
$ ./parse_pcd_db --help
Usage: parse_pcd_db [--peidb <PEI_PCD_DB.raw>] [--dxedb <DXE_PCD_DB.raw>] [--vpd <VPD.bin>]
Program to parse PCD Database raw files
--peidb <PEI_PCD_DB.raw> - provide PEI PCD database
--dxedb <DXE_PCD_DB.raw> - provide DXE PCD database
--vpd <VPD.bin> - provide VPD binary
Example:
parse_pcd_db \
--peidb "Build/OvmfX64/RELEASE_GCC5/X64/MdeModulePkg/Universal/PCD/Pei/Pcd/OUTPUT/PEIPcdDataBase.raw" \
--dxedb "Build/OvmfX64/RELEASE_GCC5/X64/MdeModulePkg/Universal/PCD/Dxe/Pcd/OUTPUT/DXEPcdDataBase.raw" \
--vpd "Build/OvmfX64/RELEASE_GCC5/FV/8C3D856A-9BE6-468E-850A-24F7A8D38E08.bin"
지금은 --vpd 옵션에 대해서 언급하지는 않겠다. --peidb와 --dxedb 인수를 제공하는 프로그램을 시작한다.
$ parse_pcd_db \
--peidb "/home/user/edk2/Build/OvmfX64/RELEASE_GCC5/X64/MdeModulePkg/Universal/PCD/Pei/Pcd/OUTPUT/PEIPcdDataBase.raw" \
--dxedb "/home/user/edk2/Build/OvmfX64/RELEASE_GCC5/X64/MdeModulePkg/Universal/PCD/Dxe/Pcd/OUTPUT/DXEPcdDataBase.raw" | less
이어지는 내용은 출력에 대한 예이다. 아마 방금 언급했던 PCD 테이블에 대한 설명에서 더 잘 설명되어 있다고 생각한다. 출력에 대한 몇 가지 설명을 해보자면,
PEI와 DXE 데이터베이스 사이의 순차적 넘버링을 볼 수 있다.
0-unitialized는 PCD가 Length ~ (Length + UninitDataBaseSize) 영역에 있음을 의미한다.
일부 PCD에는 DynamicExToken/GUID 정보가 포함되어있다. 이는 메소드를 통해 선언된 PCD이다.
포인터 유형의 PCD에는 크기 정보가 포함되어 있다.
PEI PCD DB
LocalTokenNumberTable:
1:
Token type = Data
Datum type = UINT8 (Bool)
0 - unitialized
2:
Token type = Data
Datum type = UINT64
0 - unitialized
3:
Token type = Data
Datum type = UINT64
0 - unitialized
4:
Token type = Data
Datum type = UINT64
0 - unitialized
5:
Token type = Data
Datum type = UINT64
0 - unitialized
6:
Token type = Data
Datum type = UINT8 (Bool)
0 - unitialized
7:
Token type = Data
Datum type = UINT32
0 - unitialized
8:
Token type = Data
Datum type = UINT32
0 - unitialized
9:
Token type = Data
Datum type = UINT32
Value:
0x00000040 (=64)
10:
Token type = Data
Datum type = UINT8 (Bool)
0 - unitialized
11:
Token type = Data
Datum type = UINT16
0 - unitialized
12:
Token type = Data
Datum type = UINT64
0 - unitialized
13:
Token type = Data
Datum type = UINT64
0 - unitialized
14:
Token type = Data
Datum type = UINT64
0 - unitialized
15:
Token type = Data
Datum type = UINT64
0 - unitialized
16:
Token type = Data
Datum type = UINT64
0 - unitialized
17:
Token type = Data
Datum type = UINT64
Value:
0x0000000800000000 (=34359738368)
18:
Token type = Data
Datum type = UINT8 (Bool)
0 - unitialized
19:
Token type = Data
Datum type = UINT16
Value:
0x0008 (=8)
20:
Token type = String
Datum type = Pointer
DynamicEx Token = 0x00030005
DynamicEx GUID = a1aff049-fdeb-442a-b32013ab4cb72bbc [gEfiMdeModulePkgTokenSpaceGuid]
CurrentSize = 1
MaxSize = 1
Value:
00 | .
21:
Token type = Data
Datum type = UINT16
DynamicEx Token = 0x00030004
DynamicEx GUID = a1aff049-fdeb-442a-b32013ab4cb72bbc [gEfiMdeModulePkgTokenSpaceGuid]
0 - unitialized
22:
Token type = Data
Datum type = UINT64
DynamicEx Token = 0x00030006
DynamicEx GUID = a1aff049-fdeb-442a-b32013ab4cb72bbc [gEfiMdeModulePkgTokenSpaceGuid]
0 - unitialized
_____
DXE PCD DB
LocalTokenNumberTable:
23:
Token type = Data
Datum type = UINT32
0 - unitialized
24:
Token type = Data
Datum type = UINT32
0 - unitialized
25:
Token type = Data
Datum type = UINT64
0 - unitialized
26:
Token type = Data
Datum type = UINT64
0 - unitialized
27:
Token type = Data
Datum type = UINT64
0 - unitialized
28:
Token type = Data
Datum type = UINT32
Value:
0x00000280 (=640)
29:
Token type = Data
Datum type = UINT32
Value:
0x000001e0 (=480)
30:
Token type = Data
Datum type = UINT8
0 - unitialized
31:
Token type = Data
Datum type = UINT16
Value:
0x0208 (=520)
32:
Token type = Data
Datum type = UINT8 (Bool)
0 - unitialized
33:
Token type = Data
Datum type = UINT32
Value:
0x00000320 (=800)
34:
Token type = Data
Datum type = UINT32
Value:
0x00000258 (=600)
35:
Token type = Data
Datum type = UINT16
0 - unitialized
36:
Token type = Data
Datum type = UINT8
Value:
0x01 (=1)
37:
Token type = Data
Datum type = UINT8
Value:
0x01 (=1)
38:
Token type = Data
Datum type = UINT64
0 - unitialized
39:
Token type = Data
Datum type = UINT8 (Bool)
0 - unitialized
40:
Token type = Data
Datum type = UINT8 (Bool)
0 - unitialized
_____
이는 Pei/Pcd.inf 파일의 모듈 이름이다. 오른쪽에 보이는 Information 부분에서 Body Size는 어느 정도 OVMF.fd에 구현되어있는지에 따라 다르지만, 우리가 실제로 빌드하여 qemu로 실행한 것과 동일한 크기이다. (hexdump를 통해 본 것과 같다.)
섹션 이름에 마우스 오른쪽 버튼을 클릭하고, Extract Body...를 선택하면섹션 데이터를 저장할 수 있다. 기본적으로 프로그램은 Section_Raw_PcdPeim_PcdPeim_body.raw를 새 파일의 이름으로 제안한다.