| /** @file | |
| Register a status code handler for printing the Boot Manager's LoadImage() | |
| and StartImage() preparations, and return codes, to the UEFI console. | |
| This feature enables users that are not accustomed to analyzing the firmware | |
| log to glean some information about UEFI boot option processing (loading and | |
| starting). | |
| This library instance filters out (ignores) status codes that are not | |
| reported by the containing firmware module. The intent is to link this | |
| library instance into BdsDxe via PlatformBootManagerLib (which BdsDxe depends | |
| upon), then catch only those status codes that BdsDxe reports (which happens | |
| via UefiBootManagerLib). Status codes reported by other modules (such as | |
| UiApp), via UefiBootManagerLib or otherwise, are meant to be ignored. | |
| Copyright (C) 2019, Red Hat, Inc. | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include <Library/BaseMemoryLib.h> | |
| #include <Library/DebugLib.h> | |
| #include <Library/DevicePathLib.h> | |
| #include <Library/MemoryAllocationLib.h> | |
| #include <Library/PcdLib.h> | |
| #include <Library/PrintLib.h> | |
| #include <Library/UefiBootManagerLib.h> | |
| #include <Library/UefiBootServicesTableLib.h> | |
| #include <Library/UefiLib.h> | |
| #include <Library/UefiRuntimeServicesTableLib.h> | |
| #include <Protocol/ReportStatusCodeHandler.h> | |
| #include <Guid/GlobalVariable.h> | |
| #include <Guid/StatusCodeDataTypeId.h> | |
| #include <Pi/PiStatusCode.h> | |
| // | |
| // Convenience variables for the status codes that are relevant for LoadImage() | |
| // and StartImage() preparations and return codes. | |
| // | |
| STATIC EFI_STATUS_CODE_VALUE mLoadPrep; | |
| STATIC EFI_STATUS_CODE_VALUE mLoadFail; | |
| STATIC EFI_STATUS_CODE_VALUE mStartPrep; | |
| STATIC EFI_STATUS_CODE_VALUE mStartFail; | |
| /** | |
| Handle status codes reported through ReportStatusCodeLib / | |
| EFI_STATUS_CODE_PROTOCOL.ReportStatusCode(). Format matching status codes to | |
| the system console. | |
| The highest TPL at which this handler can be registered with | |
| EFI_RSC_HANDLER_PROTOCOL.Register() is TPL_CALLBACK. That's because | |
| HandleStatusCode() uses the UEFI variable services. | |
| The parameter list of this function precisely matches that of | |
| EFI_STATUS_CODE_PROTOCOL.ReportStatusCode(). | |
| The return status of this function is ignored by the caller, but the function | |
| still returns sensible codes: | |
| @retval EFI_SUCCESS The status code has been processed; either | |
| as a no-op, due to filtering, or by | |
| formatting it to the system console. | |
| @retval EFI_INVALID_PARAMETER Unknown or malformed contents have been | |
| detected in Data. | |
| @retval EFI_INCOMPATIBLE_VERSION Unexpected UEFI variable behavior has been | |
| encountered. | |
| @return Error codes propagated from underlying | |
| services. | |
| **/ | |
| STATIC | |
| EFI_STATUS | |
| EFIAPI | |
| HandleStatusCode ( | |
| IN EFI_STATUS_CODE_TYPE CodeType, | |
| IN EFI_STATUS_CODE_VALUE Value, | |
| IN UINT32 Instance, | |
| IN EFI_GUID *CallerId, | |
| IN EFI_STATUS_CODE_DATA *Data | |
| ) | |
| { | |
| UINTN VariableSize; | |
| UINT16 BootCurrent; | |
| EFI_STATUS Status; | |
| CHAR16 BootOptionName[ARRAY_SIZE (L"Boot####")]; | |
| EFI_BOOT_MANAGER_LOAD_OPTION BmBootOption; | |
| BOOLEAN DevPathStringIsDynamic; | |
| CHAR16 *DevPathString; | |
| // | |
| // Ignore all status codes that are irrelevant for LoadImage() and | |
| // StartImage() preparations and return codes. | |
| // | |
| if ((Value != mLoadPrep) && (Value != mLoadFail) && | |
| (Value != mStartPrep) && (Value != mStartFail)) | |
| { | |
| return EFI_SUCCESS; | |
| } | |
| // | |
| // Ignore status codes that are not reported by the same containing module. | |
| // | |
| if (!CompareGuid (CallerId, &gEfiCallerIdGuid)) { | |
| return EFI_SUCCESS; | |
| } | |
| // | |
| // Sanity-check Data in case of failure reports. | |
| // | |
| if (((Value == mLoadFail) || (Value == mStartFail)) && | |
| ((Data == NULL) || | |
| (Data->HeaderSize != sizeof *Data) || | |
| (Data->Size != sizeof (EFI_RETURN_STATUS_EXTENDED_DATA) - sizeof *Data) || | |
| !CompareGuid (&Data->Type, &gEfiStatusCodeSpecificDataGuid))) | |
| { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a:%a: malformed Data\n", | |
| gEfiCallerBaseName, | |
| __func__ | |
| )); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| // | |
| // Get the number of the Boot#### option that the status code applies to. | |
| // | |
| VariableSize = sizeof BootCurrent; | |
| Status = gRT->GetVariable ( | |
| EFI_BOOT_CURRENT_VARIABLE_NAME, | |
| &gEfiGlobalVariableGuid, | |
| NULL /* Attributes */, | |
| &VariableSize, | |
| &BootCurrent | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a:%a: failed to get %g:\"%s\": %r\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| &gEfiGlobalVariableGuid, | |
| EFI_BOOT_CURRENT_VARIABLE_NAME, | |
| Status | |
| )); | |
| return Status; | |
| } | |
| if (VariableSize != sizeof BootCurrent) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a:%a: got %Lu bytes for %g:\"%s\", expected %Lu\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| (UINT64)VariableSize, | |
| &gEfiGlobalVariableGuid, | |
| EFI_BOOT_CURRENT_VARIABLE_NAME, | |
| (UINT64)sizeof BootCurrent | |
| )); | |
| return EFI_INCOMPATIBLE_VERSION; | |
| } | |
| // | |
| // Get the Boot#### option that the status code applies to. | |
| // | |
| UnicodeSPrint ( | |
| BootOptionName, | |
| sizeof BootOptionName, | |
| L"Boot%04x", | |
| BootCurrent | |
| ); | |
| Status = EfiBootManagerVariableToLoadOption (BootOptionName, &BmBootOption); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a:%a: EfiBootManagerVariableToLoadOption(\"%s\"): %r\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| BootOptionName, | |
| Status | |
| )); | |
| return Status; | |
| } | |
| // | |
| // Format the device path. | |
| // | |
| DevPathStringIsDynamic = TRUE; | |
| DevPathString = ConvertDevicePathToText ( | |
| BmBootOption.FilePath, | |
| FALSE, // DisplayOnly | |
| FALSE // AllowShortcuts | |
| ); | |
| if (DevPathString == NULL) { | |
| DevPathStringIsDynamic = FALSE; | |
| DevPathString = L"<out of memory while formatting device path>"; | |
| } | |
| // | |
| // Print the message to the console. | |
| // | |
| if ((Value == mLoadPrep) || (Value == mStartPrep)) { | |
| Print ( | |
| L"%a: %a %s \"%s\" from %s\n", | |
| gEfiCallerBaseName, | |
| Value == mLoadPrep ? "loading" : "starting", | |
| BootOptionName, | |
| BmBootOption.Description, | |
| DevPathString | |
| ); | |
| } else { | |
| EFI_STATUS ReturnStatus; | |
| ReturnStatus = ((EFI_RETURN_STATUS_EXTENDED_DATA *)Data)->ReturnStatus; | |
| Print ( | |
| L"%a: failed to %a %s \"%s\" from %s: %r%a\n", | |
| gEfiCallerBaseName, | |
| Value == mLoadFail ? "load" : "start", | |
| BootOptionName, | |
| BmBootOption.Description, | |
| DevPathString, | |
| ReturnStatus, | |
| ((ReturnStatus == EFI_SECURITY_VIOLATION || | |
| (Value == mLoadFail && ReturnStatus == EFI_ACCESS_DENIED)) ? | |
| " -- rejected probably by Secure Boot" : "") | |
| ); | |
| } | |
| // | |
| // Done. | |
| // | |
| if (DevPathStringIsDynamic) { | |
| FreePool (DevPathString); | |
| } | |
| EfiBootManagerFreeLoadOption (&BmBootOption); | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Unregister HandleStatusCode() at ExitBootServices(). | |
| (See EFI_RSC_HANDLER_PROTOCOL in Volume 3 of the Platform Init spec.) | |
| @param[in] Event Event whose notification function is being invoked. | |
| @param[in] Context Pointer to EFI_RSC_HANDLER_PROTOCOL, originally looked up | |
| when HandleStatusCode() was registered. | |
| **/ | |
| STATIC | |
| VOID | |
| EFIAPI | |
| UnregisterAtExitBootServices ( | |
| IN EFI_EVENT Event, | |
| IN VOID *Context | |
| ) | |
| { | |
| EFI_RSC_HANDLER_PROTOCOL *StatusCodeRouter; | |
| StatusCodeRouter = Context; | |
| StatusCodeRouter->Unregister (HandleStatusCode); | |
| } | |
| /** | |
| Register a status code handler for printing the Boot Manager's LoadImage() | |
| and StartImage() preparations, and return codes, to the UEFI console. | |
| @retval EFI_SUCCESS The status code handler has been successfully | |
| registered. | |
| @return Error codes propagated from boot services and from | |
| EFI_RSC_HANDLER_PROTOCOL. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| PlatformBmPrintScRegisterHandler ( | |
| VOID | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_RSC_HANDLER_PROTOCOL *StatusCodeRouter; | |
| EFI_EVENT ExitBootEvent; | |
| Status = gBS->LocateProtocol ( | |
| &gEfiRscHandlerProtocolGuid, | |
| NULL /* Registration */, | |
| (VOID **)&StatusCodeRouter | |
| ); | |
| ASSERT_EFI_ERROR (Status); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| // | |
| // Set the EFI_STATUS_CODE_VALUE convenience variables. | |
| // | |
| mLoadPrep = PcdGet32 (PcdProgressCodeOsLoaderLoad); | |
| mLoadFail = (EFI_SOFTWARE_DXE_BS_DRIVER | | |
| EFI_SW_DXE_BS_EC_BOOT_OPTION_LOAD_ERROR); | |
| mStartPrep = PcdGet32 (PcdProgressCodeOsLoaderStart); | |
| mStartFail = (EFI_SOFTWARE_DXE_BS_DRIVER | | |
| EFI_SW_DXE_BS_EC_BOOT_OPTION_FAILED); | |
| // | |
| // Register the handler callback. | |
| // | |
| Status = StatusCodeRouter->Register (HandleStatusCode, TPL_CALLBACK); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a:%a: failed to register status code handler: %r\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| Status | |
| )); | |
| return Status; | |
| } | |
| // | |
| // Status code reporting and routing/handling extend into OS runtime. Since | |
| // we don't want our handler to survive the BDS phase, we have to unregister | |
| // the callback at ExitBootServices(). (See EFI_RSC_HANDLER_PROTOCOL in | |
| // Volume 3 of the Platform Init spec.) | |
| // | |
| Status = gBS->CreateEvent ( | |
| EVT_SIGNAL_EXIT_BOOT_SERVICES, // Type | |
| TPL_CALLBACK, // NotifyTpl | |
| UnregisterAtExitBootServices, // NotifyFunction | |
| StatusCodeRouter, // NotifyContext | |
| &ExitBootEvent // Event | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| // | |
| // We have to unregister the callback right now, and fail the function. | |
| // | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a:%a: failed to create ExitBootServices() event: " | |
| "%r\n", | |
| gEfiCallerBaseName, | |
| __func__, | |
| Status | |
| )); | |
| StatusCodeRouter->Unregister (HandleStatusCode); | |
| return Status; | |
| } | |
| return EFI_SUCCESS; | |
| } |