40. Key #### NVRAM 변수

GetNextVariableName() UEFI 서비스와 시스템의 모든 NVRAM 변수를 조사해보면, EFI_GLOBAL_VARIABLE GUID(gEfiGlobalVariableGuid) 아래에 다음 변수가 존재한다.

8BE4DF61-93CA-11D2-AA0D-00E098032B8C: Key0000
8BE4DF61-93CA-11D2-AA0D-00E098032B8C: Key0001

UEFI 스펙 또는 EDKII 파일(MdePkg/Include/Guid/GlobalVariable.h)를 읽고 이러한 옵션에 대한 도움말을 찾을 수 있다.

// L"Key####"       - Describes hot key relationship with a Boot#### load option

OVMF는 BdsPlatform.c 파일에서 위 옵션을 설정한다.

$ cat MdePkg/Include/Guid/GlobalVariable.h
---
VOID
PlatformRegisterOptionsAndKeys (
  VOID
  )
{
  ...

  //
  // Map F2 to Boot Manager Menu
  //
  F2.ScanCode     = SCAN_F2;
  F2.UnicodeChar  = CHAR_NULL;
  Esc.ScanCode    = SCAN_ESC;
  Esc.UnicodeChar = CHAR_NULL;
  Status = EfiBootManagerGetBootManagerMenu (&BootOption);
  ASSERT_EFI_ERROR (Status);
  Status = EfiBootManagerAddKeyOptionVariable (
             NULL, (UINT16) BootOption.OptionNumber, 0, &F2, NULL
             );
  ASSERT (Status == EFI_SUCCESS || Status == EFI_ALREADY_STARTED);
  Status = EfiBootManagerAddKeyOptionVariable (
             NULL, (UINT16) BootOption.OptionNumber, 0, &Esc, NULL
             );
  ...
}

EfiBootManagerAddKeyOptionVariable 코드는 부트 관리자 메뉴 항목에 대한 단축키로 F2ESC 키 입력을 설정한다. 그리고 필요한 모든 정보와 함께 NVRAM 변수 KeyXXXX를 추가한다,

$ cat MdeModulePkg/Library/UefiBootManagerLib/BmHotKey.c

/**
  Add the key option.
  It adds the key option variable and the key option takes affect immediately.
  @param AddedOption      Return the added key option.
  @param BootOptionNumber The boot option number for the key option.
  @param Modifier         Key shift state.
  @param ...              Parameter list of pointer of EFI_INPUT_KEY.
  @retval EFI_SUCCESS         The key option is added.
  @retval EFI_ALREADY_STARTED The hot key is already used by certain key option.
**/
EFI_STATUS
EFIAPI
EfiBootManagerAddKeyOptionVariable (
  OUT EFI_BOOT_MANAGER_KEY_OPTION *AddedOption,   OPTIONAL
  IN UINT16                       BootOptionNumber,
  IN UINT32                       Modifier,
  ...
  )
{
  EFI_BOOT_MANAGER_KEY_OPTION    KeyOption;
  ...
  UnicodeSPrint (KeyOptionName, sizeof (KeyOptionName), L"Key%04x", KeyOptionNumber);

  Status = gRT->SetVariable (                                                              //  <------ this call sets 'KeyXXXX' variable
                  KeyOptionName,
                  &gEfiGlobalVariableGuid,
                  EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE,
                  BmSizeOfKeyOption (&KeyOption),
                  &KeyOption
                  );
  if (!EFI_ERROR (Status)) {
    ...
    if (mBmHotkeyServiceStarted) {
      BmProcessKeyOption (&KeyOption);			                                   // <---- this function calls 'RegisterKeyNotify'
    }
  }

  return Status;
}

EFI_BOOT_MANAGER_KEY_OPTION이 어떻게 코딩되어있는지 확인하려면, MdeModulePkg/Include/Library/UefiBootManager.h에서 정의를 보면 된다.

#pragma pack(1)
///
/// EFI Key Option.
///
typedef struct {
  ///
  /// Specifies options about how the key will be processed.
  ///
  EFI_BOOT_KEY_DATA  KeyData;
  ///
  /// The CRC-32 which should match the CRC-32 of the entire EFI_LOAD_OPTION to
  /// which BootOption refers. If the CRC-32s do not match this value, then this key
  /// option is ignored.
  ///
  UINT32             BootOptionCrc;
  ///
  /// The Boot#### option which will be invoked if this key is pressed and the boot option
  /// is active (LOAD_OPTION_ACTIVE is set).
  ///
  UINT16             BootOption;
  ///
  /// The key codes to compare against those returned by the
  /// EFI_SIMPLE_TEXT_INPUT and EFI_SIMPLE_TEXT_INPUT_EX protocols.
  /// The number of key codes (0-3) is specified by the EFI_KEY_CODE_COUNT field in KeyOptions.
  ///
  EFI_INPUT_KEY      Keys[3];
  UINT16             OptionNumber;
} EFI_BOOT_MANAGER_KEY_OPTION;
#pragma pack()

위의 gRT->SetVariable 호출에 사용된 BmSizeOfKeyOption 함수(BmHotKey.c)를 살펴보면 다음과 같다.

$ cat MdeModulePkg/Library/UefiBootManagerLib/BmHotkey.c
---
UINTN
BmSizeOfKeyOption (
  IN CONST EFI_BOOT_MANAGER_KEY_OPTION  *KeyOption
  )
{
  return OFFSET_OF (EFI_BOOT_MANAGER_KEY_OPTION, Keys)
    + KeyOption->KeyData.Options.InputKeyCount * sizeof (EFI_INPUT_KEY);
}

EFI_BOOT_MANAGER_KEY_OPTIONOptionNumber 필드가 NVRAM에 저장되지 않고, 키 배열의 크기가 가변적임을 알 수 있다.

다른 하위 유형의 경우,

  • EFI_BOOT_KEY_DATAUefiSpec.h에 정의되어 있다.

$ cat MdePkg/Include/Uefi/UefiSpec.h
---
///
/// EFI Boot Key Data
///
typedef union {
  struct {
    ///
    /// Indicates the revision of the EFI_KEY_OPTION structure. This revision level should be 0.
    ///
    UINT32  Revision        : 8;
    ///
    /// Either the left or right Shift keys must be pressed (1) or must not be pressed (0).
    ///
    UINT32  ShiftPressed    : 1;
    ///
    /// Either the left or right Control keys must be pressed (1) or must not be pressed (0).
    ///
    UINT32  ControlPressed  : 1;
    ///
    /// Either the left or right Alt keys must be pressed (1) or must not be pressed (0).
    ///
    UINT32  AltPressed      : 1;
    ///
    /// Either the left or right Logo keys must be pressed (1) or must not be pressed (0).
    ///
    UINT32  LogoPressed     : 1;
    ///
    /// The Menu key must be pressed (1) or must not be pressed (0).
    ///
    UINT32  MenuPressed     : 1;
    ///
    /// The SysReq key must be pressed (1) or must not be pressed (0).
    ///
    UINT32  SysReqPressed    : 1;
    UINT32  Reserved        : 16;
    ///
    /// Specifies the actual number of entries in EFI_KEY_OPTION.Keys, from 0-3. If
    /// zero, then only the shift state is considered. If more than one, then the boot option will
    /// only be launched if all of the specified keys are pressed with the same shift state.
    ///
    UINT32  InputKeyCount   : 2;
  } Options;
  UINT32  PackedValue;
} EFI_BOOT_KEY_DATA;
  • EFI_INPUT_KEYSimpleTextIn.h에 정의되어 있다.

$ cat MdePkg/Include/Protocol/SimpleText.h
---
typedef struct {
  UINT16  ScanCode;
  CHAR16  UnicodeChar;
} EFI_INPUT_KEY;

이 모든 정보들을 염두해 두고, shell에서 dmpstore 명령을 호출하고 KeyXXXX 옵션을 파싱할 수 있다.

보다시피 Key0000Boot0000 Option에 대한 0x000c 스캔 코드로 단축키를 정의한다. 그리고 Key0001은 동일한 Boot0000 Option에 대해 0x0017 스캔 코드단축키를 정의한다. 또한 Boot0000은 부트메뉴인 UiApp을 나타낸다.

그리고 SimpleTextIn.h에 따르면,

$ cat MdePkg/Include/Protocol/SimpleTextIn.h
---
#define SCAN_F2         0x000C
...
#define SCAN_ESC        0x0017

따라서 이제 F2ESC 키를 사용하여 부트 프로세스를 중지하고, 부트 메뉴로 이동할 수 있다.

이 HotKey 함수 구현에 대해 더 알고 싶다면, callback 함수 BmHotkeyCallback과 이것이 설정하는 mBmHotkeyBootOption 변수를 살펴보는 것을 추천한다.(BmHotkey.c)

한가지 더 언급하자면, -nographic option을 사용하여 QEMU를 시작하면, 모든 비표준 키는 SCAN_ESC 기호로 시작하는 escape sequence를 통해 전송된다. 따라서 이 경우 모든 비표준 키가 단축키로 작동하게 된다.

Last updated