63. checkbox를 가진 HII 폼 만들기

파트 1: checkbox 요소 및 efivarstore 요소에 대한 VFR 코드

이제 사용자 입력을 폼에 실제로 저장해보자.

데이터의 경우 가장 간단한 요소를 선택한다. - checkbox (https://edk2-docs.gitbook.io/edk-ii-vfr-specification/2_vfr_description_in_bnf/211_vfr_form_definition#2.11.6.5.1-vfr-checkbox-statement-definition)

시작점

전에 만들었던 HIIStaticFormDriver 애플리케이션의 코드를 기반으로 하는 애플리케이션 HIIFormCheckbox를 생성해 보자.

UefiLessonsPkg/HIIFormCheckbox/HIIFormCheckbox.inf

[Defines]
  INF_VERSION                    = 1.25
  BASE_NAME                      = HIIFormCheckbox
  FILE_GUID                      = 771a4631-43ba-4852-9593-919d9de079f1
  MODULE_TYPE                    = UEFI_DRIVER
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = HIIFormCheckboxEntryPoint
  UNLOAD_IMAGE                   = HIIFormCheckboxUnload

[Sources]
  HIIStaticFormDriver.c
  Strings.uni
  Form.vfr

[Packages]
  MdePkg/MdePkg.dec
  MdeModulePkg/MdeModulePkg.dec

[LibraryClasses]
  UefiDriverEntryPoint
  UefiLib
  HiiLib

UefiLessonsPkg/HIIFormCheckbox/HIIFormCheckbox.c

#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Library/HiiLib.h>


extern UINT8 FormBin[];

EFI_HII_HANDLE  mHiiHandle = NULL;


EFI_STATUS
EFIAPI
HIIFormCheckboxUnload (
  EFI_HANDLE ImageHandle
  )
{
  if (mHiiHandle != NULL)
    HiiRemovePackages(mHiiHandle);

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
HIIFormCheckboxEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  mHiiHandle = HiiAddPackages(
                 &gEfiCallerIdGuid,
                 NULL,
                 HIIFormCheckboxStrings,
                 FormBin,
                 NULL
                 );
  if (mHiiHandle == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  return EFI_SUCCESS;
}

UefiLessonsPkg/HIIFormCheckbox/Strings.uni

#langdef en-US "English"

#string FORMSET_TITLE          #language en-US  "Simple Formset"
#string FORMSET_HELP           #language en-US  "This is a very simple formset"
#string FORMID1_TITLE          #language en-US  "Simple Form"

UefiLessonsPkg/HIIFormCheckbox/Form.vfr

#include <Uefi/UefiMultiPhase.h>

#define FORMSET_GUID  {0xef2acc91, 0x7b50, 0x4ab9, {0xab, 0x67, 0x2b, 0x4, 0xf8, 0xbc, 0x13, 0x5e}}

formset
  guid     = FORMSET_GUID,
  title    = STRING_TOKEN(FORMSET_TITLE),
  help     = STRING_TOKEN(FORMSET_HELP),

  form
    formid = 1,
    title = STRING_TOKEN(FORMID1_TITLE);
  endform;
endformset;

이제 드라이버 빌드 후 load HIIFormCheckbox 명령으로 로드할 수 있다.

FS0:\> load HIIFormCheckbox.efi
Image 'FS0:\HIIFormCheckbox.efi' loaded at 688B000 - Success

ShowHII.efi 애플리케이션으로 HII 리소스가 생성되었는지 확인한다.

FS0:\> ShowHII.efi
...
PackageList[20]: GUID=771A4631-43BA-4852-9593-919D9DE079F1; size=0x114
        Package[0]: type=FORMS; size=0x41
        Package[1]: type=STRINGS; size=0xBB
        Package[2]: type=END; size=0x4

그 후 DisplayHIIByGuid.efi 애플리케이션을 사용하면 다음 폼을 확인 할 수 있다.

FS0:\> DisplayHIIByGuid.efi 771A4631-43BA-4852-9593-919D9DE079F1

checkbox 요소 추가하기

이제 새로운 기능을 추가해 보자.

checkbox
  prompt = STRING_TOKEN(CHECKBOX_PROMPT),
  help = STRING_TOKEN(CHECKBOX_HELP),
endcheckbox;

그리고 UNI 파일에서 문자열을 정의한다.

#string CHECKBOX_PROMPT        #language en-US  "Checkbox prompt"
#string CHECKBOX_HELP          #language en-US  "Checkbox help"

그러면 폼에 다음 요소가 제공된다.

스페이스바로 항목을 전환할 수 있다.

하지만 현재 폼은 데이터를 저장하지 않는다. 어떤 상태에서든 폼 종료 시 checkbox를 그대로 두더라도 폼을 다시 시작하면 checkbox가 지워진다. 따라서 지금 우리의 폼은 쓸모가 없다.

IFR 코드

새 요소의 IFR 코드를 조사해 보자(Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/HIIFormCheckbox/HIIFormCheckbox/DEBUG/Form.lst).

Checkbox 요소는 EFI_IFR_CHECKBOXEFI_IFR_END opcode를 생성한다.

    checkbox
>00000039: 06 8E 05 00 06 00 01 00 00 00 FF FF 00 00
      prompt = STRING_TOKEN(0x0005),
      help = STRING_TOKEN(0x0006),
    endcheckbox;
>00000047: 29 02

다음은 EFI_IFR_CHECKBOX에 대한 정의다.

EFI_IFR_CHECKBOX

Summary:
Creates a boolean checkbox.

Prototype:

#define EFI_IFR_CHECKBOX_OP 0x06

typedef struct _EFI_IFR_CHECKBOX {
 EFI_IFR_OP_HEADER Header;
 EFI_IFR_QUESTION_HEADER Question;
 UINT8 Flags;
} EFI_IFR_CHECKBOX;

Members:
Header 		The standard question header, where Header.OpCode = EFI_IFR_CHECKBOX_OP.
Question 	The standard question header.
Flags 		Flags that describe the behavior of the question. All undefined bits should be zero.

Description:
Creates a Boolean checkbox question and adds it to the current form. The checkbox has two values:
FALSE if the box is not checked and TRUE if it is. 

잊어버린 경우를 대비하여 EFI_IFR_OP_HEADER는 2바이트를 사용하므로 06 8E 05 00 06 00 01 00 00 00 FF FF 00 00에서 06 8E를 사용한다.

typedef struct _EFI_IFR_OP_HEADER {
 UINT8 OpCode;
 UINT8 Length:7;
 UINT8 Scope:1;
} EFI_IFR_OP_HEADER;

이제 나머지 05 00 06 00 01 00 00 00 FF FF 00 00 데이터로 표시되는 EFI_IFR_QUESTION_HEADER 필드를 확인해보자.

EFI_IFR_QUESTION_HEADER

Summary:
Standard question header.

Prototype:

typedef struct _EFI_IFR_QUESTION_HEADER {
 EFI_IFR_STATEMENT_HEADER Header;
 EFI_QUESTION_ID QuestionId;
 EFI_VARSTORE_ID VarStoreId;
 union {
  EFI_STRING_ID VarName;
  UINT16 VarOffset;
 } VarStoreInfo;
 UINT8 Flags;
} EFI_IFR_QUESTION_HEADER;

Members:
Header 		The standard statement header.
QuestionId 	The unique value that identifies the particular question being defined by the opcode. The value of zero is reserved.
Flags 		A bit-mask that determines which unique settings are active for this question.
VarStoreId 	Specifies the identifier of a previously declared variable store to use when storing the question’s value.
		A value of zero indicates no associated variable store.
VarStoreInfo 	If VarStoreId refers to Buffer Storage (EFI_IFR_VARSTORE or EFI_IFR_VARSTORE_EFI), then VarStoreInfo contains a 16-bit Buffer Storage offset (VarOffset).
		If VarStoreId refers to Name/Value Storage (EFI_IFR_VARSTORE_NAME_VALUE), then VarStoreInfo contains the String ID of the name (VarName) for this name/value pair.

Description:
This is the standard header for questions.

먼저 EFI_IFR_STATEMENT_HEADER이다.

EFI_IFR_STATEMENT_HEADER

Summary:
Standard statement header.

Prototype:
typedef struct _EFI_IFR_STATEMENT_HEADER {
 EFI_STRING_ID Prompt;
 EFI_STRING_ID Help;
} EFI_IFR_STATEMENT_HEADER;

Members:
Prompt 	The string identifier of the prompt string for this particular statement. The value 0 indicates no prompt string.
Help 	The string identifier of the help string for this particular statement. The value 0 indicates no help string.

Description:
This is the standard header for statements, including questions

https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Uefi/UefiInternalFormRepresentation.h에 따르면,

typedef UINT16  EFI_STRING_ID;

따라서 기본적으로 다음을 의미한다(05 00 06 00 01 00 00 00 FF FF 00 00 데이터가 남아있다는 점을 기억하자).

EFI_STRING_ID Prompt = 00 05
EFI_STRING_ID Help = 00 06

이것은 HII String 패키지의 문자열에 대한 참조일 뿐이다.

이제 01 00 00 00 FF FF 00 00 코드가 남아있다.

동일한 UefiInternalFormRepresentation.h 파일에 따르면,

typedef UINT16  EFI_QUESTION_ID;
typedef UINT16  EFI_VARSTORE_ID;

따라서 데이터를 필드와 계속 일치시키면 다음과 같은 결과를 얻게 된다.

EFI_QUESTION_ID QuestionId;   = 00 01
EFI_VARSTORE_ID VarStoreId;   = 00 00
union {
 EFI_STRING_ID VarName;       = FF FF
 UINT16 VarOffset;
} VarStoreInfo;
UINT8 Flags;                  = 00

그리고 EFI_IFR_CHECKBOX 구조체 끝의 UINT8 Flags 필드가 남아 있는 마지막 00바이트이다.

이미 우리 문제의 원인을 추측할 수 있을 것이다. 무언가를 저장하려면 변수 저장소에 대한 링크를 만들어야한다. 하지만 현재는 VarStoreId = 0이기 때문에 위의 문서에 따르면 연결된 변수 저장소가 없음을 의미한다.

몇 가지 가능한 변수 저장소가 있다. 폼 제출 시 콜백 논리를 생성하여 변수 저장소를 전혀 사용하지 않을 수 도 있다. 무언가를 정해서 시작해야 하므로 이번 장에서는 가장 간단한 스토리지인 efivarstore를 살펴보자.

VFR에 efivarstore 추가하기

efivarstore는 간단한 UEFI 변수 저장소이다. 기본적으로 UEFI 변수에 대한 GUID+name조합과 유형 그리고 플래그를 제공해야 한다.

(https://edk2-docs.gitbook.io/edk-ii-vfr-specification/2_vfr_description_in_bnf/27_vfr_variable_store_definition#2.7.2-vfr-efi-variable-store-definition)

예를 들어 시스템에서 이름이 MyVariableName이고 EFI_VARIABLE_BOOTSERVICE_ACCESS 플래그가 있는 MyVariableGuid 아래에 있는 특수한 MyCustomStructure 유형으로 UEFI 변수를 나타내려면 다음과 같이 선언해야한다.

efivarstore MyCustomStructure
  attribute = EFI_VARIABLE_BOOTSERVICE_ACCESS,
  name      = MyVariableName,
  guid      = MyVariableGuid;

우리는 현재 이렇게 특별한 구조가 필요하지 않다. 간단하게 UINT8이 우리가 만들려는 Checkbox에 적합하다.

EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE 속성을 가진 변수를 선언하고 이름이 CheckboxValueFORMSET_GUID 아래에 배치하자.

Checkbox 값을 이 변수와 연결하려면 varid = CheckboxValue 구문을 사용하면 된다.

formset
  guid     = FORMSET_GUID,
  title    = STRING_TOKEN(FORMSET_TITLE),
  help     = STRING_TOKEN(FORMSET_HELP),

+ efivarstore UINT8,
+   attribute = EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE,
+   name  = CheckboxValue,
+   guid  = FORMSET_GUID;

  form
    formid = 1,
    title = STRING_TOKEN(FORMID1_TITLE);
    checkbox
+     varid = CheckboxValue,
      prompt = STRING_TOKEN(CHECKBOX_PROMPT),
      help = STRING_TOKEN(CHECKBOX_HELP),
    endcheckbox;
  endform;
endformset;

IFR 변경 사항 확인

이 새로운 VFR 코드가 IFR 구조를 어떻게 변경했는지 조사해보자.

먼저 우리는 efivarstore를 추가했고, 그 결과 다음 코드가 생성되었다.

  efivarstore UINT8,
>00000033: 26 28 01 00 91 CC 2A EF 50 7B B9 4A AB 67 2B 04 F8 BC 13 5E 03 00 00 00 01 00 43 68 65 63 6B 62 6F 78 56 61 6C 75 65 00
    attribute = 0x00000002 | 0x00000001,
    name = CheckboxValue,
    guid = {0xef2acc91, 0x7b50, 0x4ab9, {0xab, 0x67, 0x2b, 0x4, 0xf8, 0xbc, 0x13, 0x5e}};

이것은 다음의 뜻인 opcode 0x26으로 시작한다.

#define EFI_IFR_VARSTORE_EFI_OP         0x26

따라서 처음 2바이트는 EFI_IFR_OP_HEADER이다.

나머지 데이터는 EFI_IFR_VARSTORE_EFI 구조체이다.

EFI_IFR_VARSTORE_EFI

Summary:
Creates a variable storage short-cut for EFI variable storage.

Prototype:
#define EFI_IFR_VARSTORE_EFI_OP 0x26

typedef struct _EFI_IFR_VARSTORE_EFI {
 EFI_IFR_OP_HEADER Header;
 EFI_VARSTORE_ID VarStoreId;
 EFI_GUID Guid;
 UINT32 Attributes
 UINT16 Size;
 //UINT8 Name[];
} EFI_IFR_VARSTORE_EFI;

Members:
Header 		The byte sequence that defines the type of opcode as well as the length of the opcode being defined. For this tag,
		Header.OpCode = EFI_IFR_VARSTORE_EFI_OP.
VarStoreId 	The variable store identifier, which is unique within the current form set. This field is the value that uniquely identifies
		this variable store definition instance from others. Question headers refer to this value to designate which is the active
		variable that is being used. A value of zero is invalid.
Guid 		The EFI variable’s GUID definition. This field comprises one half of the EFI variable name, with the other half being the
		human-readable aspect of the name, which is specified in the Name field below.
Attributes	Specifies the flags to use for the variable.
Size		The size of the variable store.
Name		A null-terminated ASCII string that specifies one half of the EFI name for this variable store. The other half is specified in
		the Guid field (above). The Name field is not actually included in the structure but is included here to help illustrate the
		encoding of the opcode. The size of the string, including the null termination, is included in the opcode's header size.

Description:
This opcode describes an EFI Variable Variable Store within a form set. The Guid and Name specified here will be used with GetVariable() and SetVariable().

바이너리 데이터가 구조체 필드를 채우는 방식을 살펴보자.

EFI_VARSTORE_ID VarStoreId    // 01 00
EFI_GUID Guid;                // 91 CC 2A EF 50 7B B9 4A AB 67 2B 04 F8 BC 13 5E (= FORMSET_GUID) 
UINT32 Attributes             // 03 00 00 00
UINT16 Size;                  // 01 00
//UINT8 Name[];               // 43 68 65 63 6B 62 6F 78 56 61 6C 75 65 00  (= "C h e c k b o x V a l u e") 

보다시피 코드는 UEFI 변수에 대한 GUID+ name 조합과 해당 유형 및 플래그를 제공한다.

이제 이 저장소를 참조하는 방식을 알아보자. 우리는 폼에 varid = CheckboxValue를 추가했었다.

>00000061: 06 8E 05 00 06 00 01 00 01 00 00 00 00 00
      varid = CheckboxValue,
      prompt = STRING_TOKEN(0x0005),
      help = STRING_TOKEN(0x0006),
    endcheckbox;

기본적으로 데이터는 다음과 같이 변경되었다.

- 06 8E 05 00 06 00 01 00 00 00 FF FF 00 00
+ 06 8E 05 00 06 00 01 00 01 00 00 00 00 00

이는 다음을 의미한다.

                                  -          +                              
EFI_VARSTORE_ID VarStoreId;   = 00 00	// 01 00
union {
 EFI_STRING_ID VarName;       = FF FF	// 00 00
 UINT16 VarOffset;
} VarStoreInfo;

이제 checkbox 코드는 id = 1(위에서 선언한 저장소의 id)인 varstore를 가리킨다.

varstore가 있는 HII Checkbox 폼

애플리케이션 폼을 다시 보면 아래쪽 패널에 몇 가지 새로운 도움말 메시지가 추가된 것을 알 수 있다.

  • F9=Reset to Defaults

  • F10=Save

이제 구성을 변경하면 구성 변경 메시지가 나타난다.

설정을 저장하려면 하단 패널에 설명된 것처럼 F10을 사용하면 된다. 그러면 다음과 같은 확인 창이 생성된다.

그러나 Y를 누르려고 하면 다음 메시지와 함께 실패한다.

제대로 작동하게 하려면 드라이버 코드에 몇 가지 수정 사항을 추가해야 한다.

Last updated