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


