# 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` 파일에서 위 옵션을 설정한다.

```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` 코드는 부트 관리자 메뉴 항목에 대한 단축키로 `F2` 및 `ESC` 키 입력을 설정한다. 그리고 필요한 모든 정보와 함께 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_OPTION`의 `OptionNumber` 필드가 NVRAM에 저장되지 않고, 키 배열의 크기가 가변적임을 알 수 있다.

다른 하위 유형의 경우,

* `EFI_BOOT_KEY_DATA`는 `UefiSpec.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_KEY`는 `SimpleTextIn.h`에 정의되어 있다.

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

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

<figure><img src="/files/vQZZz1SRzO8Z0ddl8Ims" alt=""><figcaption></figcaption></figure>

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

그리고 `SimpleTextIn.h`에 따르면,

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

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

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

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://bob-mcd-team.gitbook.io/uefi/uefi-development/driver-library/40.key-nvram-variable.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
