이번 장에서는 HII(Human Interface Infrastructure)에 대해 학습한다.
모든 BIOS는 사용자와 어떤 형태로든 상호 작용이 있다. 예로 부팅 시 표시되는 이미지, 부팅 시 특수 키를 통해 BIOS 설정을 제어하는 메뉴 선택으로의 진입 등이다. 이러한 형식의 텍스트는 사용자 지정 글꼴을 적용할 수 있다. 그리고 BIOS에서 언어 설정을 변경할 경우 인터페이스의 모든 문자열이 변경된 언어로 번역되어야 한다.
HII의 주요 목표는 이러한 휴먼 인터페이스의 부분을 쉽게 검색하고 확장하기 위해 표준화 된 인터페이스를 제공하는 것이다. HII의 외부 드라이버/애플리케이션을 사용해 새로운 요소(예: 글꼴, 문자열, 이미지 또는 양식)를 플랫폼에 쉽게 설치하거나 존재하는 요소를 내부에 반영할 수 있다.
이는 모든 HII 데이터가 전체 플랫폼의 중앙 저장소 역할을 하는 특수 데이터베이스인 HII 데이터베이스에 저장되기 때문에 가능하다.
결국 Form Browser 는 HII 데이터베이스를 사용하여 사용자 인터페이스를 표시하고 사용자와 상호 작용하는 것이다.
#!/bin/bash
##
# Copyright (c) 2021, Konstantin Aladyshev <aladyshev22@gmail.com>
#
# SPDX-License-Identifier: MIT
##
# This is a simple script that creates a basic structure for your new UEFI application
# Put this script in your edk2 folder and run it with 1 argument - your new application name
APP_NAME=${1}
UUID=$(uuidgen)
mkdir -p UefiLessonsPkg/${APP_NAME}
cat << EOF > UefiLessonsPkg/${APP_NAME}/${APP_NAME}.inf
[Defines]
INF_VERSION = 1.25
BASE_NAME = ${APP_NAME}
FILE_GUID = ${UUID}
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 1.0
ENTRY_POINT = UefiMain
[Sources]
${APP_NAME}.c
[Packages]
MdePkg/MdePkg.dec
[LibraryClasses]
UefiApplicationEntryPoint
UefiLib
EOF
cat << EOF > UefiLessonsPkg/${APP_NAME}/${APP_NAME}.c
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
return EFI_SUCCESS;
}
EOF
edk2 폴더에 해당 스크립트를 작성하고 새 애플리케이션 ShowHII 를 생성한다.
$ ./createNewApp.sh ShowHII
이렇게 하면 *.inf 파일 및 *.c 을 포함한 UefiLessonPkg/ShowHII 폴더가 생성된다. 새로 만들 애플리케이션을 UefiLessonsPkg/UefiLessonsPkg.dsc 의components section에 추가한다.
EFI_HII_DATABASE_PROTOCOL.ExportPackageLists()
Summary:
Exports the contents of one or all package lists in the HII database into a buffer.
Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_HII_DATABASE_EXPORT_PACKS) (
IN CONST EFI_HII_DATABASE_PROTOCOL *This,
IN EFI_HII_HANDLE Handle,
IN OUT UINTN *BufferSize,
OUT EFI_HII_PACKAGE_LIST_HEADER *Buffer
);
Parameters:
This A pointer to the EFI_HII_DATABASE_PROTOCOL instance.
Handle An EFI_HII_HANDLE that corresponds to the desired package list in the HII
database to export or NULL to indicate all package lists should be exported.
BufferSize On input, a pointer to the length of the buffer. On output, the length of the buffer
that is required for the exported data.
Buffer A pointer to a buffer that will contain the results of the export function.
HII 데이터베이스의 데이터가 GUID로 식별되는 패키지 목록으로 구성되기 때문에 이 함수를 통해 패키지 목록을 얻을 수 있다. 각 패키지 목록은 서로 다른 유형의 여러 패키지(form/font/strings/...)를 가질 수도 있다.
먼저 작성하는 애플리케이션에 EFI_HII_DATABASE_PROTOCOL 를 가져오고 필요한 헤더를 포함시킨다.
#include <Protocol/HiiDatabase.h>
...
EFI_STATUS Status;
EFI_HII_DATABASE_PROTOCOL* HiiDbProtocol;
Status = gBS->LocateProtocol(&gEfiHiiDatabaseProtocolGuid,
NULL,
(VOID**)&HiiDbProtocol);
if (EFI_ERROR(Status)) {
Print(L"ERROR: Could not find HII Database protocol: %r\n", Status);
return Status;
}
또한 ShowHII.inf 파일에 gEfiHiiDatabaseProtocolGuid 정보도 작성한다.
[Protocols]
gEfiHiiDatabaseProtocolGuid
다음은 프로토콜의 ExportPackageLists 함수를 사용하기 위한 정보이다.
데이터베이스에서 일부 패키지 목록이 아닌 모든 패키지 목록을 가져오려면 Handle 매개변수에 NULL 값을 사용한다.
출력 배열의 크기를 미리 알 수 없기 때문에 표준 UEFI 메커니즘을 이용한다.
먼저 BufferSize=0 으로 ExportPackageLists 를 호출한다. 반환 값으로 EFI_BUFFER_TOO_SMALL 을 받지만 BufferSize 을 필요한 크기 값으로 재설정 받을 수 있다.
이후 gBS->AllocatePool 의 호출로 필요한 크기의 버퍼를 할당한다.
올바른 버퍼와 크기로 ExportPackageLists 를 호출한다.
할당된 버퍼를 해제할 경우 Library/MemoryAllocationLib.h 에 존재하는 FreePool 함수를 사용하면 된다.
UINTN PackageListSize = 0;
EFI_HII_PACKAGE_LIST_HEADER* PackageList = NULL;
Status = HiiDbProtocol->ExportPackageLists(HiiDbProtocol,
NULL, // All package lists
&PackageListSize,
PackageList);
if (Status != EFI_BUFFER_TOO_SMALL) {
Print(L"ERROR: Could not obtain package list size\n");
return Status;
}
Status = gBS->AllocatePool(EfiBootServicesData,
PackageListSize,
(VOID**)&PackageList);
if (EFI_ERROR(Status)) {
Print(L"ERROR: Could not allocate sufficient memory for package list: %r\n", Status);
return Status;
}
Status = HiiDbProtocol->ExportPackageLists(HiiDbProtocol,
NULL,
&PackageListSize,
PackageList);
if (EFI_ERROR(Status)) {
Print(L"ERROR: Could not retrieve the package list: %r\n", Status);
FreePool(PackageList);
return Status;
}
// <Process data>
FreePool(PackageList);
수집한 데이터는 각각 EFI_HII_PACKAGE_LIST_HEADER 라는 특별한 헤더가 존재하는 패키지 목록들이다. 해당 데이터를 처리해보자.
EFI_HII_PACKAGE_LIST_HEADER
Summary:
The header found at the start of each package list.
Prototype:
typedef struct {
EFI_GUID PackageListGuid;
UINT32 PackagLength;
} EFI_HII_PACKAGE_LIST_HEADER;
Members:
PackageListGuid The unique identifier applied to the list of packages which follows.
PackageLength The size of the package list (in bytes), including the header.
데이터를 파싱하고 패키지 목록에 존재하는 모든 정보를 출력하기 위해서는 반복문이 존재하는 함수를 작성해야 한다.
다음으로 PackageLists 를 살펴보자. 각 PackageList 는 EFI_HII_PACKAGE_LIST_HEADER 바로 다음에 시작하는 여러 패키지들로 구성되어 있다. 그리고 각 패키지에는 자체 EFI_HII_PACKAGE_HEADER 가 존재한다.
EFI_HII_PACKAGE_HEADER
Summary:
The header found at the start of each package.
Prototype:
typedef struct {
UINT32 Length:24;
UINT32 Type:8;
UINT8 Data[ … ];
} EFI_HII_PACKAGE_HEADER;
Members:
Length The size of the package in bytes.
Type The package type. See EFI_HII_PACKAGE_TYPE_x, below.
다음은 사용 가능한 일부 패키지 유형에 대한 설명이다.
Package Type Description:
#define EFI_HII_PACKAGE_TYPE_ALL 0x00 // Pseudo-package type used when exporting package lists.
#define EFI_HII_PACKAGE_TYPE_GUID 0x01 // Package type where the format of the data
// is specified using a GUID immediately
// following the package header.
#define EFI_HII_PACKAGE_FORMS 0x02 // Forms package.
#define EFI_HII_PACKAGE_STRINGS 0x04 // Strings package
#define EFI_HII_PACKAGE_FONTS 0x05 // Fonts package.
#define EFI_HII_PACKAGE_IMAGES 0x06 // Images package.
#define EFI_HII_PACKAGE_SIMPLE_FONTS 0x07 // Simplified (8x19, 16x19) Fonts package
#define EFI_HII_PACKAGE_DEVICE_PATH 0x08 // Binary-encoded device path.
#define EFI_HII_PACKAGE_ANIMATIONS 0x0A // Animations package.
#define EFI_HII_PACKAGE_END 0xDF // Used to mark the end of a package list.
패키지 목록에는 필요한 만큼의 많은 패키지가 있을 수 있으며 모두 연결되어 있다. 패키지 목록의 모든 데이터 패키지 다음에는 패키지 목록의 끝을 표시하는 EFI_HII_PACKAGE_END 유형의 패키지가 존재해야 한다.
따라서 이 정보를 이용하여 패키지 목록 내의 모든 패키지 정보를 파싱하는 코드를 추가해보자.
EFI_HII_PACKAGE_HEADER* HiiPackageHeader = (EFI_HII_PACKAGE_HEADER*)((UINTN) HiiPackageListHeader + sizeof(EFI_HII_PACKAGE_LIST_HEADER));
UINTN j=0;
while ((UINTN) HiiPackageHeader < ((UINTN) HiiPackageListHeader + HiiPackageListSize)) {
Print(L"\tPackage[%d]: type=%s; size=0x%X\n", j++, PackageType(HiiPackageHeader->Type), HiiPackageHeader->Length);
// Go to next Package
HiiPackageHeader = (EFI_HII_PACKAGE_HEADER*)((UINTN) HiiPackageHeader + HiiPackageHeader->Length);
}
그리고 PackageType 라는 패키지 유형 값을 출력가능한 문자열로 변환하는 함수를 작성하자.
CHAR16* PackageType(UINTN Type)
{
switch(Type) {
case EFI_HII_PACKAGE_TYPE_ALL:
return L"ALL";
case EFI_HII_PACKAGE_TYPE_GUID:
return L"GUID";
case EFI_HII_PACKAGE_FORMS:
return L"FORMS";
case EFI_HII_PACKAGE_STRINGS:
return L"STRINGS";
case EFI_HII_PACKAGE_FONTS:
return L"FONTS";
case EFI_HII_PACKAGE_IMAGES:
return L"IMAGES";
case EFI_HII_PACKAGE_SIMPLE_FONTS:
return L"SIMPLE_FONTS";
case EFI_HII_PACKAGE_DEVICE_PATH:
return L"DEVICE_PATH";
case EFI_HII_PACKAGE_KEYBOARD_LAYOUT:
return L"KEYBOARD_LAYOUT";
case EFI_HII_PACKAGE_ANIMATIONS:
return L"ANIMATIONS";
case EFI_HII_PACKAGE_END:
return L"END";
case EFI_HII_PACKAGE_TYPE_SYSTEM_BEGIN:
return L"SYSTEM_BEGIN";
case EFI_HII_PACKAGE_TYPE_SYSTEM_END:
return L"SYSTEM_END";
}
return L"UNKNOWN";
}
위 두 가지를 추가하여 다시 애플리케이션을 빌드하고 실행하면 다음과 같은 결과를 얻을 수 있다.