73. VFR question 기본값 설정

default/defaultstore/resetbutton

요소의 기본 값 설정

HIIFormDataElements 애플리케이션에 기본 값을 추가해보자.

defualt 키워드를 사용하여 요소에 기본 값을 설정할 수 있다.

checkbox
  ...
  default = TRUE,
endcheckbox;

numeric
  ...
  default = 7,
endnumeric;

string
  ...
  default = STRING_TOKEN(STRING_DEFAULT),
endstring;

date
  ...
  default = 2021/05/22,
enddate;

time
  ...
  default = 23:55:33,
endtime;

oneof
  ...
  default = 0x33,
endoneof;

orderedlist
  ...
  default = {0x0c, 0x0b, 0x0a},
endlist;

새로 추가된 문자열 토큰 정보는 반드시 String.uni 파일에 추가하자.

#string STRING_DEFAULT         #language en-US  "String default"

형식을 로드한 직후 orderedlist 요소의 순서가 기본 설정 값으로 변경을 진행하기 전에 이미 기본 값으로 변경된 것을 볼 수 있다. 조금 이상할 수도 있지만 표준 값인 0x000000 이 요소에 영향을 주지 않기 때문이라는 정도로만 생각하자.

어쨌든 F9 키를 사용하여 값을 기본 값으로 재 설정해보자.

아래와 같은 확인 창이 나타난다.

그리고 Y 를 눌러 형식에 변경된 값을 확인해보자

IFR

IFR 코드를 확인해보면 Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/HIIFormDataElements/HIIFormDataElements/DEBUG/Form.lst

   checkbox
      ...
      default = TRUE,
>0000006A: 5B 06 00 00 00 01
    endcheckbox;

    numeric
      ...
      default = 7,
>00000086: 5B 07 00 00 01 07 00
    endnumeric;

    string
      ...
      default = STRING_TOKEN(0x001A),
>0000009F: 5B 07 00 00 07 1A 00
    endstring;

    date
      ...
      default = 2021/05/22,
>000000B6: 5B 09 00 00 06 E5 07 05 16
    enddate;

    time
      ...
      default = 23:55:33,
>000000CF: 5B 08 00 00 05 17 37 21
    endtime;

    oneof
      ...
      default = 0x33,
>000000FF: 5B 06 00 00 00 33
    endoneof;

    orderedlist
      ...
      default = {0x0c, 0x0b, 0x0a},
>0000012B: 5B 08 00 00 0B 0C 0B 0A
    endlist;

EFI_IFR_DEFAULT opcode를 통해 모든 기본 값이 표시되는 것을 볼 수 있다.

EFI_IFR_DEFAULT

Summary:
Provides a default value for the current question

Prototype:
#define EFI_IFR_DEFAULT_OP 0x5b

typedef struct _EFI_IFR_DEFAULT {
 EFI_IFR_OP_HEADER Header;
 UINT16 DefaultId;
 UINT8 Type;
 EFI_IFR_TYPE_VALUE Value;
} EFI_IFR_DEFAULT;

typedef struct _EFI_IFR_DEFAULT_2 {
 EFI_IFR_OP_HEADER Header;
 UINT16 DefaultId;
 UINT8 Type;
} EFI_IFR_DEFAULT_2;

Members:
Header    The sequence that defines the type of opcode as well as the length of the opcode being defined.
          For this tag, Header.OpCode = EFI_IFR_DEFAULT_OP.
DefaultId Identifies the default store for this value. The default store must have previously been created using EFI_IFR_DEFAULTSTORE.
Type      The type of data in the Value field. See EFI_IFR_TYPE_x in EFI_IFR_ONE_OF_OPTION.
Value     The default value. The actual size of this field depends on Type. If Type is EFI_IFR_TYPE_OTHER, then the default value
          is provided by a nested EFI_IFR_VALUE.

Description:
This opcode specifies a default value for the current question. There are two forms. The first (EFI_IFR_DEFAULT) assumes that the default value is a constant, embedded directly in the Value member. The second (EFI_IFR_DEFAULT_2) assumes that the default value is specified using a nested EFI_IFR_VALUE opcode.

위 모든 구문은 값 필드가 포함된 구조체의 첫 번째 형식(EFI_IFR_DEFAULT)을 사용한다. 가능한 값 유형은 다음과 같다.

#define EFI_IFR_TYPE_NUM_SIZE_8    0x00
#define EFI_IFR_TYPE_NUM_SIZE_16   0x01
#define EFI_IFR_TYPE_NUM_SIZE_32   0x02
#define EFI_IFR_TYPE_NUM_SIZE_64   0x03
#define EFI_IFR_TYPE_BOOLEAN       0x04
#define EFI_IFR_TYPE_TIME          0x05
#define EFI_IFR_TYPE_DATE          0x06
#define EFI_IFR_TYPE_STRING        0x07
#define EFI_IFR_TYPE_OTHER         0x08
#define EFI_IFR_TYPE_UNDEFINED     0x09
#define EFI_IFR_TYPE_ACTION        0x0A
#define EFI_IFR_TYPE_BUFFER        0x0B
#define EFI_IFR_TYPE_REF           0x0C
#define EFI_IFR_OPTION_DEFAULT     0x10
#define EFI_IFR_OPTION_DEFAULT_MFG 0x20

또한 위 모든 구문의 DefaultId 값이 0 인 것을 알 수 있따. 이제 그럼 기본 저장소가 무엇인지에 대해 알아보자.

기본 저장소

먼저 UEFI formset에서 여러 기본 저장소를 정의할 수 있다는 것을 이해해야 한다.

만약 HIIFormDataElements application의 Form.list 파일을 보게 된다면 formset이 이미 2개의 기본 저장소를 가진 것을 볼 수 있다. Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/HIIFormDataElements/HIIFormDataElements/DEBUG/Form.lst

formset
>00000000: 0E A7 07 C5 1B 53 91 91 A2 4F 94 46 B8 44 E3 5D D1 2A 02 00 03 00 01 71 99 03 93 45 85 04 4B B4 5E 32 EB 83 26 04 0E
>00000027: 5C 06 00 00 00 00
>0000002D: 5C 06 00 00 01 00

Opcode 0x5cEFI_IFR_DEFAULTSTORE 에 해당한다.

EFI_IFR_DEFAULTSTORE

Summary:
Provides a declaration for the type of default values that a question can be associated with.

Prototype:

#define EFI_IFR_DEFAULTSTORE_OP 0x5c

typedef struct _EFI_IFR_DEFAULTSTORE {
 EFI_IFR_OP_HEADER Header;
 EFI_STRING_ID DefaultName;
 UINT16 DefaultId;
} EFI_IFR_DEFAULTSTORE;

Members:
Header      The sequence that defines the type of opcode as well as the length of the opcode being defined.
            For this tag, Header.OpCode = EFI_IFR_DEFAULTSTORE_OP
DefaultName A string token reference for the human readable string associated with the type of default being declared.
DefaultId   The default identifier, which is unique within the current form set. The default identifier creates a group of defaults.

Description:
Declares a class of default which can then have question default values associated with. An EFI_IFR_DEFAULTSTORE with a specified DefaultId must appear in the IFR before it can be referenced by an EFI_IFR_DEFAULT.

코드에서 ID가 0x00000x0001 이면서이름이 없는 두 개의 기본 저장소를 정의한다.

일반적으로 UEFI 스은 기본 저장소 ID(UINT16)에 대해 가능한 모든 값을 4개의 클래스로 나눈다.

  • 0x0000-0x3fff: UEFI Spec에 의해 예약되어짐

  • 0x4000-0x7fff: 플랫폼 공급자 사용

  • 0x8000-0xbfff: 하드웨어 공급업체 사용

  • 0xc000-0xffff: 펌웨어 공급업체 사용

첫 번째 그룹에는 이미 정의된 3개의 클래스가 있다.

  • 0x0000 - "standard defaults"(정상 작동을 위한 시스템/장치를 준비하는데 사용하는 기본 값)

  • 0x0001 - "manufacturing defaults"(제조를 위해 시스템/장치를 준비하는데 사용하는 기본 값)

  • 0x0002 - "safe defaults"("안전 모드" 또는 낮은 위험모드에서 시스템을 부팅하는데 사용되는 기본 값)

이를 통해 formset에 "standard defaults"과 "manufacturing defaults"이라는 기본 저장소가 있다는 것을 알 수 있다. 이들은 항상 VFR 컴파일러에 의해 모든 formset에 생성되어 진다.

위의 값에 대한 모든 정의 아래에서 볼 수 있다. https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Uefi/UefiInternalFormRepresentation.h

#define EFI_HII_DEFAULT_CLASS_STANDARD       0x0000
#define EFI_HII_DEFAULT_CLASS_MANUFACTURING  0x0001
#define EFI_HII_DEFAULT_CLASS_SAFE           0x0002
#define EFI_HII_DEFAULT_CLASS_PLATFORM_BEGIN 0x4000
#define EFI_HII_DEFAULT_CLASS_PLATFORM_END   0x7fff
#define EFI_HII_DEFAULT_CLASS_HARDWARE_BEGIN 0x8000
#define EFI_HII_DEFAULT_CLASS_HARDWARE_END   0xbfff
#define EFI_HII_DEFAULT_CLASS_FIRMWARE_BEGIN 0xc000
#define EFI_HII_DEFAULT_CLASS_FIRMWARE_END   0xffff

VFR의 기본 저장소 정의

만약 VFR에 여러 기본 저장소를 지원하려면 대부분 명시적 정의가 필요하다.

VFR에는 defaultstore 라는 특별한 요소가 존재한다. https://edk2-docs.gitbook.io/edk-ii-vfr-specification/2_vfr_description_in_bnf/26_vfr_default_stores_definition

기본적인 문법은 다음과 같다.

defaultstore <STORAGE NAME>,
  prompt      = <STRING TOKEN>,
  attribute   = <STORAGE ID>;

"standard defaults" 와 "manufacturing defaults" 를 명시적으로 정의하기 위해 다음과 같이 사용할 수 있다.

defaultstore StandardDefault,
  prompt      = STRING_TOKEN(STANDARD_DEFAULT_PROMPT),
  attribute   = 0x0000;

defaultstore ManufactureDefault,
  prompt      = STRING_TOKEN(MFG_DEFAULT_PROMPT),
  attribute   = 0x0001;

물론 *.uni 파일에 새 문자열 토큰도 추가해야 한다.

#string STANDARD_DEFAULT_PROMPT     #language en-US "Standard default"
#string MFG_DEFAULT_PROMPT          #language en-US "Manufacture default"

이제 저장소에 다른 정의를 제공할 수 있다.

numeric
  ...
  default = 7, defaultstore = StandardDefault,
  default = 8, defaultstore = ManufactureDefault,
endnumeric;

IFR

IFR 코드를 통해 생성한 defaultsotre 코드가 EFI_IFR_DEFAULTSTORE opcode를 어떻게 변환했는지 확인해보자.

formset
>00000000: 0E A7 07 C5 1B 53 91 91 A2 4F 94 46 B8 44 E3 5D D1 2A 02 00 03 00 01 71 99 03 93 45 85 04 4B B4 5E 32 EB 83 26 04 0E
>00000027: 5C 06 1B 00 00 00
>0000002D: 5C 06 1E 00 01 00

실질적으로 변경된 사항은 없으며 DefaultName 필드의 문자열 토큰만 업데이트 되었다.

하지만 이제 기본 값이 저장소를 참조하는 방법을 볼 수가있다.

    numeric
       ...
       default = 7, defaultstore = StandardDefault,
 >00000086: 5B 07 00 00 01 07 00                            // reference storage 0x0000
       default = 8, defaultstore = ManufactureDefault,
 >0000008D: 5B 07 01 00 01 08 00                            // reference storage 0x0001
     endnumeric;

resetbutton

이전에는 값을 기본 값으로 설정하기 위해서 F9 키를 form browser에서 사용했다. 기본 저장소가 있는 지금은 어떻게 해야 할까?

이러한 종류의 기능을 위해 VFRresetbutton 이라는 요소를 제공한다. 이 요소는 question 값을 기본 저장소의 값으로 설정할 수 있는 버튼을 생성할 수 있다.

두 개의 기본 저장소에 대해 2개의 버튼을 생성하자.

resetbutton
      defaultstore = MyStandardDefault,                              // <STORAGE NAME> of the target `defaultstore` element
      prompt   = STRING_TOKEN(STR_STANDARD_DEFAULT_PROMPT),
      help     = STRING_TOKEN(STR_STANDARD_DEFAULT_HELP),
    endresetbutton;

    resetbutton
      defaultstore = MyManufactureDefault,
      prompt   = STRING_TOKEN(STR_MANUFACTURE_DEFAULT_PROMPT),
      help     = STRING_TOKEN(STR_MANUFACTURE_DEFAULT_HELP),
    endresetbutton;

계속 강조하지만 *.uni 에 새 문자열 토큰 생성하는 것을 잊지 말자.

#string BTN_STANDARD_DEFAULT_PROMPT #language en-US "Reset to standard default prompt"
#string BTN_STANDARD_DEFAULT_HELP   #language en-US "Reset to standard default help"
#string BTN_MFG_DEFAULT_PROMPT      #language en-US "Reset to manufacture default prompt"
#string BTN_MFG_DEFAULT_HELP        #language en-US "Reset to manufacture default help"

이 코드는 다음과 같은 형태를 만들어 낼 것이다.

첫 번째 버튼이 numeric 값을 7로 설정하는 걸 확인할 수 있다.

두 번째 버튼으로 8을 설정한다.

IFR

resetbutton 의 IFR 코드는 다음과 같다.

    resetbutton
>00000146: 0D 88 1F 00 20 00 01 00
      defaultstore = ManufactureDefault,
      prompt = STRING_TOKEN(0x001F),
      help = STRING_TOKEN(0x0020),
    endresetbutton;

이 요소는 EFI_IFR_RESET_BUTTON opcode에 의해 정의되어 졌다.

EFI_IFR_RESET_BUTTON

Summary:
Create a reset or submit button on the current form.

Prototype:

#define EFI_IFR_RESET_BUTTON_OP 0x0d

typedef struct _EFI_IFR_RESET_BUTTON {
 EFI_IFR_OP_HEADER Header;
 EFI_IFR_STATEMENT_HEADER Statement;
 EFI_DEFAULT_ID DefaultId;
} EFI_IFR_RESET_BUTTON;

typedef UINT16 EFI_DEFAULT_ID;

Members:
Header     The standard header, where Header.OpCode = EFI_IFR_RESET_BUTTON_OP.
Statement  Standard statement header, including the prompt and help text.
DefaultId  Specifies the set of default store to use when restoring the defaults to the questions on this form.

Description:
This opcode creates a user-selectable button that resets the question values for all questions on the current form to the default values specified by DefaultId.

기본 값을 정의하는 다른 방법

oneof 는 모든 HII 메뉴에서 가장 공통적인 요소이며 flags 필드와 함께 옵션 대신 "standard storage" 과 "manufacturing storage" 값을 정의할 수 있다. 이를 통해 요소 가독성을 높힐 수 있다.

oneof
  varid = FormData.OneOfValue,
  prompt = STRING_TOKEN(ONEOF_PROMPT),
  help = STRING_TOKEN(ONEOF_HELP),
  option text = STRING_TOKEN(ONEOF_OPTION1), value = 0x00, flags = 0;
  option text = STRING_TOKEN(ONEOF_OPTION2), value = 0x33, flags = MANUFACTURING;
  option text = STRING_TOKEN(ONEOF_OPTION3), value = 0x55, flags = DEFAULT;
endoneof;

이 경우 0x55 값은 "standard storage", 0x33 값은 "manufacturing storage"로 설정되었다. 앞서 만들었던 resetbuuton를 통해 이 기능을 확인할 수 있다.

flags 로 정의된 기본 값과 default 키워드로 정의된 것을 혼합하여 사용할 수 있지만, 이 경우 UEFI Spec은 엄격한 우선 순위를 갖는다. 하지만 사용하지 않는 것을 추천한다.

flags 는 오직 "standard storage" 와 "manufacturing storage" 에 대한 참조 생성에 이용된다는 점을 기억하자. 기본 저장소 ID는 다른 사용자 지정 저장소에 대한 참조를 정의할 수 없다.

일정하지 않은 기본 값

기본 값을 표현식으로 정의할 수 있다. 이 경우에는 defaults value = <...> 와 같은 문법이 사용된다. 대부분의 경우 기본 값이 다른 question 값에 속해 있을 때 사용된다.

oneof
  name = OneOfQuestion,
  varid = FormData.OneOfValue,
  prompt = STRING_TOKEN(ONEOF_PROMPT),
  help = STRING_TOKEN(ONEOF_HELP),
  option text = STRING_TOKEN(ONEOF_OPTION1), value = 0x00, flags = 0;
  option text = STRING_TOKEN(ONEOF_OPTION2), value = 0x33, flags = MANUFACTURING;
  option text = STRING_TOKEN(ONEOF_OPTION3), value = 0x55, flags = DEFAULT;
endoneof;

numeric
  name = NumericQuestion,
  varid = FormData.NumericValue,
  prompt = STRING_TOKEN(NUMERIC_PROMPT),
  help = STRING_TOKEN(NUMERIC_HELP),
  flags = NUMERIC_SIZE_2 | DISPLAY_UINT_HEX,
  minimum = 0,
  maximum = 0xff,
  step = 1,
  default value = questionref(OneOfQuestion),
endnumeric;

이 코드를 통해 "standard storage"로 재설정하면 numeric 기본값이 0x55 로 변경되는 결과와 "manufacturing storage"로 재설정하면 numberic 기본값이 0x33 으로 변경되는 결과를 얻을 수 있다.

참조하는 name 은실제 questionref(< name>) 사용 전에 정의되어야 한다는 점을 유의하자.

지금 IFR 코드를 보면 완전히 다른 방식으로 인코딩 되었다는 것을 볼 수 있다.

 numeric
      ...
      default value = questionref(OneOfQuestion),
>000000AE: 5B 85 00 00 08	// EFI_IFR_DEFAULT_2 (structure form without a `Value` field)
>000000B3: 5A 82                // EFI_IFR_VALUE
>000000B5: 40 04 02 00		// EFI_IFR_QUESTION_REF1

이 경우에 EFI_IFR_DEFAULT_2 구조체의 Type 필드가 EFI_IFR_TYPE_OTHER 값임이 확인된다.

EFI_IFR_VALUEEFI_IFR_QUESTION_REF1 에 대한 정의이다.

EFI_IFR_VALUE

Summary:
Provides a value for the current question or default.

Prototype:

#define EFI_IFR_VALUE_OP 0x5a

typedef struct _EFI_IFR_VALUE {
 EFI_IFR_OP_HEADER Header;
} EFI_IFR_VALUE;

Members:
Header 	The sequence that defines the type of opcode as well as the length of the opcode being defined.
	For this tag, Header.OpCode = EFI_IFR_VALUE_OP

Description:
Creates a value for the current question or default with no storage. The value is the result of the expression nested in the scope.
EFI_IFR_QUESTION_REF1

Summary:
Push a question’s value on the expression stack

Prototype:

#define EFI_IFR_QUESTION_REF1_OP 0x40

typedef struct _EFI_IFR_QUESTION_REF1 {
 EFI_IFR_OP_HEADER Header;
 EFI_QUESTION_ID QuestionId;
} EFI_IFR_QUESTION_REF1;

Members:
Header 		The byte sequence that defines the type of opcode as well as the length of the opcode being defined.
		Header.OpCode = EFI_IFR_QUESTION_REF1_OP.
QuestionId 	The question’s identifier, which must be unique within the form set.

Description:
Push the value of the question specified by QuestionId on to the expression stack

기본 저장소 우선 순위

만약 resetbutton 요소를 이용해 기본 저장소의 값을 ID=0x5555 로 정의했으나 default 와 같은 요소로 기본 저장소의 값을 다른 값으로 (예로 ID=0x3333) 지정했을 경우에 어떤 일이 발생하는지 알아보자.

아래는이해를 위한 예시이다.

defaultstore FirstDefault,
  prompt      = STRING_TOKEN(FIRST_DEFAULT_PROMPT),
  attribute   = 0x3333;

defaultstore SecondDefault,
  prompt      = STRING_TOKEN(SECOND_DEFAULT_PROMPT),
  attribute   = 0x4444;

defaultstore ThirdDefault,
  prompt      = STRING_TOKEN(THIRD_DEFAULT_PROMPT),
  attribute   = 0x5555;

numeric
  varid = FormData.NumericValue,
  prompt = STRING_TOKEN(NUMERIC_PROMPT),
  help = STRING_TOKEN(NUMERIC_HELP),
  minimum = 0,
  maximum = 10,
  default = 7, defaultstore = FirstDefault,
  default = 8, defaultstore = SecondDefault,
endnumeric;

resetbutton
  defaultstore = TrirdDefault,
  prompt   = STRING_TOKEN(BTN_THIRD_DEFAULT_PROMPT),
  help     = STRING_TOKEN(BTN_THIRD_DEFAULT_HELP),
endresetbutton;

이 경우 numeric 은 가장 낮은 ID를 가진 defaultstore 에서 값을 가져온다. 따라서 numeric 값은 7로 설정된다.

이는 코드를 단순화하는데 도움이 되는 일반적인 규칙이다. 예로 몇 가지 요소에 대해서만 "standard defaults" 과 다른 "manufacturing storage" 기본 값을 지원하려는 경우 이러한 요소에 대해서만 추가 코드를 작성해야 한다.

Last updated