/** @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 ((DEBUG_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 ((DEBUG_INFO, "[Bds]Hotkey for %s pressed - %r\n", OptionName, Status)); | |
if (EFI_ERROR (Status)) { | |
mBmHotkeyBootOption.OptionNumber = LoadOptionNumberUnassigned; | |
} | |
} else { | |
DEBUG ((DEBUG_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 ((DEBUG_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 (( | |
DEBUG_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); | |
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 ((DEBUG_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 ((DEBUG_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; | |
} |