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