/** @file Implementation of loading microcode on processors. Copyright (c) 2015 - 2021, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "MpLib.h" /** Detect whether specified processor can find matching microcode patch and load it. @param[in] CpuMpData The pointer to CPU MP Data structure. @param[in] ProcessorNumber The handle number of the processor. The range is from 0 to the total number of logical processors minus 1. **/ VOID MicrocodeDetect ( IN CPU_MP_DATA *CpuMpData, IN UINTN ProcessorNumber ) { CPU_MICROCODE_HEADER *Microcode; UINTN MicrocodeEnd; CPU_AP_DATA *BspData; UINT32 LatestRevision; CPU_MICROCODE_HEADER *LatestMicrocode; UINT32 ThreadId; EDKII_PEI_MICROCODE_CPU_ID MicrocodeCpuId; if (CpuMpData->MicrocodePatchRegionSize == 0) { // // There is no microcode patches // return; } GetProcessorLocationByApicId (GetInitialApicId (), NULL, NULL, &ThreadId); if (ThreadId != 0) { // // Skip loading microcode if it is not the first thread in one core. // return; } GetProcessorMicrocodeCpuId (&MicrocodeCpuId); if (ProcessorNumber != (UINTN) CpuMpData->BspNumber) { // // Direct use microcode of BSP if AP is the same as BSP. // Assume BSP calls this routine() before AP. // BspData = &(CpuMpData->CpuData[CpuMpData->BspNumber]); if ((BspData->ProcessorSignature == MicrocodeCpuId.ProcessorSignature) && (BspData->PlatformId == MicrocodeCpuId.PlatformId) && (BspData->MicrocodeEntryAddr != 0)) { LatestMicrocode = (CPU_MICROCODE_HEADER *)(UINTN) BspData->MicrocodeEntryAddr; LatestRevision = LatestMicrocode->UpdateRevision; goto LoadMicrocode; } } // // BSP or AP which is different from BSP runs here // Use 0 as the starting revision to search for microcode because MicrocodePatchInfo HOB needs // the latest microcode location even it's loaded to the processor. // LatestRevision = 0; LatestMicrocode = NULL; Microcode = (CPU_MICROCODE_HEADER *) (UINTN) CpuMpData->MicrocodePatchAddress; MicrocodeEnd = (UINTN) Microcode + (UINTN) CpuMpData->MicrocodePatchRegionSize; do { if (!IsValidMicrocode (Microcode, MicrocodeEnd - (UINTN) Microcode, LatestRevision, &MicrocodeCpuId, 1, TRUE)) { // // It is the padding data between the microcode patches for microcode patches alignment. // Because the microcode patch is the multiple of 1-KByte, the padding data should not // exist if the microcode patch alignment value is not larger than 1-KByte. So, the microcode // alignment value should be larger than 1-KByte. We could skip SIZE_1KB padding data to // find the next possible microcode patch header. // Microcode = (CPU_MICROCODE_HEADER *) ((UINTN) Microcode + SIZE_1KB); continue; } LatestMicrocode = Microcode; LatestRevision = LatestMicrocode->UpdateRevision; Microcode = (CPU_MICROCODE_HEADER *) (((UINTN) Microcode) + GetMicrocodeLength (Microcode)); } while ((UINTN) Microcode < MicrocodeEnd); LoadMicrocode: if (LatestRevision != 0) { // // Save the detected microcode patch entry address (including the microcode // patch header) for each processor even it's the same as the loaded one. // It will be used when building the microcode patch cache HOB. // CpuMpData->CpuData[ProcessorNumber].MicrocodeEntryAddr = (UINTN) LatestMicrocode; } if (LatestRevision > GetProcessorMicrocodeSignature ()) { // // BIOS only authenticate updates that contain a numerically larger revision // than the currently loaded revision, where Current Signature < New Update // Revision. A processor with no loaded update is considered to have a // revision equal to zero. // LoadMicrocode (LatestMicrocode); } // // It's possible that the microcode fails to load. Just capture the CPU microcode revision after loading. // CpuMpData->CpuData[ProcessorNumber].MicrocodeRevision = GetProcessorMicrocodeSignature (); } /** Actual worker function that shadows the required microcode patches into memory. @param[in, out] CpuMpData The pointer to CPU MP Data structure. @param[in] Patches The pointer to an array of information on the microcode patches that will be loaded into memory. @param[in] PatchCount The number of microcode patches that will be loaded into memory. @param[in] TotalLoadSize The total size of all the microcode patches to be loaded. **/ VOID ShadowMicrocodePatchWorker ( IN OUT CPU_MP_DATA *CpuMpData, IN MICROCODE_PATCH_INFO *Patches, IN UINTN PatchCount, IN UINTN TotalLoadSize ) { UINTN Index; VOID *MicrocodePatchInRam; UINT8 *Walker; ASSERT ((Patches != NULL) && (PatchCount != 0)); MicrocodePatchInRam = AllocatePages (EFI_SIZE_TO_PAGES (TotalLoadSize)); if (MicrocodePatchInRam == NULL) { return; } // // Load all the required microcode patches into memory // for (Walker = MicrocodePatchInRam, Index = 0; Index < PatchCount; Index++) { CopyMem ( Walker, (VOID *) Patches[Index].Address, Patches[Index].Size ); Walker += Patches[Index].Size; } // // Update the microcode patch related fields in CpuMpData // CpuMpData->MicrocodePatchAddress = (UINTN) MicrocodePatchInRam; CpuMpData->MicrocodePatchRegionSize = TotalLoadSize; DEBUG (( DEBUG_INFO, "%a: Required microcode patches have been loaded at 0x%lx, with size 0x%lx.\n", __FUNCTION__, CpuMpData->MicrocodePatchAddress, CpuMpData->MicrocodePatchRegionSize )); return; } /** Shadow the required microcode patches data into memory according to PCD PcdCpuMicrocodePatchAddress and PcdCpuMicrocodePatchRegionSize. @param[in, out] CpuMpData The pointer to CPU MP Data structure. **/ VOID ShadowMicrocodePatchByPcd ( IN OUT CPU_MP_DATA *CpuMpData ) { UINTN Index; CPU_MICROCODE_HEADER *MicrocodeEntryPoint; UINTN MicrocodeEnd; UINTN TotalSize; MICROCODE_PATCH_INFO *PatchInfoBuffer; UINTN MaxPatchNumber; UINTN PatchCount; UINTN TotalLoadSize; EDKII_PEI_MICROCODE_CPU_ID *MicrocodeCpuIds; BOOLEAN Valid; // // Initialize the microcode patch related fields in CpuMpData as the values // specified by the PCD pair. If the microcode patches are loaded into memory, // these fields will be updated. // CpuMpData->MicrocodePatchAddress = PcdGet64 (PcdCpuMicrocodePatchAddress); CpuMpData->MicrocodePatchRegionSize = PcdGet64 (PcdCpuMicrocodePatchRegionSize); MicrocodeEntryPoint = (CPU_MICROCODE_HEADER *) (UINTN) CpuMpData->MicrocodePatchAddress; MicrocodeEnd = (UINTN) MicrocodeEntryPoint + (UINTN) CpuMpData->MicrocodePatchRegionSize; if ((MicrocodeEntryPoint == NULL) || ((UINTN) MicrocodeEntryPoint == MicrocodeEnd)) { // // There is no microcode patches // return; } PatchCount = 0; MaxPatchNumber = DEFAULT_MAX_MICROCODE_PATCH_NUM; TotalLoadSize = 0; PatchInfoBuffer = AllocatePool (MaxPatchNumber * sizeof (MICROCODE_PATCH_INFO)); if (PatchInfoBuffer == NULL) { return; } MicrocodeCpuIds = AllocatePages ( EFI_SIZE_TO_PAGES (CpuMpData->CpuCount * sizeof (EDKII_PEI_MICROCODE_CPU_ID)) ); if (MicrocodeCpuIds == NULL) { FreePool (PatchInfoBuffer); return; } for (Index = 0; Index < CpuMpData->CpuCount; Index++) { MicrocodeCpuIds[Index].PlatformId = CpuMpData->CpuData[Index].PlatformId; MicrocodeCpuIds[Index].ProcessorSignature = CpuMpData->CpuData[Index].ProcessorSignature; } // // Process the header of each microcode patch within the region. // The purpose is to decide which microcode patch(es) will be loaded into memory. // Microcode checksum is not verified because it's slow when performing on flash. // do { Valid = IsValidMicrocode ( MicrocodeEntryPoint, MicrocodeEnd - (UINTN) MicrocodeEntryPoint, 0, MicrocodeCpuIds, CpuMpData->CpuCount, FALSE ); if (!Valid) { // // Padding data between the microcode patches, skip 1KB to check next entry. // MicrocodeEntryPoint = (CPU_MICROCODE_HEADER *) (((UINTN) MicrocodeEntryPoint) + SIZE_1KB); continue; } PatchCount++; if (PatchCount > MaxPatchNumber) { // // Current 'PatchInfoBuffer' cannot hold the information, double the size // and allocate a new buffer. // if (MaxPatchNumber > MAX_UINTN / 2 / sizeof (MICROCODE_PATCH_INFO)) { // // Overflow check for MaxPatchNumber // goto OnExit; } PatchInfoBuffer = ReallocatePool ( MaxPatchNumber * sizeof (MICROCODE_PATCH_INFO), 2 * MaxPatchNumber * sizeof (MICROCODE_PATCH_INFO), PatchInfoBuffer ); if (PatchInfoBuffer == NULL) { goto OnExit; } MaxPatchNumber = MaxPatchNumber * 2; } TotalSize = GetMicrocodeLength (MicrocodeEntryPoint); // // Store the information of this microcode patch // PatchInfoBuffer[PatchCount - 1].Address = (UINTN) MicrocodeEntryPoint; PatchInfoBuffer[PatchCount - 1].Size = TotalSize; TotalLoadSize += TotalSize; // // Process the next microcode patch // MicrocodeEntryPoint = (CPU_MICROCODE_HEADER *) ((UINTN) MicrocodeEntryPoint + TotalSize); } while ((UINTN) MicrocodeEntryPoint < MicrocodeEnd); if (PatchCount != 0) { DEBUG (( DEBUG_INFO, "%a: 0x%x microcode patches will be loaded into memory, with size 0x%x.\n", __FUNCTION__, PatchCount, TotalLoadSize )); ShadowMicrocodePatchWorker (CpuMpData, PatchInfoBuffer, PatchCount, TotalLoadSize); } OnExit: if (PatchInfoBuffer != NULL) { FreePool (PatchInfoBuffer); } FreePages (MicrocodeCpuIds, EFI_SIZE_TO_PAGES (CpuMpData->CpuCount * sizeof (EDKII_PEI_MICROCODE_CPU_ID))); } /** Shadow the required microcode patches data into memory. @param[in, out] CpuMpData The pointer to CPU MP Data structure. **/ VOID ShadowMicrocodeUpdatePatch ( IN OUT CPU_MP_DATA *CpuMpData ) { EFI_STATUS Status; Status = PlatformShadowMicrocode (CpuMpData); if (EFI_ERROR (Status)) { ShadowMicrocodePatchByPcd (CpuMpData); } } /** Get the cached microcode patch base address and size from the microcode patch information cache HOB. @param[out] Address Base address of the microcode patches data. It will be updated if the microcode patch information cache HOB is found. @param[out] RegionSize Size of the microcode patches data. It will be updated if the microcode patch information cache HOB is found. @retval TRUE The microcode patch information cache HOB is found. @retval FALSE The microcode patch information cache HOB is not found. **/ BOOLEAN GetMicrocodePatchInfoFromHob ( UINT64 *Address, UINT64 *RegionSize ) { EFI_HOB_GUID_TYPE *GuidHob; EDKII_MICROCODE_PATCH_HOB *MicrocodePathHob; GuidHob = GetFirstGuidHob (&gEdkiiMicrocodePatchHobGuid); if (GuidHob == NULL) { DEBUG((DEBUG_INFO, "%a: Microcode patch cache HOB is not found.\n", __FUNCTION__)); return FALSE; } MicrocodePathHob = GET_GUID_HOB_DATA (GuidHob); *Address = MicrocodePathHob->MicrocodePatchAddress; *RegionSize = MicrocodePathHob->MicrocodePatchRegionSize; DEBUG(( DEBUG_INFO, "%a: MicrocodeBase = 0x%lx, MicrocodeSize = 0x%lx\n", __FUNCTION__, *Address, *RegionSize )); return TRUE; }