alder_lake_bios/Oem/L05/FeatureCommon/InsydeL05ModulePkg/BiosSelfHealingPei/BiosSelfHealingPei.c

595 lines
19 KiB
C

/** @file
BIOS Self-Healing PEI Module.
;******************************************************************************
;* Copyright (c) 2020, Insyde Software Corporation. All Rights Reserved.
;*
;* You may not reproduce, distribute, publish, display, perform, modify, adapt,
;* transmit, broadcast, present, recite, release, license or otherwise exploit
;* any part of this publication in any form, by any means, without the prior
;* written permission of Insyde Software Corporation.
;*
;******************************************************************************
*/
#include "BiosSelfHealingPei.h"
EFI_PEI_NOTIFY_DESCRIPTOR mSelfHealingInitDescriptor = {
(EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
#if (FixedPcdGet32 (PcdL05ChipsetName) == L05_CHIPSET_NAME_CEZANNE || \
FixedPcdGet32 (PcdL05ChipsetName) == L05_CHIPSET_NAME_REMBRANDT)
&gAmdPspCommonServicePpiGuid,
#else
&gEfiPeiReadOnlyVariable2PpiGuid,
#endif
SelfHealingInitCallback
};
EFI_PEI_NOTIFY_DESCRIPTOR mStage2EcNotifyDescriptor = {
(EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
&gEfiPeiMemoryDiscoveredPpiGuid,
Stage2EcNotifyCallback
};
EFI_PEI_NOTIFY_DESCRIPTOR mClearWdtDescriptor = {
(EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
&gEfiEndOfPeiSignalPpiGuid,
ClearWdtCallback
};
EFI_PEI_NOTIFY_DESCRIPTOR mInstallFirmwareAuthHookDescriptor = {
(EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
&gEfiPeiMemoryDiscoveredPpiGuid,
InstallFirmwareAuthHook
};
EFI_PEI_NOTIFY_DESCRIPTOR mFirmwareAuthHookDescriptor = {
(EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
&gEfiPeiRecoveryModulePpiGuid,
FirmwareAuthHookCallback
};
FIRMWARE_AUTHENTICATION_PPI mFirmwareAuthPpi = {
VerifyFirmware
};
EFI_PEI_PPI_DESCRIPTOR mFirmwareAuthPpiList = {
(EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
&gFirmwareAuthenticationPpiGuid,
&mFirmwareAuthPpi
};
UINT32 mIgnoreFvBase[] = {
FixedPcdGet32 (PcdFlashNvStorageVariableDefaultsBase)
};
/**
Check if BIOS Self-Healing is enabled or not.
@retval TRUE BIOS Self-Healing is enabled.
@retval FALSE BIOS Self-Healing is disabled.
**/
BOOLEAN
BiosSelfHealingEnabled (
VOID
)
{
SYSTEM_CONFIGURATION *SetupNvData;
UINTN BufferSize;
BOOLEAN BiosSelfHealingIsEnabled;
SetupNvData = NULL;
BufferSize = 0;
BiosSelfHealingIsEnabled = FALSE;
CommonGetVariableDataAndSize (
L"Setup",
&gSystemConfigurationGuid,
&BufferSize,
(VOID **) &SetupNvData
);
if (SetupNvData != NULL) {
BiosSelfHealingIsEnabled = (SetupNvData->L05BiosSelfHealing == 1) ? TRUE : FALSE;
}
return BiosSelfHealingIsEnabled;
}
/**
Callback function to update fv trust status after verifying region.
@param Event A pointer to the Event that triggered the callback.
@param Handle Checkpoint handle.
**/
VOID
EFIAPI
CpVerifyFvCallback (
IN EFI_EVENT Event,
IN H2O_CP_HANDLE Handle
)
{
EFI_STATUS Status;
H2O_BASE_CP_VERIFY_FV_DATA *VerifyFvData;
UINTN Index;
DEBUG ((DEBUG_INFO, "\nBiosSelfHealingPei: CpVerifyFvCallback is triggered\n"));
Status = H2OCpLookup (Handle, (VOID **) &VerifyFvData, NULL);
if (EFI_ERROR (Status)) {
return;
}
for (Index = 0; Index < (sizeof (mIgnoreFvBase) / sizeof (UINT32)); Index++) {
if (VerifyFvData->FvBase == mIgnoreFvBase[Index]) {
VerifyFvData->Status = H2O_BDS_TASK_UPDATE;
VerifyFvData->Trusted = TRUE;
DEBUG ((DEBUG_INFO, "IGNORE!!! VerifyFvData->FvBase: 0x%lx.\n", VerifyFvData->FvBase));
break;
}
}
}
/**
Callback function to perform Self-Healing function initialization.
@param PeiServices General purpose services available to every PEIM.
@param NotifyDescriptor Pointer of the notificaiton data structure.
@param Ppi Pointer of PPI.
@retval EFI_SUCCESS Operation is successful.
@retval Others An unexpected error occurred.
**/
EFI_STATUS
SelfHealingInitCallback (
IN EFI_PEI_SERVICES **PeiServices,
IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor,
IN VOID *Ppi
)
{
EFI_STATUS Status;
DEBUG ((DEBUG_INFO, "BiosSelfHealingPei: SelfHealingInitCallback is triggered\n"));
//[-start-220125-BAIN000092-remove]//
#ifndef LCFC_SUPPORT
#if (FixedPcdGet32 (PcdL05ChipsetName) == L05_CHIPSET_NAME_ALDERLAKE || \
FixedPcdGet32 (PcdL05ChipsetName) == L05_CHIPSET_NAME_TIGERLAKE)
//
// If run crisis recovery successfully in Top Swap mode, then disable Top Swap before end of SiInit
//
if (PcdGetBool (PcdL05TopSwapEnable) &&
(CheckPbbrSyncFlag () || (ReadCmos8 (EfiL05BiosSelfHealingModeSwitch) == V_EFI_L05_BIOS_SELF_HEALING_MODE_CRISIS_RECOVERY_COMPLETED))) {
TopSwapSet (FALSE);
ResetCold ();
}
#endif
#endif
//[-end-220125-BAIN000092-remove]//
#if (FixedPcdGet32 (PcdL05ChipsetName) == L05_CHIPSET_NAME_CEZANNE || \
FixedPcdGet32 (PcdL05ChipsetName) == L05_CHIPSET_NAME_REMBRANDT)
{
BOOLEAN CmosBshFlag = FALSE;
//
// Sync Top Swap status to PCD
//
if (ReadCmos8 (EfiL05BiosSelfHealingModeSwitch) == V_EFI_L05_BIOS_SELF_HEALING_MODE_FORCE_ENTER_BSH) {
WriteCmos8 (EfiL05BiosSelfHealingModeSwitch, V_EFI_L05_BIOS_SELF_HEALING_MODE_NORMAL);
CmosBshFlag = TRUE;
}
PcdSetBoolS (PcdL05TopSwapEnable, TopSwapStatus () ? TRUE : CmosBshFlag);
DEBUG ((DEBUG_INFO, "BiosSelfHealingPei: CmosSwitch status: %x\n", CmosBshFlag));
DEBUG ((DEBUG_INFO, "BiosSelfHealingPei: PcdL05TopSwapEnable status: %x\n", (UINTN) PcdGetBool (PcdL05TopSwapEnable)));
}
#endif
//
// Check if BIOS Self-Healing function is enabled or not
//
PcdSetBoolS (PcdL05BiosSelfHealingEnable, BiosSelfHealingEnabled ());
DEBUG ((DEBUG_INFO, "BiosSelfHealingPei: PcdL05BiosSelfHealingEnable status: %x\n", (UINTN) PcdGetBool (PcdL05BiosSelfHealingEnable)));
if (!PcdGetBool (PcdL05BiosSelfHealingEnable)) {
return EFI_SUCCESS;
}
//
// [Lenovo BIOS Self-Healing Design Guidance Specification v1.9]
// 2.1 Overview
// Notify EC that PEI has started (Stage 1)
//
OemSvcNotifyEcToPeiStart (TRUE);
//
// [Self-Healing EC WDT timer optimize]
// Split into two stages to notify EC - "PEI start (before memory training)" and "Memory ready"
// Before memory ready, EC will reset timer 3 times to extend WDT timer if Port 80 keep changing
//
Status = PeiServicesNotifyPpi (&mStage2EcNotifyDescriptor);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Register callback to clear WDT during POST
//
if (!PcdGetBool (PcdL05TopSwapEnable)) {
Status = PeiServicesNotifyPpi (&mClearWdtDescriptor);
if (EFI_ERROR (Status)) {
return Status;
}
}
#if (FixedPcdGet32 (PcdL05ChipsetName) != L05_CHIPSET_NAME_ALDERLAKE)
//
// Register InstallFirmwareAuthHookDescriptor
//
Status = PeiServicesNotifyPpi (&mInstallFirmwareAuthHookDescriptor);
if (EFI_ERROR (Status)) {
return Status;
}
#endif
return EFI_SUCCESS;
}
/**
Callback function to notify EC that memory is ready.
@param PeiServices General purpose services available to every PEIM.
@param NotifyDescriptor Pointer of the notificaiton data structure.
@param Ppi Pointer of PPI.
@retval EFI_SUCCESS Operation is successful.
@retval Others An unexpected error occurred.
**/
EFI_STATUS
Stage2EcNotifyCallback (
IN EFI_PEI_SERVICES **PeiServices,
IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor,
IN VOID *Ppi
)
{
DEBUG ((DEBUG_INFO, "BiosSelfHealingPei: Stage2EcNotifyCallback is triggered\n"));
//
// [Self-Healing EC WDT timer optimize]
// Split into two stages to notify EC - "PEI start (before memory training)" and "Memory ready"
// Before memory ready, EC will reset timer 3 times to extend WDT timer if Port 80 keep changing
//
OemSvcNotifyEcToPeiStart (FALSE);
return EFI_SUCCESS;
}
/**
Clear WDT callback.
@param PeiServices General purpose services available to every PEIM.
@param NotifyDescriptor Pointer of the notificaiton data structure.
@param Ppi Pointer of PPI.
@retval EFI_SUCCESS Operation is successful.
@retval Others An unexpected error occurred.
**/
EFI_STATUS
ClearWdtCallback (
IN EFI_PEI_SERVICES **PeiServices,
IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor,
IN VOID *Ppi
)
{
DEBUG ((DEBUG_INFO, "BiosSelfHealingPei: ClearWdtCallback is triggered\n"));
//
// [Lenovo BIOS Self-Healing Design Guidance Specification v1.9]
// 2.2 Detect
// When BIOS flash happened, before erase/write BIOS should notify EC to set flag (WdtFlag should be
// keeped even EC power lost, such as stored in the EC EEPROM) to enable WDT, the WdtFlag should be
// cleared when EC received BIOS "cancel WDT" command.
// On every boot, EC check the WdtFlag flag to decide if need to start a 15s WDT,
// and stop the WDT if BIOS send "stop/cancel WDT" command.
//
OemSvcNotifyEcToClearWdt ();
return EFI_SUCCESS;
}
/**
Install FirmwareAuthHookDescriptor.
@param PeiServices General purpose services available to every PEIM.
@param NotifyDescriptor Pointer of the notificaiton data structure.
@param Ppi Pointer of PPI.
@retval EFI_SUCCESS Operation is successful.
@retval Others An unexpected error occurred.
**/
EFI_STATUS
InstallFirmwareAuthHook (
IN EFI_PEI_SERVICES **PeiServices,
IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor,
IN VOID *Ppi
)
{
EFI_STATUS Status;
//
// If system in Top Swap mode and BIOS recovery hotkey is not pressed,
// register callback to hook FirmwareAuthenticationPpi
//
if (PcdGetBool (PcdL05TopSwapEnable) && !PcdGetBool (PcdL05BiosRecoveryHotkeyFlag)) {
Status = PeiServicesNotifyPpi (&mFirmwareAuthHookDescriptor);
if (EFI_ERROR (Status)) {
return Status;
}
}
return EFI_SUCCESS;
}
/**
Callback function to hook FirmwareAuthenticationPpi.
@param PeiServices General purpose services available to every PEIM.
@param NotifyDescriptor Pointer of the notificaiton data structure.
@param Ppi Pointer of PPI.
@retval EFI_SUCCESS Operation is successful.
@retval Others An unexpected error occurred.
**/
EFI_STATUS
FirmwareAuthHookCallback (
IN EFI_PEI_SERVICES **PeiServices,
IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor,
IN VOID *Ppi
)
{
EFI_STATUS Status;
EFI_PEI_PPI_DESCRIPTOR *OldFirmwareAuthPpiDescriptor;
FIRMWARE_AUTHENTICATION_PPI *OldFirmwareAuthPpi;
DEBUG ((DEBUG_INFO, "BiosSelfHealingPei: FirmwareAuthHookCallback is triggered\n"));
Status = (*PeiServices)->LocatePpi (
PeiServices,
&gFirmwareAuthenticationPpiGuid,
0,
&OldFirmwareAuthPpiDescriptor,
(VOID **)&OldFirmwareAuthPpi
);
if (Status == EFI_SUCCESS) {
Status = (*PeiServices)->ReInstallPpi (
PeiServices,
OldFirmwareAuthPpiDescriptor,
&mFirmwareAuthPpiList
);
} else {
Status = (*PeiServices)->InstallPpi (
PeiServices,
&mFirmwareAuthPpiList
);
}
return EFI_SUCCESS;
}
/**
SHA256 hash calculation.
@param Message The message data to be calculated.
@param MessageSize The size in byte of the message data.
@param Digest The caclulated HASH digest.
@retval EFI_SUCCESS The HASH value is calculated.
@retval EFI_SECURITY_VIOLATION Failed to calculate the hash.
**/
EFI_STATUS
CalculateSha256Hash (
IN UINT8 *Message,
IN UINTN MessageSize,
OUT UINT8 *Digest
)
{
EFI_STATUS Status;
VOID *HashCtx;
UINTN CtxSize;
SetMem (Digest, SHA256_DIGEST_SIZE, 0);
CtxSize = Sha256GetContextSize ();
HashCtx = NULL;
HashCtx = AllocatePool (CtxSize);
if (HashCtx == NULL) {
return EFI_OUT_OF_RESOURCES;
}
if (!Sha256Init (HashCtx)) {
Status = EFI_SECURITY_VIOLATION;
goto Done;
}
if(!Sha256Update (HashCtx, Message, MessageSize)) {
Status = EFI_SECURITY_VIOLATION;
goto Done;
}
if(!Sha256Final (HashCtx, Digest)) {
Status = EFI_SECURITY_VIOLATION;
} else {
Status = EFI_SUCCESS;
}
Done:
FreePool (HashCtx);
return Status;
}
/**
This function looks up the FV hash in the FDM.
@param Fdm Point to the FDM header.
@param Guid Specify the GUID of an FV.
@retval Non-null Success.
@retval Null Not found.
**/
VOID *
LookUpFvHash (
IN UINT8 *Fdm,
IN EFI_GUID *Guid
)
{
STATIC CHAR8 FdmSignature[] = {'H', 'F', 'D', 'M'};
UINT32 EntrySize;
UINT8 *EndPtr;
H2O_FLASH_DEVICE_MAP_ENTRY *Entry;
if ((Fdm == NULL) || CompareMem (Fdm, FdmSignature, sizeof (FdmSignature))) {
return NULL;
}
EntrySize = ((H2O_FLASH_DEVICE_MAP_HEADER *) Fdm)->EntrySize;
EndPtr = Fdm + ((H2O_FLASH_DEVICE_MAP_HEADER *) Fdm)->Size;
for (Entry = (H2O_FLASH_DEVICE_MAP_ENTRY *)(Fdm + ((H2O_FLASH_DEVICE_MAP_HEADER *) Fdm)->Offset);
(UINT8 *)Entry < EndPtr;
Entry = (H2O_FLASH_DEVICE_MAP_ENTRY *)(((UINT8 *) Entry) + EntrySize)) {
if (CompareGuid ((EFI_GUID *) &Entry->RegionId, Guid)) {
return ((UINT8 *) Entry + sizeof (H2O_FLASH_DEVICE_MAP_ENTRY));
}
}
return NULL;
}
/**
Firmware verification with SHA256 hash.
@param FirmwareFileData Firmware file data buffer.
@param FirmwareFileSize The firmware file size including signature.
@retval EFI_SUCCESS The firmware verification is successful.
@retval EFI_OUT_OF_RESOURCES Out of resources.
@retval EFI_SECURITY_VIOLATION Failed to verify the firmware.
**/
EFI_STATUS
VerifyFirmware (
IN UINT8 *FirmwareFileData,
IN UINTN FirmwareFileSize
)
{
EFI_STATUS Status;
UINTN BiosImageOffset;
UINT8 *FvData;
UINTN FvSize;
UINTN HashSize;
UINT8 *OnboardFvHash;
UINT8 *Digest;
Status = EFI_SUCCESS;
OnboardFvHash = NULL;
//
// Initial setting for Image data and hash information
//
BiosImageOffset = 0;
HashSize = SHA256_DIGEST_SIZE;
//
// Look up FV hash in onboard FDM
//
OnboardFvHash = LookUpFvHash ((UINT8 *)(UINTN) PcdGet64 (PcdH2OFlashDeviceMapStart), &gH2OFlashMapRegionDxeFvGuid);
if (OnboardFvHash == NULL) {
return EFI_SECURITY_VIOLATION;
}
//
// Calculate FV hash of Image
//
FvData = FirmwareFileData + (BiosImageOffset + (UINTN) (PcdGet32 (PcdFlashFvMainBase) - PcdGet32 (PcdFlashAreaBaseAddress)));
FvSize = (UINTN) PcdGet32 (PcdFlashFvMainSize);
Digest = AllocatePool (HashSize);
if (Digest == NULL){
return EFI_SECURITY_VIOLATION;
}
Status = CalculateSha256Hash (FvData, FvSize, Digest);
if (EFI_ERROR (Status)) {
FreePool (Digest);
return EFI_SECURITY_VIOLATION;
}
if (CompareMem (OnboardFvHash, Digest, HashSize)) {
Status = EFI_SECURITY_VIOLATION;
} else {
Status = EFI_SUCCESS;
}
DEBUG ((DEBUG_INFO, "BiosSelfHealingPei: VerifyFirmware status: %r\n", Status));
FreePool (Digest);
return Status;
}
/**
BIOS Self-Healing PEI Entry.
@param FfsHeader Points to the FFS file header to be checked.
@param PeiServices General purpose services available to every PEIM.
@retval EFI_SUCCESS The operation completed successfully.
@retval Others An unexpected error occurred.
**/
EFI_STATUS
EFIAPI
BiosSelfHealingPeiEntryPoint (
IN EFI_PEI_FILE_HANDLE FfsHeader,
IN CONST EFI_PEI_SERVICES **PeiServices
)
{
EFI_STATUS Status;
H2O_CP_HANDLE CpHandle;
Status = EFI_SUCCESS;
CpHandle = NULL;
#if (FixedPcdGet32 (PcdL05ChipsetName) == L05_CHIPSET_NAME_TIGERLAKE || \
FixedPcdGet32 (PcdL05ChipsetName) == L05_CHIPSET_NAME_ALDERLAKE)
//
// Sync Top Swap status to PCD
//
PcdSetBoolS (PcdL05TopSwapEnable, TopSwapStatus ());
DEBUG ((DEBUG_INFO, "BiosSelfHealingPei: PcdL05TopSwapEnable status: %x\n", (UINTN) PcdGetBool (PcdL05TopSwapEnable)));
#endif
//
// Register checkpoint callback to update fv trust status after verifying region
// (only execute in Top Swap mode)
//
if (PcdGetBool (PcdL05TopSwapEnable)) {
if (FeaturePcdGet (PcdH2OBaseCpVerifyFvSupported)) {
Status = H2OCpRegisterHandler (
&gH2OBaseCpVerifyFvGuid,
CpVerifyFvCallback,
H2O_CP_MEDIUM,
&CpHandle
);
if (EFI_ERROR (Status)) {
return Status;
}
}
}
//
// Register callback to perform Self-Healing function initialization
//
Status = PeiServicesNotifyPpi (&mSelfHealingInitDescriptor);
if (EFI_ERROR (Status)) {
return Status;
}
return Status;
}