text: /** @file
    Hotkey library functions.
  
  Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR>
  (C) Copyright 2016 Hewlett Packard Enterprise Development LP<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent
  
  **/
  
  #include "InternalBm.h"
  
  //
  // Lock for linked list
  //
  EFI_LOCK                     mBmHotkeyLock            = EFI_INITIALIZE_LOCK_VARIABLE (TPL_NOTIFY);
  LIST_ENTRY                   mBmHotkeyList            = INITIALIZE_LIST_HEAD_VARIABLE (mBmHotkeyList);
  EFI_EVENT                    mBmHotkeyTriggered       = NULL;
  BOOLEAN                      mBmHotkeyServiceStarted  = FALSE;
  UINTN                        mBmHotkeySupportCount    = 0;
  
  //
  // Set OptionNumber as unassigned value to indicate the option isn't initialized
  //
  EFI_BOOT_MANAGER_LOAD_OPTION mBmHotkeyBootOption      = { LoadOptionNumberUnassigned };
  
  EFI_BOOT_MANAGER_KEY_OPTION  *mBmContinueKeyOption    = NULL;
  VOID                         *mBmTxtInExRegistration  = NULL;
  
  
  /**
    Return the buffer size of the EFI_BOOT_MANAGER_KEY_OPTION data.
  
    @param   KeyOption            The input key option info.
  
    @retval  The buffer size of the key option data.
  **/
  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);
  }
  
  /**
  
    Check whether the input key option is valid.
  
    @param   KeyOption          Key option.
    @param   KeyOptionSize      Size of the key option.
  
    @retval  TRUE               Input key option is valid.
    @retval  FALSE              Input key option is not valid.
  **/
  BOOLEAN
  BmIsKeyOptionValid (
    IN CONST EFI_BOOT_MANAGER_KEY_OPTION *KeyOption,
    IN       UINTN                       KeyOptionSize
  )
  {
    UINT16   OptionName[BM_OPTION_NAME_LEN];
    UINT8    *BootOption;
    UINTN    BootOptionSize;
    UINT32   Crc;
  
    if (BmSizeOfKeyOption (KeyOption) != KeyOptionSize) {
      return FALSE;
    }
  
    //
    // Check whether corresponding Boot Option exist
    //
    UnicodeSPrint (
      OptionName, sizeof (OptionName), L"%s%04x",
      mBmLoadOptionName[LoadOptionTypeBoot], KeyOption->BootOption
      );
    GetEfiGlobalVariable2 (OptionName, (VOID **) &BootOption, &BootOptionSize);
  
    if (BootOption == NULL) {
      return FALSE;
    }
  
    //
    // Check CRC for Boot Option
    //
    gBS->CalculateCrc32 (BootOption, BootOptionSize, &Crc);
    FreePool(BootOption);
  
    return (BOOLEAN) (KeyOption->BootOptionCrc == Crc);
  }
  
  /**
  
    Check whether the input variable is an key option variable.
  
    @param   Name               Input variable name.
    @param   Guid               Input variable guid.
    @param   OptionNumber       The option number of this key option variable.
  
    @retval  TRUE               Input variable is a key option variable.
    @retval  FALSE              Input variable is not a key option variable.
  **/
  BOOLEAN
  BmIsKeyOptionVariable (
    CHAR16        *Name,
    EFI_GUID      *Guid,
    UINT16        *OptionNumber
    )
  {
    UINTN         Index;
    UINTN         Uint;
  
    if (!CompareGuid (Guid, &gEfiGlobalVariableGuid) ||
        (StrSize (Name) != sizeof (L"Key####")) ||
        (StrnCmp (Name, L"Key", 3) != 0)
       ) {
      return FALSE;
    }
  
    *OptionNumber = 0;
    for (Index = 3; Index < 7; Index++) {
      Uint = BmCharToUint (Name[Index]);
      if (Uint == -1) {
        return FALSE;
      } else {
        *OptionNumber = (UINT16) Uint + *OptionNumber * 0x10;
      }
    }
  
    return TRUE;
  }
  
  typedef struct {
    EFI_BOOT_MANAGER_KEY_OPTION *KeyOptions;
    UINTN                       KeyOptionCount;
  } BM_COLLECT_KEY_OPTIONS_PARAM;
  
  /**
    Visitor function to collect the key options from NV storage.
  
    @param Name    Variable name.
    @param Guid    Variable GUID.
    @param Context The same context passed to BmForEachVariable.
  **/
  VOID
  BmCollectKeyOptions (
    CHAR16               *Name,
    EFI_GUID             *Guid,
    VOID                 *Context
    )
  {
    UINTN                        Index;
    BM_COLLECT_KEY_OPTIONS_PARAM *Param;
    VOID                         *KeyOption;
    UINT16                       OptionNumber;
    UINTN                        KeyOptionSize;
  
    Param = (BM_COLLECT_KEY_OPTIONS_PARAM *) Context;
  
    if (BmIsKeyOptionVariable (Name, Guid, &OptionNumber)) {
      GetEfiGlobalVariable2 (Name, &KeyOption, &KeyOptionSize);
      ASSERT (KeyOption != NULL);
      if (BmIsKeyOptionValid (KeyOption, KeyOptionSize)) {
        Param->KeyOptions = ReallocatePool (
                              Param->KeyOptionCount * sizeof (EFI_BOOT_MANAGER_KEY_OPTION),
                              (Param->KeyOptionCount + 1) * sizeof (EFI_BOOT_MANAGER_KEY_OPTION),
                              Param->KeyOptions
                              );
        ASSERT (Param->KeyOptions != NULL);
        //
        // Insert the key option in order
        //
        for (Index = 0; Index < Param->KeyOptionCount; Index++) {
          if (OptionNumber < Param->KeyOptions[Index].OptionNumber) {
            break;
          }
        }
        CopyMem(&Param->KeyOptions[Index + 1], &Param->KeyOptions[Index], (Param->KeyOptionCount - Index) * sizeof (EFI_BOOT_MANAGER_KEY_OPTION));
        CopyMem(&Param->KeyOptions[Index], KeyOption, KeyOptionSize);
        Param->KeyOptions[Index].OptionNumber = OptionNumber;
        Param->KeyOptionCount++;
      }
      FreePool(KeyOption);
    }
  }
  
  /**
    Return the array of key options.
  
    @param Count  Return the number of key options.
  
    @retval NULL  No key option.
    @retval Other Pointer to the key options.
  **/
  EFI_BOOT_MANAGER_KEY_OPTION *
  BmGetKeyOptions (
    OUT UINTN     *Count
    )
  {
    BM_COLLECT_KEY_OPTIONS_PARAM Param;
  
    if (Count == NULL) {
      return NULL;
    }
  
    Param.KeyOptions = NULL;
    Param.KeyOptionCount = 0;
  
    BmForEachVariable (BmCollectKeyOptions, (VOID *) &Param);
  
    *Count = Param.KeyOptionCount;
  
    return Param.KeyOptions;
  }
  
  /**
    Check whether the bit is set in the value.
  
    @param   Value            The value need to be check.
    @param   Bit              The bit filed need to be check.
  
    @retval  TRUE             The bit is set.
    @retval  FALSE            The bit is not set.
  **/
  BOOLEAN
  BmBitSet (
    IN UINT32   Value,
    IN UINT32   Bit
    )
  {
    return (BOOLEAN) ((Value & Bit) != 0);
  }
  
  /**
    Initialize the KeyData and Key[] in the EFI_BOOT_MANAGER_KEY_OPTION.
  
    @param  Modifier   Input key info.
    @param  Args       Va_list info.
    @param  KeyOption  Key info which need to update.
  
    @retval  EFI_SUCCESS             Succeed to initialize the KeyData and Key[].
    @return  EFI_INVALID_PARAMETER   Input parameter error.
  **/
  EFI_STATUS
  BmInitializeKeyFields (
    IN UINT32                       Modifier,
    IN VA_LIST                      Args,
    OUT EFI_BOOT_MANAGER_KEY_OPTION *KeyOption
    )
  {
    EFI_INPUT_KEY                   *Key;
  
    if (KeyOption == NULL) {
      return EFI_INVALID_PARAMETER;
    }
  
    Key = NULL;
    while (KeyOption->KeyData.Options.InputKeyCount < sizeof (KeyOption->Keys) / sizeof (KeyOption->Keys[0])) {
      Key = VA_ARG (Args, EFI_INPUT_KEY *);
      if (Key == NULL) {
        break;
      }
      CopyMem(
        &KeyOption->Keys[KeyOption->KeyData.Options.InputKeyCount],
        Key,
        sizeof (EFI_INPUT_KEY)
        );
      KeyOption->KeyData.Options.InputKeyCount++;
    }
  
    if (Key != NULL) {
      //
      // Too many keys
      //
      return EFI_INVALID_PARAMETER;
    }
  
    if ((Modifier & ~(EFI_BOOT_MANAGER_SHIFT_PRESSED
                   | EFI_BOOT_MANAGER_CONTROL_PRESSED
                   | EFI_BOOT_MANAGER_ALT_PRESSED
                   | EFI_BOOT_MANAGER_LOGO_PRESSED
                   | EFI_BOOT_MANAGER_MENU_KEY_PRESSED
                   | EFI_BOOT_MANAGER_SYS_REQ_PRESSED
                   )) != 0) {
      return EFI_INVALID_PARAMETER;
    }
  
    if (BmBitSet (Modifier, EFI_BOOT_MANAGER_SHIFT_PRESSED)) {
      KeyOption->KeyData.Options.ShiftPressed = 1;
    }
    if (BmBitSet (Modifier, EFI_BOOT_MANAGER_CONTROL_PRESSED)) {
      KeyOption->KeyData.Options.ControlPressed = 1;
    }
    if (BmBitSet (Modifier, EFI_BOOT_MANAGER_ALT_PRESSED)) {
      KeyOption->KeyData.Options.AltPressed = 1;
    }
    if (BmBitSet (Modifier, EFI_BOOT_MANAGER_LOGO_PRESSED)) {
      KeyOption->KeyData.Options.LogoPressed = 1;
    }
    if (BmBitSet (Modifier, EFI_BOOT_MANAGER_MENU_KEY_PRESSED)) {
      KeyOption->KeyData.Options.MenuPressed = 1;
    }
    if (BmBitSet (Modifier, EFI_BOOT_MANAGER_SYS_REQ_PRESSED)) {
      KeyOption->KeyData.Options.SysReqPressed = 1;
    }
  
    return EFI_SUCCESS;
  }
  
  /**
    Try to boot the boot option triggered by hot key.
  **/
  VOID
  EFIAPI
  EfiBootManagerHotkeyBoot (
    VOID
    )
  {
    if (mBmHotkeyBootOption.OptionNumber != LoadOptionNumberUnassigned) {
      EfiBootManagerBoot (&mBmHotkeyBootOption);
      EfiBootManagerFreeLoadOption (&mBmHotkeyBootOption);
      mBmHotkeyBootOption.OptionNumber = LoadOptionNumberUnassigned;
    }
  }
  
  /**
    This is the common notification function for HotKeys, it will be registered
    with SimpleTextInEx protocol interface - RegisterKeyNotify() of ConIn handle.
  
    @param KeyData         A pointer to a buffer that is filled in with the keystroke
                           information for the key that was pressed.
  
    @retval  EFI_SUCCESS   KeyData is successfully processed.
    @return  EFI_NOT_FOUND Fail to find boot option variable.
  **/
  EFI_STATUS
  EFIAPI
  BmHotkeyCallback (
    IN EFI_KEY_DATA     *KeyData
  )
  {
    LIST_ENTRY                    *Link;
    BM_HOTKEY                     *Hotkey;
    CHAR16                        OptionName[BM_OPTION_NAME_LEN];
    EFI_STATUS                    Status;
    EFI_KEY_DATA                  *HotkeyData;
  
    if (mBmHotkeyBootOption.OptionNumber != LoadOptionNumberUnassigned) {
      //
      // Do not process sequential hotkey stroke until the current boot option returns
      //
      return EFI_SUCCESS;
    }
  
    DEBUG ((EFI_D_INFO, "[Bds]BmHotkeyCallback: %04x:%04x\n", KeyData->Key.ScanCode, KeyData->Key.UnicodeChar));
  
    EfiAcquireLock (&mBmHotkeyLock);
    for ( Link = GetFirstNode (&mBmHotkeyList)
        ; !IsNull (&mBmHotkeyList, Link)
        ; Link = GetNextNode (&mBmHotkeyList, Link)
        ) {
      Hotkey = BM_HOTKEY_FROM_LINK (Link);
  
      //
      // Is this Key Stroke we are waiting for?
      //
      ASSERT (Hotkey->WaitingKey < (sizeof (Hotkey->KeyData) / sizeof (Hotkey->KeyData[0])));
      HotkeyData = &Hotkey->KeyData[Hotkey->WaitingKey];
      if ((KeyData->Key.ScanCode == HotkeyData->Key.ScanCode) &&
          (KeyData->Key.UnicodeChar == HotkeyData->Key.UnicodeChar) &&
          (((KeyData->KeyState.KeyShiftState & EFI_SHIFT_STATE_VALID) != 0) ?
            (KeyData->KeyState.KeyShiftState == HotkeyData->KeyState.KeyShiftState) : TRUE
          )
         ) {
  
        //
        // Receive an expecting key stroke, transit to next waiting state
        //
        Hotkey->WaitingKey++;
  
        if (Hotkey->WaitingKey == Hotkey->CodeCount) {
          //
          // Reset to initial waiting state
          //
          Hotkey->WaitingKey = 0;
          //
          // Received the whole key stroke sequence
          //
          Status = gBS->SignalEvent (mBmHotkeyTriggered);
          ASSERT_EFI_ERROR(Status);
  
          if (!Hotkey->IsContinue) {
            //
            // Launch its BootOption
            //
            UnicodeSPrint (
              OptionName, sizeof (OptionName), L"%s%04x",
              mBmLoadOptionName[LoadOptionTypeBoot], Hotkey->BootOption
              );
            Status = EfiBootManagerVariableToLoadOption (OptionName, &mBmHotkeyBootOption);
            DEBUG ((EFI_D_INFO, "[Bds]Hotkey for %s pressed - %r\n", OptionName, Status));
            if (EFI_ERROR(Status)) {
              mBmHotkeyBootOption.OptionNumber = LoadOptionNumberUnassigned;
            }
          } else {
            DEBUG ((EFI_D_INFO, "[Bds]Continue key pressed!\n"));
          }
        }
      } else {
        //
        // Receive an unexpected key stroke, reset to initial waiting state
        //
        Hotkey->WaitingKey = 0;
      }
  
    }
    EfiReleaseLock (&mBmHotkeyLock);
  
    return EFI_SUCCESS;
  }
  
  /**
    Return the active Simple Text Input Ex handle array.
    If the SystemTable.ConsoleInHandle is NULL, the function returns all
    founded Simple Text Input Ex handles.
    Otherwise, it just returns the ConsoleInHandle.
  
    @param Count  Return the handle count.
  
    @retval The active console handles.
  **/
  EFI_HANDLE *
  BmGetActiveConsoleIn (
    OUT UINTN                             *Count
    )
  {
    EFI_STATUS                            Status;
    EFI_HANDLE                            *Handles;
  
    Handles = NULL;
    *Count  = 0;
  
    if (gST->ConsoleInHandle != NULL) {
      Status = gBS->OpenProtocol (
                      gST->ConsoleInHandle,
                      &gEfiSimpleTextInputExProtocolGuid,
                      NULL,
                      gImageHandle,
                      NULL,
                      EFI_OPEN_PROTOCOL_TEST_PROTOCOL
                      );
      if (!EFI_ERROR(Status)) {
        Handles = AllocateCopyPool(sizeof (EFI_HANDLE), &gST->ConsoleInHandle);
        if (Handles != NULL) {
          *Count = 1;
        }
      }
    } else {
      Status = gBS->LocateHandleBuffer (
                      ByProtocol,
                      &gEfiSimpleTextInputExProtocolGuid,
                      NULL,
                      Count,
                      &Handles
                      );
    }
  
    return Handles;
  }
  
  /**
    Unregister hotkey notify list.
  
    @param    Hotkey                Hotkey list.
  
    @retval   EFI_SUCCESS           Unregister hotkey notify success.
    @retval   Others                Unregister hotkey notify failed.
  **/
  EFI_STATUS
  BmUnregisterHotkeyNotify (
    IN BM_HOTKEY                          *Hotkey
    )
  {
    EFI_STATUS                            Status;
    UINTN                                 Index;
    UINTN                                 KeyIndex;
    EFI_HANDLE                            *Handles;
    UINTN                                 HandleCount;
    EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL     *TxtInEx;
    VOID                                  *NotifyHandle;
  
    Handles = BmGetActiveConsoleIn (&HandleCount);
    for (Index = 0; Index < HandleCount; Index++) {
      Status = gBS->HandleProtocol (Handles[Index], &gEfiSimpleTextInputExProtocolGuid, (VOID **) &TxtInEx);
      ASSERT_EFI_ERROR(Status);
      for (KeyIndex = 0; KeyIndex < Hotkey->CodeCount; KeyIndex++) {
        Status = TxtInEx->RegisterKeyNotify (
                            TxtInEx,
                            &Hotkey->KeyData[KeyIndex],
                            BmHotkeyCallback,
                            &NotifyHandle
                            );
        if (!EFI_ERROR(Status)) {
          Status = TxtInEx->UnregisterKeyNotify (TxtInEx, NotifyHandle);
          DEBUG ((EFI_D_INFO, "[Bds]UnregisterKeyNotify: %04x/%04x %r\n", Hotkey->KeyData[KeyIndex].Key.ScanCode, Hotkey->KeyData[KeyIndex].Key.UnicodeChar, Status));
        }
      }
    }
  
    if (Handles != NULL) {
      FreePool(Handles);
    }
  
    return EFI_SUCCESS;
  }
  
  /**
    Register hotkey notify list.
  
    @param    TxtInEx               Pointer to EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL protocol.
    @param    Hotkey                Hotkey list.
  
    @retval   EFI_SUCCESS           Register hotkey notify success.
    @retval   Others                Register hotkey notify failed.
  **/
  EFI_STATUS
  BmRegisterHotkeyNotify (
    IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL  *TxtInEx,
    IN BM_HOTKEY                          *Hotkey
    )
  {
    EFI_STATUS                            Status;
    UINTN                                 Index;
    VOID                                  *NotifyHandle;
  
    for (Index = 0; Index < Hotkey->CodeCount; Index++) {
      Status = TxtInEx->RegisterKeyNotify (
                          TxtInEx,
                          &Hotkey->KeyData[Index],
                          BmHotkeyCallback,
                          &NotifyHandle
                          );
      DEBUG ((
        EFI_D_INFO,
        "[Bds]RegisterKeyNotify: %04x/%04x %08x/%02x %r\n",
        Hotkey->KeyData[Index].Key.ScanCode,
        Hotkey->KeyData[Index].Key.UnicodeChar,
        Hotkey->KeyData[Index].KeyState.KeyShiftState,
        Hotkey->KeyData[Index].KeyState.KeyToggleState,
        Status
        ));
      if (EFI_ERROR(Status)) {
        //
        // some of the hotkey registry failed
        // do not unregister all in case we have both CTRL-ALT-P and CTRL-ALT-P-R
        //
        break;
      }
    }
  
    return EFI_SUCCESS;
  }
  
  /**
    Generate key shift state base on the input key option info.
  
    @param    Depth                 Which key is checked.
    @param    KeyOption             Input key option info.
    @param    KeyShiftState         Input key shift state.
    @param    KeyShiftStates        Return possible key shift state array.
    @param    KeyShiftStateCount    Possible key shift state count.
  **/
  VOID
  BmGenerateKeyShiftState (
    IN UINTN                             Depth,
    IN EFI_BOOT_MANAGER_KEY_OPTION       *KeyOption,
    IN UINT32                            KeyShiftState,
    IN UINT32                            *KeyShiftStates,
    IN UINTN                             *KeyShiftStateCount
    )
  {
    switch (Depth) {
    case 0:
      if (KeyOption->KeyData.Options.ShiftPressed) {
        BmGenerateKeyShiftState (Depth + 1, KeyOption, KeyShiftState | EFI_RIGHT_SHIFT_PRESSED, KeyShiftStates, KeyShiftStateCount);
        BmGenerateKeyShiftState (Depth + 1, KeyOption, KeyShiftState | EFI_LEFT_SHIFT_PRESSED,  KeyShiftStates, KeyShiftStateCount);
      } else {
        BmGenerateKeyShiftState (Depth + 1, KeyOption, KeyShiftState, KeyShiftStates, KeyShiftStateCount);
      }
      break;
  
    case 1:
      if (KeyOption->KeyData.Options.ControlPressed) {
        BmGenerateKeyShiftState (Depth + 1, KeyOption, KeyShiftState | EFI_RIGHT_CONTROL_PRESSED, KeyShiftStates, KeyShiftStateCount);
        BmGenerateKeyShiftState (Depth + 1, KeyOption, KeyShiftState | EFI_LEFT_CONTROL_PRESSED,  KeyShiftStates, KeyShiftStateCount);
      } else {
        BmGenerateKeyShiftState (Depth + 1, KeyOption, KeyShiftState, KeyShiftStates, KeyShiftStateCount);
      }
      break;
  
    case 2:
      if (KeyOption->KeyData.Options.AltPressed) {
        BmGenerateKeyShiftState (Depth + 1, KeyOption, KeyShiftState | EFI_RIGHT_ALT_PRESSED, KeyShiftStates, KeyShiftStateCount);
        BmGenerateKeyShiftState (Depth + 1, KeyOption, KeyShiftState | EFI_LEFT_ALT_PRESSED,  KeyShiftStates, KeyShiftStateCount);
      } else {
        BmGenerateKeyShiftState (Depth + 1, KeyOption, KeyShiftState, KeyShiftStates, KeyShiftStateCount);
      }
      break;
    case  3:
      if (KeyOption->KeyData.Options.LogoPressed) {
        BmGenerateKeyShiftState (Depth + 1, KeyOption, KeyShiftState | EFI_RIGHT_LOGO_PRESSED, KeyShiftStates, KeyShiftStateCount);
        BmGenerateKeyShiftState (Depth + 1, KeyOption, KeyShiftState | EFI_LEFT_LOGO_PRESSED,  KeyShiftStates, KeyShiftStateCount);
      } else {
        BmGenerateKeyShiftState (Depth + 1, KeyOption, KeyShiftState, KeyShiftStates, KeyShiftStateCount);
      }
      break;
    case 4:
      if (KeyOption->KeyData.Options.MenuPressed) {
        KeyShiftState |= EFI_MENU_KEY_PRESSED;
      }
      if (KeyOption->KeyData.Options.SysReqPressed) {
        KeyShiftState |= EFI_SYS_REQ_PRESSED;
      }
      KeyShiftStates[*KeyShiftStateCount] = KeyShiftState;
      (*KeyShiftStateCount)++;
      break;
    }
  }
  
  /**
    Add it to hot key database, register it to existing TxtInEx.
    New TxtInEx will be automatically registered with all the hot key in dababase
  
    @param    KeyOption  Input key option info.
  **/
  EFI_STATUS
  BmProcessKeyOption (
    IN EFI_BOOT_MANAGER_KEY_OPTION       *KeyOption
    )
  {
    EFI_STATUS                           Status;
    EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL    *TxtInEx;
    EFI_HANDLE                           *Handles;
    UINTN                                HandleCount;
    UINTN                                HandleIndex;
    UINTN                                Index;
    BM_HOTKEY                            *Hotkey;
    UINTN                                KeyIndex;
    //
    // 16 is enough to enumerate all the possible combination of LEFT_XXX and RIGHT_XXX
    //
    UINT32                               KeyShiftStates[16];
    UINTN                                KeyShiftStateCount;
  
    if (KeyOption->KeyData.Options.InputKeyCount > mBmHotkeySupportCount) {
      return EFI_UNSUPPORTED;
    }
  
    KeyShiftStateCount = 0;
    BmGenerateKeyShiftState (0, KeyOption, EFI_SHIFT_STATE_VALID, KeyShiftStates, &KeyShiftStateCount);
    ASSERT (KeyShiftStateCount <= ARRAY_SIZE (KeyShiftStates));
  
    EfiAcquireLock (&mBmHotkeyLock);
  
    Handles = BmGetActiveConsoleIn (&HandleCount);
  
    for (Index = 0; Index < KeyShiftStateCount; Index++) {
      Hotkey = AllocateZeroPool(sizeof (BM_HOTKEY));
      ASSERT (Hotkey != NULL);
  
      Hotkey->Signature  = BM_HOTKEY_SIGNATURE;
      Hotkey->BootOption = KeyOption->BootOption;
      Hotkey->IsContinue = (BOOLEAN) (KeyOption == mBmContinueKeyOption);
      Hotkey->CodeCount  = (UINT8) KeyOption->KeyData.Options.InputKeyCount;
  
      for (KeyIndex = 0; KeyIndex < Hotkey->CodeCount; KeyIndex++) {
        CopyMem(&Hotkey->KeyData[KeyIndex].Key, &KeyOption->Keys[KeyIndex], sizeof (EFI_INPUT_KEY));
        Hotkey->KeyData[KeyIndex].KeyState.KeyShiftState = KeyShiftStates[Index];
      }
      InsertTailList (&mBmHotkeyList, &Hotkey->Link);
  
      for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) {
        Status = gBS->HandleProtocol (Handles[HandleIndex], &gEfiSimpleTextInputExProtocolGuid, (VOID **) &TxtInEx);
        ASSERT_EFI_ERROR(Status);
        if (EFI_ERROR(Status)) continue;
        BmRegisterHotkeyNotify (TxtInEx, Hotkey);
      }
    }
  
    if (Handles != NULL) {
      FreePool(Handles);
    }
    EfiReleaseLock (&mBmHotkeyLock);
  
    return EFI_SUCCESS;
  }
  
  /**
    Callback function for SimpleTextInEx protocol install events
  
    @param Event           the event that is signaled.
    @param Context         not used here.
  
  **/
  VOID
  EFIAPI
  BmTxtInExCallback (
    IN EFI_EVENT    Event,
    IN VOID         *Context
    )
  {
    EFI_STATUS                         Status;
    UINTN                              BufferSize;
    EFI_HANDLE                         Handle;
    EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL  *TxtInEx;
    LIST_ENTRY                         *Link;
  
    while (TRUE) {
      BufferSize = sizeof (EFI_HANDLE);
      Status = gBS->LocateHandle (
                      ByRegisterNotify,
                      NULL,
                      mBmTxtInExRegistration,
                      &BufferSize,
                      &Handle
                      );
      if (EFI_ERROR(Status)) {
        //
        // If no more notification events exist
        //
        return ;
      }
  
      Status = gBS->HandleProtocol (
                      Handle,
                      &gEfiSimpleTextInputExProtocolGuid,
                      (VOID **) &TxtInEx
                      );
      ASSERT_EFI_ERROR(Status);
  
      //
      // Register the hot key notification for the existing items in the list
      //
      EfiAcquireLock (&mBmHotkeyLock);
      for (Link = GetFirstNode (&mBmHotkeyList); !IsNull (&mBmHotkeyList, Link); Link = GetNextNode (&mBmHotkeyList, Link)) {
        BmRegisterHotkeyNotify (TxtInEx, BM_HOTKEY_FROM_LINK (Link));
      }
      EfiReleaseLock (&mBmHotkeyLock);
    }
  }
  
  /**
    Free the key options returned from BmGetKeyOptions.
  
    @param KeyOptions     Pointer to the key options.
    @param KeyOptionCount Number of the key options.
  
    @retval EFI_SUCCESS   The key options are freed.
    @retval EFI_NOT_FOUND KeyOptions is NULL.
  **/
  EFI_STATUS
  BmFreeKeyOptions (
    IN EFI_BOOT_MANAGER_KEY_OPTION    *KeyOptions,
    IN UINTN                          KeyOptionCount
    )
  {
    if (KeyOptions != NULL) {
      FreePool(KeyOptions);
      return EFI_SUCCESS;
    } else {
      return EFI_NOT_FOUND;
    }
  }
  
  /**
    Register the key option to exit the waiting of the Boot Manager timeout.
    Platform should ensure that the continue key option isn't conflict with
    other boot key options.
  
    @param Modifier     Key shift state.
    @param  ...         Parameter list of pointer of EFI_INPUT_KEY.
  
    @retval EFI_SUCCESS         Successfully register the continue key option.
    @retval EFI_ALREADY_STARTED The continue key option is already registered.
  **/
  EFI_STATUS
  EFIAPI
  EfiBootManagerRegisterContinueKeyOption (
    IN UINT32           Modifier,
    ...
    )
  {
    EFI_STATUS                   Status;
    EFI_BOOT_MANAGER_KEY_OPTION  KeyOption;
    VA_LIST                      Args;
  
    if (mBmContinueKeyOption != NULL) {
      return EFI_ALREADY_STARTED;
    }
  
    ZeroMem (&KeyOption, sizeof (EFI_BOOT_MANAGER_KEY_OPTION));
    VA_START (Args, Modifier);
    Status = BmInitializeKeyFields (Modifier, Args, &KeyOption);
    VA_END (Args);
  
    if (!EFI_ERROR(Status)) {
      mBmContinueKeyOption = AllocateCopyPool(sizeof (EFI_BOOT_MANAGER_KEY_OPTION), &KeyOption);
      ASSERT (mBmContinueKeyOption != NULL);
      if (mBmHotkeyServiceStarted) {
        BmProcessKeyOption (mBmContinueKeyOption);
      }
    }
  
    return Status;
  }
  
  /**
    Stop the hotkey processing.
  
    @param    Event          Event pointer related to hotkey service.
    @param    Context        Context pass to this function.
  **/
  VOID
  EFIAPI
  BmStopHotkeyService (
    IN EFI_EVENT    Event,
    IN VOID         *Context
    )
  {
    LIST_ENTRY            *Link;
    BM_HOTKEY             *Hotkey;
  
    DEBUG ((EFI_D_INFO, "[Bds]Stop Hotkey Service!\n"));
    gBS->CloseEvent (Event);
  
    EfiAcquireLock (&mBmHotkeyLock);
    for (Link = GetFirstNode (&mBmHotkeyList); !IsNull (&mBmHotkeyList, Link); ) {
      Hotkey = BM_HOTKEY_FROM_LINK (Link);
      BmUnregisterHotkeyNotify (Hotkey);
      Link   = RemoveEntryList (Link);
      FreePool(Hotkey);
    }
    EfiReleaseLock (&mBmHotkeyLock);
  }
  
  /**
    Start the hot key service so that the key press can trigger the boot option.
  
    @param HotkeyTriggered  Return the waitable event and it will be signaled
                            when a valid hot key is pressed.
  
    @retval EFI_SUCCESS     The hot key service is started.
  **/
  EFI_STATUS
  EFIAPI
  EfiBootManagerStartHotkeyService (
    IN EFI_EVENT                 *HotkeyTriggered
    )
  {
    EFI_STATUS                   Status;
    EFI_BOOT_MANAGER_KEY_OPTION  *KeyOptions;
    UINTN                        KeyOptionCount;
    UINTN                        Index;
    EFI_EVENT                    Event;
    UINT32                       *BootOptionSupport;
  
    GetEfiGlobalVariable2 (EFI_BOOT_OPTION_SUPPORT_VARIABLE_NAME, (VOID **) &BootOptionSupport, NULL);
    if (BootOptionSupport != NULL) {
      if ((*BootOptionSupport & EFI_BOOT_OPTION_SUPPORT_KEY)  != 0) {
        mBmHotkeySupportCount = ((*BootOptionSupport & EFI_BOOT_OPTION_SUPPORT_COUNT) >> LowBitSet32 (EFI_BOOT_OPTION_SUPPORT_COUNT));
      }
      FreePool(BootOptionSupport);
    }
  
    if (mBmHotkeySupportCount == 0) {
      DEBUG ((EFI_D_INFO, "Bds: BootOptionSupport NV variable forbids starting the hotkey service.\n"));
      return EFI_UNSUPPORTED;
    }
  
    Status = gBS->CreateEvent (
                    EVT_NOTIFY_WAIT,
                    TPL_CALLBACK,
                    EfiEventEmptyFunction,
                    NULL,
                    &mBmHotkeyTriggered
                    );
    ASSERT_EFI_ERROR(Status);
  
    if (HotkeyTriggered != NULL) {
      *HotkeyTriggered = mBmHotkeyTriggered;
    }
  
    KeyOptions = BmGetKeyOptions (&KeyOptionCount);
    for (Index = 0; Index < KeyOptionCount; Index ++) {
      BmProcessKeyOption (&KeyOptions[Index]);
    }
    BmFreeKeyOptions (KeyOptions, KeyOptionCount);
  
    if (mBmContinueKeyOption != NULL) {
      BmProcessKeyOption (mBmContinueKeyOption);
    }
  
    //
    // Hook hotkey on every future SimpleTextInputEx instance when
    // SystemTable.ConsoleInHandle == NULL, which means the console
    // manager (ConSplitter) is absent.
    //
    if (gST->ConsoleInHandle == NULL) {
      EfiCreateProtocolNotifyEvent (
        &gEfiSimpleTextInputExProtocolGuid,
        TPL_CALLBACK,
        BmTxtInExCallback,
        NULL,
        &mBmTxtInExRegistration
        );
    }
  
    Status = EfiCreateEventReadyToBootEx (
               TPL_CALLBACK,
               BmStopHotkeyService,
               NULL,
               &Event
               );
    ASSERT_EFI_ERROR(Status);
  
    mBmHotkeyServiceStarted = TRUE;
    return Status;
  }
  
  /**
    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_STATUS                     Status;
    VA_LIST                        Args;
    VOID                           *BootOption;
    UINTN                          BootOptionSize;
    CHAR16                         BootOptionName[BM_OPTION_NAME_LEN];
    EFI_BOOT_MANAGER_KEY_OPTION    KeyOption;
    EFI_BOOT_MANAGER_KEY_OPTION    *KeyOptions;
    UINTN                          KeyOptionCount;
    UINTN                          Index;
    UINTN                          KeyOptionNumber;
    CHAR16                         KeyOptionName[sizeof ("Key####")];
  
    UnicodeSPrint (
      BootOptionName, sizeof (BootOptionName), L"%s%04x",
      mBmLoadOptionName[LoadOptionTypeBoot], BootOptionNumber
      );
    GetEfiGlobalVariable2 (BootOptionName, &BootOption, &BootOptionSize);
  
    if (BootOption == NULL) {
      return EFI_NOT_FOUND;
    }
  
    ZeroMem (&KeyOption, sizeof (EFI_BOOT_MANAGER_KEY_OPTION));
    KeyOption.BootOption = BootOptionNumber;
    Status = gBS->CalculateCrc32 (BootOption, BootOptionSize, &KeyOption.BootOptionCrc);
    ASSERT_EFI_ERROR(Status);
    FreePool(BootOption);
  
    VA_START (Args, Modifier);
    Status = BmInitializeKeyFields (Modifier, Args, &KeyOption);
    VA_END (Args);
    if (EFI_ERROR(Status)) {
      return Status;
    }
  
    KeyOptionNumber = LoadOptionNumberUnassigned;
    //
    // Check if the hot key sequence was defined already
    //
    KeyOptions = BmGetKeyOptions (&KeyOptionCount);
    for (Index = 0; Index < KeyOptionCount; Index++) {
      if ((KeyOptions[Index].KeyData.PackedValue == KeyOption.KeyData.PackedValue) &&
        (CompareMem (KeyOptions[Index].Keys, KeyOption.Keys, KeyOption.KeyData.Options.InputKeyCount * sizeof (EFI_INPUT_KEY)) == 0)) {
        break;
      }
  
      if ((KeyOptionNumber == LoadOptionNumberUnassigned) &&
          (KeyOptions[Index].OptionNumber > Index)
         ){
        KeyOptionNumber = Index;
      }
    }
    BmFreeKeyOptions (KeyOptions, KeyOptionCount);
  
    if (Index < KeyOptionCount) {
      return EFI_ALREADY_STARTED;
    }
  
    if (KeyOptionNumber == LoadOptionNumberUnassigned) {
      KeyOptionNumber = KeyOptionCount;
    }
  
    UnicodeSPrint (KeyOptionName, sizeof (KeyOptionName), L"Key%04x", KeyOptionNumber);
  
    Status = gRT->SetVariable (
                    KeyOptionName,
                    &gEfiGlobalVariableGuid,
                    EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE,
                    BmSizeOfKeyOption (&KeyOption),
                    &KeyOption
                    );
    if (!EFI_ERROR(Status)) {
      //
      // Return the Key Option in case needed by caller
      //
      if (AddedOption != NULL) {
        CopyMem(AddedOption, &KeyOption, sizeof (EFI_BOOT_MANAGER_KEY_OPTION));
      }
  
      //
      // Register the newly added hot key
      // Calling this function before EfiBootManagerStartHotkeyService doesn't
      // need to call BmProcessKeyOption
      //
      if (mBmHotkeyServiceStarted) {
        BmProcessKeyOption (&KeyOption);
      }
    }
  
    return Status;
  }
  
  /**
    Delete the Key Option variable and unregister the hot key
  
    @param DeletedOption  Return the deleted key options.
    @param Modifier       Key shift state.
    @param ...            Parameter list of pointer of EFI_INPUT_KEY.
  
    @retval EFI_SUCCESS   The key option is deleted.
    @retval EFI_NOT_FOUND The key option cannot be found.
  **/
  EFI_STATUS
  EFIAPI
  EfiBootManagerDeleteKeyOptionVariable (
    IN EFI_BOOT_MANAGER_KEY_OPTION *DeletedOption, OPTIONAL
    IN UINT32                      Modifier,
    ...
    )
  {
    EFI_STATUS                     Status;
    UINTN                          Index;
    VA_LIST                        Args;
    EFI_BOOT_MANAGER_KEY_OPTION    KeyOption;
    EFI_BOOT_MANAGER_KEY_OPTION    *KeyOptions;
    UINTN                          KeyOptionCount;
    LIST_ENTRY                     *Link;
    BM_HOTKEY                      *Hotkey;
    UINT32                         ShiftState;
    BOOLEAN                        Match;
    CHAR16                         KeyOptionName[sizeof ("Key####")];
  
    ZeroMem (&KeyOption, sizeof (EFI_BOOT_MANAGER_KEY_OPTION));
    VA_START (Args, Modifier);
    Status = BmInitializeKeyFields (Modifier, Args, &KeyOption);
    VA_END (Args);
  
    if (EFI_ERROR(Status)) {
      return Status;
    }
  
    EfiAcquireLock (&mBmHotkeyLock);
    //
    // Delete the key option from active hot key list
    // Could have multiple entries when modifier isn't 0 because we map the ShiftPressed to RIGHT_SHIFT and RIGHT_SHIFT
    //
    for (Link = GetFirstNode (&mBmHotkeyList); !IsNull (&mBmHotkeyList, Link); ) {
      Hotkey = BM_HOTKEY_FROM_LINK (Link);
      Match  = (BOOLEAN) (Hotkey->CodeCount == KeyOption.KeyData.Options.InputKeyCount);
  
      for (Index = 0; Match && (Index < Hotkey->CodeCount); Index++) {
        ShiftState = Hotkey->KeyData[Index].KeyState.KeyShiftState;
        if (
          (BmBitSet (ShiftState, EFI_RIGHT_SHIFT_PRESSED | EFI_LEFT_SHIFT_PRESSED) != KeyOption.KeyData.Options.ShiftPressed) ||
          (BmBitSet (ShiftState, EFI_RIGHT_CONTROL_PRESSED | EFI_LEFT_CONTROL_PRESSED) != KeyOption.KeyData.Options.ControlPressed) ||
          (BmBitSet (ShiftState, EFI_RIGHT_ALT_PRESSED | EFI_LEFT_ALT_PRESSED) != KeyOption.KeyData.Options.AltPressed) ||
          (BmBitSet (ShiftState, EFI_RIGHT_LOGO_PRESSED | EFI_LEFT_LOGO_PRESSED) != KeyOption.KeyData.Options.LogoPressed) ||
          (BmBitSet (ShiftState, EFI_MENU_KEY_PRESSED) != KeyOption.KeyData.Options.MenuPressed) ||
          (BmBitSet (ShiftState, EFI_SYS_REQ_PRESSED) != KeyOption.KeyData.Options.SysReqPressed) ||
          (CompareMem (&Hotkey->KeyData[Index].Key, &KeyOption.Keys[Index], sizeof (EFI_INPUT_KEY)) != 0)
          ) {
          //
          // Break when any field doesn't match
          //
          Match = FALSE;
          break;
        }
      }
  
      if (Match) {
        Link = RemoveEntryList (Link);
        FreePool(Hotkey);
      } else {
        Link = GetNextNode (&mBmHotkeyList, Link);
      }
    }
  
    //
    // Delete the key option from the variable
    //
    Status     = EFI_NOT_FOUND;
    KeyOptions = BmGetKeyOptions (&KeyOptionCount);
    for (Index = 0; Index < KeyOptionCount; Index++) {
      if ((KeyOptions[Index].KeyData.PackedValue == KeyOption.KeyData.PackedValue) &&
          (CompareMem (
             KeyOptions[Index].Keys, KeyOption.Keys,
             KeyOption.KeyData.Options.InputKeyCount * sizeof (EFI_INPUT_KEY)) == 0)
         ) {
        UnicodeSPrint (KeyOptionName, sizeof (KeyOptionName), L"Key%04x", KeyOptions[Index].OptionNumber);
        Status = gRT->SetVariable (
                   KeyOptionName,
                   &gEfiGlobalVariableGuid,
                   EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE,
                   0,
                   NULL
                   );
        //
        // Return the deleted key option in case needed by caller
        //
        if (DeletedOption != NULL) {
          CopyMem(DeletedOption, &KeyOptions[Index], sizeof (EFI_BOOT_MANAGER_KEY_OPTION));
        }
        break;
      }
    }
    BmFreeKeyOptions (KeyOptions, KeyOptionCount);
  
    EfiReleaseLock (&mBmHotkeyLock);
  
    return Status;
  }
  