/** @file Provides a way for 3rd party applications to register themselves for launch by the Boot Manager based on hot key Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "Hotkey.h" LIST_ENTRY mHotkeyList = INITIALIZE_LIST_HEAD_VARIABLE (mHotkeyList); BDS_COMMON_OPTION *mHotkeyBootOption = NULL; EFI_EVENT mHotkeyEvent; VOID *mHotkeyRegistration; /** Check if the Key Option is valid or not. @param KeyOption The Hot Key Option to be checked. @retval TRUE The Hot Key Option is valid. @retval FALSE The Hot Key Option is invalid. **/ BOOLEAN IsKeyOptionValid ( IN EFI_KEY_OPTION *KeyOption ) { UINT16 BootOptionName[10]; UINT8 *BootOptionVar; UINTN BootOptionSize; UINT32 Crc; // // Check whether corresponding Boot Option exist // UnicodeSPrint (BootOptionName, sizeof (BootOptionName), L"Boot%04x", KeyOption->BootOption); BootOptionVar = BdsLibGetVariableAndSize ( BootOptionName, &gEfiGlobalVariableGuid, &BootOptionSize ); if (BootOptionVar == NULL || BootOptionSize == 0) { return FALSE; } // // Check CRC for Boot Option // gBS->CalculateCrc32 (BootOptionVar, BootOptionSize, &Crc); FreePool (BootOptionVar); return (BOOLEAN) ((KeyOption->BootOptionCrc == Crc) ? TRUE : FALSE); } /** Try to boot the boot option triggered by hotkey. @retval EFI_SUCCESS There is HotkeyBootOption & it is processed @retval EFI_NOT_FOUND There is no HotkeyBootOption **/ EFI_STATUS HotkeyBoot ( VOID ) { EFI_STATUS Status; UINTN ExitDataSize; CHAR16 *ExitData; if (mHotkeyBootOption == NULL) { return EFI_NOT_FOUND; } BdsLibConnectDevicePath (mHotkeyBootOption->DevicePath); // // Clear the screen before launch this BootOption // gST->ConOut->Reset (gST->ConOut, FALSE); Status = BdsLibBootViaBootOption (mHotkeyBootOption, mHotkeyBootOption->DevicePath, &ExitDataSize, &ExitData); if (EFI_ERROR (Status)) { // // Call platform action to indicate the boot fail // mHotkeyBootOption->StatusString = GetStringById (STRING_TOKEN (STR_BOOT_FAILED)); PlatformBdsBootFail (mHotkeyBootOption, Status, ExitData, ExitDataSize); } else { // // Call platform action to indicate the boot success // mHotkeyBootOption->StatusString = GetStringById (STRING_TOKEN (STR_BOOT_SUCCEEDED)); PlatformBdsBootSuccess (mHotkeyBootOption); } FreePool (mHotkeyBootOption->Description); FreePool (mHotkeyBootOption->DevicePath); FreePool (mHotkeyBootOption->LoadOptions); FreePool (mHotkeyBootOption); mHotkeyBootOption = NULL; return EFI_SUCCESS; } /** 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 HotkeyCallback ( IN EFI_KEY_DATA *KeyData ) { BOOLEAN HotkeyCatched; LIST_ENTRY BootLists; LIST_ENTRY *Link; BDS_HOTKEY_OPTION *Hotkey; UINT16 Buffer[10]; EFI_STATUS Status; EFI_KEY_DATA *HotkeyData; if (mHotkeyBootOption != NULL) { // // Do not process sequential hotkey stroke until the current boot option returns // return EFI_SUCCESS; } Status = EFI_SUCCESS; for ( Link = GetFirstNode (&mHotkeyList) ; !IsNull (&mHotkeyList, Link) ; Link = GetNextNode (&mHotkeyList, Link) ) { HotkeyCatched = FALSE; Hotkey = BDS_HOTKEY_OPTION_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 ) ) { // // For hotkey of key combination, transit to next waiting state // Hotkey->WaitingKey++; if (Hotkey->WaitingKey == Hotkey->CodeCount) { // // Received the whole key stroke sequence // HotkeyCatched = TRUE; } } else { // // Receive an unexpected key stroke, reset to initial waiting state // Hotkey->WaitingKey = 0; } if (HotkeyCatched) { // // Reset to initial waiting state // Hotkey->WaitingKey = 0; // // Launch its BootOption // InitializeListHead (&BootLists); UnicodeSPrint (Buffer, sizeof (Buffer), L"Boot%04x", Hotkey->BootOptionNumber); mHotkeyBootOption = BdsLibVariableToOption (&BootLists, Buffer); } } return Status; } /** Register the common HotKey notify function to given SimpleTextInEx protocol instance. @param SimpleTextInEx Simple Text Input Ex protocol instance @retval EFI_SUCCESS Register hotkey notification function successfully. @retval EFI_OUT_OF_RESOURCES Unable to allocate necessary data structures. **/ EFI_STATUS HotkeyRegisterNotify ( IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *SimpleTextInEx ) { UINTN Index; EFI_STATUS Status; LIST_ENTRY *Link; BDS_HOTKEY_OPTION *Hotkey; // // Register notification function for each hotkey // Link = GetFirstNode (&mHotkeyList); while (!IsNull (&mHotkeyList, Link)) { Hotkey = BDS_HOTKEY_OPTION_FROM_LINK (Link); Index = 0; do { Status = SimpleTextInEx->RegisterKeyNotify ( SimpleTextInEx, &Hotkey->KeyData[Index], HotkeyCallback, &Hotkey->NotifyHandle ); if (EFI_ERROR (Status)) { // // some of the hotkey registry failed // return Status; } Index ++; } while ((Index < Hotkey->CodeCount) && (Index < (sizeof (Hotkey->KeyData) / sizeof (EFI_KEY_DATA)))); Link = GetNextNode (&mHotkeyList, Link); } return EFI_SUCCESS; } /** Callback function for SimpleTextInEx protocol install events @param Event the event that is signaled. @param Context not used here. **/ VOID EFIAPI HotkeyEvent ( IN EFI_EVENT Event, IN VOID *Context ) { EFI_STATUS Status; UINTN BufferSize; EFI_HANDLE Handle; EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *SimpleTextInEx; while (TRUE) { BufferSize = sizeof (EFI_HANDLE); Status = gBS->LocateHandle ( ByRegisterNotify, NULL, mHotkeyRegistration, &BufferSize, &Handle ); if (EFI_ERROR (Status)) { // // If no more notification events exist // return ; } Status = gBS->HandleProtocol ( Handle, &gEfiSimpleTextInputExProtocolGuid, (VOID **) &SimpleTextInEx ); ASSERT_EFI_ERROR (Status); HotkeyRegisterNotify (SimpleTextInEx); } } /** Insert Key Option to hotkey list. @param KeyOption The Hot Key Option to be added to hotkey list. @retval EFI_SUCCESS Add to hotkey list success. @retval EFI_OUT_OF_RESOURCES Fail to allocate memory resource. **/ EFI_STATUS HotkeyInsertList ( IN EFI_KEY_OPTION *KeyOption ) { BDS_HOTKEY_OPTION *HotkeyLeft; BDS_HOTKEY_OPTION *HotkeyRight; UINTN Index; EFI_BOOT_KEY_DATA KeyOptions; UINT32 KeyShiftStateLeft; UINT32 KeyShiftStateRight; EFI_INPUT_KEY *InputKey; EFI_KEY_DATA *KeyData; HotkeyLeft = AllocateZeroPool (sizeof (BDS_HOTKEY_OPTION)); if (HotkeyLeft == NULL) { return EFI_OUT_OF_RESOURCES; } HotkeyLeft->Signature = BDS_HOTKEY_OPTION_SIGNATURE; HotkeyLeft->BootOptionNumber = KeyOption->BootOption; KeyOptions = KeyOption->KeyData; HotkeyLeft->CodeCount = (UINT8) KeyOptions.Options.InputKeyCount; // // Map key shift state from KeyOptions to EFI_KEY_DATA.KeyState // KeyShiftStateRight = EFI_SHIFT_STATE_VALID; if (KeyOptions.Options.ShiftPressed) { KeyShiftStateRight |= EFI_RIGHT_SHIFT_PRESSED; } if (KeyOptions.Options.ControlPressed) { KeyShiftStateRight |= EFI_RIGHT_CONTROL_PRESSED; } if (KeyOptions.Options.AltPressed) { KeyShiftStateRight |= EFI_RIGHT_ALT_PRESSED; } if (KeyOptions.Options.LogoPressed) { KeyShiftStateRight |= EFI_RIGHT_LOGO_PRESSED; } if (KeyOptions.Options.MenuPressed) { KeyShiftStateRight |= EFI_MENU_KEY_PRESSED; } if (KeyOptions.Options.SysReqPressed) { KeyShiftStateRight |= EFI_SYS_REQ_PRESSED; } KeyShiftStateLeft = (KeyShiftStateRight & 0xffffff00) | ((KeyShiftStateRight & 0xff) << 1); InputKey = (EFI_INPUT_KEY *) (((UINT8 *) KeyOption) + sizeof (EFI_KEY_OPTION)); Index = 0; KeyData = &HotkeyLeft->KeyData[0]; do { // // If Key CodeCount is 0, then only KeyData[0] is used; // if Key CodeCount is n, then KeyData[0]~KeyData[n-1] are used // KeyData->Key.ScanCode = InputKey[Index].ScanCode; KeyData->Key.UnicodeChar = InputKey[Index].UnicodeChar; KeyData->KeyState.KeyShiftState = KeyShiftStateLeft; Index++; KeyData++; } while (Index < HotkeyLeft->CodeCount); InsertTailList (&mHotkeyList, &HotkeyLeft->Link); if (KeyShiftStateLeft != KeyShiftStateRight) { // // Need an extra hotkey for shift key on right // HotkeyRight = AllocateCopyPool (sizeof (BDS_HOTKEY_OPTION), HotkeyLeft); if (HotkeyRight == NULL) { return EFI_OUT_OF_RESOURCES; } Index = 0; KeyData = &HotkeyRight->KeyData[0]; do { // // Key.ScanCode and Key.UnicodeChar have already been initialized, // only need to update KeyState.KeyShiftState // KeyData->KeyState.KeyShiftState = KeyShiftStateRight; Index++; KeyData++; } while (Index < HotkeyRight->CodeCount); InsertTailList (&mHotkeyList, &HotkeyRight->Link); } return EFI_SUCCESS; } /** Return TRUE when the variable pointed by Name and Guid is a Key#### variable. @param Name The name of the variable. @param Guid The GUID of the variable. @param OptionNumber Return the option number parsed from the Name. @retval TRUE The variable pointed by Name and Guid is a Key#### variable. @retval FALSE The variable pointed by Name and Guid isn't a Key#### variable. **/ BOOLEAN IsKeyOptionVariable ( CHAR16 *Name, EFI_GUID *Guid, UINT16 *OptionNumber ) { UINTN Index; 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++) { if ((Name[Index] >= L'0') && (Name[Index] <= L'9')) { *OptionNumber = *OptionNumber * 16 + Name[Index] - L'0'; } else if ((Name[Index] >= L'A') && (Name[Index] <= L'F')) { *OptionNumber = *OptionNumber * 16 + Name[Index] - L'A' + 10; } else { return FALSE; } } return TRUE; } /** Return an array of key option numbers. @param Count Return the count of key option numbers. @return UINT16* Pointer to an array of key option numbers; **/ UINT16 * EFIAPI HotkeyGetOptionNumbers ( OUT UINTN *Count ) { EFI_STATUS Status; UINTN Index; CHAR16 *Name; EFI_GUID Guid; UINTN NameSize; UINTN NewNameSize; UINT16 *OptionNumbers; UINT16 OptionNumber; if (Count == NULL) { return NULL; } *Count = 0; OptionNumbers = NULL; NameSize = sizeof (CHAR16); Name = AllocateZeroPool (NameSize); ASSERT (Name != NULL); while (TRUE) { NewNameSize = NameSize; Status = gRT->GetNextVariableName (&NewNameSize, Name, &Guid); if (Status == EFI_BUFFER_TOO_SMALL) { Name = ReallocatePool (NameSize, NewNameSize, Name); ASSERT (Name != NULL); Status = gRT->GetNextVariableName (&NewNameSize, Name, &Guid); NameSize = NewNameSize; } if (Status == EFI_NOT_FOUND) { break; } ASSERT_EFI_ERROR (Status); if (IsKeyOptionVariable (Name ,&Guid, &OptionNumber)) { OptionNumbers = ReallocatePool ( *Count * sizeof (UINT16), (*Count + 1) * sizeof (UINT16), OptionNumbers ); ASSERT (OptionNumbers != NULL); for (Index = 0; Index < *Count; Index++) { if (OptionNumber < OptionNumbers[Index]) { break; } } CopyMem (&OptionNumbers[Index + 1], &OptionNumbers[Index], (*Count - Index) * sizeof (UINT16)); OptionNumbers[Index] = OptionNumber; (*Count)++; } } FreePool (Name); return OptionNumbers; } /** Process all the "Key####" variables, associate Hotkeys with corresponding Boot Options. @retval EFI_SUCCESS Hotkey services successfully initialized. **/ EFI_STATUS InitializeHotkeyService ( VOID ) { EFI_STATUS Status; UINT32 BootOptionSupport; UINT16 *KeyOptionNumbers; UINTN KeyOptionCount; UINTN Index; CHAR16 KeyOptionName[8]; EFI_KEY_OPTION *KeyOption; // // Export our capability - EFI_BOOT_OPTION_SUPPORT_KEY and EFI_BOOT_OPTION_SUPPORT_APP. // with maximum number of key presses of 3 // Do not report the hotkey capability if PcdConInConnectOnDemand is enabled. // BootOptionSupport = EFI_BOOT_OPTION_SUPPORT_APP; if (!PcdGetBool (PcdConInConnectOnDemand)) { BootOptionSupport |= EFI_BOOT_OPTION_SUPPORT_KEY; SET_BOOT_OPTION_SUPPORT_KEY_COUNT (BootOptionSupport, 3); } Status = gRT->SetVariable ( L"BootOptionSupport", &gEfiGlobalVariableGuid, EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, sizeof (UINT32), &BootOptionSupport ); // // Platform needs to make sure setting volatile variable before calling 3rd party code shouldn't fail. // ASSERT_EFI_ERROR (Status); KeyOptionNumbers = HotkeyGetOptionNumbers (&KeyOptionCount); for (Index = 0; Index < KeyOptionCount; Index ++) { UnicodeSPrint (KeyOptionName, sizeof (KeyOptionName), L"Key%04x", KeyOptionNumbers[Index]); GetEfiGlobalVariable2 (KeyOptionName, (VOID **) &KeyOption, NULL); ASSERT (KeyOption != NULL); if (IsKeyOptionValid (KeyOption)) { HotkeyInsertList (KeyOption); } FreePool (KeyOption); } if (KeyOptionNumbers != NULL) { FreePool (KeyOptionNumbers); } // // Register Protocol notify for Hotkey service // Status = gBS->CreateEvent ( EVT_NOTIFY_SIGNAL, TPL_CALLBACK, HotkeyEvent, NULL, &mHotkeyEvent ); ASSERT_EFI_ERROR (Status); // // Register for protocol notifications on this event // Status = gBS->RegisterProtocolNotify ( &gEfiSimpleTextInputExProtocolGuid, mHotkeyEvent, &mHotkeyRegistration ); ASSERT_EFI_ERROR (Status); return Status; }