/** @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", | |
__func__, | |
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); | |
} | |
} |