/** @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 UINT64 *MicrocodePatchAddress, | |
OUT UINT64 *MicrocodePatchRegionSize | |
) | |
{ | |
*MicrocodePatchAddress = PcdGet64(PcdCpuMicrocodePatchAddress); | |
*MicrocodePatchRegionSize = PcdGet64(PcdCpuMicrocodePatchRegionSize); | |
if ((*MicrocodePatchAddress == 0) || (*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(); | |
} | |
/** | |
Get current Microcode information. | |
@param[out] ImageDescriptor Microcode ImageDescriptor | |
@param[in] DescriptorCount The count of Microcode ImageDescriptor allocated. | |
@return Microcode count | |
**/ | |
UINTN | |
GetMicrocodeInfo ( | |
OUT EFI_FIRMWARE_IMAGE_DESCRIPTOR *ImageDescriptor, OPTIONAL | |
IN UINTN DescriptorCount OPTIONAL | |
) | |
{ | |
BOOLEAN Result; | |
UINT64 MicrocodePatchAddress; | |
UINT64 MicrocodePatchRegionSize; | |
CPU_MICROCODE_HEADER *MicrocodeEntryPoint; | |
UINTN MicrocodeEnd; | |
UINTN TotalSize; | |
UINTN Count; | |
UINT64 ImageAttributes; | |
UINT32 CurrentRevision; | |
Result = GetMicrocodeRegion(&MicrocodePatchAddress, &MicrocodePatchRegionSize); | |
if (!Result) { | |
DEBUG((DEBUG_ERROR, "Fail to get Microcode Region\n")); | |
return 0; | |
} | |
DEBUG((DEBUG_INFO, "Microcode Region - 0x%lx - 0x%lx\n", MicrocodePatchAddress, MicrocodePatchRegionSize)); | |
Count = 0; | |
CurrentRevision = GetCurrentMicrocodeSignature(); | |
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; | |
} | |
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 (CurrentRevision == MicrocodeEntryPoint->UpdateRevision) { | |
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; | |
} | |
} 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; | |
} | |
/** | |
Read Microcode. | |
@param[in] ImageIndex The index of Microcode image. | |
@param[in, out] Image The Microcode image buffer. | |
@param[in, out] ImageSize The size of Microcode image buffer in bytes. | |
@retval EFI_SUCCESS The Microcode image is read. | |
@retval EFI_NOT_FOUND The Microcode image is not found. | |
**/ | |
EFI_STATUS | |
MicrocodeRead ( | |
IN UINTN ImageIndex, | |
IN OUT VOID *Image, | |
IN OUT UINTN *ImageSize | |
) | |
{ | |
BOOLEAN Result; | |
UINT64 MicrocodePatchAddress; | |
UINT64 MicrocodePatchRegionSize; | |
CPU_MICROCODE_HEADER *MicrocodeEntryPoint; | |
UINTN MicrocodeEnd; | |
UINTN TotalSize; | |
UINTN Count; | |
Result = GetMicrocodeRegion(&MicrocodePatchAddress, &MicrocodePatchRegionSize); | |
if (!Result) { | |
DEBUG((DEBUG_ERROR, "Fail to get Microcode Region\n")); | |
return EFI_NOT_FOUND; | |
} | |
DEBUG((DEBUG_INFO, "Microcode Region - 0x%lx - 0x%lx\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; | |
} | |
} 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 EFI_NOT_FOUND; | |
} | |
/** | |
Verify Microcode. | |
Caution: This function may receive untrusted input. | |
@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(). | |
@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 VOID *Image, | |
IN UINTN ImageSize, | |
IN BOOLEAN TryLoad, | |
OUT UINT32 *LastAttemptStatus, | |
OUT CHAR16 **AbortReason | |
) | |
{ | |
UINTN Index; | |
CPU_MICROCODE_HEADER *MicrocodeEntryPoint; | |
UINTN TotalSize; | |
UINTN DataSize; | |
UINT32 CurrentRevision; | |
UINT32 CurrentProcessorSignature; | |
UINT8 CurrentPlatformId; | |
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 | |
// | |
CorrectMicrocode = FALSE; | |
CurrentProcessorSignature = GetCurrentProcessorSignature(); | |
CurrentPlatformId = GetCurrentPlatformId(); | |
if ((MicrocodeEntryPoint->ProcessorSignature.Uint32 != CurrentProcessorSignature) || | |
((MicrocodeEntryPoint->ProcessorFlags & (1 << CurrentPlatformId)) == 0)) { | |
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 | |
// | |
if ((ExtendedTable->ProcessorSignature.Uint32 == CurrentProcessorSignature) && | |
(ExtendedTable->ProcessorFlag & (1 << CurrentPlatformId))) { | |
// | |
// Find one | |
// | |
CorrectMicrocode = TRUE; | |
break; | |
} | |
} | |
ExtendedTable++; | |
} | |
} | |
} | |
} | |
} | |
if (!CorrectMicrocode) { | |
DEBUG((DEBUG_ERROR, "VerifyMicrocode - fail on CurrentProcessorSignature/ProcessorFlags\n")); | |
*LastAttemptStatus = LAST_ATTEMPT_STATUS_ERROR_INCORRECT_VERSION; | |
if (AbortReason != NULL) { | |
if (MicrocodeEntryPoint->ProcessorSignature.Uint32 != CurrentProcessorSignature) { | |
*AbortReason = AllocateCopyPool(sizeof(L"UnsupportedProcessSignature"), L"UnsupportedProcessSignature"); | |
} else { | |
*AbortReason = AllocateCopyPool(sizeof(L"UnsupportedProcessorFlags"), L"UnsupportedProcessorFlags"); | |
} | |
} | |
return EFI_UNSUPPORTED; | |
} | |
} | |
// | |
// Check UpdateRevision | |
// | |
CurrentRevision = GetCurrentMicrocodeSignature(); | |
if (MicrocodeEntryPoint->UpdateRevision < CurrentRevision) { | |
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 = LoadMicrocode((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 current Microcode in used. | |
@return current Microcode in used. | |
**/ | |
VOID * | |
GetCurrentMicrocodeInUse ( | |
VOID | |
) | |
{ | |
BOOLEAN Result; | |
EFI_STATUS Status; | |
UINT64 MicrocodePatchAddress; | |
UINT64 MicrocodePatchRegionSize; | |
CPU_MICROCODE_HEADER *MicrocodeEntryPoint; | |
UINTN MicrocodeEnd; | |
UINTN TotalSize; | |
UINTN Count; | |
UINT32 AttemptStatus; | |
Result = GetMicrocodeRegion(&MicrocodePatchAddress, &MicrocodePatchRegionSize); | |
if (!Result) { | |
DEBUG((DEBUG_ERROR, "Fail to get Microcode Region\n")); | |
return NULL; | |
} | |
DEBUG((DEBUG_INFO, "Microcode Region - 0x%lx - 0x%lx\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; | |
} | |
Status = VerifyMicrocode(MicrocodeEntryPoint, TotalSize, FALSE, &AttemptStatus, NULL); | |
if (!EFI_ERROR(Status)) { | |
return MicrocodeEntryPoint; | |
} | |
} 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 NULL; | |
} | |
/** | |
Get current Microcode used region size. | |
@return current Microcode used region size. | |
**/ | |
UINTN | |
GetCurrentMicrocodeUsedRegionSize ( | |
VOID | |
) | |
{ | |
BOOLEAN Result; | |
UINT64 MicrocodePatchAddress; | |
UINT64 MicrocodePatchRegionSize; | |
CPU_MICROCODE_HEADER *MicrocodeEntryPoint; | |
UINTN MicrocodeEnd; | |
UINTN TotalSize; | |
UINTN Count; | |
UINTN MicrocodeUsedEnd; | |
Result = GetMicrocodeRegion(&MicrocodePatchAddress, &MicrocodePatchRegionSize); | |
if (!Result) { | |
DEBUG((DEBUG_ERROR, "Fail to get Microcode Region\n")); | |
return 0; | |
} | |
DEBUG((DEBUG_INFO, "Microcode Region - 0x%lx - 0x%lx\n", MicrocodePatchAddress, MicrocodePatchRegionSize)); | |
MicrocodeUsedEnd = (UINTN)MicrocodePatchAddress; | |
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; | |
} | |
} 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); | |
MicrocodeUsedEnd = (UINTN)MicrocodeEntryPoint; | |
// | |
// Get the next patch. | |
// | |
MicrocodeEntryPoint = (CPU_MICROCODE_HEADER *)(((UINTN)MicrocodeEntryPoint) + TotalSize); | |
} while (((UINTN)MicrocodeEntryPoint < MicrocodeEnd)); | |
return MicrocodeUsedEnd - (UINTN)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; | |
} | |
/** | |
Write Microcode. | |
Caution: This function may receive untrusted input. | |
@param[in] ImageIndex The index of Microcode image. | |
@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 UINTN ImageIndex, | |
IN VOID *Image, | |
IN UINTN ImageSize, | |
OUT UINT32 *LastAttemptVersion, | |
OUT UINT32 *LastAttemptStatus, | |
OUT CHAR16 **AbortReason | |
) | |
{ | |
BOOLEAN Result; | |
EFI_STATUS Status; | |
UINT64 MicrocodePatchAddress; | |
UINT64 MicrocodePatchRegionSize; | |
CPU_MICROCODE_HEADER *CurrentMicrocodeEntryPoint; | |
UINTN CurrentTotalSize; | |
UINTN UsedRegionSize; | |
VOID *AlignedImage; | |
Result = GetMicrocodeRegion(&MicrocodePatchAddress, &MicrocodePatchRegionSize); | |
if (!Result) { | |
DEBUG((DEBUG_ERROR, "Fail to get Microcode Region\n")); | |
return EFI_NOT_FOUND; | |
} | |
CurrentTotalSize = 0; | |
CurrentMicrocodeEntryPoint = GetCurrentMicrocodeInUse(); | |
if (CurrentMicrocodeEntryPoint != NULL) { | |
if (CurrentMicrocodeEntryPoint->DataSize == 0) { | |
CurrentTotalSize = 2048; | |
} else { | |
CurrentTotalSize = CurrentMicrocodeEntryPoint->TotalSize; | |
} | |
} | |
// | |
// 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; | |
Status = VerifyMicrocode(AlignedImage, ImageSize, TRUE, LastAttemptStatus, AbortReason); | |
if (EFI_ERROR(Status)) { | |
DEBUG((DEBUG_ERROR, "Fail to verify Microcode Region\n")); | |
FreePool(AlignedImage); | |
return Status; | |
} | |
DEBUG((DEBUG_INFO, "Pass VerifyMicrocode\n")); | |
if (CurrentTotalSize < ImageSize) { | |
UsedRegionSize = GetCurrentMicrocodeUsedRegionSize(); | |
if (MicrocodePatchRegionSize - UsedRegionSize >= ImageSize) { | |
// | |
// Append | |
// | |
DEBUG((DEBUG_INFO, "Append new microcode\n")); | |
Status = UpdateMicrocode(MicrocodePatchAddress + UsedRegionSize, AlignedImage, ImageSize, LastAttemptStatus); | |
} else if (MicrocodePatchRegionSize >= ImageSize) { | |
// | |
// Ignor all others and just add this one from beginning. | |
// | |
DEBUG((DEBUG_INFO, "Add new microcode from beginning\n")); | |
Status = UpdateMicrocode(MicrocodePatchAddress, AlignedImage, ImageSize, LastAttemptStatus); | |
} else { | |
DEBUG((DEBUG_ERROR, "Microcode too big\n")); | |
*LastAttemptStatus = LAST_ATTEMPT_STATUS_ERROR_INSUFFICIENT_RESOURCES; | |
Status = EFI_OUT_OF_RESOURCES; | |
} | |
} else { | |
// | |
// Replace | |
// | |
DEBUG((DEBUG_INFO, "Replace old microcode\n")); | |
Status = UpdateMicrocode((UINTN)CurrentMicrocodeEntryPoint, AlignedImage, ImageSize, LastAttemptStatus); | |
} | |
FreePool(AlignedImage); | |
return Status; | |
} | |