]> git.proxmox.com Git - mirror_edk2.git/blobdiff - OvmfPkg/Library/PlatformBmPrintScLib/StatusCodeHandler.c
OvmfPkg: add library to track boot option loading/starting on the console
[mirror_edk2.git] / OvmfPkg / Library / PlatformBmPrintScLib / StatusCodeHandler.c
diff --git a/OvmfPkg/Library/PlatformBmPrintScLib/StatusCodeHandler.c b/OvmfPkg/Library/PlatformBmPrintScLib/StatusCodeHandler.c
new file mode 100644 (file)
index 0000000..3afe9d9
--- /dev/null
@@ -0,0 +1,310 @@
+/** @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