]> git.proxmox.com Git - mirror_edk2.git/blobdiff - UefiCpuPkg/CpuDxe/CpuPageTable.c
UefiCpuPkg/CpuDxe: prevent recursive calling of InitializePageTablePool
[mirror_edk2.git] / UefiCpuPkg / CpuDxe / CpuPageTable.c
index 309f448a835763dcae70fe32977701a5802e4910..4bee8c7772b9bf3ac6342c1a49e66f7dcb95983a 100644 (file)
 \r
 #include <Base.h>\r
 #include <Uefi.h>\r
-#include <Library/BaseLib.h>\r
-#include <Library/CpuLib.h>\r
-#include <Library/BaseMemoryLib.h>\r
-#include <Library/MemoryAllocationLib.h>\r
-#include <Library/DebugLib.h>\r
-#include <Library/UefiBootServicesTableLib.h>\r
-#include <Protocol/MpService.h>\r
+#include <Library/PeCoffGetEntryPointLib.h>\r
+#include <Library/SerialPortLib.h>\r
+#include <Library/SynchronizationLib.h>\r
+#include <Library/PrintLib.h>\r
+#include <Protocol/SmmBase2.h>\r
+#include <Register/Cpuid.h>\r
+#include <Register/Msr.h>\r
 \r
 #include "CpuDxe.h"\r
 #include "CpuPageTable.h"\r
 \r
+///\r
+/// Paging registers\r
+///\r
+#define CR0_WP                      BIT16\r
+#define CR0_PG                      BIT31\r
+#define CR4_PSE                     BIT4\r
+#define CR4_PAE                     BIT5\r
+\r
 ///\r
 /// Page Table Entry\r
 ///\r
 #define PAGING_2M_ADDRESS_MASK_64 0x000FFFFFFFE00000ull\r
 #define PAGING_1G_ADDRESS_MASK_64 0x000FFFFFC0000000ull\r
 \r
+#define MAX_PF_ENTRY_COUNT        10\r
+#define MAX_DEBUG_MESSAGE_LENGTH  0x100\r
+#define IA32_PF_EC_ID             BIT4\r
+\r
 typedef enum {\r
   PageNone,\r
   Page4K,\r
@@ -87,68 +99,59 @@ PAGE_ATTRIBUTE_TABLE mPageAttributeTable[] = {
   {Page1G,  SIZE_1GB, PAGING_1G_ADDRESS_MASK_64},\r
 };\r
 \r
-/**\r
-  Enable write protection function for AP.\r
+PAGE_TABLE_POOL                   *mPageTablePool = NULL;\r
+BOOLEAN                           mPageTablePoolLock = FALSE;\r
+PAGE_TABLE_LIB_PAGING_CONTEXT     mPagingContext;\r
+EFI_SMM_BASE2_PROTOCOL            *mSmmBase2 = NULL;\r
 \r
-  @param[in,out] Buffer  The pointer to private data buffer.\r
-**/\r
-VOID\r
-EFIAPI\r
-SyncCpuEnableWriteProtection (\r
-  IN OUT VOID *Buffer\r
-  )\r
-{\r
-  AsmWriteCr0 (AsmReadCr0 () | BIT16);\r
-}\r
+//\r
+// Record the page fault exception count for one instruction execution.\r
+//\r
+UINTN                     *mPFEntryCount;\r
+UINT64                    *(*mLastPFEntryPointer)[MAX_PF_ENTRY_COUNT];\r
 \r
 /**\r
-  CpuFlushTlb function for AP.\r
-\r
-  @param[in,out] Buffer  The pointer to private data buffer.\r
+ Check if current execution environment is in SMM mode or not, via\r
+ EFI_SMM_BASE2_PROTOCOL.\r
+\r
+ This is necessary because of the fact that MdePkg\Library\SmmMemoryAllocationLib\r
+ supports to free memory outside SMRAM. The library will call gBS->FreePool() or\r
+ gBS->FreePages() and then SetMemorySpaceAttributes interface in turn to change\r
+ memory paging attributes during free operation, if some memory related features\r
+ are enabled (like Heap Guard).\r
+\r
+ This means that SetMemorySpaceAttributes() has chance to run in SMM mode. This\r
+ will cause incorrect result because SMM mode always loads its own page tables,\r
+ which are usually different from DXE. This function can be used to detect such\r
+ situation and help to avoid further misoperations.\r
+\r
+  @retval TRUE    In SMM mode.\r
+  @retval FALSE   Not in SMM mode.\r
 **/\r
-VOID\r
-EFIAPI\r
-SyncCpuFlushTlb (\r
-  IN OUT VOID *Buffer\r
+BOOLEAN\r
+IsInSmm (\r
+  VOID\r
   )\r
 {\r
-  CpuFlushTlb();\r
-}\r
+  BOOLEAN                 InSmm;\r
 \r
-/**\r
-  Sync memory page attributes for AP.\r
+  InSmm = FALSE;\r
+  if (mSmmBase2 == NULL) {\r
+    gBS->LocateProtocol (&gEfiSmmBase2ProtocolGuid, NULL, (VOID **)&mSmmBase2);\r
+  }\r
 \r
-  @param[in] Procedure            A pointer to the function to be run on enabled APs of\r
-                                  the system.\r
-**/\r
-VOID\r
-SyncMemoryPageAttributesAp (\r
-  IN EFI_AP_PROCEDURE            Procedure\r
-  )\r
-{\r
-  EFI_STATUS                Status;\r
-  EFI_MP_SERVICES_PROTOCOL  *MpService;\r
-\r
-  Status = gBS->LocateProtocol (\r
-                  &gEfiMpServiceProtocolGuid,\r
-                  NULL,\r
-                  (VOID **)&MpService\r
-                  );\r
-  //\r
-  // Synchronize the update with all APs\r
-  //\r
-  if (!EFI_ERROR (Status)) {\r
-    Status = MpService->StartupAllAPs (\r
-                          MpService,          // This\r
-                          Procedure,          // Procedure\r
-                          FALSE,              // SingleThread\r
-                          NULL,               // WaitEvent\r
-                          0,                  // TimeoutInMicrosecsond\r
-                          NULL,               // ProcedureArgument\r
-                          NULL                // FailedCpuList\r
-                          );\r
-    ASSERT (Status == EFI_SUCCESS || Status == EFI_NOT_STARTED || Status == EFI_NOT_READY);\r
+  if (mSmmBase2 != NULL) {\r
+    mSmmBase2->InSmm (mSmmBase2, &InSmm);\r
   }\r
+\r
+  //\r
+  // mSmmBase2->InSmm() can only detect if the caller is running in SMRAM\r
+  // or from SMM driver. It cannot tell if the caller is running in SMM mode.\r
+  // Check page table base address to guarantee that because SMM mode willl\r
+  // load its own page table.\r
+  //\r
+  return (InSmm &&\r
+          mPagingContext.ContextData.X64.PageTableBase != (UINT64)AsmReadCr3());\r
 }\r
 \r
 /**\r
@@ -161,49 +164,60 @@ GetCurrentPagingContext (
   IN OUT PAGE_TABLE_LIB_PAGING_CONTEXT     *PagingContext\r
   )\r
 {\r
-  UINT32                         RegEax;\r
-  UINT32                         RegEdx;\r
+  UINT32                          RegEax;\r
+  CPUID_EXTENDED_CPU_SIG_EDX      RegEdx;\r
+  MSR_IA32_EFER_REGISTER          MsrEfer;\r
 \r
-  ZeroMem(PagingContext, sizeof(*PagingContext));\r
-  if (sizeof(UINTN) == sizeof(UINT64)) {\r
-    PagingContext->MachineType = IMAGE_FILE_MACHINE_X64;\r
-  } else {\r
-    PagingContext->MachineType = IMAGE_FILE_MACHINE_I386;\r
-  }\r
-  if ((AsmReadCr0 () & BIT31) != 0) {\r
-    PagingContext->ContextData.X64.PageTableBase = (AsmReadCr3 () & PAGING_4K_ADDRESS_MASK_64);\r
-    if ((AsmReadCr0 () & BIT16) == 0) {\r
-      AsmWriteCr0 (AsmReadCr0 () | BIT16);\r
-      SyncMemoryPageAttributesAp (SyncCpuEnableWriteProtection);\r
+  //\r
+  // Don't retrieve current paging context from processor if in SMM mode.\r
+  //\r
+  if (!IsInSmm ()) {\r
+    ZeroMem (&mPagingContext, sizeof(mPagingContext));\r
+    if (sizeof(UINTN) == sizeof(UINT64)) {\r
+      mPagingContext.MachineType = IMAGE_FILE_MACHINE_X64;\r
+    } else {\r
+      mPagingContext.MachineType = IMAGE_FILE_MACHINE_I386;\r
+    }\r
+    if ((AsmReadCr0 () & CR0_PG) != 0) {\r
+      mPagingContext.ContextData.X64.PageTableBase = (AsmReadCr3 () & PAGING_4K_ADDRESS_MASK_64);\r
+    } else {\r
+      mPagingContext.ContextData.X64.PageTableBase = 0;\r
     }\r
-  } else {\r
-    PagingContext->ContextData.X64.PageTableBase = 0;\r
-  }\r
 \r
-  if ((AsmReadCr4 () & BIT4) != 0) {\r
-    PagingContext->ContextData.Ia32.Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PSE;\r
-  }\r
-  if ((AsmReadCr4 () & BIT5) != 0) {\r
-    PagingContext->ContextData.Ia32.Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PAE;\r
-  }\r
-  if ((AsmReadCr0 () & BIT16) != 0) {\r
-    PagingContext->ContextData.Ia32.Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_WP_ENABLE;\r
-  }\r
+    if ((AsmReadCr4 () & CR4_PSE) != 0) {\r
+      mPagingContext.ContextData.Ia32.Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PSE;\r
+    }\r
+    if ((AsmReadCr4 () & CR4_PAE) != 0) {\r
+      mPagingContext.ContextData.Ia32.Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PAE;\r
+    }\r
+    if ((AsmReadCr0 () & CR0_WP) != 0) {\r
+      mPagingContext.ContextData.Ia32.Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_WP_ENABLE;\r
+    }\r
 \r
-  AsmCpuid (0x80000000, &RegEax, NULL, NULL, NULL);\r
-  if (RegEax > 0x80000000) {\r
-    AsmCpuid (0x80000001, NULL, NULL, NULL, &RegEdx);\r
-    if ((RegEdx & BIT20) != 0) {\r
-      // XD supported\r
-      if ((AsmReadMsr64 (0xC0000080) & BIT11) != 0) {\r
-        // XD activated\r
-        PagingContext->ContextData.Ia32.Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_XD_ACTIVATED;\r
+    AsmCpuid (CPUID_EXTENDED_FUNCTION, &RegEax, NULL, NULL, NULL);\r
+    if (RegEax >= CPUID_EXTENDED_CPU_SIG) {\r
+      AsmCpuid (CPUID_EXTENDED_CPU_SIG, NULL, NULL, NULL, &RegEdx.Uint32);\r
+\r
+      if (RegEdx.Bits.NX != 0) {\r
+        // XD supported\r
+        MsrEfer.Uint64 = AsmReadMsr64(MSR_CORE_IA32_EFER);\r
+        if (MsrEfer.Bits.NXE != 0) {\r
+          // XD activated\r
+          mPagingContext.ContextData.Ia32.Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_XD_ACTIVATED;\r
+        }\r
+      }\r
+\r
+      if (RegEdx.Bits.Page1GB != 0) {\r
+        mPagingContext.ContextData.Ia32.Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PAGE_1G_SUPPORT;\r
       }\r
-    }\r
-    if ((RegEdx & BIT26) != 0) {\r
-      PagingContext->ContextData.Ia32.Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PAGE_1G_SUPPORT;\r
     }\r
   }\r
+\r
+  //\r
+  // This can avoid getting SMM paging context if in SMM mode. We cannot assume\r
+  // SMM mode shares the same paging context as DXE.\r
+  //\r
+  CopyMem (PagingContext, &mPagingContext, sizeof (mPagingContext));\r
 }\r
 \r
 /**\r
@@ -442,8 +456,8 @@ ConvertPageEntryAttribute (
   *PageEntry = NewPageEntry;\r
   if (CurrentPageEntry != NewPageEntry) {\r
     *IsModified = TRUE;\r
-    DEBUG ((DEBUG_VERBOSE, "ConvertPageEntryAttribute 0x%lx", CurrentPageEntry));
-    DEBUG ((DEBUG_VERBOSE, "->0x%lx\n", NewPageEntry));
+    DEBUG ((DEBUG_VERBOSE, "ConvertPageEntryAttribute 0x%lx", CurrentPageEntry));\r
+    DEBUG ((DEBUG_VERBOSE, "->0x%lx\n", NewPageEntry));\r
   } else {\r
     *IsModified = FALSE;\r
   }\r
@@ -522,7 +536,7 @@ SplitPage (
     ASSERT (SplitAttribute == Page4K);\r
     if (SplitAttribute == Page4K) {\r
       NewPageEntry = AllocatePagesFunc (1);\r
-      DEBUG ((DEBUG_INFO, "Split - 0x%x\n", NewPageEntry));\r
+      DEBUG ((DEBUG_VERBOSE, "Split - 0x%x\n", NewPageEntry));\r
       if (NewPageEntry == NULL) {\r
         return RETURN_OUT_OF_RESOURCES;\r
       }\r
@@ -530,7 +544,7 @@ SplitPage (
       for (Index = 0; Index < SIZE_4KB / sizeof(UINT64); Index++) {\r
         NewPageEntry[Index] = (BaseAddress + SIZE_4KB * Index) | AddressEncMask | ((*PageEntry) & PAGE_PROGATE_BITS);\r
       }\r
-      (*PageEntry) = (UINT64)(UINTN)NewPageEntry | AddressEncMask | ((*PageEntry) & PAGE_PROGATE_BITS);\r
+      (*PageEntry) = (UINT64)(UINTN)NewPageEntry | AddressEncMask | ((*PageEntry) & PAGE_ATTRIBUTE_BITS);\r
       return RETURN_SUCCESS;\r
     } else {\r
       return RETURN_UNSUPPORTED;\r
@@ -543,7 +557,7 @@ SplitPage (
     ASSERT (SplitAttribute == Page2M || SplitAttribute == Page4K);\r
     if ((SplitAttribute == Page2M || SplitAttribute == Page4K)) {\r
       NewPageEntry = AllocatePagesFunc (1);\r
-      DEBUG ((DEBUG_INFO, "Split - 0x%x\n", NewPageEntry));\r
+      DEBUG ((DEBUG_VERBOSE, "Split - 0x%x\n", NewPageEntry));\r
       if (NewPageEntry == NULL) {\r
         return RETURN_OUT_OF_RESOURCES;\r
       }\r
@@ -551,7 +565,7 @@ SplitPage (
       for (Index = 0; Index < SIZE_4KB / sizeof(UINT64); Index++) {\r
         NewPageEntry[Index] = (BaseAddress + SIZE_2MB * Index) | AddressEncMask | IA32_PG_PS | ((*PageEntry) & PAGE_PROGATE_BITS);\r
       }\r
-      (*PageEntry) = (UINT64)(UINTN)NewPageEntry | AddressEncMask | ((*PageEntry) & PAGE_PROGATE_BITS);\r
+      (*PageEntry) = (UINT64)(UINTN)NewPageEntry | AddressEncMask | ((*PageEntry) & PAGE_ATTRIBUTE_BITS);\r
       return RETURN_SUCCESS;\r
     } else {\r
       return RETURN_UNSUPPORTED;\r
@@ -561,6 +575,62 @@ SplitPage (
   }\r
 }\r
 \r
+/**\r
+ Check the WP status in CR0 register. This bit is used to lock or unlock write\r
+ access to pages marked as read-only.\r
+\r
+  @retval TRUE    Write protection is enabled.\r
+  @retval FALSE   Write protection is disabled.\r
+**/\r
+BOOLEAN\r
+IsReadOnlyPageWriteProtected (\r
+  VOID\r
+  )\r
+{\r
+  //\r
+  // To avoid unforseen consequences, don't touch paging settings in SMM mode\r
+  // in this driver.\r
+  //\r
+  if (!IsInSmm ()) {\r
+    return ((AsmReadCr0 () & CR0_WP) != 0);\r
+  }\r
+  return FALSE;\r
+}\r
+\r
+/**\r
+ Disable Write Protect on pages marked as read-only.\r
+**/\r
+VOID\r
+DisableReadOnlyPageWriteProtect (\r
+  VOID\r
+  )\r
+{\r
+  //\r
+  // To avoid unforseen consequences, don't touch paging settings in SMM mode\r
+  // in this driver.\r
+  //\r
+  if (!IsInSmm ()) {\r
+    AsmWriteCr0 (AsmReadCr0 () & ~CR0_WP);\r
+  }\r
+}\r
+\r
+/**\r
+ Enable Write Protect on pages marked as read-only.\r
+**/\r
+VOID\r
+EnableReadOnlyPageWriteProtect (\r
+  VOID\r
+  )\r
+{\r
+  //\r
+  // To avoid unforseen consequences, don't touch paging settings in SMM mode\r
+  // in this driver.\r
+  //\r
+  if (!IsInSmm ()) {\r
+    AsmWriteCr0 (AsmReadCr0 () | CR0_WP);\r
+  }\r
+}\r
+\r
 /**\r
   This function modifies the page attributes for the memory region specified by BaseAddress and\r
   Length from their current attributes to the attributes specified by Attributes.\r
@@ -609,6 +679,7 @@ ConvertMemoryPageAttributes (
   PAGE_ATTRIBUTE                    SplitAttribute;\r
   RETURN_STATUS                     Status;\r
   BOOLEAN                           IsEntryModified;\r
+  BOOLEAN                           IsWpEnabled;\r
 \r
   if ((BaseAddress & (SIZE_4KB - 1)) != 0) {\r
     DEBUG ((DEBUG_ERROR, "BaseAddress(0x%lx) is not aligned!\n", BaseAddress));\r
@@ -647,6 +718,10 @@ ConvertMemoryPageAttributes (
       DEBUG ((DEBUG_ERROR, "Non-PAE Paging!\n"));\r
       return EFI_UNSUPPORTED;\r
     }\r
+    if ((BaseAddress + Length) > BASE_4GB) {\r
+      DEBUG ((DEBUG_ERROR, "Beyond 4GB memory in 32-bit mode!\n"));\r
+      return EFI_UNSUPPORTED;\r
+    }\r
     break;\r
   case IMAGE_FILE_MACHINE_X64:\r
     ASSERT (CurrentPagingContext.ContextData.X64.PageTableBase != 0);\r
@@ -665,14 +740,27 @@ ConvertMemoryPageAttributes (
   if (IsModified != NULL) {\r
     *IsModified = FALSE;\r
   }\r
+  if (AllocatePagesFunc == NULL) {\r
+    AllocatePagesFunc = AllocatePageTableMemory;\r
+  }\r
+\r
+  //\r
+  // Make sure that the page table is changeable.\r
+  //\r
+  IsWpEnabled = IsReadOnlyPageWriteProtected ();\r
+  if (IsWpEnabled) {\r
+    DisableReadOnlyPageWriteProtect ();\r
+  }\r
 \r
   //\r
   // Below logic is to check 2M/4K page to make sure we donot waist memory.\r
   //\r
+  Status = EFI_SUCCESS;\r
   while (Length != 0) {\r
     PageEntry = GetPageTableEntry (&CurrentPagingContext, BaseAddress, &PageAttribute);\r
     if (PageEntry == NULL) {\r
-      return RETURN_UNSUPPORTED;\r
+      Status = RETURN_UNSUPPORTED;\r
+      goto Done;\r
     }\r
     PageEntryLength = PageAttributeToLength (PageAttribute);\r
     SplitAttribute = NeedSplitPage (BaseAddress, Length, PageEntry, PageAttribute);\r
@@ -690,11 +778,13 @@ ConvertMemoryPageAttributes (
       Length -= PageEntryLength;\r
     } else {\r
       if (AllocatePagesFunc == NULL) {\r
-        return RETURN_UNSUPPORTED;\r
+        Status = RETURN_UNSUPPORTED;\r
+        goto Done;\r
       }\r
       Status = SplitPage (PageEntry, PageAttribute, SplitAttribute, AllocatePagesFunc);\r
       if (RETURN_ERROR (Status)) {\r
-        return RETURN_UNSUPPORTED;\r
+        Status = RETURN_UNSUPPORTED;\r
+        goto Done;\r
       }\r
       if (IsSplitted != NULL) {\r
         *IsSplitted = TRUE;\r
@@ -709,7 +799,14 @@ ConvertMemoryPageAttributes (
     }\r
   }\r
 \r
-  return RETURN_SUCCESS;\r
+Done:\r
+  //\r
+  // Restore page table write protection, if any.\r
+  //\r
+  if (IsWpEnabled) {\r
+    EnableReadOnlyPageWriteProtect ();\r
+  }\r
+  return Status;\r
 }\r
 \r
 /**\r
@@ -759,16 +856,33 @@ AssignMemoryPageAttributes (
   if (!EFI_ERROR(Status)) {\r
     if ((PagingContext == NULL) && IsModified) {\r
       //\r
-      // Flush TLB as last step\r
+      // Flush TLB as last step.\r
+      //\r
+      // Note: Since APs will always init CR3 register in HLT loop mode or do\r
+      // TLB flush in MWAIT loop mode, there's no need to flush TLB for them\r
+      // here.\r
       //\r
       CpuFlushTlb();\r
-      SyncMemoryPageAttributesAp (SyncCpuFlushTlb);\r
     }\r
   }\r
 \r
   return Status;\r
 }\r
 \r
+/**\r
+ Check if Execute Disable feature is enabled or not.\r
+**/\r
+BOOLEAN\r
+IsExecuteDisableEnabled (\r
+  VOID\r
+  )\r
+{\r
+  MSR_CORE_IA32_EFER_REGISTER    MsrEfer;\r
+\r
+  MsrEfer.Uint64 = AsmReadMsr64 (MSR_IA32_EFER);\r
+  return (MsrEfer.Bits.NXE == 1);\r
+}\r
+\r
 /**\r
   Update GCD memory space attributes according to current page table setup.\r
 **/\r
@@ -790,7 +904,7 @@ RefreshGcdMemoryAttributesFromPaging (
   UINT64                              PageStartAddress;\r
   UINT64                              Attributes;\r
   UINT64                              Capabilities;\r
-  BOOLEAN                             DoUpdate;\r
+  UINT64                              NewAttributes;\r
   UINTN                               Index;\r
 \r
   //\r
@@ -802,17 +916,50 @@ RefreshGcdMemoryAttributesFromPaging (
 \r
   GetCurrentPagingContext (&PagingContext);\r
 \r
-  DoUpdate      = FALSE;\r
-  Capabilities  = 0;\r
-  Attributes    = 0;\r
-  BaseAddress   = 0;\r
-  PageLength    = 0;\r
+  Attributes      = 0;\r
+  NewAttributes   = 0;\r
+  BaseAddress     = 0;\r
+  PageLength      = 0;\r
+\r
+  if (IsExecuteDisableEnabled ()) {\r
+    Capabilities = EFI_MEMORY_RO | EFI_MEMORY_RP | EFI_MEMORY_XP;\r
+  } else {\r
+    Capabilities = EFI_MEMORY_RO | EFI_MEMORY_RP;\r
+  }\r
 \r
   for (Index = 0; Index < NumberOfDescriptors; Index++) {\r
     if (MemorySpaceMap[Index].GcdMemoryType == EfiGcdMemoryTypeNonExistent) {\r
       continue;\r
     }\r
 \r
+    //\r
+    // Sync the actual paging related capabilities back to GCD service first.\r
+    // As a side effect (good one), this can also help to avoid unnecessary\r
+    // memory map entries due to the different capabilities of the same type\r
+    // memory, such as multiple RT_CODE and RT_DATA entries in memory map,\r
+    // which could cause boot failure of some old Linux distro (before v4.3).\r
+    //\r
+    Status = gDS->SetMemorySpaceCapabilities (\r
+                    MemorySpaceMap[Index].BaseAddress,\r
+                    MemorySpaceMap[Index].Length,\r
+                    MemorySpaceMap[Index].Capabilities | Capabilities\r
+                    );\r
+    if (EFI_ERROR (Status)) {\r
+      //\r
+      // If we cannot udpate the capabilities, we cannot update its\r
+      // attributes either. So just simply skip current block of memory.\r
+      //\r
+      DEBUG ((\r
+        DEBUG_WARN,\r
+        "Failed to update capability: [%lu] %016lx - %016lx (%016lx -> %016lx)\r\n",\r
+        (UINT64)Index, MemorySpaceMap[Index].BaseAddress,\r
+        MemorySpaceMap[Index].BaseAddress + MemorySpaceMap[Index].Length - 1,\r
+        MemorySpaceMap[Index].Capabilities,\r
+        MemorySpaceMap[Index].Capabilities | Capabilities\r
+        ));\r
+      continue;\r
+    }\r
+\r
     if (MemorySpaceMap[Index].BaseAddress >= (BaseAddress + PageLength)) {\r
       //\r
       // Current memory space starts at a new page. Resetting PageLength will\r
@@ -826,7 +973,9 @@ RefreshGcdMemoryAttributesFromPaging (
       PageLength -= (MemorySpaceMap[Index].BaseAddress - BaseAddress);\r
     }\r
 \r
-    // Sync real page attributes to GCD\r
+    //\r
+    // Sync actual page attributes to GCD\r
+    //\r
     BaseAddress       = MemorySpaceMap[Index].BaseAddress;\r
     MemorySpaceLength = MemorySpaceMap[Index].Length;\r
     while (MemorySpaceLength > 0) {\r
@@ -842,23 +991,26 @@ RefreshGcdMemoryAttributesFromPaging (
         PageStartAddress  = (*PageEntry) & (UINT64)PageAttributeToMask(PageAttribute);\r
         PageLength        = PageAttributeToLength (PageAttribute) - (BaseAddress - PageStartAddress);\r
         Attributes        = GetAttributesFromPageEntry (PageEntry);\r
-\r
-        if (Attributes != (MemorySpaceMap[Index].Attributes & EFI_MEMORY_PAGETYPE_MASK)) {\r
-          DoUpdate = TRUE;\r
-          Attributes |= (MemorySpaceMap[Index].Attributes & ~EFI_MEMORY_PAGETYPE_MASK);\r
-          Capabilities = Attributes | MemorySpaceMap[Index].Capabilities;\r
-        } else {\r
-          DoUpdate = FALSE;\r
-        }\r
       }\r
 \r
       Length = MIN (PageLength, MemorySpaceLength);\r
-      if (DoUpdate) {\r
-        gDS->SetMemorySpaceCapabilities (BaseAddress, Length, Capabilities);\r
-        gDS->SetMemorySpaceAttributes (BaseAddress, Length, Attributes);\r
-        DEBUG ((DEBUG_INFO, "Update memory space attribute: [%02d] %016lx - %016lx (%08lx -> %08lx)\r\n",\r
-                             Index, BaseAddress, BaseAddress + Length - 1,\r
-                             MemorySpaceMap[Index].Attributes, Attributes));\r
+      if (Attributes != (MemorySpaceMap[Index].Attributes &\r
+                         EFI_MEMORY_PAGETYPE_MASK)) {\r
+        NewAttributes = (MemorySpaceMap[Index].Attributes &\r
+                         ~EFI_MEMORY_PAGETYPE_MASK) | Attributes;\r
+        Status = gDS->SetMemorySpaceAttributes (\r
+                        BaseAddress,\r
+                        Length,\r
+                        NewAttributes\r
+                        );\r
+        ASSERT_EFI_ERROR (Status);\r
+        DEBUG ((\r
+          DEBUG_VERBOSE,\r
+          "Updated memory space attribute: [%lu] %016lx - %016lx (%016lx -> %016lx)\r\n",\r
+          (UINT64)Index, BaseAddress, BaseAddress + Length - 1,\r
+          MemorySpaceMap[Index].Attributes,\r
+          NewAttributes\r
+          ));\r
       }\r
 \r
       PageLength        -= Length;\r
@@ -870,6 +1022,289 @@ RefreshGcdMemoryAttributesFromPaging (
   FreePool (MemorySpaceMap);\r
 }\r
 \r
+/**\r
+  Initialize a buffer pool for page table use only.\r
+\r
+  To reduce the potential split operation on page table, the pages reserved for\r
+  page table should be allocated in the times of PAGE_TABLE_POOL_UNIT_PAGES and\r
+  at the boundary of PAGE_TABLE_POOL_ALIGNMENT. So the page pool is always\r
+  initialized with number of pages greater than or equal to the given PoolPages.\r
+\r
+  Once the pages in the pool are used up, this method should be called again to\r
+  reserve at least another PAGE_TABLE_POOL_UNIT_PAGES. Usually this won't happen\r
+  often in practice.\r
+\r
+  @param[in] PoolPages      The least page number of the pool to be created.\r
+\r
+  @retval TRUE    The pool is initialized successfully.\r
+  @retval FALSE   The memory is out of resource.\r
+**/\r
+BOOLEAN\r
+InitializePageTablePool (\r
+  IN  UINTN                           PoolPages\r
+  )\r
+{\r
+  VOID                      *Buffer;\r
+  BOOLEAN                   IsModified;\r
+\r
+  //\r
+  // Do not allow re-entrance.\r
+  //\r
+  if (mPageTablePoolLock) {\r
+    return FALSE;\r
+  }\r
+\r
+  mPageTablePoolLock = TRUE;\r
+  IsModified = FALSE;\r
+\r
+  //\r
+  // Always reserve at least PAGE_TABLE_POOL_UNIT_PAGES, including one page for\r
+  // header.\r
+  //\r
+  PoolPages += 1;   // Add one page for header.\r
+  PoolPages = ((PoolPages - 1) / PAGE_TABLE_POOL_UNIT_PAGES + 1) *\r
+              PAGE_TABLE_POOL_UNIT_PAGES;\r
+  Buffer = AllocateAlignedPages (PoolPages, PAGE_TABLE_POOL_ALIGNMENT);\r
+  if (Buffer == NULL) {\r
+    DEBUG ((DEBUG_ERROR, "ERROR: Out of aligned pages\r\n"));\r
+    goto Done;\r
+  }\r
+\r
+  DEBUG ((\r
+    DEBUG_INFO,\r
+    "Paging: added %lu pages to page table pool\r\n",\r
+    (UINT64)PoolPages\r
+    ));\r
+\r
+  //\r
+  // Link all pools into a list for easier track later.\r
+  //\r
+  if (mPageTablePool == NULL) {\r
+    mPageTablePool = Buffer;\r
+    mPageTablePool->NextPool = mPageTablePool;\r
+  } else {\r
+    ((PAGE_TABLE_POOL *)Buffer)->NextPool = mPageTablePool->NextPool;\r
+    mPageTablePool->NextPool = Buffer;\r
+    mPageTablePool = Buffer;\r
+  }\r
+\r
+  //\r
+  // Reserve one page for pool header.\r
+  //\r
+  mPageTablePool->FreePages  = PoolPages - 1;\r
+  mPageTablePool->Offset = EFI_PAGES_TO_SIZE (1);\r
+\r
+  //\r
+  // Mark the whole pool pages as read-only.\r
+  //\r
+  ConvertMemoryPageAttributes (\r
+    NULL,\r
+    (PHYSICAL_ADDRESS)(UINTN)Buffer,\r
+    EFI_PAGES_TO_SIZE (PoolPages),\r
+    EFI_MEMORY_RO,\r
+    PageActionSet,\r
+    AllocatePageTableMemory,\r
+    NULL,\r
+    &IsModified\r
+    );\r
+  ASSERT (IsModified == TRUE);\r
+\r
+Done:\r
+  mPageTablePoolLock = FALSE;\r
+  return IsModified;\r
+}\r
+\r
+/**\r
+  This API provides a way to allocate memory for page table.\r
+\r
+  This API can be called more than once to allocate memory for page tables.\r
+\r
+  Allocates the number of 4KB pages and returns a pointer to the allocated\r
+  buffer. The buffer returned is aligned on a 4KB boundary.\r
+\r
+  If Pages is 0, then NULL is returned.\r
+  If there is not enough memory remaining to satisfy the request, then NULL is\r
+  returned.\r
+\r
+  @param  Pages                 The number of 4 KB pages to allocate.\r
+\r
+  @return A pointer to the allocated buffer or NULL if allocation fails.\r
+\r
+**/\r
+VOID *\r
+EFIAPI\r
+AllocatePageTableMemory (\r
+  IN UINTN           Pages\r
+  )\r
+{\r
+  VOID                            *Buffer;\r
+\r
+  if (Pages == 0) {\r
+    return NULL;\r
+  }\r
+\r
+  //\r
+  // Renew the pool if necessary.\r
+  //\r
+  if (mPageTablePool == NULL ||\r
+      Pages > mPageTablePool->FreePages) {\r
+    if (!InitializePageTablePool (Pages)) {\r
+      return NULL;\r
+    }\r
+  }\r
+\r
+  Buffer = (UINT8 *)mPageTablePool + mPageTablePool->Offset;\r
+\r
+  mPageTablePool->Offset     += EFI_PAGES_TO_SIZE (Pages);\r
+  mPageTablePool->FreePages  -= Pages;\r
+\r
+  return Buffer;\r
+}\r
+\r
+/**\r
+  Special handler for #DB exception, which will restore the page attributes\r
+  (not-present). It should work with #PF handler which will set pages to\r
+  'present'.\r
+\r
+  @param ExceptionType  Exception type.\r
+  @param SystemContext  Pointer to EFI_SYSTEM_CONTEXT.\r
+\r
+**/\r
+VOID\r
+EFIAPI\r
+DebugExceptionHandler (\r
+  IN EFI_EXCEPTION_TYPE   ExceptionType,\r
+  IN EFI_SYSTEM_CONTEXT   SystemContext\r
+  )\r
+{\r
+  UINTN     CpuIndex;\r
+  UINTN     PFEntry;\r
+  BOOLEAN   IsWpEnabled;\r
+\r
+  MpInitLibWhoAmI (&CpuIndex);\r
+\r
+  //\r
+  // Clear last PF entries\r
+  //\r
+  IsWpEnabled = IsReadOnlyPageWriteProtected ();\r
+  if (IsWpEnabled) {\r
+    DisableReadOnlyPageWriteProtect ();\r
+  }\r
+\r
+  for (PFEntry = 0; PFEntry < mPFEntryCount[CpuIndex]; PFEntry++) {\r
+    if (mLastPFEntryPointer[CpuIndex][PFEntry] != NULL) {\r
+      *mLastPFEntryPointer[CpuIndex][PFEntry] &= ~(UINT64)IA32_PG_P;\r
+    }\r
+  }\r
+\r
+  if (IsWpEnabled) {\r
+    EnableReadOnlyPageWriteProtect ();\r
+  }\r
+\r
+  //\r
+  // Reset page fault exception count for next page fault.\r
+  //\r
+  mPFEntryCount[CpuIndex] = 0;\r
+\r
+  //\r
+  // Flush TLB\r
+  //\r
+  CpuFlushTlb ();\r
+\r
+  //\r
+  // Clear TF in EFLAGS\r
+  //\r
+  if (mPagingContext.MachineType == IMAGE_FILE_MACHINE_I386) {\r
+    SystemContext.SystemContextIa32->Eflags &= (UINT32)~BIT8;\r
+  } else {\r
+    SystemContext.SystemContextX64->Rflags &= (UINT64)~BIT8;\r
+  }\r
+}\r
+\r
+/**\r
+  Special handler for #PF exception, which will set the pages which caused\r
+  #PF to be 'present'. The attribute of those pages should be restored in\r
+  the subsequent #DB handler.\r
+\r
+  @param ExceptionType  Exception type.\r
+  @param SystemContext  Pointer to EFI_SYSTEM_CONTEXT.\r
+\r
+**/\r
+VOID\r
+EFIAPI\r
+PageFaultExceptionHandler (\r
+  IN EFI_EXCEPTION_TYPE   ExceptionType,\r
+  IN EFI_SYSTEM_CONTEXT   SystemContext\r
+  )\r
+{\r
+  EFI_STATUS                      Status;\r
+  UINT64                          PFAddress;\r
+  PAGE_TABLE_LIB_PAGING_CONTEXT   PagingContext;\r
+  PAGE_ATTRIBUTE                  PageAttribute;\r
+  UINT64                          Attributes;\r
+  UINT64                          *PageEntry;\r
+  UINTN                           Index;\r
+  UINTN                           CpuIndex;\r
+  UINTN                           PageNumber;\r
+  BOOLEAN                         NonStopMode;\r
+\r
+  PFAddress = AsmReadCr2 () & ~EFI_PAGE_MASK;\r
+  if (PFAddress < BASE_4KB) {\r
+    NonStopMode = NULL_DETECTION_NONSTOP_MODE ? TRUE : FALSE;\r
+  } else {\r
+    NonStopMode = HEAP_GUARD_NONSTOP_MODE ? TRUE : FALSE;\r
+  }\r
+\r
+  if (NonStopMode) {\r
+    MpInitLibWhoAmI (&CpuIndex);\r
+    GetCurrentPagingContext (&PagingContext);\r
+    //\r
+    // Memory operation cross page boundary, like "rep mov" instruction, will\r
+    // cause infinite loop between this and Debug Trap handler. We have to make\r
+    // sure that current page and the page followed are both in PRESENT state.\r
+    //\r
+    PageNumber = 2;\r
+    while (PageNumber > 0) {\r
+      PageEntry = GetPageTableEntry (&PagingContext, PFAddress, &PageAttribute);\r
+      ASSERT(PageEntry != NULL);\r
+\r
+      if (PageEntry != NULL) {\r
+        Attributes = GetAttributesFromPageEntry (PageEntry);\r
+        if ((Attributes & EFI_MEMORY_RP) != 0) {\r
+          Attributes &= ~EFI_MEMORY_RP;\r
+          Status = AssignMemoryPageAttributes (&PagingContext, PFAddress,\r
+                                               EFI_PAGE_SIZE, Attributes, NULL);\r
+          if (!EFI_ERROR(Status)) {\r
+            Index = mPFEntryCount[CpuIndex];\r
+            //\r
+            // Re-retrieve page entry because above calling might update page\r
+            // table due to table split.\r
+            //\r
+            PageEntry = GetPageTableEntry (&PagingContext, PFAddress, &PageAttribute);\r
+            mLastPFEntryPointer[CpuIndex][Index++] = PageEntry;\r
+            mPFEntryCount[CpuIndex] = Index;\r
+          }\r
+        }\r
+      }\r
+\r
+      PFAddress += EFI_PAGE_SIZE;\r
+      --PageNumber;\r
+    }\r
+  }\r
+\r
+  //\r
+  // Initialize the serial port before dumping.\r
+  //\r
+  SerialPortInitialize ();\r
+  //\r
+  // Display ExceptionType, CPU information and Image information\r
+  //\r
+  DumpCpuContext (ExceptionType, SystemContext);\r
+  if (!NonStopMode) {\r
+    CpuDeadLoop ();\r
+  }\r
+}\r
+\r
 /**\r
   Initialize the Page Table lib.\r
 **/\r
@@ -881,6 +1316,27 @@ InitializePageTableLib (
   PAGE_TABLE_LIB_PAGING_CONTEXT     CurrentPagingContext;\r
 \r
   GetCurrentPagingContext (&CurrentPagingContext);\r
+\r
+  //\r
+  // Reserve memory of page tables for future uses, if paging is enabled.\r
+  //\r
+  if (CurrentPagingContext.ContextData.X64.PageTableBase != 0 &&\r
+      (CurrentPagingContext.ContextData.Ia32.Attributes &\r
+       PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PAE) != 0) {\r
+    DisableReadOnlyPageWriteProtect ();\r
+    InitializePageTablePool (1);\r
+    EnableReadOnlyPageWriteProtect ();\r
+  }\r
+\r
+  if (HEAP_GUARD_NONSTOP_MODE || NULL_DETECTION_NONSTOP_MODE) {\r
+    mPFEntryCount = (UINTN *)AllocateZeroPool (sizeof (UINTN) * mNumberOfProcessors);\r
+    ASSERT (mPFEntryCount != NULL);\r
+\r
+    mLastPFEntryPointer = (UINT64 *(*)[MAX_PF_ENTRY_COUNT])\r
+                          AllocateZeroPool (sizeof (mLastPFEntryPointer[0]) * mNumberOfProcessors);\r
+    ASSERT (mLastPFEntryPointer != NULL);\r
+  }\r
+\r
   DEBUG ((DEBUG_INFO, "CurrentPagingContext:\n", CurrentPagingContext.MachineType));\r
   DEBUG ((DEBUG_INFO, "  MachineType   - 0x%x\n", CurrentPagingContext.MachineType));\r
   DEBUG ((DEBUG_INFO, "  PageTableBase - 0x%x\n", CurrentPagingContext.ContextData.X64.PageTableBase));\r