| /** @file | |
| Library functions which relates with driver health. | |
| Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR> | |
| (C) Copyright 2015 Hewlett-Packard Development Company, L.P.<BR> | |
| (C) Copyright 2016 Hewlett Packard Enterprise Development LP<BR> | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include "InternalBm.h" | |
| GLOBAL_REMOVE_IF_UNREFERENCED | |
| CHAR16 *mBmHealthStatusText[] = { | |
| L"Healthy", | |
| L"Repair Required", | |
| L"Configuration Required", | |
| L"Failed", | |
| L"Reconnect Required", | |
| L"Reboot Required" | |
| }; | |
| /** | |
| Return the controller name. | |
| @param DriverHealthHandle The handle on which the Driver Health protocol instance is retrieved. | |
| @param ControllerHandle The handle of a controller that the driver specified by DriverBindingHandle is managing. | |
| This handle specifies the controller whose name is to be returned. | |
| @param ChildHandle The handle of the child controller to retrieve the name of. This is an | |
| optional parameter that may be NULL. It will be NULL for device drivers. | |
| It will also be NULL for bus drivers that attempt to retrieve the name | |
| of the bus controller. It will not be NULL for a bus driver that attempts | |
| to retrieve the name of a child controller. | |
| @return A pointer to the Unicode string to return. This Unicode string is the name of the controller | |
| specified by ControllerHandle and ChildHandle. | |
| **/ | |
| CHAR16 * | |
| BmGetControllerName ( | |
| IN EFI_HANDLE DriverHealthHandle, | |
| IN EFI_HANDLE ControllerHandle, | |
| IN EFI_HANDLE ChildHandle | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| CHAR16 *ControllerName; | |
| CHAR8 *LanguageVariable; | |
| CHAR8 *BestLanguage; | |
| BOOLEAN Iso639Language; | |
| EFI_COMPONENT_NAME_PROTOCOL *ComponentName; | |
| ControllerName = NULL; | |
| // | |
| // Locate Component Name (2) protocol on the driver binging handle. | |
| // | |
| Iso639Language = FALSE; | |
| Status = gBS->HandleProtocol ( | |
| DriverHealthHandle, | |
| &gEfiComponentName2ProtocolGuid, | |
| (VOID **)&ComponentName | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| Status = gBS->HandleProtocol ( | |
| DriverHealthHandle, | |
| &gEfiComponentNameProtocolGuid, | |
| (VOID **)&ComponentName | |
| ); | |
| if (!EFI_ERROR (Status)) { | |
| Iso639Language = TRUE; | |
| } | |
| } | |
| if (!EFI_ERROR (Status)) { | |
| GetEfiGlobalVariable2 (Iso639Language ? L"Lang" : L"PlatformLang", (VOID **)&LanguageVariable, NULL); | |
| BestLanguage = GetBestLanguage ( | |
| ComponentName->SupportedLanguages, | |
| Iso639Language, | |
| (LanguageVariable != NULL) ? LanguageVariable : "", | |
| Iso639Language ? "eng" : "en-US", | |
| NULL | |
| ); | |
| if (LanguageVariable != NULL) { | |
| FreePool (LanguageVariable); | |
| } | |
| Status = ComponentName->GetControllerName ( | |
| ComponentName, | |
| ControllerHandle, | |
| ChildHandle, | |
| BestLanguage, | |
| &ControllerName | |
| ); | |
| } | |
| if (!EFI_ERROR (Status)) { | |
| return AllocateCopyPool (StrSize (ControllerName), ControllerName); | |
| } else { | |
| return ConvertDevicePathToText ( | |
| DevicePathFromHandle (ChildHandle != NULL ? ChildHandle : ControllerHandle), | |
| FALSE, | |
| FALSE | |
| ); | |
| } | |
| } | |
| /** | |
| Display a set of messages returned by the GetHealthStatus () service of the EFI Driver Health Protocol | |
| @param DriverHealthInfo Pointer to the Driver Health information entry. | |
| **/ | |
| VOID | |
| BmDisplayMessages ( | |
| IN EFI_BOOT_MANAGER_DRIVER_HEALTH_INFO *DriverHealthInfo | |
| ) | |
| { | |
| UINTN Index; | |
| EFI_STRING String; | |
| CHAR16 *ControllerName; | |
| if ((DriverHealthInfo->MessageList == NULL) || | |
| (DriverHealthInfo->MessageList[0].HiiHandle == NULL)) | |
| { | |
| return; | |
| } | |
| ControllerName = BmGetControllerName ( | |
| DriverHealthInfo->DriverHealthHandle, | |
| DriverHealthInfo->ControllerHandle, | |
| DriverHealthInfo->ChildHandle | |
| ); | |
| DEBUG ((DEBUG_INFO, "Controller: %s\n", ControllerName)); | |
| Print (L"Controller: %s\n", ControllerName); | |
| for (Index = 0; DriverHealthInfo->MessageList[Index].HiiHandle != NULL; Index++) { | |
| String = HiiGetString ( | |
| DriverHealthInfo->MessageList[Index].HiiHandle, | |
| DriverHealthInfo->MessageList[Index].StringId, | |
| NULL | |
| ); | |
| if (String != NULL) { | |
| Print (L" %s\n", String); | |
| DEBUG ((DEBUG_INFO, " %s\n", String)); | |
| FreePool (String); | |
| } | |
| } | |
| if (ControllerName != NULL) { | |
| FreePool (ControllerName); | |
| } | |
| } | |
| /** | |
| The repair notify function. | |
| @param Value A value between 0 and Limit that identifies the current progress | |
| of the repair operation. | |
| @param Limit The maximum value of Value for the current repair operation. | |
| If Limit is 0, then the completion progress is indeterminate. | |
| For example, a driver that wants to specify progress in percent | |
| would use a Limit value of 100. | |
| @retval EFI_SUCCESS Successfully return from the notify function. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| BmRepairNotify ( | |
| IN UINTN Value, | |
| IN UINTN Limit | |
| ) | |
| { | |
| DEBUG ((DEBUG_INFO, "[BDS]RepairNotify: %d/%d\n", Value, Limit)); | |
| Print (L"[BDS]RepairNotify: %d/%d\n", Value, Limit); | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Collect the Driver Health status of a single controller. | |
| @param DriverHealthInfo A pointer to the array containing all of the platform driver health information. | |
| @param Count Return the updated array count. | |
| @param DriverHealthHandle The handle on which the Driver Health protocol instance is retrieved. | |
| @param ControllerHandle The handle of the controller.. | |
| @param ChildHandle The handle of the child controller to retrieve the health | |
| status on. This is an optional parameter that may be NULL. | |
| @retval Status The status returned from GetHealthStatus. | |
| @retval EFI_ABORTED The health status is healthy so no further query is needed. | |
| **/ | |
| EFI_STATUS | |
| BmGetSingleControllerHealthStatus ( | |
| IN OUT EFI_BOOT_MANAGER_DRIVER_HEALTH_INFO **DriverHealthInfo, | |
| IN OUT UINTN *Count, | |
| IN EFI_HANDLE DriverHealthHandle, | |
| IN EFI_HANDLE ControllerHandle OPTIONAL, | |
| IN EFI_HANDLE ChildHandle OPTIONAL | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_DRIVER_HEALTH_PROTOCOL *DriverHealth; | |
| EFI_DRIVER_HEALTH_HII_MESSAGE *MessageList; | |
| EFI_HII_HANDLE FormHiiHandle; | |
| EFI_DRIVER_HEALTH_STATUS HealthStatus; | |
| ASSERT (DriverHealthHandle != NULL); | |
| // | |
| // Retrieve the Driver Health Protocol from DriverHandle | |
| // | |
| Status = gBS->HandleProtocol ( | |
| DriverHealthHandle, | |
| &gEfiDriverHealthProtocolGuid, | |
| (VOID **)&DriverHealth | |
| ); | |
| ASSERT_EFI_ERROR (Status); | |
| if (ControllerHandle == NULL) { | |
| // | |
| // If ControllerHandle is NULL, the return the cumulative health status of the driver | |
| // | |
| Status = DriverHealth->GetHealthStatus (DriverHealth, NULL, NULL, &HealthStatus, NULL, NULL); | |
| if (!EFI_ERROR (Status) && (HealthStatus == EfiDriverHealthStatusHealthy)) { | |
| *DriverHealthInfo = ReallocatePool ( | |
| (*Count) * sizeof (EFI_BOOT_MANAGER_DRIVER_HEALTH_INFO), | |
| (*Count + 1) * sizeof (EFI_BOOT_MANAGER_DRIVER_HEALTH_INFO), | |
| *DriverHealthInfo | |
| ); | |
| ASSERT (*DriverHealthInfo != NULL); | |
| (*DriverHealthInfo)[*Count].DriverHealthHandle = DriverHealthHandle; | |
| (*DriverHealthInfo)[*Count].DriverHealth = DriverHealth; | |
| (*DriverHealthInfo)[*Count].HealthStatus = HealthStatus; | |
| *Count = *Count + 1; | |
| Status = EFI_ABORTED; | |
| } | |
| return Status; | |
| } | |
| MessageList = NULL; | |
| FormHiiHandle = NULL; | |
| // | |
| // Collect the health status with the optional HII message list | |
| // | |
| Status = DriverHealth->GetHealthStatus (DriverHealth, ControllerHandle, ChildHandle, &HealthStatus, &MessageList, &FormHiiHandle); | |
| if (!EFI_ERROR (Status)) { | |
| *DriverHealthInfo = ReallocatePool ( | |
| (*Count) * sizeof (EFI_BOOT_MANAGER_DRIVER_HEALTH_INFO), | |
| (*Count + 1) * sizeof (EFI_BOOT_MANAGER_DRIVER_HEALTH_INFO), | |
| *DriverHealthInfo | |
| ); | |
| ASSERT (*DriverHealthInfo != NULL); | |
| (*DriverHealthInfo)[*Count].DriverHealth = DriverHealth; | |
| (*DriverHealthInfo)[*Count].DriverHealthHandle = DriverHealthHandle; | |
| (*DriverHealthInfo)[*Count].ControllerHandle = ControllerHandle; | |
| (*DriverHealthInfo)[*Count].ChildHandle = ChildHandle; | |
| (*DriverHealthInfo)[*Count].HiiHandle = FormHiiHandle; | |
| (*DriverHealthInfo)[*Count].MessageList = MessageList; | |
| (*DriverHealthInfo)[*Count].HealthStatus = HealthStatus; | |
| *Count = *Count + 1; | |
| } | |
| return Status; | |
| } | |
| /** | |
| Return all the Driver Health information. | |
| When the cumulative health status of all the controllers managed by the | |
| driver who produces the EFI_DRIVER_HEALTH_PROTOCOL is healthy, only one | |
| EFI_BOOT_MANAGER_DRIVER_HEALTH_INFO entry is created for such | |
| EFI_DRIVER_HEALTH_PROTOCOL instance. | |
| Otherwise, every controller creates one EFI_BOOT_MANAGER_DRIVER_HEALTH_INFO | |
| entry. Additionally every child controller creates one | |
| EFI_BOOT_MANAGER_DRIVER_HEALTH_INFO entry if the driver is a bus driver. | |
| @param Count Return the count of the Driver Health information. | |
| @retval NULL No Driver Health information is returned. | |
| @retval !NULL Pointer to the Driver Health information array. | |
| **/ | |
| EFI_BOOT_MANAGER_DRIVER_HEALTH_INFO * | |
| EFIAPI | |
| EfiBootManagerGetDriverHealthInfo ( | |
| UINTN *Count | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| UINTN NumHandles; | |
| EFI_HANDLE *DriverHealthHandles; | |
| UINTN DriverHealthIndex; | |
| EFI_HANDLE *Handles; | |
| UINTN HandleCount; | |
| UINTN ControllerIndex; | |
| UINTN ChildIndex; | |
| EFI_BOOT_MANAGER_DRIVER_HEALTH_INFO *DriverHealthInfo; | |
| // | |
| // Initialize local variables | |
| // | |
| *Count = 0; | |
| DriverHealthInfo = NULL; | |
| Handles = NULL; | |
| DriverHealthHandles = NULL; | |
| NumHandles = 0; | |
| HandleCount = 0; | |
| Status = gBS->LocateHandleBuffer ( | |
| ByProtocol, | |
| &gEfiDriverHealthProtocolGuid, | |
| NULL, | |
| &NumHandles, | |
| &DriverHealthHandles | |
| ); | |
| if ((Status == EFI_NOT_FOUND) || (NumHandles == 0)) { | |
| // | |
| // If there are no Driver Health Protocols handles, then return EFI_NOT_FOUND | |
| // | |
| return NULL; | |
| } | |
| ASSERT_EFI_ERROR (Status); | |
| ASSERT (DriverHealthHandles != NULL); | |
| // | |
| // Check the health status of all controllers in the platform | |
| // Start by looping through all the Driver Health Protocol handles in the handle database | |
| // | |
| for (DriverHealthIndex = 0; DriverHealthIndex < NumHandles; DriverHealthIndex++) { | |
| // | |
| // Get the cumulative health status of the driver | |
| // | |
| Status = BmGetSingleControllerHealthStatus (&DriverHealthInfo, Count, DriverHealthHandles[DriverHealthIndex], NULL, NULL); | |
| if (EFI_ERROR (Status)) { | |
| continue; | |
| } | |
| // | |
| // See if the list of all handles in the handle database has been retrieved | |
| // | |
| // | |
| if (Handles == NULL) { | |
| // | |
| // Retrieve the list of all handles from the handle database | |
| // | |
| Status = gBS->LocateHandleBuffer ( | |
| AllHandles, | |
| NULL, | |
| NULL, | |
| &HandleCount, | |
| &Handles | |
| ); | |
| ASSERT_EFI_ERROR (Status); | |
| } | |
| // | |
| // Loop through all the controller handles in the handle database | |
| // | |
| for (ControllerIndex = 0; ControllerIndex < HandleCount; ControllerIndex++) { | |
| Status = BmGetSingleControllerHealthStatus (&DriverHealthInfo, Count, DriverHealthHandles[DriverHealthIndex], Handles[ControllerIndex], NULL); | |
| if (EFI_ERROR (Status)) { | |
| continue; | |
| } | |
| // | |
| // Loop through all the child handles in the handle database | |
| // | |
| for (ChildIndex = 0; ChildIndex < HandleCount; ChildIndex++) { | |
| Status = BmGetSingleControllerHealthStatus (&DriverHealthInfo, Count, DriverHealthHandles[DriverHealthIndex], Handles[ControllerIndex], Handles[ChildIndex]); | |
| if (EFI_ERROR (Status)) { | |
| continue; | |
| } | |
| } | |
| } | |
| } | |
| Status = EFI_SUCCESS; | |
| if (Handles != NULL) { | |
| FreePool (Handles); | |
| } | |
| if (DriverHealthHandles != NULL) { | |
| FreePool (DriverHealthHandles); | |
| } | |
| return DriverHealthInfo; | |
| } | |
| /** | |
| Free the Driver Health information array. | |
| @param DriverHealthInfo Pointer to array of the Driver Health information. | |
| @param Count Count of the array. | |
| @retval EFI_SUCCESS The array is freed. | |
| @retval EFI_INVALID_PARAMETER The array is NULL. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| EfiBootManagerFreeDriverHealthInfo ( | |
| EFI_BOOT_MANAGER_DRIVER_HEALTH_INFO *DriverHealthInfo, | |
| UINTN Count | |
| ) | |
| { | |
| UINTN Index; | |
| for (Index = 0; Index < Count; Index++) { | |
| if (DriverHealthInfo[Index].MessageList != NULL) { | |
| FreePool (DriverHealthInfo[Index].MessageList); | |
| } | |
| } | |
| return gBS->FreePool (DriverHealthInfo); | |
| } | |
| /** | |
| Repair all the controllers according to the Driver Health status queried. | |
| @param ReconnectRepairCount To record the number of recursive call of | |
| this function itself. | |
| **/ | |
| VOID | |
| BmRepairAllControllers ( | |
| UINTN ReconnectRepairCount | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_BOOT_MANAGER_DRIVER_HEALTH_INFO *DriverHealthInfo; | |
| EFI_DRIVER_HEALTH_STATUS HealthStatus; | |
| UINTN Count; | |
| UINTN Index; | |
| BOOLEAN RepairRequired; | |
| BOOLEAN ConfigurationRequired; | |
| BOOLEAN ReconnectRequired; | |
| BOOLEAN RebootRequired; | |
| EFI_HII_HANDLE *HiiHandles; | |
| EFI_FORM_BROWSER2_PROTOCOL *FormBrowser2; | |
| UINT32 MaxRepairCount; | |
| UINT32 RepairCount; | |
| // | |
| // Configure PcdDriverHealthConfigureForm to ZeroGuid to disable driver health check. | |
| // | |
| if (IsZeroGuid (PcdGetPtr (PcdDriverHealthConfigureForm))) { | |
| return; | |
| } | |
| Status = gBS->LocateProtocol (&gEfiFormBrowser2ProtocolGuid, NULL, (VOID **)&FormBrowser2); | |
| ASSERT_EFI_ERROR (Status); | |
| MaxRepairCount = PcdGet32 (PcdMaxRepairCount); | |
| RepairCount = 0; | |
| do { | |
| RepairRequired = FALSE; | |
| ConfigurationRequired = FALSE; | |
| // | |
| // Deal with Repair Required | |
| // | |
| DriverHealthInfo = EfiBootManagerGetDriverHealthInfo (&Count); | |
| for (Index = 0; Index < Count; Index++) { | |
| if (DriverHealthInfo[Index].HealthStatus == EfiDriverHealthStatusConfigurationRequired) { | |
| ConfigurationRequired = TRUE; | |
| } | |
| if (DriverHealthInfo[Index].HealthStatus == EfiDriverHealthStatusRepairRequired) { | |
| RepairRequired = TRUE; | |
| BmDisplayMessages (&DriverHealthInfo[Index]); | |
| Status = DriverHealthInfo[Index].DriverHealth->Repair ( | |
| DriverHealthInfo[Index].DriverHealth, | |
| DriverHealthInfo[Index].ControllerHandle, | |
| DriverHealthInfo[Index].ChildHandle, | |
| BmRepairNotify | |
| ); | |
| if (!EFI_ERROR (Status) && !ConfigurationRequired) { | |
| Status = DriverHealthInfo[Index].DriverHealth->GetHealthStatus ( | |
| DriverHealthInfo[Index].DriverHealth, | |
| DriverHealthInfo[Index].ControllerHandle, | |
| DriverHealthInfo[Index].ChildHandle, | |
| &HealthStatus, | |
| NULL, | |
| NULL | |
| ); | |
| if (!EFI_ERROR (Status) && (HealthStatus == EfiDriverHealthStatusConfigurationRequired)) { | |
| ConfigurationRequired = TRUE; | |
| } | |
| } | |
| } | |
| } | |
| if (ConfigurationRequired) { | |
| HiiHandles = HiiGetHiiHandles (NULL); | |
| if (HiiHandles != NULL) { | |
| for (Index = 0; HiiHandles[Index] != NULL; Index++) { | |
| Status = FormBrowser2->SendForm ( | |
| FormBrowser2, | |
| &HiiHandles[Index], | |
| 1, | |
| PcdGetPtr (PcdDriverHealthConfigureForm), | |
| 0, | |
| NULL, | |
| NULL | |
| ); | |
| if (!EFI_ERROR (Status)) { | |
| break; | |
| } | |
| } | |
| FreePool (HiiHandles); | |
| } | |
| } | |
| EfiBootManagerFreeDriverHealthInfo (DriverHealthInfo, Count); | |
| RepairCount++; | |
| } while ((RepairRequired || ConfigurationRequired) && ((MaxRepairCount == 0) || (RepairCount < MaxRepairCount))); | |
| RebootRequired = FALSE; | |
| ReconnectRequired = FALSE; | |
| DriverHealthInfo = EfiBootManagerGetDriverHealthInfo (&Count); | |
| for (Index = 0; Index < Count; Index++) { | |
| BmDisplayMessages (&DriverHealthInfo[Index]); | |
| if (DriverHealthInfo[Index].HealthStatus == EfiDriverHealthStatusReconnectRequired) { | |
| Status = gBS->DisconnectController (DriverHealthInfo[Index].ControllerHandle, NULL, NULL); | |
| if (EFI_ERROR (Status)) { | |
| // | |
| // Disconnect failed. Need to promote reconnect to a reboot. | |
| // | |
| RebootRequired = TRUE; | |
| } else { | |
| gBS->ConnectController (DriverHealthInfo[Index].ControllerHandle, NULL, NULL, TRUE); | |
| ReconnectRequired = TRUE; | |
| } | |
| } | |
| if (DriverHealthInfo[Index].HealthStatus == EfiDriverHealthStatusRebootRequired) { | |
| RebootRequired = TRUE; | |
| } | |
| } | |
| EfiBootManagerFreeDriverHealthInfo (DriverHealthInfo, Count); | |
| DEBUG_CODE_BEGIN (); | |
| CHAR16 *ControllerName; | |
| DriverHealthInfo = EfiBootManagerGetDriverHealthInfo (&Count); | |
| for (Index = 0; Index < Count; Index++) { | |
| ControllerName = BmGetControllerName ( | |
| DriverHealthInfo[Index].DriverHealthHandle, | |
| DriverHealthInfo[Index].ControllerHandle, | |
| DriverHealthInfo[Index].ChildHandle | |
| ); | |
| DEBUG (( | |
| DEBUG_INFO, | |
| "%02d: %s - %s\n", | |
| Index, | |
| ControllerName, | |
| mBmHealthStatusText[DriverHealthInfo[Index].HealthStatus] | |
| )); | |
| if (ControllerName != NULL) { | |
| FreePool (ControllerName); | |
| } | |
| } | |
| EfiBootManagerFreeDriverHealthInfo (DriverHealthInfo, Count); | |
| DEBUG_CODE_END (); | |
| if (ReconnectRequired) { | |
| if (ReconnectRepairCount < MAX_RECONNECT_REPAIR) { | |
| BmRepairAllControllers (ReconnectRepairCount + 1); | |
| } else { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "[%a:%d] Repair failed after %d retries.\n", | |
| __FUNCTION__, | |
| DEBUG_LINE_NUMBER, | |
| ReconnectRepairCount | |
| )); | |
| } | |
| } | |
| if (RebootRequired) { | |
| DEBUG ((DEBUG_INFO, "[BDS] One of the Driver Health instances requires rebooting.\n")); | |
| gRT->ResetSystem (EfiResetWarm, EFI_SUCCESS, 0, NULL); | |
| } | |
| } |