/** @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 { | |
Print ( | |
L"%a: failed to %a %s \"%s\" from %s: %r\n", | |
gEfiCallerBaseName, | |
Value == mLoadFail ? "load" : "start", | |
BootOptionName, | |
BmBootOption.Description, | |
DevPathString, | |
((EFI_RETURN_STATUS_EXTENDED_DATA *)Data)->ReturnStatus | |
); | |
} | |
// | |
// 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; | |
} |