diff options
Diffstat (limited to 'src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei')
12 files changed, 3463 insertions, 0 deletions
diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/Capsule.h b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/Capsule.h new file mode 100644 index 00000000..80799157 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/Capsule.h @@ -0,0 +1,123 @@ +/** @file + +Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR> + +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef _CAPSULE_PEIM_H_ +#define _CAPSULE_PEIM_H_ + +#include <PiPei.h> +#include <Uefi/UefiSpec.h> + +#include <Ppi/Capsule.h> +#include <Ppi/LoadFile.h> +#include <Ppi/ReadOnlyVariable2.h> +#include <Guid/CapsuleVendor.h> + +#include <Library/BaseLib.h> +#include <Library/DebugLib.h> +#include <Library/PeimEntryPoint.h> +#include <Library/PeiServicesLib.h> +#include <Library/BaseMemoryLib.h> +#include <Library/HobLib.h> +#include <Library/PeiServicesTablePointerLib.h> +#include <Library/PrintLib.h> +#include <Library/PeCoffLib.h> +#include <Library/PeCoffGetEntryPointLib.h> +#include <Library/PcdLib.h> +#include <Library/ReportStatusCodeLib.h> +#include <Library/DebugAgentLib.h> +#include <Library/MemoryAllocationLib.h> +#include <IndustryStandard/PeImage.h> +#include "Common/CommonHeader.h" + +#ifdef MDE_CPU_IA32 + +#pragma pack(1) + +// +// Page-Map Level-4 Offset (PML4) and +// Page-Directory-Pointer Offset (PDPE) entries 4K & 2MB +// + +typedef union { + struct { + UINT64 Present:1; // 0 = Not present in memory, 1 = Present in memory + UINT64 ReadWrite:1; // 0 = Read-Only, 1= Read/Write + UINT64 UserSupervisor:1; // 0 = Supervisor, 1=User + UINT64 WriteThrough:1; // 0 = Write-Back caching, 1=Write-Through caching + UINT64 CacheDisabled:1; // 0 = Cached, 1=Non-Cached + UINT64 Accessed:1; // 0 = Not accessed, 1 = Accessed (set by CPU) + UINT64 Reserved:1; // Reserved + UINT64 MustBeZero:2; // Must Be Zero + UINT64 Available:3; // Available for use by system software + UINT64 PageTableBaseAddress:40; // Page Table Base Address + UINT64 AvabilableHigh:11; // Available for use by system software + UINT64 Nx:1; // No Execute bit + } Bits; + UINT64 Uint64; +} PAGE_MAP_AND_DIRECTORY_POINTER; + +// +// Page Table Entry 2MB +// +typedef union { + struct { + UINT64 Present:1; // 0 = Not present in memory, 1 = Present in memory + UINT64 ReadWrite:1; // 0 = Read-Only, 1= Read/Write + UINT64 UserSupervisor:1; // 0 = Supervisor, 1=User + UINT64 WriteThrough:1; // 0 = Write-Back caching, 1=Write-Through caching + UINT64 CacheDisabled:1; // 0 = Cached, 1=Non-Cached + UINT64 Accessed:1; // 0 = Not accessed, 1 = Accessed (set by CPU) + UINT64 Dirty:1; // 0 = Not Dirty, 1 = written by processor on access to page + UINT64 MustBe1:1; // Must be 1 + UINT64 Global:1; // 0 = Not global page, 1 = global page TLB not cleared on CR3 write + UINT64 Available:3; // Available for use by system software + UINT64 PAT:1; // + UINT64 MustBeZero:8; // Must be zero; + UINT64 PageTableBaseAddress:31; // Page Table Base Address + UINT64 AvabilableHigh:11; // Available for use by system software + UINT64 Nx:1; // 0 = Execute Code, 1 = No Code Execution + } Bits; + UINT64 Uint64; +} PAGE_TABLE_ENTRY; + +// +// Page Table Entry 1GB +// +typedef union { + struct { + UINT64 Present:1; // 0 = Not present in memory, 1 = Present in memory + UINT64 ReadWrite:1; // 0 = Read-Only, 1= Read/Write + UINT64 UserSupervisor:1; // 0 = Supervisor, 1=User + UINT64 WriteThrough:1; // 0 = Write-Back caching, 1=Write-Through caching + UINT64 CacheDisabled:1; // 0 = Cached, 1=Non-Cached + UINT64 Accessed:1; // 0 = Not accessed, 1 = Accessed (set by CPU) + UINT64 Dirty:1; // 0 = Not Dirty, 1 = written by processor on access to page + UINT64 MustBe1:1; // Must be 1 + UINT64 Global:1; // 0 = Not global page, 1 = global page TLB not cleared on CR3 write + UINT64 Available:3; // Available for use by system software + UINT64 PAT:1; // + UINT64 MustBeZero:17; // Must be zero; + UINT64 PageTableBaseAddress:22; // Page Table Base Address + UINT64 AvabilableHigh:11; // Available for use by system software + UINT64 Nx:1; // 0 = Execute Code, 1 = No Code Execution + } Bits; + UINT64 Uint64; +} PAGE_TABLE_1G_ENTRY; + +#pragma pack() + +typedef +EFI_STATUS +(*COALESCE_ENTRY) ( + SWITCH_32_TO_64_CONTEXT *EntrypointContext, + SWITCH_64_TO_32_CONTEXT *ReturnContext + ); + +#endif + +#endif diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/CapsulePei.inf b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/CapsulePei.inf new file mode 100644 index 00000000..1956568d --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/CapsulePei.inf @@ -0,0 +1,94 @@ +## @file +# Capsule update PEIM supports EFI and UEFI. +# +# Caution: This module requires additional review when modified. +# This driver will have external input - capsule image. +# This external input must be validated carefully to avoid security issue like +# buffer overflow, integer overflow. +# +# Copyright (c) 2006 - 2019, Intel Corporation. All rights reserved.<BR> +# Copyright (c) 2017, AMD Incorporated. All rights reserved.<BR> +# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = CapsulePei + MODULE_UNI_FILE = CapsulePei.uni + FILE_GUID = C779F6D8-7113-4AA1-9648-EB1633C7D53B + MODULE_TYPE = PEIM + VERSION_STRING = 1.0 + + ENTRY_POINT = CapsuleMain + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 EBC +# + +[Sources] + UefiCapsule.c + Capsule.h + Common/CapsuleCoalesce.c + Common/CommonHeader.h + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + + +[LibraryClasses] + BaseLib + HobLib + BaseMemoryLib + MemoryAllocationLib + PeiServicesLib + PeimEntryPoint + DebugLib + PeiServicesTablePointerLib + PrintLib + ReportStatusCodeLib + +[LibraryClasses.IA32] + PeCoffGetEntryPointLib + PcdLib + DebugAgentLib + +[Guids] + ## SOMETIMES_CONSUMES ## Variable:L"CapsuleUpdateData" + ## SOMETIMES_CONSUMES ## Variable:L"CapsuleLongModeBuffer" + gEfiCapsuleVendorGuid + +[Ppis] + gEfiPeiReadOnlyVariable2PpiGuid ## CONSUMES + gEfiPeiCapsulePpiGuid ## PRODUCES + +[Ppis.IA32] + gEfiPeiLoadFilePpiGuid ## SOMETIMES_CONSUMES + +[Pcd.IA32] + gEfiMdeModulePkgTokenSpaceGuid.PcdCapsuleCoalesceFile ## SOMETIMES_CONSUMES + gEfiMdeModulePkgTokenSpaceGuid.PcdUse1GPageTable ## SOMETIMES_CONSUMES + gEfiMdeModulePkgTokenSpaceGuid.PcdPteMemoryEncryptionAddressOrMask ## CONSUMES + +[FeaturePcd.IA32] + gEfiMdeModulePkgTokenSpaceGuid.PcdDxeIplSwitchToLongMode ## CONSUMES + +[Depex] + gEfiPeiReadOnlyVariable2PpiGuid + +# [BootMode] +# FLASH_UPDATE ## SOMETIMES_CONSUMES + +# [Hob.IA32] +# UNDEFINED ## SOMETIMES_CONSUMES # CPU + +# [Hob] +# UNDEFINED ## SOMETIMES_PRODUCES # UEFI_CAPSULE + + +[UserExtensions.TianoCore."ExtraFiles"] + CapsulePeiExtra.uni diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/CapsulePei.uni b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/CapsulePei.uni new file mode 100644 index 00000000..528946a9 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/CapsulePei.uni @@ -0,0 +1,19 @@ +// /** @file
+// Capsule update PEIM supports EFI and UEFI.
+//
+// Caution: This module requires additional review when modified.
+// This driver will have external input - capsule image.
+// This external input must be validated carefully to avoid security issue like
+// buffer overflow, integer overflow.
+//
+// Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR>
+//
+// SPDX-License-Identifier: BSD-2-Clause-Patent
+//
+// **/
+
+
+#string STR_MODULE_ABSTRACT #language en-US "Capsule update PEIM supporting EFI and UEFI"
+
+#string STR_MODULE_DESCRIPTION #language en-US "Capsule update PEIM supporting EFI and UEFI"
+
diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/CapsulePeiExtra.uni b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/CapsulePeiExtra.uni new file mode 100644 index 00000000..40539cfd --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/CapsulePeiExtra.uni @@ -0,0 +1,14 @@ +// /** @file
+// CapsulePei Localized Strings and Content
+//
+// Copyright (c) 2013 - 2018, Intel Corporation. All rights reserved.<BR>
+//
+// SPDX-License-Identifier: BSD-2-Clause-Patent
+//
+// **/
+
+#string STR_PROPERTIES_MODULE_NAME
+#language en-US
+"Firmware Update PEI Module"
+
+
diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/CapsuleX64.inf b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/CapsuleX64.inf new file mode 100644 index 00000000..758dae6f --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/CapsuleX64.inf @@ -0,0 +1,52 @@ +## @file +# CapsuleX64 module handles >4GB capsule blocks. +# +# The X64 entrypoint to process capsule in long mode. +# This module is built as X64. +# +# Caution: This module requires additional review when modified. +# This driver will have external input - capsule image. +# This external input must be validated carefully to avoid security issue like +# buffer overflow, integer overflow. +# +# Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR> +# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = CapsuleX64 + MODULE_UNI_FILE = CapsuleX64.uni + FILE_GUID = F7FDE4A6-294C-493c-B50F-9734553BB757 + MODULE_TYPE = PEIM + VERSION_STRING = 1.0 + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = X64 +# + +[Sources] + X64/X64Entry.c + X64/PageFaultHandler.nasm + Common/CapsuleCoalesce.c + Common/CommonHeader.h + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + +[LibraryClasses] + BaseLib + DebugLib + CpuExceptionHandlerLib + DebugAgentLib + +[Depex] + FALSE + +[UserExtensions.TianoCore."ExtraFiles"] + CapsuleX64Extra.uni diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/CapsuleX64.uni b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/CapsuleX64.uni new file mode 100644 index 00000000..8b140088 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/CapsuleX64.uni @@ -0,0 +1,23 @@ +// /** @file
+// CapsuleX64 module handles >4GB capsule blocks.
+//
+// The X64 entrypoint to process capsule in long mode.
+// This module is built as X64.
+//
+// Caution: This module requires additional review when modified.
+// This driver will have external input - capsule image.
+// This external input must be validated carefully to avoid security issue like
+// buffer overflow, integer overflow.
+//
+// Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR>
+//
+// SPDX-License-Identifier: BSD-2-Clause-Patent
+//
+// **/
+
+
+#string STR_MODULE_ABSTRACT #language en-US "Handles >4GB capsule blocks"
+
+#string STR_MODULE_DESCRIPTION #language en-US "The X64 entry point to process capsule in long mode. This module is built as X64.<BR><BR>\n"
+ "This driver will have external input - capsule image. This external input must be validated carefully to avoid security issues like buffer overflow or integer overflow.<BR>"
+
diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/CapsuleX64Extra.uni b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/CapsuleX64Extra.uni new file mode 100644 index 00000000..a6dffdd6 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/CapsuleX64Extra.uni @@ -0,0 +1,14 @@ +// /** @file
+// CapsuleX64 Localized Strings and Content
+//
+// Copyright (c) 2013 - 2018, Intel Corporation. All rights reserved.<BR>
+//
+// SPDX-License-Identifier: BSD-2-Clause-Patent
+//
+// **/
+
+#string STR_PROPERTIES_MODULE_NAME
+#language en-US
+"Firmware Update PEI Module over 4GB"
+
+
diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/Common/CapsuleCoalesce.c b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/Common/CapsuleCoalesce.c new file mode 100644 index 00000000..bec67e47 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/Common/CapsuleCoalesce.c @@ -0,0 +1,1291 @@ +/** @file + The logic to process capsule. + + Caution: This module requires additional review when modified. + This driver will have external input - capsule image. + This external input must be validated carefully to avoid security issue like + buffer overflow, integer overflow. + + CapsuleDataCoalesce() will do basic validation before coalesce capsule data + into memory. + +(C) Copyright 2014 Hewlett-Packard Development Company, L.P.<BR> +Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR> +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include <Uefi.h> +#include <PiPei.h> + +#include <Guid/CapsuleVendor.h> + +#include <Library/BaseMemoryLib.h> +#include <Library/DebugLib.h> +#include <Library/PrintLib.h> +#include <Library/BaseLib.h> + +#include "CommonHeader.h" + +#define MIN_COALESCE_ADDR (1024 * 1024) + +/** + Given a pointer to the capsule block list, info on the available system + memory, and the size of a buffer, find a free block of memory where a + buffer of the given size can be copied to safely. + + @param BlockList Pointer to head of capsule block descriptors + @param MemBase Pointer to the base of memory in which we want to find free space + @param MemSize The size of the block of memory pointed to by MemBase + @param DataSize How big a free block we want to find + + @return A pointer to a memory block of at least DataSize that lies somewhere + between MemBase and (MemBase + MemSize). The memory pointed to does not + contain any of the capsule block descriptors or capsule blocks pointed to + by the BlockList. + +**/ +UINT8 * +FindFreeMem ( + EFI_CAPSULE_BLOCK_DESCRIPTOR *BlockList, + UINT8 *MemBase, + UINTN MemSize, + UINTN DataSize + ); + +/** + The capsule block descriptors may be fragmented and spread all over memory. + To simplify the coalescing of capsule blocks, first coalesce all the + capsule block descriptors low in memory. + + The descriptors passed in can be fragmented throughout memory. Here + they are relocated into memory to turn them into a contiguous (null + terminated) array. + + @param PeiServices pointer to PEI services table + @param BlockList pointer to the capsule block descriptors + @param NumDescriptors number of capsule data block descriptors, whose Length is non-zero. + @param MemBase base of system memory in which we can work + @param MemSize size of the system memory pointed to by MemBase + + @retval NULL could not relocate the descriptors + @retval Pointer to the base of the successfully-relocated block descriptors. + +**/ +EFI_CAPSULE_BLOCK_DESCRIPTOR * +RelocateBlockDescriptors ( + IN EFI_PEI_SERVICES **PeiServices, + IN EFI_CAPSULE_BLOCK_DESCRIPTOR *BlockList, + IN UINTN NumDescriptors, + IN UINT8 *MemBase, + IN UINTN MemSize + ); + +/** + Check every capsule header. + + @param CapsuleHeader The pointer to EFI_CAPSULE_HEADER + + @retval FALSE Capsule is OK + @retval TRUE Capsule is corrupted + +**/ +BOOLEAN +IsCapsuleCorrupted ( + IN EFI_CAPSULE_HEADER *CapsuleHeader + ); + +/** + Determine if two buffers overlap in memory. + + @param Buff1 pointer to first buffer + @param Size1 size of Buff1 + @param Buff2 pointer to second buffer + @param Size2 size of Buff2 + + @retval TRUE Buffers overlap in memory. + @retval FALSE Buffer doesn't overlap. + +**/ +BOOLEAN +IsOverlapped ( + UINT8 *Buff1, + UINTN Size1, + UINT8 *Buff2, + UINTN Size2 + ); + +/** + Given a pointer to a capsule block descriptor, traverse the list to figure + out how many legitimate descriptors there are, and how big the capsule it + refers to is. + + @param Desc Pointer to the capsule block descriptors + @param NumDescriptors Optional pointer to where to return the number of capsule data descriptors, whose Length is non-zero. + @param CapsuleSize Optional pointer to where to return the capsule image size + @param CapsuleNumber Optional pointer to where to return the number of capsule + + @retval EFI_NOT_FOUND No descriptors containing data in the list + @retval EFI_SUCCESS Return data is valid + +**/ +EFI_STATUS +GetCapsuleInfo ( + IN EFI_CAPSULE_BLOCK_DESCRIPTOR *Desc, + IN OUT UINTN *NumDescriptors OPTIONAL, + IN OUT UINTN *CapsuleSize OPTIONAL, + IN OUT UINTN *CapsuleNumber OPTIONAL + ); + +/** + Given a pointer to the capsule block list, info on the available system + memory, and the size of a buffer, find a free block of memory where a + buffer of the given size can be copied to safely. + + @param BlockList Pointer to head of capsule block descriptors + @param MemBase Pointer to the base of memory in which we want to find free space + @param MemSize The size of the block of memory pointed to by MemBase + @param DataSize How big a free block we want to find + + @return A pointer to a memory block of at least DataSize that lies somewhere + between MemBase and (MemBase + MemSize). The memory pointed to does not + contain any of the capsule block descriptors or capsule blocks pointed to + by the BlockList. + +**/ +UINT8 * +FindFreeMem ( + EFI_CAPSULE_BLOCK_DESCRIPTOR *BlockList, + UINT8 *MemBase, + UINTN MemSize, + UINTN DataSize + ) +{ + UINTN Size; + EFI_CAPSULE_BLOCK_DESCRIPTOR *CurrDesc; + EFI_CAPSULE_BLOCK_DESCRIPTOR *TempDesc; + UINT8 *MemEnd; + BOOLEAN Failed; + + // + // Need at least enough to copy the data to at the end of the buffer, so + // say the end is less the data size for easy comparisons here. + // + MemEnd = MemBase + MemSize - DataSize; + CurrDesc = BlockList; + // + // Go through all the descriptor blocks and see if any obstruct the range + // + while (CurrDesc != NULL) { + // + // Get the size of this block list and see if it's in the way + // + Failed = FALSE; + TempDesc = CurrDesc; + Size = sizeof (EFI_CAPSULE_BLOCK_DESCRIPTOR); + while (TempDesc->Length != 0) { + Size += sizeof (EFI_CAPSULE_BLOCK_DESCRIPTOR); + TempDesc++; + } + + if (IsOverlapped (MemBase, DataSize, (UINT8 *) CurrDesc, Size)) { + // + // Set our new base to the end of this block list and start all over + // + MemBase = (UINT8 *) CurrDesc + Size; + CurrDesc = BlockList; + if (MemBase > MemEnd) { + return NULL; + } + + Failed = TRUE; + } + // + // Now go through all the blocks and make sure none are in the way + // + while ((CurrDesc->Length != 0) && (!Failed)) { + if (IsOverlapped (MemBase, DataSize, (UINT8 *) (UINTN) CurrDesc->Union.DataBlock, (UINTN) CurrDesc->Length)) { + // + // Set our new base to the end of this block and start all over + // + Failed = TRUE; + MemBase = (UINT8 *) ((UINTN) CurrDesc->Union.DataBlock) + CurrDesc->Length; + CurrDesc = BlockList; + if (MemBase > MemEnd) { + return NULL; + } + } + CurrDesc++; + } + // + // Normal continuation -- jump to next block descriptor list + // + if (!Failed) { + CurrDesc = (EFI_CAPSULE_BLOCK_DESCRIPTOR *) (UINTN) CurrDesc->Union.ContinuationPointer; + } + } + return MemBase; +} + +/** + Validate capsule by MemoryResource. + + @param MemoryResource Pointer to the buffer of memory resource descriptor. + @param Address Address to be validated. + @param Size Size to be validated. + + @retval TRUE No memory resource descriptor reported in HOB list before capsule Coalesce, + or it is valid in one MemoryResource. + FALSE It is not in any MemoryResource. + +**/ +BOOLEAN +ValidateCapsuleByMemoryResource ( + IN MEMORY_RESOURCE_DESCRIPTOR *MemoryResource, + IN EFI_PHYSICAL_ADDRESS Address, + IN UINT64 Size + ) +{ + UINTN Index; + + // + // Sanity Check + // + if (Size > MAX_ADDRESS) { + DEBUG ((DEBUG_ERROR, "ERROR: Size(0x%lx) > MAX_ADDRESS\n", Size)); + return FALSE; + } + + // + // Sanity Check + // + if (Address > (MAX_ADDRESS - Size)) { + DEBUG ((DEBUG_ERROR, "ERROR: Address(0x%lx) > (MAX_ADDRESS - Size(0x%lx))\n", Address, Size)); + return FALSE; + } + + if (MemoryResource == NULL) { + // + // No memory resource descriptor reported in HOB list before capsule Coalesce. + // + return TRUE; + } + + for (Index = 0; MemoryResource[Index].ResourceLength != 0; Index++) { + if ((Address >= MemoryResource[Index].PhysicalStart) && + ((Address + Size) <= (MemoryResource[Index].PhysicalStart + MemoryResource[Index].ResourceLength))) { + DEBUG ((DEBUG_INFO, "Address(0x%lx) Size(0x%lx) in MemoryResource[0x%x] - Start(0x%lx) Length(0x%lx)\n", + Address, Size, + Index, MemoryResource[Index].PhysicalStart, MemoryResource[Index].ResourceLength)); + return TRUE; + } + } + + DEBUG ((DEBUG_ERROR, "ERROR: Address(0x%lx) Size(0x%lx) not in any MemoryResource\n", Address, Size)); + return FALSE; +} + +/** + Check the integrity of the capsule descriptors. + + @param BlockList Pointer to the capsule descriptors + @param MemoryResource Pointer to the buffer of memory resource descriptor. + + @retval NULL BlockList is not valid. + @retval LastBlockDesc Last one Block in BlockList + +**/ +EFI_CAPSULE_BLOCK_DESCRIPTOR * +ValidateCapsuleIntegrity ( + IN EFI_CAPSULE_BLOCK_DESCRIPTOR *BlockList, + IN MEMORY_RESOURCE_DESCRIPTOR *MemoryResource + ) +{ + EFI_CAPSULE_HEADER *CapsuleHeader; + UINT64 CapsuleSize; + UINTN CapsuleCount; + EFI_CAPSULE_BLOCK_DESCRIPTOR *Ptr; + + DEBUG ((DEBUG_INFO, "ValidateCapsuleIntegrity\n")); + + // + // Go through the list to look for inconsistencies. Check for: + // * misaligned block descriptors. + // * The first capsule header guid + // * The first capsule header flag + // * The first capsule header HeaderSize + // * Below check will be done in ValidateCapsuleByMemoryResource() + // Length > MAX_ADDRESS + // Ptr + sizeof (EFI_CAPSULE_BLOCK_DESCRIPTOR) > MAX_ADDRESS + // DataBlock + Length > MAX_ADDRESS + // + CapsuleSize = 0; + CapsuleCount = 0; + Ptr = BlockList; + + if (!ValidateCapsuleByMemoryResource (MemoryResource, (EFI_PHYSICAL_ADDRESS) (UINTN) Ptr, sizeof (EFI_CAPSULE_BLOCK_DESCRIPTOR))) { + return NULL; + } + + DEBUG ((DEBUG_INFO, "Ptr - 0x%p\n", Ptr)); + DEBUG ((DEBUG_INFO, "Ptr->Length - 0x%lx\n", Ptr->Length)); + DEBUG ((DEBUG_INFO, "Ptr->Union - 0x%lx\n", Ptr->Union.ContinuationPointer)); + while ((Ptr->Length != 0) || (Ptr->Union.ContinuationPointer != (EFI_PHYSICAL_ADDRESS) (UINTN) NULL)) { + // + // Make sure the descriptor is aligned at UINT64 in memory + // + if ((UINTN) Ptr & (sizeof(UINT64) - 1)) { + DEBUG ((DEBUG_ERROR, "ERROR: BlockList address failed alignment check\n")); + return NULL; + } + + if (Ptr->Length == 0) { + // + // Descriptor points to another list of block descriptors somewhere + // else. + // + Ptr = (EFI_CAPSULE_BLOCK_DESCRIPTOR *) (UINTN) Ptr->Union.ContinuationPointer; + if (!ValidateCapsuleByMemoryResource (MemoryResource, (EFI_PHYSICAL_ADDRESS) (UINTN) Ptr, sizeof (EFI_CAPSULE_BLOCK_DESCRIPTOR))) { + return NULL; + } + DEBUG ((DEBUG_INFO, "Ptr(C) - 0x%p\n", Ptr)); + DEBUG ((DEBUG_INFO, "Ptr->Length - 0x%lx\n", Ptr->Length)); + DEBUG ((DEBUG_INFO, "Ptr->Union - 0x%lx\n", Ptr->Union.ContinuationPointer)); + } else { + if (!ValidateCapsuleByMemoryResource (MemoryResource, Ptr->Union.DataBlock, Ptr->Length)) { + return NULL; + } + + // + //To enhance the reliability of check-up, the first capsule's header is checked here. + //More reliabilities check-up will do later. + // + if (CapsuleSize == 0) { + // + //Move to the first capsule to check its header. + // + CapsuleHeader = (EFI_CAPSULE_HEADER*)((UINTN)Ptr->Union.DataBlock); + // + // Sanity check + // + if (Ptr->Length < sizeof(EFI_CAPSULE_HEADER)) { + DEBUG ((DEBUG_ERROR, "ERROR: Ptr->Length(0x%lx) < sizeof(EFI_CAPSULE_HEADER)\n", Ptr->Length)); + return NULL; + } + // + // Make sure HeaderSize field is valid + // + if (CapsuleHeader->HeaderSize > CapsuleHeader->CapsuleImageSize) { + DEBUG ((DEBUG_ERROR, "ERROR: CapsuleHeader->HeaderSize(0x%x) > CapsuleHeader->CapsuleImageSize(0x%x)\n", CapsuleHeader->HeaderSize, CapsuleHeader->CapsuleImageSize)); + return NULL; + } + if (IsCapsuleCorrupted (CapsuleHeader)) { + return NULL; + } + CapsuleCount ++; + CapsuleSize = CapsuleHeader->CapsuleImageSize; + } + + if (CapsuleSize >= Ptr->Length) { + CapsuleSize = CapsuleSize - Ptr->Length; + } else { + DEBUG ((DEBUG_ERROR, "ERROR: CapsuleSize(0x%lx) < Ptr->Length(0x%lx)\n", CapsuleSize, Ptr->Length)); + // + // Sanity check + // + return NULL; + } + + // + // Move to next BLOCK descriptor + // + Ptr++; + if (!ValidateCapsuleByMemoryResource (MemoryResource, (EFI_PHYSICAL_ADDRESS) (UINTN) Ptr, sizeof (EFI_CAPSULE_BLOCK_DESCRIPTOR))) { + return NULL; + } + DEBUG ((DEBUG_INFO, "Ptr(B) - 0x%p\n", Ptr)); + DEBUG ((DEBUG_INFO, "Ptr->Length - 0x%lx\n", Ptr->Length)); + DEBUG ((DEBUG_INFO, "Ptr->Union - 0x%lx\n", Ptr->Union.ContinuationPointer)); + } + } + + if (CapsuleCount == 0) { + // + // No any capsule is found in BlockList + // + DEBUG ((DEBUG_ERROR, "ERROR: CapsuleCount(0x%x) == 0\n", CapsuleCount)); + return NULL; + } + + if (CapsuleSize != 0) { + // + // Capsule data is incomplete. + // + DEBUG ((DEBUG_ERROR, "ERROR: CapsuleSize(0x%lx) != 0\n", CapsuleSize)); + return NULL; + } + + return Ptr; +} + +/** + The capsule block descriptors may be fragmented and spread all over memory. + To simplify the coalescing of capsule blocks, first coalesce all the + capsule block descriptors low in memory. + + The descriptors passed in can be fragmented throughout memory. Here + they are relocated into memory to turn them into a contiguous (null + terminated) array. + + @param PeiServices pointer to PEI services table + @param BlockList pointer to the capsule block descriptors + @param NumDescriptors number of capsule data block descriptors, whose Length is non-zero. + @param MemBase base of system memory in which we can work + @param MemSize size of the system memory pointed to by MemBase + + @retval NULL could not relocate the descriptors + @retval Pointer to the base of the successfully-relocated block descriptors. + +**/ +EFI_CAPSULE_BLOCK_DESCRIPTOR * +RelocateBlockDescriptors ( + IN EFI_PEI_SERVICES **PeiServices, + IN EFI_CAPSULE_BLOCK_DESCRIPTOR *BlockList, + IN UINTN NumDescriptors, + IN UINT8 *MemBase, + IN UINTN MemSize + ) +{ + EFI_CAPSULE_BLOCK_DESCRIPTOR *NewBlockList; + EFI_CAPSULE_BLOCK_DESCRIPTOR *CurrBlockDescHead; + EFI_CAPSULE_BLOCK_DESCRIPTOR *TempBlockDesc; + EFI_CAPSULE_BLOCK_DESCRIPTOR *PrevBlockDescTail; + UINTN BufferSize; + UINT8 *RelocBuffer; + UINTN BlockListSize; + + // + // Get the info on the blocks and descriptors. Since we're going to move + // the descriptors low in memory, adjust the base/size values accordingly here. + // NumDescriptors is the number of legit data descriptors, so add one for + // a terminator. (Already done by caller, no check is needed.) + // + + BufferSize = NumDescriptors * sizeof (EFI_CAPSULE_BLOCK_DESCRIPTOR); + NewBlockList = (EFI_CAPSULE_BLOCK_DESCRIPTOR *) MemBase; + if (MemSize < BufferSize) { + return NULL; + } + + MemSize -= BufferSize; + MemBase += BufferSize; + // + // Go through all the blocks and make sure none are in the way + // + TempBlockDesc = BlockList; + while (TempBlockDesc->Union.ContinuationPointer != (EFI_PHYSICAL_ADDRESS) (UINTN) NULL) { + if (TempBlockDesc->Length == 0) { + // + // Next block of descriptors + // + TempBlockDesc = (EFI_CAPSULE_BLOCK_DESCRIPTOR *) (UINTN) TempBlockDesc->Union.ContinuationPointer; + } else { + // + // If the capsule data pointed to by this descriptor is in the way, + // move it. + // + if (IsOverlapped ( + (UINT8 *) NewBlockList, + BufferSize, + (UINT8 *) (UINTN) TempBlockDesc->Union.DataBlock, + (UINTN) TempBlockDesc->Length + )) { + // + // Relocate the block + // + RelocBuffer = FindFreeMem (BlockList, MemBase, MemSize, (UINTN) TempBlockDesc->Length); + if (RelocBuffer == NULL) { + return NULL; + } + + CopyMem ((VOID *) RelocBuffer, (VOID *) (UINTN) TempBlockDesc->Union.DataBlock, (UINTN) TempBlockDesc->Length); + DEBUG ((DEBUG_INFO, "Capsule relocate descriptors from/to/size 0x%lX 0x%lX 0x%lX\n", TempBlockDesc->Union.DataBlock, (UINT64)(UINTN)RelocBuffer, TempBlockDesc->Length)); + TempBlockDesc->Union.DataBlock = (EFI_PHYSICAL_ADDRESS) (UINTN) RelocBuffer; + } + TempBlockDesc++; + } + } + // + // Now go through all the block descriptors to make sure that they're not + // in the memory region we want to copy them to. + // + CurrBlockDescHead = BlockList; + PrevBlockDescTail = NULL; + while ((CurrBlockDescHead != NULL) && (CurrBlockDescHead->Union.ContinuationPointer != (EFI_PHYSICAL_ADDRESS) (UINTN) NULL)) { + // + // Get the size of this list then see if it overlaps our low region + // + TempBlockDesc = CurrBlockDescHead; + BlockListSize = sizeof (EFI_CAPSULE_BLOCK_DESCRIPTOR); + while (TempBlockDesc->Length != 0) { + BlockListSize += sizeof (EFI_CAPSULE_BLOCK_DESCRIPTOR); + TempBlockDesc++; + } + + if (IsOverlapped ( + (UINT8 *) NewBlockList, + BufferSize, + (UINT8 *) CurrBlockDescHead, + BlockListSize + )) { + // + // Overlaps, so move it out of the way + // + RelocBuffer = FindFreeMem (BlockList, MemBase, MemSize, BlockListSize); + if (RelocBuffer == NULL) { + return NULL; + } + CopyMem ((VOID *) RelocBuffer, (VOID *) CurrBlockDescHead, BlockListSize); + DEBUG ((DEBUG_INFO, "Capsule reloc descriptor block #2\n")); + // + // Point the previous block's next point to this copied version. If + // the tail pointer is null, then this is the first descriptor block. + // + if (PrevBlockDescTail == NULL) { + BlockList = (EFI_CAPSULE_BLOCK_DESCRIPTOR *) RelocBuffer; + } else { + PrevBlockDescTail->Union.DataBlock = (EFI_PHYSICAL_ADDRESS) (UINTN) RelocBuffer; + } + } + // + // Save our new tail and jump to the next block list + // + PrevBlockDescTail = TempBlockDesc; + CurrBlockDescHead = (EFI_CAPSULE_BLOCK_DESCRIPTOR *) (UINTN) TempBlockDesc->Union.ContinuationPointer; + } + // + // Cleared out low memory. Now copy the descriptors down there. + // + TempBlockDesc = BlockList; + CurrBlockDescHead = NewBlockList; + while ((TempBlockDesc != NULL) && (TempBlockDesc->Union.ContinuationPointer != (EFI_PHYSICAL_ADDRESS) (UINTN) NULL)) { + if (TempBlockDesc->Length != 0) { + CurrBlockDescHead->Union.DataBlock = TempBlockDesc->Union.DataBlock; + CurrBlockDescHead->Length = TempBlockDesc->Length; + CurrBlockDescHead++; + TempBlockDesc++; + } else { + TempBlockDesc = (EFI_CAPSULE_BLOCK_DESCRIPTOR *) (UINTN) TempBlockDesc->Union.ContinuationPointer; + } + } + // + // Null terminate + // + CurrBlockDescHead->Union.ContinuationPointer = (EFI_PHYSICAL_ADDRESS) (UINTN) NULL; + CurrBlockDescHead->Length = 0; + return NewBlockList; +} + +/** + Determine if two buffers overlap in memory. + + @param Buff1 pointer to first buffer + @param Size1 size of Buff1 + @param Buff2 pointer to second buffer + @param Size2 size of Buff2 + + @retval TRUE Buffers overlap in memory. + @retval FALSE Buffer doesn't overlap. + +**/ +BOOLEAN +IsOverlapped ( + UINT8 *Buff1, + UINTN Size1, + UINT8 *Buff2, + UINTN Size2 + ) +{ + // + // If buff1's end is less than the start of buff2, then it's ok. + // Also, if buff1's start is beyond buff2's end, then it's ok. + // + if (((Buff1 + Size1) <= Buff2) || (Buff1 >= (Buff2 + Size2))) { + return FALSE; + } + + return TRUE; +} + +/** + Given a pointer to a capsule block descriptor, traverse the list to figure + out how many legitimate descriptors there are, and how big the capsule it + refers to is. + + @param Desc Pointer to the capsule block descriptors + @param NumDescriptors Optional pointer to where to return the number of capsule data descriptors, whose Length is non-zero. + @param CapsuleSize Optional pointer to where to return the capsule image size + @param CapsuleNumber Optional pointer to where to return the number of capsule + + @retval EFI_NOT_FOUND No descriptors containing data in the list + @retval EFI_SUCCESS Return data is valid + +**/ +EFI_STATUS +GetCapsuleInfo ( + IN EFI_CAPSULE_BLOCK_DESCRIPTOR *Desc, + IN OUT UINTN *NumDescriptors OPTIONAL, + IN OUT UINTN *CapsuleSize OPTIONAL, + IN OUT UINTN *CapsuleNumber OPTIONAL + ) +{ + UINTN Count; + UINTN Size; + UINTN Number; + UINTN ThisCapsuleImageSize; + EFI_CAPSULE_HEADER *CapsuleHeader; + + DEBUG ((DEBUG_INFO, "GetCapsuleInfo enter\n")); + + ASSERT (Desc != NULL); + + Count = 0; + Size = 0; + Number = 0; + ThisCapsuleImageSize = 0; + + while (Desc->Union.ContinuationPointer != (EFI_PHYSICAL_ADDRESS) (UINTN) NULL) { + if (Desc->Length == 0) { + // + // Descriptor points to another list of block descriptors somewhere + // + Desc = (EFI_CAPSULE_BLOCK_DESCRIPTOR *) (UINTN) Desc->Union.ContinuationPointer; + } else { + // + // Sanity Check + // It is needed, because ValidateCapsuleIntegrity() only validate one individual capsule Size. + // While here we need check all capsules size. + // + if (Desc->Length >= (MAX_ADDRESS - Size)) { + DEBUG ((DEBUG_ERROR, "ERROR: Desc->Length(0x%lx) >= (MAX_ADDRESS - Size(0x%x))\n", Desc->Length, Size)); + return EFI_OUT_OF_RESOURCES; + } + Size += (UINTN) Desc->Length; + Count++; + + // + // See if this is first capsule's header + // + if (ThisCapsuleImageSize == 0) { + CapsuleHeader = (EFI_CAPSULE_HEADER*)((UINTN)Desc->Union.DataBlock); + // + // This has been checked in ValidateCapsuleIntegrity() + // + Number ++; + ThisCapsuleImageSize = CapsuleHeader->CapsuleImageSize; + } + + // + // This has been checked in ValidateCapsuleIntegrity() + // + ASSERT (ThisCapsuleImageSize >= Desc->Length); + ThisCapsuleImageSize = (UINTN)(ThisCapsuleImageSize - Desc->Length); + + // + // Move to next + // + Desc++; + } + } + // + // If no descriptors, then fail + // + if (Count == 0) { + DEBUG ((DEBUG_ERROR, "ERROR: Count == 0\n")); + return EFI_NOT_FOUND; + } + + // + // checked in ValidateCapsuleIntegrity() + // + ASSERT (ThisCapsuleImageSize == 0); + + if (NumDescriptors != NULL) { + *NumDescriptors = Count; + } + + if (CapsuleSize != NULL) { + *CapsuleSize = Size; + } + + if (CapsuleNumber != NULL) { + *CapsuleNumber = Number; + } + + return EFI_SUCCESS; +} + +/** + Check every capsule header. + + @param CapsuleHeader The pointer to EFI_CAPSULE_HEADER + + @retval FALSE Capsule is OK + @retval TRUE Capsule is corrupted + +**/ +BOOLEAN +IsCapsuleCorrupted ( + IN EFI_CAPSULE_HEADER *CapsuleHeader + ) +{ + // + //A capsule to be updated across a system reset should contain CAPSULE_FLAGS_PERSIST_ACROSS_RESET. + // + if ((CapsuleHeader->Flags & CAPSULE_FLAGS_PERSIST_ACROSS_RESET) == 0) { + return TRUE; + } + // + //Make sure the flags combination is supported by the platform. + // + if ((CapsuleHeader->Flags & (CAPSULE_FLAGS_PERSIST_ACROSS_RESET | CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE)) == CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE) { + return TRUE; + } + if ((CapsuleHeader->Flags & (CAPSULE_FLAGS_PERSIST_ACROSS_RESET | CAPSULE_FLAGS_INITIATE_RESET)) == CAPSULE_FLAGS_INITIATE_RESET) { + return TRUE; + } + + return FALSE; +} + +/** + Try to verify the integrity of a capsule test pattern before the + capsule gets coalesced. This can be useful in narrowing down + where capsule data corruption occurs. + + The test pattern mode fills in memory with a counting UINT32 value. + If the capsule is not divided up in a multiple of 4-byte blocks, then + things get messy doing the check. Therefore there are some cases + here where we just give up and skip the pre-coalesce check. + + @param PeiServices PEI services table + @param Desc Pointer to capsule descriptors +**/ +VOID +CapsuleTestPatternPreCoalesce ( + IN EFI_PEI_SERVICES **PeiServices, + IN EFI_CAPSULE_BLOCK_DESCRIPTOR *Desc + ) +{ + UINT32 *TestPtr; + UINT32 TestCounter; + UINT32 TestSize; + + DEBUG ((DEBUG_INFO, "CapsuleTestPatternPreCoalesce\n")); + + // + // Find first data descriptor + // + while ((Desc->Length == 0) && (Desc->Union.ContinuationPointer != (EFI_PHYSICAL_ADDRESS) (UINTN) NULL)) { + Desc = (EFI_CAPSULE_BLOCK_DESCRIPTOR *) (UINTN) Desc->Union.ContinuationPointer; + } + + if (Desc->Union.ContinuationPointer == 0) { + return ; + } + // + // First one better be long enough to at least hold the test signature + // + if (Desc->Length < sizeof (UINT32)) { + DEBUG ((DEBUG_INFO, "Capsule test pattern pre-coalesce punted #1\n")); + return ; + } + + TestPtr = (UINT32 *) (UINTN) Desc->Union.DataBlock; + // + // 0x54534554 "TEST" + // + if (*TestPtr != 0x54534554) { + return ; + } + + TestCounter = 0; + TestSize = (UINT32) Desc->Length - 2 * sizeof (UINT32); + // + // Skip over the signature and the size fields in the pattern data header + // + TestPtr += 2; + while (1) { + if ((TestSize & 0x03) != 0) { + DEBUG ((DEBUG_INFO, "Capsule test pattern pre-coalesce punted #2\n")); + return ; + } + + while (TestSize > 0) { + if (*TestPtr != TestCounter) { + DEBUG ((DEBUG_INFO, "Capsule test pattern pre-coalesce failed data corruption check\n")); + return ; + } + + TestSize -= sizeof (UINT32); + TestCounter++; + TestPtr++; + } + Desc++; + while ((Desc->Length == 0) && (Desc->Union.ContinuationPointer != (EFI_PHYSICAL_ADDRESS) (UINTN) NULL)) { + Desc = (EFI_CAPSULE_BLOCK_DESCRIPTOR *) (UINTN) Desc->Union.ContinuationPointer; + } + + if (Desc->Union.ContinuationPointer == (EFI_PHYSICAL_ADDRESS) (UINTN) NULL) { + return ; + } + TestSize = (UINT32) Desc->Length; + TestPtr = (UINT32 *) (UINTN) Desc->Union.DataBlock; + } +} + +/** + Checks for the presence of capsule descriptors. + Get capsule descriptors from variable CapsuleUpdateData, CapsuleUpdateData1, CapsuleUpdateData2... + + @param BlockListBuffer Pointer to the buffer of capsule descriptors variables + @param MemoryResource Pointer to the buffer of memory resource descriptor. + @param BlockDescriptorList Pointer to the capsule descriptors list + + @retval EFI_SUCCESS a valid capsule is present + @retval EFI_NOT_FOUND if a valid capsule is not present +**/ +EFI_STATUS +BuildCapsuleDescriptors ( + IN EFI_PHYSICAL_ADDRESS *BlockListBuffer, + IN MEMORY_RESOURCE_DESCRIPTOR *MemoryResource, + OUT EFI_CAPSULE_BLOCK_DESCRIPTOR **BlockDescriptorList + ) +{ + UINTN Index; + EFI_CAPSULE_BLOCK_DESCRIPTOR *LastBlock; + EFI_CAPSULE_BLOCK_DESCRIPTOR *TempBlock; + EFI_CAPSULE_BLOCK_DESCRIPTOR *HeadBlock; + + DEBUG ((DEBUG_INFO, "BuildCapsuleDescriptors enter\n")); + + LastBlock = NULL; + HeadBlock = NULL; + TempBlock = NULL; + Index = 0; + + while (BlockListBuffer[Index] != 0) { + // + // Test integrity of descriptors. + // + if (BlockListBuffer[Index] < MAX_ADDRESS) { + TempBlock = ValidateCapsuleIntegrity ((EFI_CAPSULE_BLOCK_DESCRIPTOR *)(UINTN)BlockListBuffer[Index], MemoryResource); + if (TempBlock != NULL) { + if (LastBlock == NULL) { + LastBlock = TempBlock; + + // + // Return the base of the block descriptors + // + HeadBlock = (EFI_CAPSULE_BLOCK_DESCRIPTOR *)(UINTN)BlockListBuffer[Index]; + } else { + // + // Combine the different BlockList into single BlockList. + // + LastBlock->Union.DataBlock = (EFI_PHYSICAL_ADDRESS)(UINTN)BlockListBuffer[Index]; + LastBlock->Length = 0; + LastBlock = TempBlock; + } + } + } else { + DEBUG ((DEBUG_ERROR, "ERROR: BlockListBuffer[Index](0x%lx) < MAX_ADDRESS\n", BlockListBuffer[Index])); + } + Index ++; + } + + if (HeadBlock != NULL) { + *BlockDescriptorList = HeadBlock; + return EFI_SUCCESS; + } + return EFI_NOT_FOUND; +} + +/** + The function to coalesce a fragmented capsule in memory. + + Memory Map for coalesced capsule: + MemBase + ---->+---------------------------+<-----------+ + MemSize | ------------------------- | | + | | Capsule [Num-1] | | | + | ------------------------- | | + | | ................ | | | + | ------------------------- | | + | | Capsule [1] | | | + | ------------------------- | | + | | Capsule [0] | | | + | ------------------------- | | + | Capsule Image | | +CapsuleImageBase-->+---------------------------+ + | ------------------------- | | + | | CapsuleOffset[Num-1] | | | + | ------------------------- | | + | | ................ | | CapsuleSize + | ------------------------- | | + | | CapsuleOffset[1] | | | + | ------------------------- | | + | | CapsuleOffset[0] | | | + |---------------------------| | + | | CapsuleNumber | | | + | ------------------------- | | + | | CapsuleAllImageSize | | | + | ------------------------- | | + | PrivateData | | + DestPtr ---->+---------------------------+<-----------+ + | | | + | FreeMem | FreeMemSize + | | | + FreeMemBase --->+---------------------------+<-----------+ + | Terminator | + +---------------------------+ + | BlockDescriptor n | + +---------------------------+ + | ................. | + +---------------------------+ + | BlockDescriptor 1 | + +---------------------------+ + | BlockDescriptor 0 | + +---------------------------+ + | PrivateDataDesc 0 | + MemBase ---->+---------------------------+<----- BlockList + + Caution: This function may receive untrusted input. + The capsule data is external input, so this routine will do basic validation before + coalesce capsule data into memory. + + @param PeiServices General purpose services available to every PEIM. + @param BlockListBuffer Pointer to the buffer of Capsule Descriptor Variables. + @param MemoryResource Pointer to the buffer of memory resource descriptor. + @param MemoryBase Pointer to the base of a block of memory that we can walk + all over while trying to coalesce our buffers. + On output, this variable will hold the base address of + a coalesced capsule. + @param MemorySize Size of the memory region pointed to by MemoryBase. + On output, this variable will contain the size of the + coalesced capsule. + + @retval EFI_NOT_FOUND If we could not find the capsule descriptors. + + @retval EFI_BUFFER_TOO_SMALL + If we could not coalesce the capsule in the memory + region provided to us. + + @retval EFI_SUCCESS Processed the capsule successfully. +**/ +EFI_STATUS +EFIAPI +CapsuleDataCoalesce ( + IN EFI_PEI_SERVICES **PeiServices, + IN EFI_PHYSICAL_ADDRESS *BlockListBuffer, + IN MEMORY_RESOURCE_DESCRIPTOR *MemoryResource, + IN OUT VOID **MemoryBase, + IN OUT UINTN *MemorySize + ) +{ + VOID *NewCapsuleBase; + VOID *CapsuleImageBase; + UINTN CapsuleIndex; + UINT8 *FreeMemBase; + UINT8 *DestPtr; + UINTN DestLength; + UINT8 *RelocPtr; + UINTN CapsuleTimes; + UINT64 SizeLeft; + UINT64 CapsuleImageSize; + UINTN CapsuleSize; + UINTN CapsuleNumber; + UINTN DescriptorsSize; + UINTN FreeMemSize; + UINTN NumDescriptors; + BOOLEAN CapsuleBeginFlag; + EFI_STATUS Status; + EFI_CAPSULE_HEADER *CapsuleHeader; + EFI_CAPSULE_PEIM_PRIVATE_DATA PrivateData; + EFI_CAPSULE_PEIM_PRIVATE_DATA *PrivateDataPtr; + EFI_CAPSULE_BLOCK_DESCRIPTOR *BlockList; + EFI_CAPSULE_BLOCK_DESCRIPTOR *CurrentBlockDesc; + EFI_CAPSULE_BLOCK_DESCRIPTOR *TempBlockDesc; + EFI_CAPSULE_BLOCK_DESCRIPTOR PrivateDataDesc[2]; + + DEBUG ((DEBUG_INFO, "CapsuleDataCoalesce enter\n")); + + CapsuleIndex = 0; + SizeLeft = 0; + CapsuleTimes = 0; + CapsuleImageSize = 0; + PrivateDataPtr = NULL; + CapsuleHeader = NULL; + CapsuleBeginFlag = TRUE; + CapsuleSize = 0; + NumDescriptors = 0; + + // + // Build capsule descriptors list + // + Status = BuildCapsuleDescriptors (BlockListBuffer, MemoryResource, &BlockList); + if (EFI_ERROR (Status)) { + return Status; + } + + DEBUG_CODE ( + CapsuleTestPatternPreCoalesce (PeiServices, BlockList); + ); + + // + // Get the size of our descriptors and the capsule size. GetCapsuleInfo() + // returns the number of descriptors that actually point to data, so add + // one for a terminator. Do that below. + // + Status = GetCapsuleInfo (BlockList, &NumDescriptors, &CapsuleSize, &CapsuleNumber); + if (EFI_ERROR (Status)) { + return Status; + } + DEBUG ((DEBUG_INFO, "CapsuleSize - 0x%x\n", CapsuleSize)); + DEBUG ((DEBUG_INFO, "CapsuleNumber - 0x%x\n", CapsuleNumber)); + DEBUG ((DEBUG_INFO, "NumDescriptors - 0x%x\n", NumDescriptors)); + if ((CapsuleSize == 0) || (NumDescriptors == 0) || (CapsuleNumber == 0)) { + return EFI_NOT_FOUND; + } + + if (CapsuleNumber - 1 >= (MAX_ADDRESS - (sizeof (EFI_CAPSULE_PEIM_PRIVATE_DATA) + sizeof(UINT64))) / sizeof(UINT64)) { + DEBUG ((DEBUG_ERROR, "ERROR: CapsuleNumber - 0x%x\n", CapsuleNumber)); + return EFI_BUFFER_TOO_SMALL; + } + + // + // Initialize our local copy of private data. When we're done, we'll create a + // descriptor for it as well so that it can be put into free memory without + // trashing anything. + // + PrivateData.Signature = EFI_CAPSULE_PEIM_PRIVATE_DATA_SIGNATURE; + PrivateData.CapsuleAllImageSize = (UINT64) CapsuleSize; + PrivateData.CapsuleNumber = (UINT64) CapsuleNumber; + PrivateData.CapsuleOffset[0] = 0; + // + // NOTE: Only data in sizeof (EFI_CAPSULE_PEIM_PRIVATE_DATA) is valid, CapsuleOffset field is uninitialized at this moment. + // The code sets partial length here for Descriptor.Length check, but later it will use full length to reserve those PrivateData region. + // + PrivateDataDesc[0].Union.DataBlock = (EFI_PHYSICAL_ADDRESS) (UINTN) &PrivateData; + PrivateDataDesc[0].Length = sizeof (EFI_CAPSULE_PEIM_PRIVATE_DATA); + PrivateDataDesc[1].Union.DataBlock = (EFI_PHYSICAL_ADDRESS) (UINTN) BlockList; + PrivateDataDesc[1].Length = 0; + // + // Add PrivateDataDesc[0] in beginning, as it is new descriptor. PrivateDataDesc[1] is NOT needed. + // In addition, one NULL terminator is added in the end. See RelocateBlockDescriptors(). + // + NumDescriptors += 2; + // + // Sanity check + // + if (CapsuleSize >= (MAX_ADDRESS - (sizeof (EFI_CAPSULE_PEIM_PRIVATE_DATA) + (CapsuleNumber - 1) * sizeof(UINT64) + sizeof(UINT64)))) { + DEBUG ((DEBUG_ERROR, "ERROR: CapsuleSize - 0x%x\n", CapsuleSize)); + return EFI_BUFFER_TOO_SMALL; + } + // + // Need add sizeof(UINT64) for PrivateData alignment + // + CapsuleSize += sizeof (EFI_CAPSULE_PEIM_PRIVATE_DATA) + (CapsuleNumber - 1) * sizeof(UINT64) + sizeof(UINT64); + BlockList = PrivateDataDesc; + // + // Sanity check + // + if (NumDescriptors >= (MAX_ADDRESS / sizeof(EFI_CAPSULE_BLOCK_DESCRIPTOR))) { + DEBUG ((DEBUG_ERROR, "ERROR: NumDescriptors - 0x%x\n", NumDescriptors)); + return EFI_BUFFER_TOO_SMALL; + } + DescriptorsSize = NumDescriptors * sizeof (EFI_CAPSULE_BLOCK_DESCRIPTOR); + // + // Sanity check + // + if (DescriptorsSize >= (MAX_ADDRESS - CapsuleSize)) { + DEBUG ((DEBUG_ERROR, "ERROR: DescriptorsSize - 0x%lx, CapsuleSize - 0x%lx\n", (UINT64)DescriptorsSize, (UINT64)CapsuleSize)); + return EFI_BUFFER_TOO_SMALL; + } + + // + // Don't go below some min address. If the base is below it, + // then move it up and adjust the size accordingly. + // + DEBUG ((DEBUG_INFO, "Capsule Memory range from 0x%8X to 0x%8X\n", (UINTN) *MemoryBase, (UINTN)*MemoryBase + *MemorySize)); + if ((UINTN)*MemoryBase < (UINTN) MIN_COALESCE_ADDR) { + if (((UINTN)*MemoryBase + *MemorySize) < (UINTN) MIN_COALESCE_ADDR) { + DEBUG ((DEBUG_ERROR, "ERROR: *MemoryBase + *MemorySize - 0x%x\n", (UINTN)*MemoryBase + *MemorySize)); + return EFI_BUFFER_TOO_SMALL; + } else { + *MemorySize = *MemorySize - ((UINTN) MIN_COALESCE_ADDR - (UINTN) *MemoryBase); + *MemoryBase = (VOID *) (UINTN) MIN_COALESCE_ADDR; + } + } + + if (*MemorySize <= (CapsuleSize + DescriptorsSize)) { + DEBUG ((DEBUG_ERROR, "ERROR: CapsuleSize + DescriptorsSize - 0x%x\n", CapsuleSize + DescriptorsSize)); + return EFI_BUFFER_TOO_SMALL; + } + + FreeMemBase = *MemoryBase; + FreeMemSize = *MemorySize; + DEBUG ((DEBUG_INFO, "Capsule Free Memory from 0x%8X to 0x%8X\n", (UINTN) FreeMemBase, (UINTN) FreeMemBase + FreeMemSize)); + + // + // Relocate all the block descriptors to low memory to make further + // processing easier. + // + BlockList = RelocateBlockDescriptors (PeiServices, BlockList, NumDescriptors, FreeMemBase, FreeMemSize); + if (BlockList == NULL) { + // + // Not enough room to relocate the descriptors + // + return EFI_BUFFER_TOO_SMALL; + } + + // + // Take the top of memory for the capsule. UINT64 align up. + // + DestPtr = FreeMemBase + FreeMemSize - CapsuleSize; + DestPtr = (UINT8 *) (((UINTN)DestPtr + sizeof (UINT64) - 1) & ~(sizeof (UINT64) - 1)); + FreeMemBase = (UINT8 *) BlockList + DescriptorsSize; + FreeMemSize = (UINTN) DestPtr - (UINTN) FreeMemBase; + NewCapsuleBase = (VOID *) DestPtr; + CapsuleImageBase = (UINT8 *)NewCapsuleBase + sizeof(EFI_CAPSULE_PEIM_PRIVATE_DATA) + (CapsuleNumber - 1) * sizeof(UINT64); + + PrivateDataPtr = (EFI_CAPSULE_PEIM_PRIVATE_DATA *) NewCapsuleBase; + + // + // Move all the blocks to the top (high) of memory. + // Relocate all the obstructing blocks. Note that the block descriptors + // were coalesced when they were relocated, so we can just ++ the pointer. + // + CurrentBlockDesc = BlockList; + while ((CurrentBlockDesc->Length != 0) || (CurrentBlockDesc->Union.ContinuationPointer != (EFI_PHYSICAL_ADDRESS) (UINTN) NULL)) { + if (CapsuleTimes == 0) { + // + // The first entry is the block descriptor for EFI_CAPSULE_PEIM_PRIVATE_DATA. + // CapsuleOffset field is uninitialized at this time. No need copy it, but need to reserve for future use. + // + ASSERT (CurrentBlockDesc->Union.DataBlock == (UINT64)(UINTN)&PrivateData); + DestLength = sizeof (EFI_CAPSULE_PEIM_PRIVATE_DATA) + (CapsuleNumber - 1) * sizeof(UINT64); + } else { + DestLength = (UINTN)CurrentBlockDesc->Length; + } + // + // See if any of the remaining capsule blocks are in the way + // + TempBlockDesc = CurrentBlockDesc; + while (TempBlockDesc->Length != 0) { + // + // Is this block in the way of where we want to copy the current descriptor to? + // + if (IsOverlapped ( + (UINT8 *) DestPtr, + (UINTN) DestLength, + (UINT8 *) (UINTN) TempBlockDesc->Union.DataBlock, + (UINTN) TempBlockDesc->Length + )) { + // + // Relocate the block + // + RelocPtr = FindFreeMem (BlockList, FreeMemBase, FreeMemSize, (UINTN) TempBlockDesc->Length); + if (RelocPtr == NULL) { + return EFI_BUFFER_TOO_SMALL; + } + + CopyMem ((VOID *) RelocPtr, (VOID *) (UINTN) TempBlockDesc->Union.DataBlock, (UINTN) TempBlockDesc->Length); + DEBUG ((DEBUG_INFO, "Capsule reloc data block from 0x%8X to 0x%8X with size 0x%8X\n", + (UINTN) TempBlockDesc->Union.DataBlock, (UINTN) RelocPtr, (UINTN) TempBlockDesc->Length)); + + TempBlockDesc->Union.DataBlock = (EFI_PHYSICAL_ADDRESS) (UINTN) RelocPtr; + } + // + // Next descriptor + // + TempBlockDesc++; + } + // + // Ok, we made it through. Copy the block. + // we just support greping one capsule from the lists of block descs list. + // + CapsuleTimes ++; + // + //Skip the first block descriptor that filled with EFI_CAPSULE_PEIM_PRIVATE_DATA + // + if (CapsuleTimes > 1) { + // + //For every capsule entry point, check its header to determine whether to relocate it. + //If it is invalid, skip it and move on to the next capsule. If it is valid, relocate it. + // + if (CapsuleBeginFlag) { + CapsuleBeginFlag = FALSE; + CapsuleHeader = (EFI_CAPSULE_HEADER*)(UINTN)CurrentBlockDesc->Union.DataBlock; + SizeLeft = CapsuleHeader->CapsuleImageSize; + + // + // No more check here is needed, because IsCapsuleCorrupted() already in ValidateCapsuleIntegrity() + // + ASSERT (CapsuleIndex < CapsuleNumber); + + // + // Relocate this capsule + // + CapsuleImageSize += SizeLeft; + // + // Cache the begin offset of this capsule + // + ASSERT (PrivateDataPtr->Signature == EFI_CAPSULE_PEIM_PRIVATE_DATA_SIGNATURE); + ASSERT ((UINTN)DestPtr >= (UINTN)CapsuleImageBase); + PrivateDataPtr->CapsuleOffset[CapsuleIndex++] = (UINTN)DestPtr - (UINTN)CapsuleImageBase; + } + + // + // Below ASSERT is checked in ValidateCapsuleIntegrity() + // + ASSERT (CurrentBlockDesc->Length <= SizeLeft); + + CopyMem ((VOID *) DestPtr, (VOID *) (UINTN) (CurrentBlockDesc->Union.DataBlock), (UINTN)CurrentBlockDesc->Length); + DEBUG ((DEBUG_INFO, "Capsule coalesce block no.0x%lX from 0x%lX to 0x%lX with size 0x%lX\n",(UINT64)CapsuleTimes, + CurrentBlockDesc->Union.DataBlock, (UINT64)(UINTN)DestPtr, CurrentBlockDesc->Length)); + DestPtr += CurrentBlockDesc->Length; + SizeLeft -= CurrentBlockDesc->Length; + + if (SizeLeft == 0) { + // + //Here is the end of the current capsule image. + // + CapsuleBeginFlag = TRUE; + } + } else { + // + // The first entry is the block descriptor for EFI_CAPSULE_PEIM_PRIVATE_DATA. + // CapsuleOffset field is uninitialized at this time. No need copy it, but need to reserve for future use. + // + ASSERT (CurrentBlockDesc->Length == sizeof (EFI_CAPSULE_PEIM_PRIVATE_DATA)); + ASSERT ((UINTN)DestPtr == (UINTN)NewCapsuleBase); + CopyMem ((VOID *) DestPtr, (VOID *) (UINTN) CurrentBlockDesc->Union.DataBlock, (UINTN) CurrentBlockDesc->Length); + DestPtr += sizeof (EFI_CAPSULE_PEIM_PRIVATE_DATA) + (CapsuleNumber - 1) * sizeof(UINT64); + } + // + //Walk through the block descriptor list. + // + CurrentBlockDesc++; + } + // + // We return the base of memory we want reserved, and the size. + // The memory peim should handle it appropriately from there. + // + *MemorySize = (UINTN) CapsuleSize; + *MemoryBase = (VOID *) NewCapsuleBase; + + ASSERT (PrivateDataPtr->Signature == EFI_CAPSULE_PEIM_PRIVATE_DATA_SIGNATURE); + ASSERT (PrivateDataPtr->CapsuleAllImageSize == CapsuleImageSize); + ASSERT (PrivateDataPtr->CapsuleNumber == CapsuleIndex); + + return EFI_SUCCESS; +} diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/Common/CommonHeader.h b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/Common/CommonHeader.h new file mode 100644 index 00000000..6ae67cb1 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/Common/CommonHeader.h @@ -0,0 +1,115 @@ +/** @file + Common header file. + +Copyright (c) 2011 - 2016, Intel Corporation. All rights reserved.<BR> +Copyright (c) 2017, AMD Incorporated. All rights reserved.<BR> + +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef _CAPSULE_COMMON_HEADER_ +#define _CAPSULE_COMMON_HEADER_ + +// +// 8 extra pages for PF handler. +// +#define EXTRA_PAGE_TABLE_PAGES 8 + +#define PAGING_1G_ADDRESS_MASK_64 0x000FFFFFC0000000ull + +// +// This capsule PEIM puts its private data at the start of the +// coalesced capsule. Here's the structure definition. +// +#define EFI_CAPSULE_PEIM_PRIVATE_DATA_SIGNATURE SIGNATURE_32 ('C', 'a', 'p', 'P') + +#pragma pack(1) +typedef struct { + UINT64 Signature; + UINT64 CapsuleAllImageSize; + UINT64 CapsuleNumber; + UINT64 CapsuleOffset[1]; +} EFI_CAPSULE_PEIM_PRIVATE_DATA; +#pragma pack() + +typedef struct { + /// + /// The physical start address of the resource region. + /// + EFI_PHYSICAL_ADDRESS PhysicalStart; + /// + /// The number of bytes of the resource region. + /// + UINT64 ResourceLength; +} MEMORY_RESOURCE_DESCRIPTOR; + +#define CAPSULE_TEST_SIGNATURE SIGNATURE_32('T', 'E', 'S', 'T') + +#if defined (MDE_CPU_IA32) || defined (MDE_CPU_X64) +#pragma pack(1) +typedef struct { + EFI_PHYSICAL_ADDRESS EntryPoint; + EFI_PHYSICAL_ADDRESS StackBufferBase; + UINT64 StackBufferLength; + EFI_PHYSICAL_ADDRESS JumpBuffer; + EFI_PHYSICAL_ADDRESS BlockListAddr; + EFI_PHYSICAL_ADDRESS MemoryResource; + EFI_PHYSICAL_ADDRESS MemoryBase64Ptr; + EFI_PHYSICAL_ADDRESS MemorySize64Ptr; + BOOLEAN Page1GSupport; + UINT64 AddressEncMask; +} SWITCH_32_TO_64_CONTEXT; + +typedef struct { + UINT16 ReturnCs; + EFI_PHYSICAL_ADDRESS ReturnEntryPoint; + UINT64 ReturnStatus; + // + // NOTICE: + // Be careful about the Base field of IA32_DESCRIPTOR + // that is UINTN type. + // To extend new field for this structure, add it to + // right before this Gdtr field. + // + IA32_DESCRIPTOR Gdtr; +} SWITCH_64_TO_32_CONTEXT; +#pragma pack() +#endif + +/** + The function to coalesce a fragmented capsule in memory. + + @param PeiServices General purpose services available to every PEIM. + @param BlockListBuffer Point to the buffer of Capsule Descriptor Variables. + @param MemoryResource Pointer to the buffer of memory resource descriptor. + @param MemoryBase Pointer to the base of a block of memory that we can walk + all over while trying to coalesce our buffers. + On output, this variable will hold the base address of + a coalesced capsule. + @param MemorySize Size of the memory region pointed to by MemoryBase. + On output, this variable will contain the size of the + coalesced capsule. + + @retval EFI_NOT_FOUND if we can't determine the boot mode + if the boot mode is not flash-update + if we could not find the capsule descriptors + + @retval EFI_BUFFER_TOO_SMALL + if we could not coalesce the capsule in the memory + region provided to us + + @retval EFI_SUCCESS if there's no capsule, or if we processed the + capsule successfully. +**/ +EFI_STATUS +EFIAPI +CapsuleDataCoalesce ( + IN EFI_PEI_SERVICES **PeiServices, + IN EFI_PHYSICAL_ADDRESS *BlockListBuffer, + IN MEMORY_RESOURCE_DESCRIPTOR *MemoryResource, + IN OUT VOID **MemoryBase, + IN OUT UINTN *MemorySize + ); + +#endif diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/UefiCapsule.c b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/UefiCapsule.c new file mode 100644 index 00000000..cae4bf37 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/UefiCapsule.c @@ -0,0 +1,1332 @@ +/** @file + Capsule update PEIM for UEFI2.0 + +Copyright (c) 2006 - 2019, Intel Corporation. All rights reserved.<BR> +Copyright (c) 2017, AMD Incorporated. All rights reserved.<BR> + +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "Capsule.h" + +#define DEFAULT_SG_LIST_HEADS (20) + +#ifdef MDE_CPU_IA32 +// +// Global Descriptor Table (GDT) +// +GLOBAL_REMOVE_IF_UNREFERENCED IA32_SEGMENT_DESCRIPTOR mGdtEntries[] = { +/* selector { Global Segment Descriptor } */ +/* 0x00 */ {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, //null descriptor +/* 0x08 */ {{0xffff, 0, 0, 0x3, 1, 0, 1, 0xf, 0, 0, 1, 1, 0}}, //linear data segment descriptor +/* 0x10 */ {{0xffff, 0, 0, 0xf, 1, 0, 1, 0xf, 0, 0, 1, 1, 0}}, //linear code segment descriptor +/* 0x18 */ {{0xffff, 0, 0, 0x3, 1, 0, 1, 0xf, 0, 0, 1, 1, 0}}, //system data segment descriptor +/* 0x20 */ {{0xffff, 0, 0, 0xb, 1, 0, 1, 0xf, 0, 0, 1, 1, 0}}, //system code segment descriptor +/* 0x28 */ {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, //spare segment descriptor +/* 0x30 */ {{0xffff, 0, 0, 0x3, 1, 0, 1, 0xf, 0, 0, 1, 1, 0}}, //system data segment descriptor +/* 0x38 */ {{0xffff, 0, 0, 0xb, 1, 0, 1, 0xf, 0, 1, 0, 1, 0}}, //system code segment descriptor +/* 0x40 */ {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, //spare segment descriptor +}; + +// +// IA32 Gdt register +// +GLOBAL_REMOVE_IF_UNREFERENCED CONST IA32_DESCRIPTOR mGdt = { + sizeof (mGdtEntries) - 1, + (UINTN) mGdtEntries + }; + + +/** + The function will check if 1G page is supported. + + @retval TRUE 1G page is supported. + @retval FALSE 1G page is not supported. + +**/ +BOOLEAN +IsPage1GSupport ( + VOID + ) +{ + UINT32 RegEax; + UINT32 RegEdx; + BOOLEAN Page1GSupport; + + Page1GSupport = FALSE; + if (PcdGetBool(PcdUse1GPageTable)) { + AsmCpuid (0x80000000, &RegEax, NULL, NULL, NULL); + if (RegEax >= 0x80000001) { + AsmCpuid (0x80000001, NULL, NULL, NULL, &RegEdx); + if ((RegEdx & BIT26) != 0) { + Page1GSupport = TRUE; + } + } + } + + return Page1GSupport; +} + +/** + Calculate the total size of page table. + + @param[in] Page1GSupport 1G page support or not. + + @return The size of page table. + +**/ +UINTN +CalculatePageTableSize ( + IN BOOLEAN Page1GSupport + ) +{ + UINTN ExtraPageTablePages; + UINTN TotalPagesNum; + UINT8 PhysicalAddressBits; + UINT32 NumberOfPml4EntriesNeeded; + UINT32 NumberOfPdpEntriesNeeded; + + // + // Create 4G page table by default, + // and let PF handler to handle > 4G request. + // + PhysicalAddressBits = 32; + ExtraPageTablePages = EXTRA_PAGE_TABLE_PAGES; + + // + // Calculate the table entries needed. + // + if (PhysicalAddressBits <= 39 ) { + NumberOfPml4EntriesNeeded = 1; + NumberOfPdpEntriesNeeded = (UINT32)LShiftU64 (1, (PhysicalAddressBits - 30)); + } else { + NumberOfPml4EntriesNeeded = (UINT32)LShiftU64 (1, (PhysicalAddressBits - 39)); + NumberOfPdpEntriesNeeded = 512; + } + + if (!Page1GSupport) { + TotalPagesNum = (NumberOfPdpEntriesNeeded + 1) * NumberOfPml4EntriesNeeded + 1; + } else { + TotalPagesNum = NumberOfPml4EntriesNeeded + 1; + } + TotalPagesNum += ExtraPageTablePages; + + return EFI_PAGES_TO_SIZE (TotalPagesNum); +} + +/** + Allocates and fills in the Page Directory and Page Table Entries to + establish a 4G page table. + + @param[in] PageTablesAddress The base address of page table. + @param[in] Page1GSupport 1G page support or not. + +**/ +VOID +Create4GPageTables ( + IN EFI_PHYSICAL_ADDRESS PageTablesAddress, + IN BOOLEAN Page1GSupport + ) +{ + UINT8 PhysicalAddressBits; + EFI_PHYSICAL_ADDRESS PageAddress; + UINTN IndexOfPml4Entries; + UINTN IndexOfPdpEntries; + UINTN IndexOfPageDirectoryEntries; + UINT32 NumberOfPml4EntriesNeeded; + UINT32 NumberOfPdpEntriesNeeded; + PAGE_MAP_AND_DIRECTORY_POINTER *PageMapLevel4Entry; + PAGE_MAP_AND_DIRECTORY_POINTER *PageMap; + PAGE_MAP_AND_DIRECTORY_POINTER *PageDirectoryPointerEntry; + PAGE_TABLE_ENTRY *PageDirectoryEntry; + UINTN BigPageAddress; + PAGE_TABLE_1G_ENTRY *PageDirectory1GEntry; + UINT64 AddressEncMask; + + // + // Make sure AddressEncMask is contained to smallest supported address field. + // + AddressEncMask = PcdGet64 (PcdPteMemoryEncryptionAddressOrMask) & PAGING_1G_ADDRESS_MASK_64; + + // + // Create 4G page table by default, + // and let PF handler to handle > 4G request. + // + PhysicalAddressBits = 32; + + // + // Calculate the table entries needed. + // + if (PhysicalAddressBits <= 39 ) { + NumberOfPml4EntriesNeeded = 1; + NumberOfPdpEntriesNeeded = (UINT32)LShiftU64 (1, (PhysicalAddressBits - 30)); + } else { + NumberOfPml4EntriesNeeded = (UINT32)LShiftU64 (1, (PhysicalAddressBits - 39)); + NumberOfPdpEntriesNeeded = 512; + } + + // + // Pre-allocate big pages to avoid later allocations. + // + BigPageAddress = (UINTN) PageTablesAddress; + + // + // By architecture only one PageMapLevel4 exists - so lets allocate storage for it. + // + PageMap = (VOID *) BigPageAddress; + BigPageAddress += SIZE_4KB; + + PageMapLevel4Entry = PageMap; + PageAddress = 0; + for (IndexOfPml4Entries = 0; IndexOfPml4Entries < NumberOfPml4EntriesNeeded; IndexOfPml4Entries++, PageMapLevel4Entry++) { + // + // Each PML4 entry points to a page of Page Directory Pointer entires. + // So lets allocate space for them and fill them in in the IndexOfPdpEntries loop. + // + PageDirectoryPointerEntry = (VOID *) BigPageAddress; + BigPageAddress += SIZE_4KB; + + // + // Make a PML4 Entry + // + PageMapLevel4Entry->Uint64 = (UINT64)(UINTN)PageDirectoryPointerEntry | AddressEncMask; + PageMapLevel4Entry->Bits.ReadWrite = 1; + PageMapLevel4Entry->Bits.Present = 1; + + if (Page1GSupport) { + PageDirectory1GEntry = (VOID *) PageDirectoryPointerEntry; + + for (IndexOfPageDirectoryEntries = 0; IndexOfPageDirectoryEntries < 512; IndexOfPageDirectoryEntries++, PageDirectory1GEntry++, PageAddress += SIZE_1GB) { + // + // Fill in the Page Directory entries + // + PageDirectory1GEntry->Uint64 = (UINT64)PageAddress | AddressEncMask; + PageDirectory1GEntry->Bits.ReadWrite = 1; + PageDirectory1GEntry->Bits.Present = 1; + PageDirectory1GEntry->Bits.MustBe1 = 1; + } + } else { + for (IndexOfPdpEntries = 0; IndexOfPdpEntries < NumberOfPdpEntriesNeeded; IndexOfPdpEntries++, PageDirectoryPointerEntry++) { + // + // Each Directory Pointer entries points to a page of Page Directory entires. + // So allocate space for them and fill them in in the IndexOfPageDirectoryEntries loop. + // + PageDirectoryEntry = (VOID *) BigPageAddress; + BigPageAddress += SIZE_4KB; + + // + // Fill in a Page Directory Pointer Entries + // + PageDirectoryPointerEntry->Uint64 = (UINT64)(UINTN)PageDirectoryEntry | AddressEncMask; + PageDirectoryPointerEntry->Bits.ReadWrite = 1; + PageDirectoryPointerEntry->Bits.Present = 1; + + for (IndexOfPageDirectoryEntries = 0; IndexOfPageDirectoryEntries < 512; IndexOfPageDirectoryEntries++, PageDirectoryEntry++, PageAddress += SIZE_2MB) { + // + // Fill in the Page Directory entries + // + PageDirectoryEntry->Uint64 = (UINT64)PageAddress | AddressEncMask; + PageDirectoryEntry->Bits.ReadWrite = 1; + PageDirectoryEntry->Bits.Present = 1; + PageDirectoryEntry->Bits.MustBe1 = 1; + } + } + + for (; IndexOfPdpEntries < 512; IndexOfPdpEntries++, PageDirectoryPointerEntry++) { + ZeroMem ( + PageDirectoryPointerEntry, + sizeof(PAGE_MAP_AND_DIRECTORY_POINTER) + ); + } + } + } + + // + // For the PML4 entries we are not using fill in a null entry. + // + for (; IndexOfPml4Entries < 512; IndexOfPml4Entries++, PageMapLevel4Entry++) { + ZeroMem ( + PageMapLevel4Entry, + sizeof (PAGE_MAP_AND_DIRECTORY_POINTER) + ); + } +} + +/** + Return function from long mode to 32-bit mode. + + @param EntrypointContext Context for mode switching + @param ReturnContext Context for mode switching + +**/ +VOID +ReturnFunction ( + SWITCH_32_TO_64_CONTEXT *EntrypointContext, + SWITCH_64_TO_32_CONTEXT *ReturnContext + ) +{ + // + // Restore original GDT + // + AsmWriteGdtr (&ReturnContext->Gdtr); + + // + // return to original caller + // + LongJump ((BASE_LIBRARY_JUMP_BUFFER *)(UINTN)EntrypointContext->JumpBuffer, 1); + + // + // never be here + // + ASSERT (FALSE); +} + +/** + Thunk function from 32-bit protection mode to long mode. + + @param PageTableAddress Page table base address + @param Context Context for mode switching + @param ReturnContext Context for mode switching + + @retval EFI_SUCCESS Function successfully executed. + +**/ +EFI_STATUS +Thunk32To64 ( + EFI_PHYSICAL_ADDRESS PageTableAddress, + SWITCH_32_TO_64_CONTEXT *Context, + SWITCH_64_TO_32_CONTEXT *ReturnContext + ) +{ + UINTN SetJumpFlag; + EFI_STATUS Status; + + // + // Save return address, LongJump will return here then + // + SetJumpFlag = SetJump ((BASE_LIBRARY_JUMP_BUFFER *) (UINTN) Context->JumpBuffer); + + if (SetJumpFlag == 0) { + + // + // Build 4G Page Tables. + // + Create4GPageTables (PageTableAddress, Context->Page1GSupport); + + // + // Create 64-bit GDT + // + AsmWriteGdtr (&mGdt); + + // + // Write CR3 + // + AsmWriteCr3 ((UINTN) PageTableAddress); + + DEBUG (( + DEBUG_INFO, + "%a() Stack Base: 0x%lx, Stack Size: 0x%lx\n", + __FUNCTION__, + Context->StackBufferBase, + Context->StackBufferLength + )); + + // + // Disable interrupt of Debug timer, since the IDT table cannot work in long mode + // + SaveAndSetDebugTimerInterrupt (FALSE); + // + // Transfer to long mode + // + AsmEnablePaging64 ( + 0x38, + (UINT64) Context->EntryPoint, + (UINT64)(UINTN) Context, + (UINT64)(UINTN) ReturnContext, + Context->StackBufferBase + Context->StackBufferLength + ); + } + + // + // Convert to 32-bit Status and return + // + Status = EFI_SUCCESS; + if ((UINTN) ReturnContext->ReturnStatus != 0) { + Status = ENCODE_ERROR ((UINTN) ReturnContext->ReturnStatus); + } + + return Status; +} + +/** + If in 32 bit protection mode, and coalesce image is of X64, switch to long mode. + + @param LongModeBuffer The context of long mode. + @param CoalesceEntry Entry of coalesce image. + @param BlockListAddr Address of block list. + @param MemoryResource Pointer to the buffer of memory resource descriptor. + @param MemoryBase Base of memory range. + @param MemorySize Size of memory range. + + @retval EFI_SUCCESS Successfully switched to long mode and execute coalesce. + @retval Others Failed to execute coalesce in long mode. + +**/ +EFI_STATUS +ModeSwitch ( + IN EFI_CAPSULE_LONG_MODE_BUFFER *LongModeBuffer, + IN COALESCE_ENTRY CoalesceEntry, + IN EFI_PHYSICAL_ADDRESS BlockListAddr, + IN MEMORY_RESOURCE_DESCRIPTOR *MemoryResource, + IN OUT VOID **MemoryBase, + IN OUT UINTN *MemorySize + ) +{ + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS MemoryBase64; + UINT64 MemorySize64; + EFI_PHYSICAL_ADDRESS MemoryEnd64; + SWITCH_32_TO_64_CONTEXT Context; + SWITCH_64_TO_32_CONTEXT ReturnContext; + BASE_LIBRARY_JUMP_BUFFER JumpBuffer; + EFI_PHYSICAL_ADDRESS ReservedRangeBase; + EFI_PHYSICAL_ADDRESS ReservedRangeEnd; + BOOLEAN Page1GSupport; + + ZeroMem (&Context, sizeof (SWITCH_32_TO_64_CONTEXT)); + ZeroMem (&ReturnContext, sizeof (SWITCH_64_TO_32_CONTEXT)); + + MemoryBase64 = (UINT64) (UINTN) *MemoryBase; + MemorySize64 = (UINT64) (UINTN) *MemorySize; + MemoryEnd64 = MemoryBase64 + MemorySize64; + + Page1GSupport = IsPage1GSupport (); + + // + // Merge memory range reserved for stack and page table + // + if (LongModeBuffer->StackBaseAddress < LongModeBuffer->PageTableAddress) { + ReservedRangeBase = LongModeBuffer->StackBaseAddress; + ReservedRangeEnd = LongModeBuffer->PageTableAddress + CalculatePageTableSize (Page1GSupport); + } else { + ReservedRangeBase = LongModeBuffer->PageTableAddress; + ReservedRangeEnd = LongModeBuffer->StackBaseAddress + LongModeBuffer->StackSize; + } + + // + // Check if memory range reserved is overlap with MemoryBase ~ MemoryBase + MemorySize. + // If they are overlapped, get a larger range to process capsule data. + // + if (ReservedRangeBase <= MemoryBase64) { + if (ReservedRangeEnd < MemoryEnd64) { + MemoryBase64 = ReservedRangeEnd; + } else { + DEBUG ((DEBUG_ERROR, "Memory is not enough to process capsule!\n")); + return EFI_OUT_OF_RESOURCES; + } + } else if (ReservedRangeBase < MemoryEnd64) { + if (ReservedRangeEnd < MemoryEnd64 && + ReservedRangeBase - MemoryBase64 < MemoryEnd64 - ReservedRangeEnd) { + MemoryBase64 = ReservedRangeEnd; + } else { + MemorySize64 = (UINT64)(UINTN)(ReservedRangeBase - MemoryBase64); + } + } + + // + // Initialize context jumping to 64-bit enviroment + // + Context.JumpBuffer = (EFI_PHYSICAL_ADDRESS)(UINTN)&JumpBuffer; + Context.StackBufferBase = LongModeBuffer->StackBaseAddress; + Context.StackBufferLength = LongModeBuffer->StackSize; + Context.EntryPoint = (EFI_PHYSICAL_ADDRESS)(UINTN)CoalesceEntry; + Context.BlockListAddr = BlockListAddr; + Context.MemoryResource = (EFI_PHYSICAL_ADDRESS)(UINTN)MemoryResource; + Context.MemoryBase64Ptr = (EFI_PHYSICAL_ADDRESS)(UINTN)&MemoryBase64; + Context.MemorySize64Ptr = (EFI_PHYSICAL_ADDRESS)(UINTN)&MemorySize64; + Context.Page1GSupport = Page1GSupport; + Context.AddressEncMask = PcdGet64 (PcdPteMemoryEncryptionAddressOrMask) & PAGING_1G_ADDRESS_MASK_64; + + // + // Prepare data for return back + // + ReturnContext.ReturnCs = 0x10; + ReturnContext.ReturnEntryPoint = (EFI_PHYSICAL_ADDRESS)(UINTN)ReturnFunction; + // + // Will save the return status of processing capsule + // + ReturnContext.ReturnStatus = 0; + + // + // Save original GDT + // + AsmReadGdtr ((IA32_DESCRIPTOR *)&ReturnContext.Gdtr); + + Status = Thunk32To64 (LongModeBuffer->PageTableAddress, &Context, &ReturnContext); + + if (!EFI_ERROR (Status)) { + *MemoryBase = (VOID *) (UINTN) MemoryBase64; + *MemorySize = (UINTN) MemorySize64; + } + + return Status; + +} + +/** + Locates the coalesce image entry point, and detects its machine type. + + @param CoalesceImageEntryPoint Pointer to coalesce image entry point for output. + @param CoalesceImageMachineType Pointer to machine type of coalesce image. + + @retval EFI_SUCCESS Coalesce image successfully located. + @retval Others Failed to locate the coalesce image. + +**/ +EFI_STATUS +FindCapsuleCoalesceImage ( + OUT EFI_PHYSICAL_ADDRESS *CoalesceImageEntryPoint, + OUT UINT16 *CoalesceImageMachineType + ) +{ + EFI_STATUS Status; + UINTN Instance; + EFI_PEI_LOAD_FILE_PPI *LoadFile; + EFI_PEI_FV_HANDLE VolumeHandle; + EFI_PEI_FILE_HANDLE FileHandle; + EFI_PHYSICAL_ADDRESS CoalesceImageAddress; + UINT64 CoalesceImageSize; + UINT32 AuthenticationState; + + Instance = 0; + + while (TRUE) { + Status = PeiServicesFfsFindNextVolume (Instance++, &VolumeHandle); + if (EFI_ERROR (Status)) { + return Status; + } + Status = PeiServicesFfsFindFileByName (PcdGetPtr(PcdCapsuleCoalesceFile), VolumeHandle, &FileHandle); + if (!EFI_ERROR (Status)) { + Status = PeiServicesLocatePpi (&gEfiPeiLoadFilePpiGuid, 0, NULL, (VOID **) &LoadFile); + ASSERT_EFI_ERROR (Status); + + Status = LoadFile->LoadFile ( + LoadFile, + FileHandle, + &CoalesceImageAddress, + &CoalesceImageSize, + CoalesceImageEntryPoint, + &AuthenticationState + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Unable to find PE32 section in CapsuleX64 image ffs %r!\n", Status)); + return Status; + } + *CoalesceImageMachineType = PeCoffLoaderGetMachineType ((VOID *) (UINTN) CoalesceImageAddress); + break; + } else { + continue; + } + } + + return Status; +} + +/** + Gets the reserved long mode buffer. + + @param LongModeBuffer Pointer to the long mode buffer for output. + + @retval EFI_SUCCESS Long mode buffer successfully retrieved. + @retval Others Variable storing long mode buffer not found. + +**/ +EFI_STATUS +GetLongModeContext ( + OUT EFI_CAPSULE_LONG_MODE_BUFFER *LongModeBuffer + ) +{ + EFI_STATUS Status; + UINTN Size; + EFI_PEI_READ_ONLY_VARIABLE2_PPI *PPIVariableServices; + + Status = PeiServicesLocatePpi ( + &gEfiPeiReadOnlyVariable2PpiGuid, + 0, + NULL, + (VOID **) &PPIVariableServices + ); + ASSERT_EFI_ERROR (Status); + + Size = sizeof (EFI_CAPSULE_LONG_MODE_BUFFER); + Status = PPIVariableServices->GetVariable ( + PPIVariableServices, + EFI_CAPSULE_LONG_MODE_BUFFER_NAME, + &gEfiCapsuleVendorGuid, + NULL, + &Size, + LongModeBuffer + ); + if (EFI_ERROR (Status)) { + DEBUG (( DEBUG_ERROR, "Error Get LongModeBuffer variable %r!\n", Status)); + } + return Status; +} +#endif + +#if defined (MDE_CPU_IA32) || defined (MDE_CPU_X64) +/** + Get physical address bits. + + @return Physical address bits. + +**/ +UINT8 +GetPhysicalAddressBits ( + VOID + ) +{ + UINT32 RegEax; + UINT8 PhysicalAddressBits; + VOID *Hob; + + // + // Get physical address bits supported. + // + Hob = GetFirstHob (EFI_HOB_TYPE_CPU); + if (Hob != NULL) { + PhysicalAddressBits = ((EFI_HOB_CPU *) Hob)->SizeOfMemorySpace; + } else { + AsmCpuid (0x80000000, &RegEax, NULL, NULL, NULL); + if (RegEax >= 0x80000008) { + AsmCpuid (0x80000008, &RegEax, NULL, NULL, NULL); + PhysicalAddressBits = (UINT8) RegEax; + } else { + PhysicalAddressBits = 36; + } + } + + // + // IA-32e paging translates 48-bit linear addresses to 52-bit physical addresses. + // + ASSERT (PhysicalAddressBits <= 52); + if (PhysicalAddressBits > 48) { + PhysicalAddressBits = 48; + } + + return PhysicalAddressBits; +} +#endif + +/** + Sort memory resource entries based upon PhysicalStart, from low to high. + + @param[in, out] MemoryResource A pointer to the memory resource entry buffer. + +**/ +VOID +SortMemoryResourceDescriptor ( + IN OUT MEMORY_RESOURCE_DESCRIPTOR *MemoryResource + ) +{ + MEMORY_RESOURCE_DESCRIPTOR *MemoryResourceEntry; + MEMORY_RESOURCE_DESCRIPTOR *NextMemoryResourceEntry; + MEMORY_RESOURCE_DESCRIPTOR TempMemoryResource; + + MemoryResourceEntry = MemoryResource; + NextMemoryResourceEntry = MemoryResource + 1; + while (MemoryResourceEntry->ResourceLength != 0) { + while (NextMemoryResourceEntry->ResourceLength != 0) { + if (MemoryResourceEntry->PhysicalStart > NextMemoryResourceEntry->PhysicalStart) { + CopyMem (&TempMemoryResource, MemoryResourceEntry, sizeof (MEMORY_RESOURCE_DESCRIPTOR)); + CopyMem (MemoryResourceEntry, NextMemoryResourceEntry, sizeof (MEMORY_RESOURCE_DESCRIPTOR)); + CopyMem (NextMemoryResourceEntry, &TempMemoryResource, sizeof (MEMORY_RESOURCE_DESCRIPTOR)); + } + + NextMemoryResourceEntry = NextMemoryResourceEntry + 1; + } + + MemoryResourceEntry = MemoryResourceEntry + 1; + NextMemoryResourceEntry = MemoryResourceEntry + 1; + } +} + +/** + Merge continous memory resource entries. + + @param[in, out] MemoryResource A pointer to the memory resource entry buffer. + +**/ +VOID +MergeMemoryResourceDescriptor ( + IN OUT MEMORY_RESOURCE_DESCRIPTOR *MemoryResource + ) +{ + MEMORY_RESOURCE_DESCRIPTOR *MemoryResourceEntry; + MEMORY_RESOURCE_DESCRIPTOR *NewMemoryResourceEntry; + MEMORY_RESOURCE_DESCRIPTOR *NextMemoryResourceEntry; + MEMORY_RESOURCE_DESCRIPTOR *MemoryResourceEnd; + + MemoryResourceEntry = MemoryResource; + NewMemoryResourceEntry = MemoryResource; + while (MemoryResourceEntry->ResourceLength != 0) { + CopyMem (NewMemoryResourceEntry, MemoryResourceEntry, sizeof (MEMORY_RESOURCE_DESCRIPTOR)); + NextMemoryResourceEntry = MemoryResourceEntry + 1; + + while ((NextMemoryResourceEntry->ResourceLength != 0) && + (NextMemoryResourceEntry->PhysicalStart == (MemoryResourceEntry->PhysicalStart + MemoryResourceEntry->ResourceLength))) { + MemoryResourceEntry->ResourceLength += NextMemoryResourceEntry->ResourceLength; + if (NewMemoryResourceEntry != MemoryResourceEntry) { + NewMemoryResourceEntry->ResourceLength += NextMemoryResourceEntry->ResourceLength; + } + + NextMemoryResourceEntry = NextMemoryResourceEntry + 1; + } + + MemoryResourceEntry = NextMemoryResourceEntry; + NewMemoryResourceEntry = NewMemoryResourceEntry + 1; + } + + // + // Set NULL terminate memory resource descriptor after merging. + // + MemoryResourceEnd = NewMemoryResourceEntry; + ZeroMem (MemoryResourceEnd, sizeof (MEMORY_RESOURCE_DESCRIPTOR)); +} + +/** + Build memory resource descriptor from resource descriptor in HOB list. + + @return Pointer to the buffer of memory resource descriptor. + NULL if no memory resource descriptor reported in HOB list + before capsule Coalesce. + +**/ +MEMORY_RESOURCE_DESCRIPTOR * +BuildMemoryResourceDescriptor ( + VOID + ) +{ + EFI_PEI_HOB_POINTERS Hob; + UINTN Index; + EFI_HOB_RESOURCE_DESCRIPTOR *ResourceDescriptor; + MEMORY_RESOURCE_DESCRIPTOR *MemoryResource; + EFI_STATUS Status; + + // + // Get the count of memory resource descriptor. + // + Index = 0; + Hob.Raw = GetFirstHob (EFI_HOB_TYPE_RESOURCE_DESCRIPTOR); + while (Hob.Raw != NULL) { + ResourceDescriptor = (EFI_HOB_RESOURCE_DESCRIPTOR *) Hob.Raw; + if (ResourceDescriptor->ResourceType == EFI_RESOURCE_SYSTEM_MEMORY) { + Index++; + } + Hob.Raw = GET_NEXT_HOB (Hob); + Hob.Raw = GetNextHob (EFI_HOB_TYPE_RESOURCE_DESCRIPTOR, Hob.Raw); + } + + if (Index == 0) { + DEBUG ((DEBUG_INFO | DEBUG_WARN, "No memory resource descriptor reported in HOB list before capsule Coalesce\n")); +#if defined (MDE_CPU_IA32) || defined (MDE_CPU_X64) + // + // Allocate memory to hold memory resource descriptor, + // include extra one NULL terminate memory resource descriptor. + // + Status = PeiServicesAllocatePool ((1 + 1) * sizeof (MEMORY_RESOURCE_DESCRIPTOR), (VOID **) &MemoryResource); + ASSERT_EFI_ERROR (Status); + ZeroMem (MemoryResource, (1 + 1) * sizeof (MEMORY_RESOURCE_DESCRIPTOR)); + + MemoryResource[0].PhysicalStart = 0; + MemoryResource[0].ResourceLength = LShiftU64 (1, GetPhysicalAddressBits ()); + DEBUG ((DEBUG_INFO, "MemoryResource[0x0] - Start(0x%0lx) Length(0x%0lx)\n", + MemoryResource[0x0].PhysicalStart, MemoryResource[0x0].ResourceLength)); + return MemoryResource; +#else + return NULL; +#endif + } + + // + // Allocate memory to hold memory resource descriptor, + // include extra one NULL terminate memory resource descriptor. + // + Status = PeiServicesAllocatePool ((Index + 1) * sizeof (MEMORY_RESOURCE_DESCRIPTOR), (VOID **) &MemoryResource); + ASSERT_EFI_ERROR (Status); + ZeroMem (MemoryResource, (Index + 1) * sizeof (MEMORY_RESOURCE_DESCRIPTOR)); + + // + // Get the content of memory resource descriptor. + // + Index = 0; + Hob.Raw = GetFirstHob (EFI_HOB_TYPE_RESOURCE_DESCRIPTOR); + while (Hob.Raw != NULL) { + ResourceDescriptor = (EFI_HOB_RESOURCE_DESCRIPTOR *) Hob.Raw; + if (ResourceDescriptor->ResourceType == EFI_RESOURCE_SYSTEM_MEMORY) { + DEBUG ((DEBUG_INFO, "MemoryResource[0x%x] - Start(0x%0lx) Length(0x%0lx)\n", + Index, ResourceDescriptor->PhysicalStart, ResourceDescriptor->ResourceLength)); + MemoryResource[Index].PhysicalStart = ResourceDescriptor->PhysicalStart; + MemoryResource[Index].ResourceLength = ResourceDescriptor->ResourceLength; + Index++; + } + Hob.Raw = GET_NEXT_HOB (Hob); + Hob.Raw = GetNextHob (EFI_HOB_TYPE_RESOURCE_DESCRIPTOR, Hob.Raw); + } + + SortMemoryResourceDescriptor (MemoryResource); + MergeMemoryResourceDescriptor (MemoryResource); + + DEBUG ((DEBUG_INFO, "Dump MemoryResource[] after sorted and merged\n")); + for (Index = 0; MemoryResource[Index].ResourceLength != 0; Index++) { + DEBUG (( + DEBUG_INFO, + " MemoryResource[0x%x] - Start(0x%0lx) Length(0x%0lx)\n", + Index, + MemoryResource[Index].PhysicalStart, + MemoryResource[Index].ResourceLength + )); + } + + return MemoryResource; +} + +/** + Check if the capsules are staged. + + @retval TRUE The capsules are staged. + @retval FALSE The capsules are not staged. + +**/ +BOOLEAN +AreCapsulesStaged ( + VOID + ) +{ + EFI_STATUS Status; + UINTN Size; + EFI_PEI_READ_ONLY_VARIABLE2_PPI *PPIVariableServices; + EFI_PHYSICAL_ADDRESS CapsuleDataPtr64; + + CapsuleDataPtr64 = 0; + + Status = PeiServicesLocatePpi( + &gEfiPeiReadOnlyVariable2PpiGuid, + 0, + NULL, + (VOID **)&PPIVariableServices + ); + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Failed to find ReadOnlyVariable2PPI\n")); + return FALSE; + } + + // + // Check for Update capsule + // + Size = sizeof (CapsuleDataPtr64); + Status = PPIVariableServices->GetVariable ( + PPIVariableServices, + EFI_CAPSULE_VARIABLE_NAME, + &gEfiCapsuleVendorGuid, + NULL, + &Size, + (VOID *)&CapsuleDataPtr64 + ); + + if (!EFI_ERROR (Status)) { + return TRUE; + } + + return FALSE; +} + +/** + Check all the variables for SG list heads and get the count and addresses. + + @param ListLength A pointer would return the SG list length. + @param HeadList A ponter to the capsule SG list. + + @retval EFI_SUCCESS a valid capsule is present + @retval EFI_NOT_FOUND if a valid capsule is not present + @retval EFI_INVALID_PARAMETER the input parameter is invalid + @retval EFI_OUT_OF_RESOURCES fail to allocate memory + +**/ +EFI_STATUS +GetScatterGatherHeadEntries ( + OUT UINTN *ListLength, + OUT EFI_PHYSICAL_ADDRESS **HeadList + ) +{ + EFI_STATUS Status; + UINTN Size; + UINTN Index; + UINTN TempIndex; + UINTN ValidIndex; + BOOLEAN Flag; + CHAR16 CapsuleVarName[30]; + CHAR16 *TempVarName; + EFI_PHYSICAL_ADDRESS CapsuleDataPtr64; + EFI_PEI_READ_ONLY_VARIABLE2_PPI *PPIVariableServices; + EFI_PHYSICAL_ADDRESS *TempList; + EFI_PHYSICAL_ADDRESS *EnlargedTempList; + UINTN TempListLength; + + Index = 0; + TempVarName = NULL; + CapsuleVarName[0] = 0; + ValidIndex = 0; + CapsuleDataPtr64 = 0; + + if ((ListLength == NULL) || (HeadList == NULL)) { + DEBUG ((DEBUG_ERROR, "%a Invalid parameters. Inputs can't be NULL\n", __FUNCTION__)); + ASSERT (ListLength != NULL); + ASSERT (HeadList != NULL); + return EFI_INVALID_PARAMETER; + } + + *ListLength = 0; + *HeadList = NULL; + + Status = PeiServicesLocatePpi ( + &gEfiPeiReadOnlyVariable2PpiGuid, + 0, + NULL, + (VOID **)&PPIVariableServices + ); + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Failed to find ReadOnlyVariable2PPI\n")); + return Status; + } + + // + // Allocate memory for sg list head + // + TempListLength = DEFAULT_SG_LIST_HEADS * sizeof (EFI_PHYSICAL_ADDRESS); + TempList = AllocateZeroPool (TempListLength); + if (TempList == NULL) { + DEBUG((DEBUG_ERROR, "Failed to allocate memory\n")); + return EFI_OUT_OF_RESOURCES; + } + + // + // setup var name buffer for update capsules + // + StrCpyS (CapsuleVarName, sizeof (CapsuleVarName) / sizeof (CHAR16), EFI_CAPSULE_VARIABLE_NAME); + TempVarName = CapsuleVarName + StrLen (CapsuleVarName); + while (TRUE) { + if (Index != 0) { + UnicodeValueToStringS ( + TempVarName, + (sizeof (CapsuleVarName) - ((UINTN)TempVarName - (UINTN)CapsuleVarName)), + 0, + Index, + 0 + ); + } + Size = sizeof (CapsuleDataPtr64); + Status = PPIVariableServices->GetVariable ( + PPIVariableServices, + CapsuleVarName, + &gEfiCapsuleVendorGuid, + NULL, + &Size, + (VOID *)&CapsuleDataPtr64 + ); + + if (EFI_ERROR (Status)) { + if (Status != EFI_NOT_FOUND) { + DEBUG ((DEBUG_ERROR, "Unexpected error getting Capsule Update variable. Status = %r\n")); + } + break; + } + + // + // If this BlockList has been linked before, skip this variable + // + Flag = FALSE; + for (TempIndex = 0; TempIndex < ValidIndex; TempIndex++) { + if (TempList[TempIndex] == CapsuleDataPtr64) { + Flag = TRUE; + break; + } + } + if (Flag) { + Index++; + continue; + } + + // + // The TempList is full, enlarge it + // + if ((ValidIndex + 1) >= TempListLength) { + EnlargedTempList = AllocateZeroPool (TempListLength * 2); + if (EnlargedTempList == NULL) { + DEBUG ((DEBUG_ERROR, "Fail to allocate memory!\n")); + return EFI_OUT_OF_RESOURCES; + } + CopyMem (EnlargedTempList, TempList, TempListLength); + FreePool (TempList); + TempList = EnlargedTempList; + TempListLength *= 2; + } + + // + // add it to the cached list + // + TempList[ValidIndex++] = CapsuleDataPtr64; + Index++; + } + + if (ValidIndex == 0) { + DEBUG ((DEBUG_ERROR, "%a didn't find any SG lists in variables\n", __FUNCTION__)); + return EFI_NOT_FOUND; + } + + *HeadList = AllocateZeroPool ((ValidIndex + 1) * sizeof (EFI_PHYSICAL_ADDRESS)); + if (*HeadList == NULL) { + DEBUG ((DEBUG_ERROR, "Failed to allocate memory\n")); + return EFI_OUT_OF_RESOURCES; + } + + CopyMem (*HeadList, TempList, (ValidIndex) * sizeof (EFI_PHYSICAL_ADDRESS)); + *ListLength = ValidIndex; + + return EFI_SUCCESS; +} + +/** + Capsule PPI service to coalesce a fragmented capsule in memory. + + @param PeiServices General purpose services available to every PEIM. + @param MemoryBase Pointer to the base of a block of memory that we can walk + all over while trying to coalesce our buffers. + On output, this variable will hold the base address of + a coalesced capsule. + @param MemorySize Size of the memory region pointed to by MemoryBase. + On output, this variable will contain the size of the + coalesced capsule. + + @retval EFI_NOT_FOUND if we can't determine the boot mode + if the boot mode is not flash-update + if we could not find the capsule descriptors + + @retval EFI_BUFFER_TOO_SMALL + if we could not coalesce the capsule in the memory + region provided to us + + @retval EFI_SUCCESS if there's no capsule, or if we processed the + capsule successfully. +**/ +EFI_STATUS +EFIAPI +CapsuleCoalesce ( + IN EFI_PEI_SERVICES **PeiServices, + IN OUT VOID **MemoryBase, + IN OUT UINTN *MemorySize + ) +{ + EFI_STATUS Status; + EFI_BOOT_MODE BootMode; + UINTN ListLength; + EFI_PHYSICAL_ADDRESS *VariableArrayAddress; + MEMORY_RESOURCE_DESCRIPTOR *MemoryResource; +#ifdef MDE_CPU_IA32 + UINT16 CoalesceImageMachineType; + EFI_PHYSICAL_ADDRESS CoalesceImageEntryPoint; + COALESCE_ENTRY CoalesceEntry; + EFI_CAPSULE_LONG_MODE_BUFFER LongModeBuffer; +#endif + + ListLength = 0; + VariableArrayAddress = NULL; + + // + // Someone should have already ascertained the boot mode. If it's not + // capsule update, then return normally. + // + Status = PeiServicesGetBootMode (&BootMode); + if (EFI_ERROR (Status) || (BootMode != BOOT_ON_FLASH_UPDATE)) { + DEBUG ((DEBUG_ERROR, "Boot mode is not correct for capsule update path.\n")); + Status = EFI_NOT_FOUND; + goto Done; + } + + // + // Get SG list entries + // + Status = GetScatterGatherHeadEntries (&ListLength, &VariableArrayAddress); + if (EFI_ERROR (Status) || VariableArrayAddress == NULL) { + DEBUG ((DEBUG_ERROR, "%a failed to get Scatter Gather List Head Entries. Status = %r\n", __FUNCTION__, Status)); + goto Done; + } + + MemoryResource = BuildMemoryResourceDescriptor (); + +#ifdef MDE_CPU_IA32 + if (FeaturePcdGet (PcdDxeIplSwitchToLongMode)) { + // + // Switch to 64-bit mode to process capsule data when: + // 1. When DXE phase is 64-bit + // 2. When the buffer for 64-bit transition exists + // 3. When Capsule X64 image is built in BIOS image + // In 64-bit mode, we can process capsule data above 4GB. + // + CoalesceImageEntryPoint = 0; + Status = GetLongModeContext (&LongModeBuffer); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Fail to find the variable for long mode context!\n")); + Status = EFI_NOT_FOUND; + goto Done; + } + + Status = FindCapsuleCoalesceImage (&CoalesceImageEntryPoint, &CoalesceImageMachineType); + if ((EFI_ERROR (Status)) || (CoalesceImageMachineType != EFI_IMAGE_MACHINE_X64)) { + DEBUG ((DEBUG_ERROR, "Fail to find CapsuleX64 module in FV!\n")); + Status = EFI_NOT_FOUND; + goto Done; + } + ASSERT (CoalesceImageEntryPoint != 0); + CoalesceEntry = (COALESCE_ENTRY) (UINTN) CoalesceImageEntryPoint; + Status = ModeSwitch (&LongModeBuffer, CoalesceEntry, (EFI_PHYSICAL_ADDRESS)(UINTN)VariableArrayAddress, MemoryResource, MemoryBase, MemorySize); + } else { + // + // Capsule is processed in IA32 mode. + // + Status = CapsuleDataCoalesce (PeiServices, (EFI_PHYSICAL_ADDRESS *)(UINTN)VariableArrayAddress, MemoryResource, MemoryBase, MemorySize); + } +#else + // + // Process capsule directly. + // + Status = CapsuleDataCoalesce (PeiServices, (EFI_PHYSICAL_ADDRESS *)(UINTN)VariableArrayAddress, MemoryResource, MemoryBase, MemorySize); +#endif + + DEBUG ((DEBUG_INFO, "Capsule Coalesce Status = %r!\n", Status)); + + if (Status == EFI_BUFFER_TOO_SMALL) { + DEBUG ((DEBUG_ERROR, "There is not enough memory to process capsule!\n")); + } + + if (Status == EFI_NOT_FOUND) { + DEBUG ((DEBUG_ERROR, "Fail to parse capsule descriptor in memory!\n")); + REPORT_STATUS_CODE ( + EFI_ERROR_CODE | EFI_ERROR_MAJOR, + (EFI_SOFTWARE_PEI_MODULE | EFI_SW_PEI_EC_INVALID_CAPSULE_DESCRIPTOR) + ); + } + +Done: + return Status; +} + +/** + Determine if we're in capsule update boot mode. + + @param PeiServices PEI services table + + @retval EFI_SUCCESS if we have a capsule available + @retval EFI_NOT_FOUND no capsule detected + +**/ +EFI_STATUS +EFIAPI +CheckCapsuleUpdate ( + IN EFI_PEI_SERVICES **PeiServices + ) +{ + if (AreCapsulesStaged ()) { + return EFI_SUCCESS; + } else { + return EFI_NOT_FOUND; + } +} +/** + This function will look at a capsule and determine if it's a test pattern. + If it is, then it will verify it and emit an error message if corruption is detected. + + @param PeiServices Standard pei services pointer + @param CapsuleBase Base address of coalesced capsule, which is preceeded + by private data. Very implementation specific. + + @retval TRUE Capsule image is the test image + @retval FALSE Capsule image is not the test image. + +**/ +BOOLEAN +CapsuleTestPattern ( + IN EFI_PEI_SERVICES **PeiServices, + IN VOID *CapsuleBase + ) +{ + UINT32 *TestPtr; + UINT32 TestCounter; + UINT32 TestSize; + BOOLEAN RetValue; + + RetValue = FALSE; + + // + // Look at the capsule data and determine if it's a test pattern. If it + // is, then test it now. + // + TestPtr = (UINT32 *) CapsuleBase; + // + // 0x54534554 "TEST" + // + if (*TestPtr == 0x54534554) { + RetValue = TRUE; + DEBUG ((DEBUG_INFO, "Capsule test pattern mode activated...\n")); + TestSize = TestPtr[1] / sizeof (UINT32); + // + // Skip over the signature and the size fields in the pattern data header + // + TestPtr += 2; + TestCounter = 0; + while (TestSize > 0) { + if (*TestPtr != TestCounter) { + DEBUG ((DEBUG_INFO, "Capsule test pattern mode FAILED: BaseAddr/FailAddr 0x%X 0x%X\n", (UINT32)(UINTN)(EFI_CAPSULE_PEIM_PRIVATE_DATA *)CapsuleBase, (UINT32)(UINTN)TestPtr)); + return TRUE; + } + + TestPtr++; + TestCounter++; + TestSize--; + } + + DEBUG ((DEBUG_INFO, "Capsule test pattern mode SUCCESS\n")); + } + + return RetValue; +} + +/** + Capsule PPI service that gets called after memory is available. The + capsule coalesce function, which must be called first, returns a base + address and size, which can be anything actually. Once the memory init + PEIM has discovered memory, then it should call this function and pass in + the base address and size returned by the coalesce function. Then this + function can create a capsule HOB and return. + + @param PeiServices standard pei services pointer + @param CapsuleBase address returned by the capsule coalesce function. Most + likely this will actually be a pointer to private data. + @param CapsuleSize value returned by the capsule coalesce function. + + @retval EFI_VOLUME_CORRUPTED CapsuleBase does not appear to point to a + coalesced capsule + @retval EFI_SUCCESS if all goes well. +**/ +EFI_STATUS +EFIAPI +CreateState ( + IN EFI_PEI_SERVICES **PeiServices, + IN VOID *CapsuleBase, + IN UINTN CapsuleSize + ) +{ + EFI_STATUS Status; + EFI_CAPSULE_PEIM_PRIVATE_DATA *PrivateData; + UINTN Size; + EFI_PHYSICAL_ADDRESS NewBuffer; + UINTN CapsuleNumber; + UINT32 Index; + EFI_PHYSICAL_ADDRESS BaseAddress; + UINT64 Length; + + PrivateData = (EFI_CAPSULE_PEIM_PRIVATE_DATA *) CapsuleBase; + if (PrivateData->Signature != EFI_CAPSULE_PEIM_PRIVATE_DATA_SIGNATURE) { + return EFI_VOLUME_CORRUPTED; + } + if (PrivateData->CapsuleAllImageSize >= MAX_ADDRESS) { + DEBUG ((DEBUG_ERROR, "CapsuleAllImageSize too big - 0x%lx\n", PrivateData->CapsuleAllImageSize)); + return EFI_OUT_OF_RESOURCES; + } + if (PrivateData->CapsuleNumber >= MAX_ADDRESS) { + DEBUG ((DEBUG_ERROR, "CapsuleNumber too big - 0x%lx\n", PrivateData->CapsuleNumber)); + return EFI_OUT_OF_RESOURCES; + } + // + // Capsule Number and Capsule Offset is in the tail of Capsule data. + // + Size = (UINTN)PrivateData->CapsuleAllImageSize; + CapsuleNumber = (UINTN)PrivateData->CapsuleNumber; + // + // Allocate the memory so that it gets preserved into DXE + // + Status = PeiServicesAllocatePages ( + EfiRuntimeServicesData, + EFI_SIZE_TO_PAGES (Size), + &NewBuffer + ); + + if (Status != EFI_SUCCESS) { + DEBUG ((DEBUG_ERROR, "AllocatePages Failed!\n")); + return Status; + } + // + // Copy to our new buffer for DXE + // + DEBUG ((DEBUG_INFO, "Capsule copy from 0x%8X to 0x%8X with size 0x%8X\n", (UINTN)((UINT8 *)PrivateData + sizeof(EFI_CAPSULE_PEIM_PRIVATE_DATA) + (CapsuleNumber - 1) * sizeof(UINT64)), (UINTN) NewBuffer, Size)); + CopyMem ((VOID *) (UINTN) NewBuffer, (VOID *) (UINTN) ((UINT8 *)PrivateData + sizeof(EFI_CAPSULE_PEIM_PRIVATE_DATA) + (CapsuleNumber - 1) * sizeof(UINT64)), Size); + // + // Check for test data pattern. If it is the test pattern, then we'll + // test it and still create the HOB so that it can be used to verify + // that capsules don't get corrupted all the way into BDS. BDS will + // still try to turn it into a firmware volume, but will think it's + // corrupted so nothing will happen. + // + DEBUG_CODE ( + CapsuleTestPattern (PeiServices, (VOID *) (UINTN) NewBuffer); + ); + + // + // Build the UEFI Capsule Hob for each capsule image. + // + for (Index = 0; Index < CapsuleNumber; Index ++) { + BaseAddress = NewBuffer + PrivateData->CapsuleOffset[Index]; + Length = ((EFI_CAPSULE_HEADER *)((UINTN) BaseAddress))->CapsuleImageSize; + + BuildCvHob (BaseAddress, Length); + } + + return EFI_SUCCESS; +} + +CONST EFI_PEI_CAPSULE_PPI mCapsulePpi = { + CapsuleCoalesce, + CheckCapsuleUpdate, + CreateState +}; + +CONST EFI_PEI_PPI_DESCRIPTOR mUefiPpiListCapsule = { + (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), + &gEfiPeiCapsulePpiGuid, + (EFI_PEI_CAPSULE_PPI *) &mCapsulePpi +}; + +/** + Entry point function for the PEIM + + @param FileHandle Handle of the file being invoked. + @param PeiServices Describes the list of possible PEI Services. + + @return EFI_SUCCESS If we installed our PPI + +**/ +EFI_STATUS +EFIAPI +CapsuleMain ( + IN EFI_PEI_FILE_HANDLE FileHandle, + IN CONST EFI_PEI_SERVICES **PeiServices + ) +{ + // + // Just produce our PPI + // + return PeiServicesInstallPpi (&mUefiPpiListCapsule); +} diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/X64/PageFaultHandler.nasm b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/X64/PageFaultHandler.nasm new file mode 100644 index 00000000..c2b5f2e0 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/X64/PageFaultHandler.nasm @@ -0,0 +1,81 @@ +;; @file +; This is the assembly code for page fault handler hook. +; +; Copyright (c) 2015, Intel Corporation. All rights reserved.<BR> +; +; SPDX-License-Identifier: BSD-2-Clause-Patent +; +;; + +extern ASM_PFX(PageFaultHandler) + + DEFAULT REL + SECTION .text + +global ASM_PFX(PageFaultHandlerHook) +ASM_PFX(PageFaultHandlerHook): + add rsp, -0x10 + ; save rax + mov [rsp + 0x8], rax + + ;push rax ; save all volatile registers + push rcx + push rdx + push r8 + push r9 + push r10 + push r11 + ; save volatile fp registers + ; 68h + 08h(for alignment) + add rsp, -0x70 + stmxcsr [rsp + 0x60] + movdqa [rsp + 0x0], xmm0 + movdqa [rsp + 0x10], xmm1 + movdqa [rsp + 0x20], xmm2 + movdqa [rsp + 0x30], xmm3 + movdqa [rsp + 0x40], xmm4 + movdqa [rsp + 0x50], xmm5 + + add rsp, -0x20 + call ASM_PFX(PageFaultHandler) + add rsp, 0x20 + + ; load volatile fp registers + ldmxcsr [rsp + 0x60] + movdqa xmm0, [rsp + 0x0] + movdqa xmm1, [rsp + 0x10] + movdqa xmm2, [rsp + 0x20] + movdqa xmm3, [rsp + 0x30] + movdqa xmm4, [rsp + 0x40] + movdqa xmm5, [rsp + 0x50] + add rsp, 0x70 + + pop r11 + pop r10 + pop r9 + pop r8 + pop rdx + pop rcx + ;pop rax ; restore all volatile registers + + add rsp, 0x10 + + ; rax returned from PageFaultHandler is NULL or OriginalHandler address + ; NULL if the page fault is handled by PageFaultHandler + ; OriginalHandler address if the page fault is not handled by PageFaultHandler + test rax, rax + + ; save OriginalHandler address + mov [rsp - 0x10], rax + ; restore rax + mov rax, [rsp - 0x8] + + jz .0 + + ; jump to OriginalHandler + jmp qword [rsp - 0x10] + +.0: + add rsp, 0x8 ; skip error code for PF + iretq + diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/X64/X64Entry.c b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/X64/X64Entry.c new file mode 100644 index 00000000..de650856 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Universal/CapsulePei/X64/X64Entry.c @@ -0,0 +1,305 @@ +/** @file + The X64 entrypoint is used to process capsule in long mode. + +Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR> +Copyright (c) 2017, AMD Incorporated. All rights reserved.<BR> + +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include <Library/DebugLib.h> +#include <Library/BaseMemoryLib.h> +#include <Library/CpuExceptionHandlerLib.h> +#include <Library/DebugAgentLib.h> +#include "CommonHeader.h" + +#define EXCEPTION_VECTOR_NUMBER 0x22 + +#define IA32_PG_P BIT0 +#define IA32_PG_RW BIT1 +#define IA32_PG_PS BIT7 + +typedef struct _PAGE_FAULT_CONTEXT { + BOOLEAN Page1GSupport; + UINT64 PhyMask; + UINTN PageFaultBuffer; + UINTN PageFaultIndex; + UINT64 AddressEncMask; + // + // Store the uplink information for each page being used. + // + UINT64 *PageFaultUplink[EXTRA_PAGE_TABLE_PAGES]; + VOID *OriginalHandler; +} PAGE_FAULT_CONTEXT; + +typedef struct _PAGE_FAULT_IDT_TABLE { + PAGE_FAULT_CONTEXT PageFaultContext; + IA32_IDT_GATE_DESCRIPTOR IdtEntryTable[EXCEPTION_VECTOR_NUMBER]; +} PAGE_FAULT_IDT_TABLE; + +/** + Page fault handler. + +**/ +VOID +EFIAPI +PageFaultHandlerHook ( + VOID + ); + +/** + Hook IDT with our page fault handler so that the on-demand paging works on page fault. + + @param[in, out] IdtEntry Pointer to IDT entry. + @param[in, out] PageFaultContext Pointer to page fault context. + +**/ +VOID +HookPageFaultHandler ( + IN OUT IA32_IDT_GATE_DESCRIPTOR *IdtEntry, + IN OUT PAGE_FAULT_CONTEXT *PageFaultContext + ) +{ + UINT32 RegEax; + UINT8 PhysicalAddressBits; + UINTN PageFaultHandlerHookAddress; + + AsmCpuid (0x80000000, &RegEax, NULL, NULL, NULL); + if (RegEax >= 0x80000008) { + AsmCpuid (0x80000008, &RegEax, NULL, NULL, NULL); + PhysicalAddressBits = (UINT8) RegEax; + } else { + PhysicalAddressBits = 36; + } + PageFaultContext->PhyMask = LShiftU64 (1, PhysicalAddressBits) - 1; + PageFaultContext->PhyMask &= (1ull << 48) - SIZE_4KB; + + // + // Set Page Fault entry to catch >4G access + // + PageFaultHandlerHookAddress = (UINTN)PageFaultHandlerHook; + PageFaultContext->OriginalHandler = (VOID *)(UINTN)(LShiftU64 (IdtEntry->Bits.OffsetUpper, 32) + IdtEntry->Bits.OffsetLow + (IdtEntry->Bits.OffsetHigh << 16)); + IdtEntry->Bits.OffsetLow = (UINT16)PageFaultHandlerHookAddress; + IdtEntry->Bits.Selector = (UINT16)AsmReadCs (); + IdtEntry->Bits.Reserved_0 = 0; + IdtEntry->Bits.GateType = IA32_IDT_GATE_TYPE_INTERRUPT_32; + IdtEntry->Bits.OffsetHigh = (UINT16)(PageFaultHandlerHookAddress >> 16); + IdtEntry->Bits.OffsetUpper = (UINT32)(PageFaultHandlerHookAddress >> 32); + IdtEntry->Bits.Reserved_1 = 0; + + if (PageFaultContext->Page1GSupport) { + PageFaultContext->PageFaultBuffer = (UINTN)(AsmReadCr3 () & PageFaultContext->PhyMask) + EFI_PAGES_TO_SIZE(2); + }else { + PageFaultContext->PageFaultBuffer = (UINTN)(AsmReadCr3 () & PageFaultContext->PhyMask) + EFI_PAGES_TO_SIZE(6); + } + PageFaultContext->PageFaultIndex = 0; + ZeroMem (PageFaultContext->PageFaultUplink, sizeof (PageFaultContext->PageFaultUplink)); +} + +/** + Acquire page for page fault. + + @param[in, out] PageFaultContext Pointer to page fault context. + @param[in, out] Uplink Pointer to up page table entry. + +**/ +VOID +AcquirePage ( + IN OUT PAGE_FAULT_CONTEXT *PageFaultContext, + IN OUT UINT64 *Uplink + ) +{ + UINTN Address; + UINT64 AddressEncMask; + + Address = PageFaultContext->PageFaultBuffer + EFI_PAGES_TO_SIZE (PageFaultContext->PageFaultIndex); + ZeroMem ((VOID *) Address, EFI_PAGES_TO_SIZE (1)); + + AddressEncMask = PageFaultContext->AddressEncMask; + + // + // Cut the previous uplink if it exists and wasn't overwritten. + // + if ((PageFaultContext->PageFaultUplink[PageFaultContext->PageFaultIndex] != NULL) && + ((*PageFaultContext->PageFaultUplink[PageFaultContext->PageFaultIndex] & ~AddressEncMask & PageFaultContext->PhyMask) == Address)) { + *PageFaultContext->PageFaultUplink[PageFaultContext->PageFaultIndex] = 0; + } + + // + // Link & Record the current uplink. + // + *Uplink = Address | AddressEncMask | IA32_PG_P | IA32_PG_RW; + PageFaultContext->PageFaultUplink[PageFaultContext->PageFaultIndex] = Uplink; + + PageFaultContext->PageFaultIndex = (PageFaultContext->PageFaultIndex + 1) % EXTRA_PAGE_TABLE_PAGES; +} + +/** + The page fault handler that on-demand read >4G memory/MMIO. + + @retval NULL The page fault is correctly handled. + @retval OriginalHandler The page fault is not handled and is passed through to original handler. + +**/ +VOID * +EFIAPI +PageFaultHandler ( + VOID + ) +{ + IA32_DESCRIPTOR Idtr; + PAGE_FAULT_CONTEXT *PageFaultContext; + UINT64 PhyMask; + UINT64 *PageTable; + UINT64 PFAddress; + UINTN PTIndex; + UINT64 AddressEncMask; + + // + // Get the IDT Descriptor. + // + AsmReadIdtr ((IA32_DESCRIPTOR *) &Idtr); + // + // Then get page fault context by IDT Descriptor. + // + PageFaultContext = (PAGE_FAULT_CONTEXT *) (UINTN) (Idtr.Base - sizeof (PAGE_FAULT_CONTEXT)); + PhyMask = PageFaultContext->PhyMask; + AddressEncMask = PageFaultContext->AddressEncMask; + + PFAddress = AsmReadCr2 (); + DEBUG ((EFI_D_ERROR, "CapsuleX64 - PageFaultHandler: Cr2 - %lx\n", PFAddress)); + + if (PFAddress >= PhyMask + SIZE_4KB) { + return PageFaultContext->OriginalHandler; + } + PFAddress &= PhyMask; + + PageTable = (UINT64*)(UINTN)(AsmReadCr3 () & PhyMask); + + PTIndex = BitFieldRead64 (PFAddress, 39, 47); + // PML4E + if ((PageTable[PTIndex] & IA32_PG_P) == 0) { + AcquirePage (PageFaultContext, &PageTable[PTIndex]); + } + PageTable = (UINT64*)(UINTN)(PageTable[PTIndex] & ~AddressEncMask & PhyMask); + PTIndex = BitFieldRead64 (PFAddress, 30, 38); + // PDPTE + if (PageFaultContext->Page1GSupport) { + PageTable[PTIndex] = ((PFAddress | AddressEncMask) & ~((1ull << 30) - 1)) | IA32_PG_P | IA32_PG_RW | IA32_PG_PS; + } else { + if ((PageTable[PTIndex] & IA32_PG_P) == 0) { + AcquirePage (PageFaultContext, &PageTable[PTIndex]); + } + PageTable = (UINT64*)(UINTN)(PageTable[PTIndex] & ~AddressEncMask & PhyMask); + PTIndex = BitFieldRead64 (PFAddress, 21, 29); + // PD + PageTable[PTIndex] = ((PFAddress | AddressEncMask) & ~((1ull << 21) - 1)) | IA32_PG_P | IA32_PG_RW | IA32_PG_PS; + } + + return NULL; +} + + +/** + The X64 entrypoint is used to process capsule in long mode then + return to 32-bit protected mode. + + @param EntrypointContext Pointer to the context of long mode. + @param ReturnContext Pointer to the context of 32-bit protected mode. + + @retval This function should never return actually. + +**/ +EFI_STATUS +EFIAPI +_ModuleEntryPoint ( + SWITCH_32_TO_64_CONTEXT *EntrypointContext, + SWITCH_64_TO_32_CONTEXT *ReturnContext +) +{ + EFI_STATUS Status; + IA32_DESCRIPTOR Ia32Idtr; + IA32_DESCRIPTOR X64Idtr; + PAGE_FAULT_IDT_TABLE PageFaultIdtTable; + IA32_IDT_GATE_DESCRIPTOR *IdtEntry; + + // + // Save the IA32 IDT Descriptor + // + AsmReadIdtr ((IA32_DESCRIPTOR *) &Ia32Idtr); + + // + // Setup X64 IDT table + // + ZeroMem (PageFaultIdtTable.IdtEntryTable, sizeof (IA32_IDT_GATE_DESCRIPTOR) * EXCEPTION_VECTOR_NUMBER); + X64Idtr.Base = (UINTN) PageFaultIdtTable.IdtEntryTable; + X64Idtr.Limit = (UINT16) (sizeof (IA32_IDT_GATE_DESCRIPTOR) * EXCEPTION_VECTOR_NUMBER - 1); + AsmWriteIdtr ((IA32_DESCRIPTOR *) &X64Idtr); + + // + // Setup the default CPU exception handlers + // + Status = InitializeCpuExceptionHandlers (NULL); + ASSERT_EFI_ERROR (Status); + + // + // Hook page fault handler to handle >4G request. + // + PageFaultIdtTable.PageFaultContext.Page1GSupport = EntrypointContext->Page1GSupport; + PageFaultIdtTable.PageFaultContext.AddressEncMask = EntrypointContext->AddressEncMask; + IdtEntry = (IA32_IDT_GATE_DESCRIPTOR *) (X64Idtr.Base + (14 * sizeof (IA32_IDT_GATE_DESCRIPTOR))); + HookPageFaultHandler (IdtEntry, &(PageFaultIdtTable.PageFaultContext)); + + // + // Initialize Debug Agent to support source level debug + // + InitializeDebugAgent (DEBUG_AGENT_INIT_THUNK_PEI_IA32TOX64, (VOID *) &Ia32Idtr, NULL); + + // + // Call CapsuleDataCoalesce to process capsule. + // + Status = CapsuleDataCoalesce ( + NULL, + (EFI_PHYSICAL_ADDRESS *) (UINTN) EntrypointContext->BlockListAddr, + (MEMORY_RESOURCE_DESCRIPTOR *) (UINTN) EntrypointContext->MemoryResource, + (VOID **) (UINTN) EntrypointContext->MemoryBase64Ptr, + (UINTN *) (UINTN) EntrypointContext->MemorySize64Ptr + ); + + ReturnContext->ReturnStatus = Status; + + DEBUG (( + DEBUG_INFO, + "%a() Stack Base: 0x%lx, Stack Size: 0x%lx\n", + __FUNCTION__, + EntrypointContext->StackBufferBase, + EntrypointContext->StackBufferLength + )); + + // + // Disable interrupt of Debug timer, since the new IDT table cannot work in long mode + // + SaveAndSetDebugTimerInterrupt (FALSE); + // + // Restore IA32 IDT table + // + AsmWriteIdtr ((IA32_DESCRIPTOR *) &Ia32Idtr); + + // + // Finish to coalesce capsule, and return to 32-bit mode. + // + AsmDisablePaging64 ( + ReturnContext->ReturnCs, + (UINT32) ReturnContext->ReturnEntryPoint, + (UINT32) (UINTN) EntrypointContext, + (UINT32) (UINTN) ReturnContext, + (UINT32) (EntrypointContext->StackBufferBase + EntrypointContext->StackBufferLength) + ); + + // + // Should never be here. + // + ASSERT (FALSE); + return EFI_SUCCESS; +} |