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