/** @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; }