| /** @file | |
| SetImage instance to update Microcode. | |
| Caution: This module requires additional review when modified. | |
| This module will have external input - capsule image. | |
| This external input must be validated carefully to avoid security issue like | |
| buffer overflow, integer overflow. | |
| MicrocodeWrite() and VerifyMicrocode() will receive untrusted input and do basic validation. | |
| Copyright (c) 2016, Intel Corporation. All rights reserved.<BR> | |
| This program and the accompanying materials | |
| are licensed and made available under the terms and conditions of the BSD License | |
| which accompanies this distribution. The full text of the license may be found at | |
| http://opensource.org/licenses/bsd-license.php | |
| THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
| **/ | |
| #include "MicrocodeUpdate.h" | |
| /** | |
| Get Microcode Region. | |
| @param[out] MicrocodePatchAddress The address of Microcode | |
| @param[out] MicrocodePatchRegionSize The region size of Microcode | |
| @retval TRUE The Microcode region is returned. | |
| @retval FALSE No Microcode region. | |
| **/ | |
| BOOLEAN | |
| GetMicrocodeRegion ( | |
| OUT VOID **MicrocodePatchAddress, | |
| OUT UINTN *MicrocodePatchRegionSize | |
| ) | |
| { | |
| *MicrocodePatchAddress = (VOID *)(UINTN)PcdGet64(PcdCpuMicrocodePatchAddress); | |
| *MicrocodePatchRegionSize = (UINTN)PcdGet64(PcdCpuMicrocodePatchRegionSize); | |
| if ((*MicrocodePatchAddress == NULL) || (*MicrocodePatchRegionSize == 0)) { | |
| return FALSE; | |
| } | |
| return TRUE; | |
| } | |
| /** | |
| Get Microcode update signature of currently loaded Microcode update. | |
| @return Microcode signature. | |
| **/ | |
| UINT32 | |
| GetCurrentMicrocodeSignature ( | |
| VOID | |
| ) | |
| { | |
| UINT64 Signature; | |
| AsmWriteMsr64(MSR_IA32_BIOS_SIGN_ID, 0); | |
| AsmCpuid(CPUID_VERSION_INFO, NULL, NULL, NULL, NULL); | |
| Signature = AsmReadMsr64(MSR_IA32_BIOS_SIGN_ID); | |
| return (UINT32)RShiftU64(Signature, 32); | |
| } | |
| /** | |
| Get current processor signature. | |
| @return current processor signature. | |
| **/ | |
| UINT32 | |
| GetCurrentProcessorSignature ( | |
| VOID | |
| ) | |
| { | |
| UINT32 RegEax; | |
| AsmCpuid(CPUID_VERSION_INFO, &RegEax, NULL, NULL, NULL); | |
| return RegEax; | |
| } | |
| /** | |
| Get current platform ID. | |
| @return current platform ID. | |
| **/ | |
| UINT8 | |
| GetCurrentPlatformId ( | |
| VOID | |
| ) | |
| { | |
| UINT8 PlatformId; | |
| PlatformId = (UINT8)AsmMsrBitFieldRead64(MSR_IA32_PLATFORM_ID, 50, 52); | |
| return PlatformId; | |
| } | |
| /** | |
| Load new Microcode. | |
| @param[in] Address The address of new Microcode. | |
| @return Loaded Microcode signature. | |
| **/ | |
| UINT32 | |
| LoadMicrocode ( | |
| IN UINT64 Address | |
| ) | |
| { | |
| AsmWriteMsr64(MSR_IA32_BIOS_UPDT_TRIG, Address); | |
| return GetCurrentMicrocodeSignature(); | |
| } | |
| /** | |
| Load Microcode on an Application Processor. | |
| The function prototype for invoking a function on an Application Processor. | |
| @param[in,out] Buffer The pointer to private data buffer. | |
| **/ | |
| VOID | |
| EFIAPI | |
| MicrocodeLoadAp ( | |
| IN OUT VOID *Buffer | |
| ) | |
| { | |
| MICROCODE_LOAD_BUFFER *MicrocodeLoadBuffer; | |
| MicrocodeLoadBuffer = Buffer; | |
| MicrocodeLoadBuffer->Revision = LoadMicrocode (MicrocodeLoadBuffer->Address); | |
| } | |
| /** | |
| Load new Microcode on this processor | |
| @param[in] MicrocodeFmpPrivate The Microcode driver private data | |
| @param[in] CpuIndex The index of the processor. | |
| @param[in] Address The address of new Microcode. | |
| @return Loaded Microcode signature. | |
| **/ | |
| UINT32 | |
| LoadMicrocodeOnThis ( | |
| IN MICROCODE_FMP_PRIVATE_DATA *MicrocodeFmpPrivate, | |
| IN UINTN CpuIndex, | |
| IN UINT64 Address | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_MP_SERVICES_PROTOCOL *MpService; | |
| MICROCODE_LOAD_BUFFER MicrocodeLoadBuffer; | |
| if (CpuIndex == MicrocodeFmpPrivate->BspIndex) { | |
| return LoadMicrocode (Address); | |
| } else { | |
| MpService = MicrocodeFmpPrivate->MpService; | |
| MicrocodeLoadBuffer.Address = Address; | |
| MicrocodeLoadBuffer.Revision = 0; | |
| Status = MpService->StartupThisAP ( | |
| MpService, | |
| MicrocodeLoadAp, | |
| CpuIndex, | |
| NULL, | |
| 0, | |
| &MicrocodeLoadBuffer, | |
| NULL | |
| ); | |
| ASSERT_EFI_ERROR(Status); | |
| return MicrocodeLoadBuffer.Revision; | |
| } | |
| } | |
| /** | |
| Collect processor information. | |
| The function prototype for invoking a function on an Application Processor. | |
| @param[in,out] Buffer The pointer to private data buffer. | |
| **/ | |
| VOID | |
| EFIAPI | |
| CollectProcessorInfo ( | |
| IN OUT VOID *Buffer | |
| ) | |
| { | |
| PROCESSOR_INFO *ProcessorInfo; | |
| ProcessorInfo = Buffer; | |
| ProcessorInfo->ProcessorSignature = GetCurrentProcessorSignature(); | |
| ProcessorInfo->PlatformId = GetCurrentPlatformId(); | |
| ProcessorInfo->MicrocodeRevision = GetCurrentMicrocodeSignature(); | |
| } | |
| /** | |
| Get current Microcode information. | |
| The ProcessorInformation (BspIndex/ProcessorCount/ProcessorInfo) | |
| in MicrocodeFmpPrivate must be initialized. | |
| The MicrocodeInformation (DescriptorCount/ImageDescriptor/MicrocodeInfo) | |
| in MicrocodeFmpPrivate may not be avaiable in this function. | |
| @param[in] MicrocodeFmpPrivate The Microcode driver private data | |
| @param[in] DescriptorCount The count of Microcode ImageDescriptor allocated. | |
| @param[out] ImageDescriptor Microcode ImageDescriptor | |
| @param[out] MicrocodeInfo Microcode information | |
| @return Microcode count | |
| **/ | |
| UINTN | |
| GetMicrocodeInfo ( | |
| IN MICROCODE_FMP_PRIVATE_DATA *MicrocodeFmpPrivate, | |
| IN UINTN DescriptorCount, OPTIONAL | |
| OUT EFI_FIRMWARE_IMAGE_DESCRIPTOR *ImageDescriptor, OPTIONAL | |
| OUT MICROCODE_INFO *MicrocodeInfo OPTIONAL | |
| ) | |
| { | |
| VOID *MicrocodePatchAddress; | |
| UINTN MicrocodePatchRegionSize; | |
| CPU_MICROCODE_HEADER *MicrocodeEntryPoint; | |
| UINTN MicrocodeEnd; | |
| UINTN TotalSize; | |
| UINTN Count; | |
| UINT64 ImageAttributes; | |
| BOOLEAN IsInUse; | |
| EFI_STATUS Status; | |
| UINT32 AttemptStatus; | |
| UINTN TargetCpuIndex; | |
| MicrocodePatchAddress = MicrocodeFmpPrivate->MicrocodePatchAddress; | |
| MicrocodePatchRegionSize = MicrocodeFmpPrivate->MicrocodePatchRegionSize; | |
| DEBUG((DEBUG_INFO, "Microcode Region - 0x%x - 0x%x\n", MicrocodePatchAddress, MicrocodePatchRegionSize)); | |
| Count = 0; | |
| MicrocodeEnd = (UINTN)MicrocodePatchAddress + MicrocodePatchRegionSize; | |
| MicrocodeEntryPoint = (CPU_MICROCODE_HEADER *) (UINTN) MicrocodePatchAddress; | |
| do { | |
| if (MicrocodeEntryPoint->HeaderVersion == 0x1 && MicrocodeEntryPoint->LoaderRevision == 0x1) { | |
| // | |
| // It is the microcode header. It is not the padding data between microcode patches | |
| // becasue the padding data should not include 0x00000001 and it should be the repeated | |
| // byte format (like 0xXYXYXYXY....). | |
| // | |
| if (MicrocodeEntryPoint->DataSize == 0) { | |
| TotalSize = 2048; | |
| } else { | |
| TotalSize = MicrocodeEntryPoint->TotalSize; | |
| } | |
| TargetCpuIndex = (UINTN)-1; | |
| Status = VerifyMicrocode(MicrocodeFmpPrivate, MicrocodeEntryPoint, TotalSize, FALSE, &AttemptStatus, NULL, &TargetCpuIndex); | |
| if (!EFI_ERROR(Status)) { | |
| IsInUse = TRUE; | |
| ASSERT (TargetCpuIndex < MicrocodeFmpPrivate->ProcessorCount); | |
| MicrocodeFmpPrivate->ProcessorInfo[TargetCpuIndex].MicrocodeIndex = Count; | |
| } else { | |
| IsInUse = FALSE; | |
| } | |
| if (ImageDescriptor != NULL && DescriptorCount > Count) { | |
| ImageDescriptor[Count].ImageIndex = (UINT8)(Count + 1); | |
| CopyGuid (&ImageDescriptor[Count].ImageTypeId, &gMicrocodeFmpImageTypeIdGuid); | |
| ImageDescriptor[Count].ImageId = LShiftU64(MicrocodeEntryPoint->ProcessorFlags, 32) + MicrocodeEntryPoint->ProcessorSignature.Uint32; | |
| ImageDescriptor[Count].ImageIdName = NULL; | |
| ImageDescriptor[Count].Version = MicrocodeEntryPoint->UpdateRevision; | |
| ImageDescriptor[Count].VersionName = NULL; | |
| ImageDescriptor[Count].Size = TotalSize; | |
| ImageAttributes = IMAGE_ATTRIBUTE_IMAGE_UPDATABLE | IMAGE_ATTRIBUTE_RESET_REQUIRED; | |
| if (IsInUse) { | |
| ImageAttributes |= IMAGE_ATTRIBUTE_IN_USE; | |
| } | |
| ImageDescriptor[Count].AttributesSupported = ImageAttributes | IMAGE_ATTRIBUTE_IN_USE; | |
| ImageDescriptor[Count].AttributesSetting = ImageAttributes; | |
| ImageDescriptor[Count].Compatibilities = 0; | |
| ImageDescriptor[Count].LowestSupportedImageVersion = MicrocodeEntryPoint->UpdateRevision; // do not support rollback | |
| ImageDescriptor[Count].LastAttemptVersion = 0; | |
| ImageDescriptor[Count].LastAttemptStatus = 0; | |
| ImageDescriptor[Count].HardwareInstance = 0; | |
| } | |
| if (MicrocodeInfo != NULL && DescriptorCount > Count) { | |
| MicrocodeInfo[Count].MicrocodeEntryPoint = MicrocodeEntryPoint; | |
| MicrocodeInfo[Count].TotalSize = TotalSize; | |
| MicrocodeInfo[Count].InUse = IsInUse; | |
| } | |
| } else { | |
| // | |
| // 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. | |
| // | |
| MicrocodeEntryPoint = (CPU_MICROCODE_HEADER *) (((UINTN) MicrocodeEntryPoint) + SIZE_1KB); | |
| continue; | |
| } | |
| Count++; | |
| ASSERT(Count < 0xFF); | |
| // | |
| // Get the next patch. | |
| // | |
| MicrocodeEntryPoint = (CPU_MICROCODE_HEADER *) (((UINTN) MicrocodeEntryPoint) + TotalSize); | |
| } while (((UINTN) MicrocodeEntryPoint < MicrocodeEnd)); | |
| return Count; | |
| } | |
| /** | |
| Return matched processor information. | |
| @param[in] MicrocodeFmpPrivate The Microcode driver private data | |
| @param[in] ProcessorSignature The processor signature to be matched | |
| @param[in] ProcessorFlags The processor flags to be matched | |
| @param[in, out] TargetCpuIndex On input, the index of target CPU which tries to match the Microcode. (UINTN)-1 means to try all. | |
| On output, the index of target CPU which matches the Microcode. | |
| @return matched processor information. | |
| **/ | |
| PROCESSOR_INFO * | |
| GetMatchedProcessor ( | |
| IN MICROCODE_FMP_PRIVATE_DATA *MicrocodeFmpPrivate, | |
| IN UINT32 ProcessorSignature, | |
| IN UINT32 ProcessorFlags, | |
| IN OUT UINTN *TargetCpuIndex | |
| ) | |
| { | |
| UINTN Index; | |
| if (*TargetCpuIndex != (UINTN)-1) { | |
| Index = *TargetCpuIndex; | |
| if ((ProcessorSignature == MicrocodeFmpPrivate->ProcessorInfo[Index].ProcessorSignature) && | |
| ((ProcessorFlags & (1 << MicrocodeFmpPrivate->ProcessorInfo[Index].PlatformId)) != 0)) { | |
| return &MicrocodeFmpPrivate->ProcessorInfo[Index]; | |
| } else { | |
| return NULL; | |
| } | |
| } | |
| for (Index = 0; Index < MicrocodeFmpPrivate->ProcessorCount; Index++) { | |
| if ((ProcessorSignature == MicrocodeFmpPrivate->ProcessorInfo[Index].ProcessorSignature) && | |
| ((ProcessorFlags & (1 << MicrocodeFmpPrivate->ProcessorInfo[Index].PlatformId)) != 0)) { | |
| *TargetCpuIndex = Index; | |
| return &MicrocodeFmpPrivate->ProcessorInfo[Index]; | |
| } | |
| } | |
| return NULL; | |
| } | |
| /** | |
| Verify Microcode. | |
| Caution: This function may receive untrusted input. | |
| @param[in] MicrocodeFmpPrivate The Microcode driver private data | |
| @param[in] Image The Microcode image buffer. | |
| @param[in] ImageSize The size of Microcode image buffer in bytes. | |
| @param[in] TryLoad Try to load Microcode or not. | |
| @param[out] LastAttemptStatus The last attempt status, which will be recorded in ESRT and FMP EFI_FIRMWARE_IMAGE_DESCRIPTOR. | |
| @param[out] AbortReason A pointer to a pointer to a null-terminated string providing more | |
| details for the aborted operation. The buffer is allocated by this function | |
| with AllocatePool(), and it is the caller's responsibility to free it with a | |
| call to FreePool(). | |
| @param[in, out] TargetCpuIndex On input, the index of target CPU which tries to match the Microcode. (UINTN)-1 means to try all. | |
| On output, the index of target CPU which matches the Microcode. | |
| @retval EFI_SUCCESS The Microcode image passes verification. | |
| @retval EFI_VOLUME_CORRUPTED The Microcode image is corrupt. | |
| @retval EFI_INCOMPATIBLE_VERSION The Microcode image version is incorrect. | |
| @retval EFI_UNSUPPORTED The Microcode ProcessorSignature or ProcessorFlags is incorrect. | |
| @retval EFI_SECURITY_VIOLATION The Microcode image fails to load. | |
| **/ | |
| EFI_STATUS | |
| VerifyMicrocode ( | |
| IN MICROCODE_FMP_PRIVATE_DATA *MicrocodeFmpPrivate, | |
| IN VOID *Image, | |
| IN UINTN ImageSize, | |
| IN BOOLEAN TryLoad, | |
| OUT UINT32 *LastAttemptStatus, | |
| OUT CHAR16 **AbortReason, OPTIONAL | |
| IN OUT UINTN *TargetCpuIndex | |
| ) | |
| { | |
| UINTN Index; | |
| CPU_MICROCODE_HEADER *MicrocodeEntryPoint; | |
| UINTN TotalSize; | |
| UINTN DataSize; | |
| UINT32 CurrentRevision; | |
| PROCESSOR_INFO *ProcessorInfo; | |
| UINT32 CheckSum32; | |
| UINTN ExtendedTableLength; | |
| UINT32 ExtendedTableCount; | |
| CPU_MICROCODE_EXTENDED_TABLE *ExtendedTable; | |
| CPU_MICROCODE_EXTENDED_TABLE_HEADER *ExtendedTableHeader; | |
| BOOLEAN CorrectMicrocode; | |
| // | |
| // Check HeaderVersion | |
| // | |
| MicrocodeEntryPoint = Image; | |
| if (MicrocodeEntryPoint->HeaderVersion != 0x1) { | |
| DEBUG((DEBUG_ERROR, "VerifyMicrocode - fail on HeaderVersion\n")); | |
| *LastAttemptStatus = LAST_ATTEMPT_STATUS_ERROR_INVALID_FORMAT; | |
| if (AbortReason != NULL) { | |
| *AbortReason = AllocateCopyPool(sizeof(L"InvalidHeaderVersion"), L"InvalidHeaderVersion"); | |
| } | |
| return EFI_INCOMPATIBLE_VERSION; | |
| } | |
| // | |
| // Check LoaderRevision | |
| // | |
| if (MicrocodeEntryPoint->LoaderRevision != 0x1) { | |
| DEBUG((DEBUG_ERROR, "VerifyMicrocode - fail on LoaderRevision\n")); | |
| *LastAttemptStatus = LAST_ATTEMPT_STATUS_ERROR_INVALID_FORMAT; | |
| if (AbortReason != NULL) { | |
| *AbortReason = AllocateCopyPool(sizeof(L"InvalidLoaderVersion"), L"InvalidLoaderVersion"); | |
| } | |
| return EFI_INCOMPATIBLE_VERSION; | |
| } | |
| // | |
| // Check Size | |
| // | |
| if (MicrocodeEntryPoint->DataSize == 0) { | |
| TotalSize = 2048; | |
| } else { | |
| TotalSize = MicrocodeEntryPoint->TotalSize; | |
| } | |
| if (TotalSize <= sizeof(CPU_MICROCODE_HEADER)) { | |
| DEBUG((DEBUG_ERROR, "VerifyMicrocode - TotalSize too small\n")); | |
| *LastAttemptStatus = LAST_ATTEMPT_STATUS_ERROR_INVALID_FORMAT; | |
| if (AbortReason != NULL) { | |
| *AbortReason = AllocateCopyPool(sizeof(L"InvalidTotalSize"), L"InvalidTotalSize"); | |
| } | |
| return EFI_VOLUME_CORRUPTED; | |
| } | |
| if (TotalSize != ImageSize) { | |
| DEBUG((DEBUG_ERROR, "VerifyMicrocode - fail on TotalSize\n")); | |
| *LastAttemptStatus = LAST_ATTEMPT_STATUS_ERROR_INVALID_FORMAT; | |
| if (AbortReason != NULL) { | |
| *AbortReason = AllocateCopyPool(sizeof(L"InvalidTotalSize"), L"InvalidTotalSize"); | |
| } | |
| return EFI_VOLUME_CORRUPTED; | |
| } | |
| // | |
| // Check CheckSum32 | |
| // | |
| if (MicrocodeEntryPoint->DataSize == 0) { | |
| DataSize = 2048 - sizeof(CPU_MICROCODE_HEADER); | |
| } else { | |
| DataSize = MicrocodeEntryPoint->DataSize; | |
| } | |
| if (DataSize > TotalSize - sizeof(CPU_MICROCODE_HEADER)) { | |
| DEBUG((DEBUG_ERROR, "VerifyMicrocode - DataSize too big\n")); | |
| *LastAttemptStatus = LAST_ATTEMPT_STATUS_ERROR_INVALID_FORMAT; | |
| if (AbortReason != NULL) { | |
| *AbortReason = AllocateCopyPool(sizeof(L"InvalidDataSize"), L"InvalidDataSize"); | |
| } | |
| return EFI_VOLUME_CORRUPTED; | |
| } | |
| if ((DataSize & 0x3) != 0) { | |
| DEBUG((DEBUG_ERROR, "VerifyMicrocode - DataSize not aligned\n")); | |
| *LastAttemptStatus = LAST_ATTEMPT_STATUS_ERROR_INVALID_FORMAT; | |
| if (AbortReason != NULL) { | |
| *AbortReason = AllocateCopyPool(sizeof(L"InvalidDataSize"), L"InvalidDataSize"); | |
| } | |
| return EFI_VOLUME_CORRUPTED; | |
| } | |
| CheckSum32 = CalculateSum32((UINT32 *)MicrocodeEntryPoint, DataSize + sizeof(CPU_MICROCODE_HEADER)); | |
| if (CheckSum32 != 0) { | |
| DEBUG((DEBUG_ERROR, "VerifyMicrocode - fail on CheckSum32\n")); | |
| *LastAttemptStatus = LAST_ATTEMPT_STATUS_ERROR_INVALID_FORMAT; | |
| if (AbortReason != NULL) { | |
| *AbortReason = AllocateCopyPool(sizeof(L"InvalidChecksum"), L"InvalidChecksum"); | |
| } | |
| return EFI_VOLUME_CORRUPTED; | |
| } | |
| // | |
| // Check ProcessorSignature/ProcessorFlags | |
| // | |
| ProcessorInfo = GetMatchedProcessor (MicrocodeFmpPrivate, MicrocodeEntryPoint->ProcessorSignature.Uint32, MicrocodeEntryPoint->ProcessorFlags, TargetCpuIndex); | |
| if (ProcessorInfo == NULL) { | |
| CorrectMicrocode = FALSE; | |
| ExtendedTableLength = TotalSize - (DataSize + sizeof(CPU_MICROCODE_HEADER)); | |
| if (ExtendedTableLength != 0) { | |
| // | |
| // Extended Table exist, check if the CPU in support list | |
| // | |
| ExtendedTableHeader = (CPU_MICROCODE_EXTENDED_TABLE_HEADER *)((UINT8 *)(MicrocodeEntryPoint) + DataSize + sizeof(CPU_MICROCODE_HEADER)); | |
| // | |
| // Calculate Extended Checksum | |
| // | |
| if ((ExtendedTableLength > sizeof(CPU_MICROCODE_EXTENDED_TABLE_HEADER)) && ((ExtendedTableLength & 0x3) != 0)) { | |
| CheckSum32 = CalculateSum32((UINT32 *)ExtendedTableHeader, ExtendedTableLength); | |
| if (CheckSum32 == 0) { | |
| // | |
| // Checksum correct | |
| // | |
| ExtendedTableCount = ExtendedTableHeader->ExtendedSignatureCount; | |
| if (ExtendedTableCount <= (ExtendedTableLength - sizeof(CPU_MICROCODE_EXTENDED_TABLE_HEADER)) / sizeof(CPU_MICROCODE_EXTENDED_TABLE)) { | |
| ExtendedTable = (CPU_MICROCODE_EXTENDED_TABLE *)(ExtendedTableHeader + 1); | |
| for (Index = 0; Index < ExtendedTableCount; Index++) { | |
| CheckSum32 = CalculateSum32((UINT32 *)ExtendedTable, sizeof(CPU_MICROCODE_EXTENDED_TABLE)); | |
| if (CheckSum32 == 0) { | |
| // | |
| // Verify Header | |
| // | |
| ProcessorInfo = GetMatchedProcessor (MicrocodeFmpPrivate, ExtendedTable->ProcessorSignature.Uint32, ExtendedTable->ProcessorFlag, TargetCpuIndex); | |
| if (ProcessorInfo != NULL) { | |
| // | |
| // Find one | |
| // | |
| CorrectMicrocode = TRUE; | |
| break; | |
| } | |
| } | |
| ExtendedTable++; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (!CorrectMicrocode) { | |
| if (TryLoad) { | |
| DEBUG((DEBUG_ERROR, "VerifyMicrocode - fail on CurrentProcessorSignature/ProcessorFlags\n")); | |
| } | |
| *LastAttemptStatus = LAST_ATTEMPT_STATUS_ERROR_INCORRECT_VERSION; | |
| if (AbortReason != NULL) { | |
| *AbortReason = AllocateCopyPool(sizeof(L"UnsupportedProcessSignature/ProcessorFlags"), L"UnsupportedProcessSignature/ProcessorFlags"); | |
| } | |
| return EFI_UNSUPPORTED; | |
| } | |
| } | |
| // | |
| // Check UpdateRevision | |
| // | |
| CurrentRevision = ProcessorInfo->MicrocodeRevision; | |
| if ((MicrocodeEntryPoint->UpdateRevision < CurrentRevision) || | |
| (TryLoad && (MicrocodeEntryPoint->UpdateRevision == CurrentRevision))) { | |
| if (TryLoad) { | |
| DEBUG((DEBUG_ERROR, "VerifyMicrocode - fail on UpdateRevision\n")); | |
| } | |
| *LastAttemptStatus = LAST_ATTEMPT_STATUS_ERROR_INCORRECT_VERSION; | |
| if (AbortReason != NULL) { | |
| *AbortReason = AllocateCopyPool(sizeof(L"IncorrectRevision"), L"IncorrectRevision"); | |
| } | |
| return EFI_INCOMPATIBLE_VERSION; | |
| } | |
| // | |
| // try load MCU | |
| // | |
| if (TryLoad) { | |
| CurrentRevision = LoadMicrocodeOnThis(MicrocodeFmpPrivate, ProcessorInfo->CpuIndex, (UINTN)MicrocodeEntryPoint + sizeof(CPU_MICROCODE_HEADER)); | |
| if (MicrocodeEntryPoint->UpdateRevision != CurrentRevision) { | |
| DEBUG((DEBUG_ERROR, "VerifyMicrocode - fail on LoadMicrocode\n")); | |
| *LastAttemptStatus = LAST_ATTEMPT_STATUS_ERROR_AUTH_ERROR; | |
| if (AbortReason != NULL) { | |
| *AbortReason = AllocateCopyPool(sizeof(L"InvalidData"), L"InvalidData"); | |
| } | |
| return EFI_SECURITY_VIOLATION; | |
| } | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Get next Microcode entrypoint. | |
| @param[in] MicrocodeFmpPrivate The Microcode driver private data | |
| @param[in] MicrocodeEntryPoint Current Microcode entrypoint | |
| @return next Microcode entrypoint. | |
| **/ | |
| CPU_MICROCODE_HEADER * | |
| GetNextMicrocode ( | |
| IN MICROCODE_FMP_PRIVATE_DATA *MicrocodeFmpPrivate, | |
| IN CPU_MICROCODE_HEADER *MicrocodeEntryPoint | |
| ) | |
| { | |
| UINTN Index; | |
| for (Index = 0; Index < MicrocodeFmpPrivate->DescriptorCount; Index++) { | |
| if (MicrocodeEntryPoint == MicrocodeFmpPrivate->MicrocodeInfo[Index].MicrocodeEntryPoint) { | |
| if (Index == (UINTN)MicrocodeFmpPrivate->DescriptorCount - 1) { | |
| // it is last one | |
| return NULL; | |
| } else { | |
| // return next one | |
| return MicrocodeFmpPrivate->MicrocodeInfo[Index + 1].MicrocodeEntryPoint; | |
| } | |
| } | |
| } | |
| ASSERT(FALSE); | |
| return NULL; | |
| } | |
| /** | |
| Get current Microcode used region size. | |
| @param[in] MicrocodeFmpPrivate The Microcode driver private data | |
| @return current Microcode used region size. | |
| **/ | |
| UINTN | |
| GetCurrentMicrocodeUsedRegionSize ( | |
| IN MICROCODE_FMP_PRIVATE_DATA *MicrocodeFmpPrivate | |
| ) | |
| { | |
| if (MicrocodeFmpPrivate->DescriptorCount == 0) { | |
| return 0; | |
| } | |
| return (UINTN)MicrocodeFmpPrivate->MicrocodeInfo[MicrocodeFmpPrivate->DescriptorCount - 1].MicrocodeEntryPoint | |
| + (UINTN)MicrocodeFmpPrivate->MicrocodeInfo[MicrocodeFmpPrivate->DescriptorCount - 1].TotalSize | |
| - (UINTN)MicrocodeFmpPrivate->MicrocodePatchAddress; | |
| } | |
| /** | |
| Update Microcode. | |
| @param[in] Address The flash address of Microcode. | |
| @param[in] Image The Microcode image buffer. | |
| @param[in] ImageSize The size of Microcode image buffer in bytes. | |
| @param[out] LastAttemptStatus The last attempt status, which will be recorded in ESRT and FMP EFI_FIRMWARE_IMAGE_DESCRIPTOR. | |
| @retval EFI_SUCCESS The Microcode image is updated. | |
| @retval EFI_WRITE_PROTECTED The flash device is read only. | |
| **/ | |
| EFI_STATUS | |
| UpdateMicrocode ( | |
| IN UINT64 Address, | |
| IN VOID *Image, | |
| IN UINTN ImageSize, | |
| OUT UINT32 *LastAttemptStatus | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| DEBUG((DEBUG_INFO, "PlatformUpdate:")); | |
| DEBUG((DEBUG_INFO, " Address - 0x%lx,", Address)); | |
| DEBUG((DEBUG_INFO, " Legnth - 0x%x\n", ImageSize)); | |
| Status = MicrocodeFlashWrite ( | |
| Address, | |
| Image, | |
| ImageSize | |
| ); | |
| if (!EFI_ERROR(Status)) { | |
| *LastAttemptStatus = LAST_ATTEMPT_STATUS_SUCCESS; | |
| } else { | |
| *LastAttemptStatus = LAST_ATTEMPT_STATUS_ERROR_UNSUCCESSFUL; | |
| } | |
| return Status; | |
| } | |
| /** | |
| Update Microcode flash region. | |
| @param[in] MicrocodeFmpPrivate The Microcode driver private data | |
| @param[in] TargetMicrocodeEntryPoint Target Microcode entrypoint to be updated | |
| @param[in] Image The Microcode image buffer. | |
| @param[in] ImageSize The size of Microcode image buffer in bytes. | |
| @param[out] LastAttemptStatus The last attempt status, which will be recorded in ESRT and FMP EFI_FIRMWARE_IMAGE_DESCRIPTOR. | |
| @retval EFI_SUCCESS The Microcode image is written. | |
| @retval EFI_WRITE_PROTECTED The flash device is read only. | |
| **/ | |
| EFI_STATUS | |
| UpdateMicrocodeFlashRegion ( | |
| IN MICROCODE_FMP_PRIVATE_DATA *MicrocodeFmpPrivate, | |
| IN CPU_MICROCODE_HEADER *TargetMicrocodeEntryPoint, | |
| IN VOID *Image, | |
| IN UINTN ImageSize, | |
| OUT UINT32 *LastAttemptStatus | |
| ) | |
| { | |
| VOID *MicrocodePatchAddress; | |
| UINTN MicrocodePatchRegionSize; | |
| UINTN TargetTotalSize; | |
| UINTN UsedRegionSize; | |
| EFI_STATUS Status; | |
| VOID *MicrocodePatchScratchBuffer; | |
| UINT8 *ScratchBufferPtr; | |
| UINTN ScratchBufferSize; | |
| UINTN RestSize; | |
| UINTN AvailableSize; | |
| VOID *NextMicrocodeEntryPoint; | |
| MICROCODE_INFO *MicrocodeInfo; | |
| UINTN MicrocodeCount; | |
| UINTN Index; | |
| DEBUG((DEBUG_INFO, "UpdateMicrocodeFlashRegion: Image - 0x%x, size - 0x%x\n", Image, ImageSize)); | |
| MicrocodePatchAddress = MicrocodeFmpPrivate->MicrocodePatchAddress; | |
| MicrocodePatchRegionSize = MicrocodeFmpPrivate->MicrocodePatchRegionSize; | |
| MicrocodePatchScratchBuffer = AllocateZeroPool (MicrocodePatchRegionSize); | |
| if (MicrocodePatchScratchBuffer == NULL) { | |
| DEBUG((DEBUG_ERROR, "Fail to allocate Microcode Scratch buffer\n")); | |
| *LastAttemptStatus = LAST_ATTEMPT_STATUS_ERROR_INSUFFICIENT_RESOURCES; | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| ScratchBufferPtr = MicrocodePatchScratchBuffer; | |
| ScratchBufferSize = 0; | |
| // | |
| // Target data collection | |
| // | |
| TargetTotalSize = 0; | |
| AvailableSize = 0; | |
| NextMicrocodeEntryPoint = NULL; | |
| if (TargetMicrocodeEntryPoint != NULL) { | |
| if (TargetMicrocodeEntryPoint->DataSize == 0) { | |
| TargetTotalSize = 2048; | |
| } else { | |
| TargetTotalSize = TargetMicrocodeEntryPoint->TotalSize; | |
| } | |
| DEBUG((DEBUG_INFO, " TargetTotalSize - 0x%x\n", TargetTotalSize)); | |
| NextMicrocodeEntryPoint = GetNextMicrocode(MicrocodeFmpPrivate, TargetMicrocodeEntryPoint); | |
| DEBUG((DEBUG_INFO, " NextMicrocodeEntryPoint - 0x%x\n", NextMicrocodeEntryPoint)); | |
| if (NextMicrocodeEntryPoint != NULL) { | |
| ASSERT ((UINTN)NextMicrocodeEntryPoint >= ((UINTN)TargetMicrocodeEntryPoint + TargetTotalSize)); | |
| AvailableSize = (UINTN)NextMicrocodeEntryPoint - (UINTN)TargetMicrocodeEntryPoint; | |
| } else { | |
| AvailableSize = (UINTN)MicrocodePatchAddress + MicrocodePatchRegionSize - (UINTN)TargetMicrocodeEntryPoint; | |
| } | |
| DEBUG((DEBUG_INFO, " AvailableSize - 0x%x\n", AvailableSize)); | |
| } | |
| ASSERT (AvailableSize >= TargetTotalSize); | |
| UsedRegionSize = GetCurrentMicrocodeUsedRegionSize(MicrocodeFmpPrivate); | |
| DEBUG((DEBUG_INFO, " UsedRegionSize - 0x%x\n", UsedRegionSize)); | |
| ASSERT (UsedRegionSize >= TargetTotalSize); | |
| if (TargetMicrocodeEntryPoint != NULL) { | |
| ASSERT ((UINTN)MicrocodePatchAddress + UsedRegionSize >= ((UINTN)TargetMicrocodeEntryPoint + TargetTotalSize)); | |
| } | |
| // | |
| // Total Size means the Microcode data size. | |
| // Available Size means the Microcode data size plus the pad till (1) next Microcode or (2) the end. | |
| // | |
| // (1) | |
| // +------+-----------+-----+------+===================+ | |
| // | MCU1 | Microcode | PAD | MCU2 | Empty | | |
| // +------+-----------+-----+------+===================+ | |
| // | TotalSize | | |
| // |<-AvailableSize->| | |
| // |<- UsedRegionSize ->| | |
| // | |
| // (2) | |
| // +------+-----------+===================+ | |
| // | MCU | Microcode | Empty | | |
| // +------+-----------+===================+ | |
| // | TotalSize | | |
| // |<- AvailableSize ->| | |
| // |<-UsedRegionSize->| | |
| // | |
| // | |
| // Update based on policy | |
| // | |
| // | |
| // 1. If there is enough space to update old one in situ, replace old microcode in situ. | |
| // | |
| if (AvailableSize >= ImageSize) { | |
| DEBUG((DEBUG_INFO, "Replace old microcode in situ\n")); | |
| // | |
| // +------+------------+------+===================+ | |
| // |Other1| Old Image |Other2| Empty | | |
| // +------+------------+------+===================+ | |
| // | |
| // +------+---------+--+------+===================+ | |
| // |Other1|New Image|FF|Other2| Empty | | |
| // +------+---------+--+------+===================+ | |
| // | |
| // 1.1. Copy new image | |
| CopyMem (ScratchBufferPtr, Image, ImageSize); | |
| ScratchBufferSize += ImageSize; | |
| ScratchBufferPtr = (UINT8 *)MicrocodePatchScratchBuffer + ScratchBufferSize; | |
| // 1.2. Pad 0xFF | |
| RestSize = AvailableSize - ImageSize; | |
| if (RestSize > 0) { | |
| SetMem (ScratchBufferPtr, RestSize, 0xFF); | |
| ScratchBufferSize += RestSize; | |
| ScratchBufferPtr = (UINT8 *)MicrocodePatchScratchBuffer + ScratchBufferSize; | |
| } | |
| Status = UpdateMicrocode((UINTN)TargetMicrocodeEntryPoint, MicrocodePatchScratchBuffer, ScratchBufferSize, LastAttemptStatus); | |
| return Status; | |
| } | |
| // | |
| // 2. If there is enough space to remove old one and add new one, reorg and replace old microcode. | |
| // | |
| if (MicrocodePatchRegionSize - (UsedRegionSize - TargetTotalSize) >= ImageSize) { | |
| if (TargetMicrocodeEntryPoint == NULL) { | |
| DEBUG((DEBUG_INFO, "Append new microcode\n")); | |
| // | |
| // +------+------------+------+===================+ | |
| // |Other1| Other |Other2| Empty | | |
| // +------+------------+------+===================+ | |
| // | |
| // +------+------------+------+-----------+=======+ | |
| // |Other1| Other |Other2| New Image | Empty | | |
| // +------+------------+------+-----------+=======+ | |
| // | |
| Status = UpdateMicrocode((UINTN)MicrocodePatchAddress + UsedRegionSize, Image, ImageSize, LastAttemptStatus); | |
| } else { | |
| DEBUG((DEBUG_INFO, "Reorg and replace old microcode\n")); | |
| // | |
| // +------+------------+------+===================+ | |
| // |Other1| Old Image |Other2| Empty | | |
| // +------+------------+------+===================+ | |
| // | |
| // +------+---------------+------+================+ | |
| // |Other1| New Image |Other2| Empty | | |
| // +------+---------------+------+================+ | |
| // | |
| // 2.1. Copy new image | |
| CopyMem (ScratchBufferPtr, Image, ImageSize); | |
| ScratchBufferSize += ImageSize; | |
| ScratchBufferPtr = (UINT8 *)MicrocodePatchScratchBuffer + ScratchBufferSize; | |
| // 2.2. Copy rest images after the old image. | |
| if (NextMicrocodeEntryPoint != 0) { | |
| RestSize = (UINTN)MicrocodePatchAddress + UsedRegionSize - ((UINTN)NextMicrocodeEntryPoint); | |
| CopyMem (ScratchBufferPtr, (UINT8 *)TargetMicrocodeEntryPoint + TargetTotalSize, RestSize); | |
| ScratchBufferSize += RestSize; | |
| ScratchBufferPtr = (UINT8 *)MicrocodePatchScratchBuffer + ScratchBufferSize; | |
| } | |
| Status = UpdateMicrocode((UINTN)TargetMicrocodeEntryPoint, MicrocodePatchScratchBuffer, ScratchBufferSize, LastAttemptStatus); | |
| } | |
| return Status; | |
| } | |
| // | |
| // 3. The new image can be put in MCU region, but not all others can be put. | |
| // So all the unused MCU is removed. | |
| // | |
| if (MicrocodePatchRegionSize >= ImageSize) { | |
| // | |
| // +------+------------+------+===================+ | |
| // |Other1| Old Image |Other2| Empty | | |
| // +------+------------+------+===================+ | |
| // | |
| // +-------------------------------------+--------+ | |
| // | New Image | Other | | |
| // +-------------------------------------+--------+ | |
| // | |
| DEBUG((DEBUG_INFO, "Add new microcode from beginning\n")); | |
| MicrocodeCount = MicrocodeFmpPrivate->DescriptorCount; | |
| MicrocodeInfo = MicrocodeFmpPrivate->MicrocodeInfo; | |
| // 3.1. Copy new image | |
| CopyMem (ScratchBufferPtr, Image, ImageSize); | |
| ScratchBufferSize += ImageSize; | |
| ScratchBufferPtr = (UINT8 *)MicrocodePatchScratchBuffer + ScratchBufferSize; | |
| // 3.2. Copy some others to rest buffer | |
| for (Index = 0; Index < MicrocodeCount; Index++) { | |
| if (!MicrocodeInfo[Index].InUse) { | |
| continue; | |
| } | |
| if (MicrocodeInfo[Index].MicrocodeEntryPoint == TargetMicrocodeEntryPoint) { | |
| continue; | |
| } | |
| if (MicrocodeInfo[Index].TotalSize <= MicrocodePatchRegionSize - ScratchBufferSize) { | |
| CopyMem (ScratchBufferPtr, MicrocodeInfo[Index].MicrocodeEntryPoint, MicrocodeInfo[Index].TotalSize); | |
| ScratchBufferSize += MicrocodeInfo[Index].TotalSize; | |
| ScratchBufferPtr = (UINT8 *)MicrocodePatchScratchBuffer + ScratchBufferSize; | |
| } | |
| } | |
| // 3.3. Pad 0xFF | |
| RestSize = MicrocodePatchRegionSize - ScratchBufferSize; | |
| if (RestSize > 0) { | |
| SetMem (ScratchBufferPtr, RestSize, 0xFF); | |
| ScratchBufferSize += RestSize; | |
| ScratchBufferPtr = (UINT8 *)MicrocodePatchScratchBuffer + ScratchBufferSize; | |
| } | |
| Status = UpdateMicrocode((UINTN)MicrocodePatchAddress, MicrocodePatchScratchBuffer, ScratchBufferSize, LastAttemptStatus); | |
| return Status; | |
| } | |
| // | |
| // 4. The new image size is bigger than the whole MCU region. | |
| // | |
| DEBUG((DEBUG_ERROR, "Microcode too big\n")); | |
| *LastAttemptStatus = LAST_ATTEMPT_STATUS_ERROR_INSUFFICIENT_RESOURCES; | |
| Status = EFI_OUT_OF_RESOURCES; | |
| return Status; | |
| } | |
| /** | |
| Write Microcode. | |
| Caution: This function may receive untrusted input. | |
| @param[in] MicrocodeFmpPrivate The Microcode driver private data | |
| @param[in] Image The Microcode image buffer. | |
| @param[in] ImageSize The size of Microcode image buffer in bytes. | |
| @param[out] LastAttemptVersion The last attempt version, which will be recorded in ESRT and FMP EFI_FIRMWARE_IMAGE_DESCRIPTOR. | |
| @param[out] LastAttemptStatus The last attempt status, which will be recorded in ESRT and FMP EFI_FIRMWARE_IMAGE_DESCRIPTOR. | |
| @param[out] AbortReason A pointer to a pointer to a null-terminated string providing more | |
| details for the aborted operation. The buffer is allocated by this function | |
| with AllocatePool(), and it is the caller's responsibility to free it with a | |
| call to FreePool(). | |
| @retval EFI_SUCCESS The Microcode image is written. | |
| @retval EFI_VOLUME_CORRUPTED The Microcode image is corrupt. | |
| @retval EFI_INCOMPATIBLE_VERSION The Microcode image version is incorrect. | |
| @retval EFI_SECURITY_VIOLATION The Microcode image fails to load. | |
| @retval EFI_WRITE_PROTECTED The flash device is read only. | |
| **/ | |
| EFI_STATUS | |
| MicrocodeWrite ( | |
| IN MICROCODE_FMP_PRIVATE_DATA *MicrocodeFmpPrivate, | |
| IN VOID *Image, | |
| IN UINTN ImageSize, | |
| OUT UINT32 *LastAttemptVersion, | |
| OUT UINT32 *LastAttemptStatus, | |
| OUT CHAR16 **AbortReason | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| VOID *AlignedImage; | |
| CPU_MICROCODE_HEADER *TargetMicrocodeEntryPoint; | |
| UINTN TargetCpuIndex; | |
| UINTN TargetMicrcodeIndex; | |
| // | |
| // MCU must be 16 bytes aligned | |
| // | |
| AlignedImage = AllocateCopyPool(ImageSize, Image); | |
| if (AlignedImage == NULL) { | |
| DEBUG((DEBUG_ERROR, "Fail to allocate aligned image\n")); | |
| *LastAttemptStatus = LAST_ATTEMPT_STATUS_ERROR_INSUFFICIENT_RESOURCES; | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| *LastAttemptVersion = ((CPU_MICROCODE_HEADER *)Image)->UpdateRevision; | |
| TargetCpuIndex = (UINTN)-1; | |
| Status = VerifyMicrocode(MicrocodeFmpPrivate, AlignedImage, ImageSize, TRUE, LastAttemptStatus, AbortReason, &TargetCpuIndex); | |
| if (EFI_ERROR(Status)) { | |
| DEBUG((DEBUG_ERROR, "Fail to verify Microcode Region\n")); | |
| FreePool(AlignedImage); | |
| return Status; | |
| } | |
| DEBUG((DEBUG_INFO, "Pass VerifyMicrocode\n")); | |
| DEBUG((DEBUG_INFO, " TargetCpuIndex - 0x%x\n", TargetCpuIndex)); | |
| ASSERT (TargetCpuIndex < MicrocodeFmpPrivate->ProcessorCount); | |
| TargetMicrcodeIndex = MicrocodeFmpPrivate->ProcessorInfo[TargetCpuIndex].MicrocodeIndex; | |
| DEBUG((DEBUG_INFO, " TargetMicrcodeIndex - 0x%x\n", TargetMicrcodeIndex)); | |
| if (TargetMicrcodeIndex != (UINTN)-1) { | |
| ASSERT (TargetMicrcodeIndex < MicrocodeFmpPrivate->DescriptorCount); | |
| TargetMicrocodeEntryPoint = MicrocodeFmpPrivate->MicrocodeInfo[TargetMicrcodeIndex].MicrocodeEntryPoint; | |
| } else { | |
| TargetMicrocodeEntryPoint = NULL; | |
| } | |
| DEBUG((DEBUG_INFO, " TargetMicrocodeEntryPoint - 0x%x\n", TargetMicrocodeEntryPoint)); | |
| Status = UpdateMicrocodeFlashRegion( | |
| MicrocodeFmpPrivate, | |
| TargetMicrocodeEntryPoint, | |
| AlignedImage, | |
| ImageSize, | |
| LastAttemptStatus | |
| ); | |
| FreePool(AlignedImage); | |
| return Status; | |
| } | |