1 /** @file
2   Page table management support.
3 
4   Copyright (c) 2017 - 2019, Intel Corporation. All rights reserved.<BR>
5   Copyright (c) 2017, AMD Incorporated. All rights reserved.<BR>
6 
7   SPDX-License-Identifier: BSD-2-Clause-Patent
8 
9 **/
10 
11 #include <Base.h>
12 #include <Uefi.h>
13 #include <Library/PeCoffGetEntryPointLib.h>
14 #include <Library/SerialPortLib.h>
15 #include <Library/SynchronizationLib.h>
16 #include <Library/PrintLib.h>
17 #include <Protocol/SmmBase2.h>
18 #include <Register/Intel/Cpuid.h>
19 #include <Register/Intel/Msr.h>
20 
21 #include "CpuDxe.h"
22 #include "CpuPageTable.h"
23 
24 ///
25 /// Page Table Entry
26 ///
27 #define IA32_PG_P                   BIT0
28 #define IA32_PG_RW                  BIT1
29 #define IA32_PG_U                   BIT2
30 #define IA32_PG_WT                  BIT3
31 #define IA32_PG_CD                  BIT4
32 #define IA32_PG_A                   BIT5
33 #define IA32_PG_D                   BIT6
34 #define IA32_PG_PS                  BIT7
35 #define IA32_PG_PAT_2M              BIT12
36 #define IA32_PG_PAT_4K              IA32_PG_PS
37 #define IA32_PG_PMNT                BIT62
38 #define IA32_PG_NX                  BIT63
39 
40 #define PAGE_ATTRIBUTE_BITS         (IA32_PG_D | IA32_PG_A | IA32_PG_U | IA32_PG_RW | IA32_PG_P)
41 //
42 // Bits 1, 2, 5, 6 are reserved in the IA32 PAE PDPTE
43 // X64 PAE PDPTE does not have such restriction
44 //
45 #define IA32_PAE_PDPTE_ATTRIBUTE_BITS    (IA32_PG_P)
46 
47 #define PAGE_PROGATE_BITS           (IA32_PG_NX | PAGE_ATTRIBUTE_BITS)
48 
49 #define PAGING_4K_MASK  0xFFF
50 #define PAGING_2M_MASK  0x1FFFFF
51 #define PAGING_1G_MASK  0x3FFFFFFF
52 
53 #define PAGING_PAE_INDEX_MASK  0x1FF
54 
55 #define PAGING_4K_ADDRESS_MASK_64 0x000FFFFFFFFFF000ull
56 #define PAGING_2M_ADDRESS_MASK_64 0x000FFFFFFFE00000ull
57 #define PAGING_1G_ADDRESS_MASK_64 0x000FFFFFC0000000ull
58 
59 #define MAX_PF_ENTRY_COUNT        10
60 #define MAX_DEBUG_MESSAGE_LENGTH  0x100
61 #define IA32_PF_EC_ID             BIT4
62 
63 typedef enum {
64   PageNone,
65   Page4K,
66   Page2M,
67   Page1G,
68 } PAGE_ATTRIBUTE;
69 
70 typedef struct {
71   PAGE_ATTRIBUTE   Attribute;
72   UINT64           Length;
73   UINT64           AddressMask;
74 } PAGE_ATTRIBUTE_TABLE;
75 
76 typedef enum {
77   PageActionAssign,
78   PageActionSet,
79   PageActionClear,
80 } PAGE_ACTION;
81 
82 PAGE_ATTRIBUTE_TABLE mPageAttributeTable[] = {
83   {Page4K,  SIZE_4KB, PAGING_4K_ADDRESS_MASK_64},
84   {Page2M,  SIZE_2MB, PAGING_2M_ADDRESS_MASK_64},
85   {Page1G,  SIZE_1GB, PAGING_1G_ADDRESS_MASK_64},
86 };
87 
88 PAGE_TABLE_POOL                   *mPageTablePool = NULL;
89 BOOLEAN                           mPageTablePoolLock = FALSE;
90 PAGE_TABLE_LIB_PAGING_CONTEXT     mPagingContext;
91 EFI_SMM_BASE2_PROTOCOL            *mSmmBase2 = NULL;
92 
93 //
94 // Record the page fault exception count for one instruction execution.
95 //
96 UINTN                     *mPFEntryCount;
97 UINT64                    *(*mLastPFEntryPointer)[MAX_PF_ENTRY_COUNT];
98 
99 /**
100  Check if current execution environment is in SMM mode or not, via
101  EFI_SMM_BASE2_PROTOCOL.
102 
103  This is necessary because of the fact that MdePkg\Library\SmmMemoryAllocationLib
104  supports to free memory outside SMRAM. The library will call gBS->FreePool() or
105  gBS->FreePages() and then SetMemorySpaceAttributes interface in turn to change
106  memory paging attributes during free operation, if some memory related features
107  are enabled (like Heap Guard).
108 
109  This means that SetMemorySpaceAttributes() has chance to run in SMM mode. This
110  will cause incorrect result because SMM mode always loads its own page tables,
111  which are usually different from DXE. This function can be used to detect such
112  situation and help to avoid further misoperations.
113 
114   @retval TRUE    In SMM mode.
115   @retval FALSE   Not in SMM mode.
116 **/
117 BOOLEAN
IsInSmm(VOID)118 IsInSmm (
119   VOID
120   )
121 {
122   BOOLEAN                 InSmm;
123 
124   InSmm = FALSE;
125   if (mSmmBase2 == NULL) {
126     gBS->LocateProtocol (&gEfiSmmBase2ProtocolGuid, NULL, (VOID **)&mSmmBase2);
127   }
128 
129   if (mSmmBase2 != NULL) {
130     mSmmBase2->InSmm (mSmmBase2, &InSmm);
131   }
132 
133   //
134   // mSmmBase2->InSmm() can only detect if the caller is running in SMRAM
135   // or from SMM driver. It cannot tell if the caller is running in SMM mode.
136   // Check page table base address to guarantee that because SMM mode willl
137   // load its own page table.
138   //
139   return (InSmm &&
140           mPagingContext.ContextData.X64.PageTableBase != (UINT64)AsmReadCr3());
141 }
142 
143 /**
144   Return current paging context.
145 
146   @param[in,out]  PagingContext     The paging context.
147 **/
148 VOID
GetCurrentPagingContext(IN OUT PAGE_TABLE_LIB_PAGING_CONTEXT * PagingContext)149 GetCurrentPagingContext (
150   IN OUT PAGE_TABLE_LIB_PAGING_CONTEXT     *PagingContext
151   )
152 {
153   UINT32                          RegEax;
154   CPUID_EXTENDED_CPU_SIG_EDX      RegEdx;
155   MSR_IA32_EFER_REGISTER          MsrEfer;
156   IA32_CR4                        Cr4;
157   IA32_CR0                        Cr0;
158   UINT32                          *Attributes;
159   UINTN                           *PageTableBase;
160 
161   //
162   // Don't retrieve current paging context from processor if in SMM mode.
163   //
164   if (!IsInSmm ()) {
165     ZeroMem (&mPagingContext, sizeof(mPagingContext));
166     if (sizeof(UINTN) == sizeof(UINT64)) {
167       mPagingContext.MachineType = IMAGE_FILE_MACHINE_X64;
168     } else {
169       mPagingContext.MachineType = IMAGE_FILE_MACHINE_I386;
170     }
171 
172     GetPagingDetails (&mPagingContext.ContextData, &PageTableBase, &Attributes);
173 
174     Cr0.UintN = AsmReadCr0 ();
175     Cr4.UintN = AsmReadCr4 ();
176 
177     if (Cr0.Bits.PG != 0) {
178       *PageTableBase = (AsmReadCr3 () & PAGING_4K_ADDRESS_MASK_64);
179     } else {
180       *PageTableBase = 0;
181     }
182     if (Cr0.Bits.WP  != 0) {
183       *Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_WP_ENABLE;
184     }
185     if (Cr4.Bits.PSE != 0) {
186       *Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PSE;
187     }
188     if (Cr4.Bits.PAE != 0) {
189       *Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PAE;
190     }
191     if (Cr4.Bits.LA57 != 0) {
192       *Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_5_LEVEL;
193     }
194 
195     AsmCpuid (CPUID_EXTENDED_FUNCTION, &RegEax, NULL, NULL, NULL);
196     if (RegEax >= CPUID_EXTENDED_CPU_SIG) {
197       AsmCpuid (CPUID_EXTENDED_CPU_SIG, NULL, NULL, NULL, &RegEdx.Uint32);
198 
199       if (RegEdx.Bits.NX != 0) {
200         // XD supported
201         MsrEfer.Uint64 = AsmReadMsr64(MSR_CORE_IA32_EFER);
202         if (MsrEfer.Bits.NXE != 0) {
203           // XD activated
204           *Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_XD_ACTIVATED;
205         }
206       }
207 
208       if (RegEdx.Bits.Page1GB != 0) {
209         *Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PAGE_1G_SUPPORT;
210       }
211     }
212   }
213 
214   //
215   // This can avoid getting SMM paging context if in SMM mode. We cannot assume
216   // SMM mode shares the same paging context as DXE.
217   //
218   CopyMem (PagingContext, &mPagingContext, sizeof (mPagingContext));
219 }
220 
221 /**
222   Return length according to page attributes.
223 
224   @param[in]  PageAttributes   The page attribute of the page entry.
225 
226   @return The length of page entry.
227 **/
228 UINTN
PageAttributeToLength(IN PAGE_ATTRIBUTE PageAttribute)229 PageAttributeToLength (
230   IN PAGE_ATTRIBUTE  PageAttribute
231   )
232 {
233   UINTN  Index;
234   for (Index = 0; Index < sizeof(mPageAttributeTable)/sizeof(mPageAttributeTable[0]); Index++) {
235     if (PageAttribute == mPageAttributeTable[Index].Attribute) {
236       return (UINTN)mPageAttributeTable[Index].Length;
237     }
238   }
239   return 0;
240 }
241 
242 /**
243   Return address mask according to page attributes.
244 
245   @param[in]  PageAttributes   The page attribute of the page entry.
246 
247   @return The address mask of page entry.
248 **/
249 UINTN
PageAttributeToMask(IN PAGE_ATTRIBUTE PageAttribute)250 PageAttributeToMask (
251   IN PAGE_ATTRIBUTE  PageAttribute
252   )
253 {
254   UINTN  Index;
255   for (Index = 0; Index < sizeof(mPageAttributeTable)/sizeof(mPageAttributeTable[0]); Index++) {
256     if (PageAttribute == mPageAttributeTable[Index].Attribute) {
257       return (UINTN)mPageAttributeTable[Index].AddressMask;
258     }
259   }
260   return 0;
261 }
262 
263 /**
264   Return page table entry to match the address.
265 
266   @param[in]  PagingContext     The paging context.
267   @param[in]  Address           The address to be checked.
268   @param[out] PageAttributes    The page attribute of the page entry.
269 
270   @return The page entry.
271 **/
272 VOID *
GetPageTableEntry(IN PAGE_TABLE_LIB_PAGING_CONTEXT * PagingContext,IN PHYSICAL_ADDRESS Address,OUT PAGE_ATTRIBUTE * PageAttribute)273 GetPageTableEntry (
274   IN  PAGE_TABLE_LIB_PAGING_CONTEXT     *PagingContext,
275   IN  PHYSICAL_ADDRESS                  Address,
276   OUT PAGE_ATTRIBUTE                    *PageAttribute
277   )
278 {
279   UINTN                 Index1;
280   UINTN                 Index2;
281   UINTN                 Index3;
282   UINTN                 Index4;
283   UINTN                 Index5;
284   UINT64                *L1PageTable;
285   UINT64                *L2PageTable;
286   UINT64                *L3PageTable;
287   UINT64                *L4PageTable;
288   UINT64                *L5PageTable;
289   UINT64                AddressEncMask;
290 
291   ASSERT (PagingContext != NULL);
292 
293   Index5 = ((UINTN)RShiftU64 (Address, 48)) & PAGING_PAE_INDEX_MASK;
294   Index4 = ((UINTN)RShiftU64 (Address, 39)) & PAGING_PAE_INDEX_MASK;
295   Index3 = ((UINTN)Address >> 30) & PAGING_PAE_INDEX_MASK;
296   Index2 = ((UINTN)Address >> 21) & PAGING_PAE_INDEX_MASK;
297   Index1 = ((UINTN)Address >> 12) & PAGING_PAE_INDEX_MASK;
298 
299   // Make sure AddressEncMask is contained to smallest supported address field.
300   //
301   AddressEncMask = PcdGet64 (PcdPteMemoryEncryptionAddressOrMask) & PAGING_1G_ADDRESS_MASK_64;
302 
303   if (PagingContext->MachineType == IMAGE_FILE_MACHINE_X64) {
304     if ((PagingContext->ContextData.X64.Attributes & PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_5_LEVEL) != 0) {
305       L5PageTable = (UINT64 *)(UINTN)PagingContext->ContextData.X64.PageTableBase;
306       if (L5PageTable[Index5] == 0) {
307         *PageAttribute = PageNone;
308         return NULL;
309       }
310 
311       L4PageTable = (UINT64 *)(UINTN)(L5PageTable[Index5] & ~AddressEncMask & PAGING_4K_ADDRESS_MASK_64);
312     } else {
313       L4PageTable = (UINT64 *)(UINTN)PagingContext->ContextData.X64.PageTableBase;
314     }
315     if (L4PageTable[Index4] == 0) {
316       *PageAttribute = PageNone;
317       return NULL;
318     }
319 
320     L3PageTable = (UINT64 *)(UINTN)(L4PageTable[Index4] & ~AddressEncMask & PAGING_4K_ADDRESS_MASK_64);
321   } else {
322     ASSERT((PagingContext->ContextData.Ia32.Attributes & PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PAE) != 0);
323     L3PageTable = (UINT64 *)(UINTN)PagingContext->ContextData.Ia32.PageTableBase;
324   }
325   if (L3PageTable[Index3] == 0) {
326     *PageAttribute = PageNone;
327     return NULL;
328   }
329   if ((L3PageTable[Index3] & IA32_PG_PS) != 0) {
330     // 1G
331     *PageAttribute = Page1G;
332     return &L3PageTable[Index3];
333   }
334 
335   L2PageTable = (UINT64 *)(UINTN)(L3PageTable[Index3] & ~AddressEncMask & PAGING_4K_ADDRESS_MASK_64);
336   if (L2PageTable[Index2] == 0) {
337     *PageAttribute = PageNone;
338     return NULL;
339   }
340   if ((L2PageTable[Index2] & IA32_PG_PS) != 0) {
341     // 2M
342     *PageAttribute = Page2M;
343     return &L2PageTable[Index2];
344   }
345 
346   // 4k
347   L1PageTable = (UINT64 *)(UINTN)(L2PageTable[Index2] & ~AddressEncMask & PAGING_4K_ADDRESS_MASK_64);
348   if ((L1PageTable[Index1] == 0) && (Address != 0)) {
349     *PageAttribute = PageNone;
350     return NULL;
351   }
352   *PageAttribute = Page4K;
353   return &L1PageTable[Index1];
354 }
355 
356 /**
357   Return memory attributes of page entry.
358 
359   @param[in]  PageEntry        The page entry.
360 
361   @return Memory attributes of page entry.
362 **/
363 UINT64
GetAttributesFromPageEntry(IN UINT64 * PageEntry)364 GetAttributesFromPageEntry (
365   IN  UINT64                            *PageEntry
366   )
367 {
368   UINT64  Attributes;
369   Attributes = 0;
370   if ((*PageEntry & IA32_PG_P) == 0) {
371     Attributes |= EFI_MEMORY_RP;
372   }
373   if ((*PageEntry & IA32_PG_RW) == 0) {
374     Attributes |= EFI_MEMORY_RO;
375   }
376   if ((*PageEntry & IA32_PG_NX) != 0) {
377     Attributes |= EFI_MEMORY_XP;
378   }
379   return Attributes;
380 }
381 
382 /**
383   Modify memory attributes of page entry.
384 
385   @param[in]  PagingContext    The paging context.
386   @param[in]  PageEntry        The page entry.
387   @param[in]  Attributes       The bit mask of attributes to modify for the memory region.
388   @param[in]  PageAction       The page action.
389   @param[out] IsModified       TRUE means page table modified. FALSE means page table not modified.
390 **/
391 VOID
ConvertPageEntryAttribute(IN PAGE_TABLE_LIB_PAGING_CONTEXT * PagingContext,IN UINT64 * PageEntry,IN UINT64 Attributes,IN PAGE_ACTION PageAction,OUT BOOLEAN * IsModified)392 ConvertPageEntryAttribute (
393   IN  PAGE_TABLE_LIB_PAGING_CONTEXT     *PagingContext,
394   IN  UINT64                            *PageEntry,
395   IN  UINT64                            Attributes,
396   IN  PAGE_ACTION                       PageAction,
397   OUT BOOLEAN                           *IsModified
398   )
399 {
400   UINT64  CurrentPageEntry;
401   UINT64  NewPageEntry;
402   UINT32  *PageAttributes;
403 
404   CurrentPageEntry = *PageEntry;
405   NewPageEntry = CurrentPageEntry;
406   if ((Attributes & EFI_MEMORY_RP) != 0) {
407     switch (PageAction) {
408     case PageActionAssign:
409     case PageActionSet:
410       NewPageEntry &= ~(UINT64)IA32_PG_P;
411       break;
412     case PageActionClear:
413       NewPageEntry |= IA32_PG_P;
414       break;
415     }
416   } else {
417     switch (PageAction) {
418     case PageActionAssign:
419       NewPageEntry |= IA32_PG_P;
420       break;
421     case PageActionSet:
422     case PageActionClear:
423       break;
424     }
425   }
426   if ((Attributes & EFI_MEMORY_RO) != 0) {
427     switch (PageAction) {
428     case PageActionAssign:
429     case PageActionSet:
430       NewPageEntry &= ~(UINT64)IA32_PG_RW;
431       break;
432     case PageActionClear:
433       NewPageEntry |= IA32_PG_RW;
434       break;
435     }
436   } else {
437     switch (PageAction) {
438     case PageActionAssign:
439       NewPageEntry |= IA32_PG_RW;
440       break;
441     case PageActionSet:
442     case PageActionClear:
443       break;
444     }
445   }
446 
447   GetPagingDetails (&PagingContext->ContextData, NULL, &PageAttributes);
448 
449   if ((*PageAttributes & PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_XD_ACTIVATED) != 0) {
450     if ((Attributes & EFI_MEMORY_XP) != 0) {
451       switch (PageAction) {
452       case PageActionAssign:
453       case PageActionSet:
454         NewPageEntry |= IA32_PG_NX;
455         break;
456       case PageActionClear:
457         NewPageEntry &= ~IA32_PG_NX;
458         break;
459       }
460     } else {
461       switch (PageAction) {
462       case PageActionAssign:
463         NewPageEntry &= ~IA32_PG_NX;
464         break;
465       case PageActionSet:
466       case PageActionClear:
467         break;
468       }
469     }
470   }
471   *PageEntry = NewPageEntry;
472   if (CurrentPageEntry != NewPageEntry) {
473     *IsModified = TRUE;
474     DEBUG ((DEBUG_VERBOSE, "ConvertPageEntryAttribute 0x%lx", CurrentPageEntry));
475     DEBUG ((DEBUG_VERBOSE, "->0x%lx\n", NewPageEntry));
476   } else {
477     *IsModified = FALSE;
478   }
479 }
480 
481 /**
482   This function returns if there is need to split page entry.
483 
484   @param[in]  BaseAddress      The base address to be checked.
485   @param[in]  Length           The length to be checked.
486   @param[in]  PageEntry        The page entry to be checked.
487   @param[in]  PageAttribute    The page attribute of the page entry.
488 
489   @retval SplitAttributes on if there is need to split page entry.
490 **/
491 PAGE_ATTRIBUTE
NeedSplitPage(IN PHYSICAL_ADDRESS BaseAddress,IN UINT64 Length,IN UINT64 * PageEntry,IN PAGE_ATTRIBUTE PageAttribute)492 NeedSplitPage (
493   IN  PHYSICAL_ADDRESS                  BaseAddress,
494   IN  UINT64                            Length,
495   IN  UINT64                            *PageEntry,
496   IN  PAGE_ATTRIBUTE                    PageAttribute
497   )
498 {
499   UINT64                PageEntryLength;
500 
501   PageEntryLength = PageAttributeToLength (PageAttribute);
502 
503   if (((BaseAddress & (PageEntryLength - 1)) == 0) && (Length >= PageEntryLength)) {
504     return PageNone;
505   }
506 
507   if (((BaseAddress & PAGING_2M_MASK) != 0) || (Length < SIZE_2MB)) {
508     return Page4K;
509   }
510 
511   return Page2M;
512 }
513 
514 /**
515   This function splits one page entry to small page entries.
516 
517   @param[in]  PageEntry         The page entry to be splitted.
518   @param[in]  PageAttribute     The page attribute of the page entry.
519   @param[in]  SplitAttribute    How to split the page entry.
520   @param[in]  AllocatePagesFunc If page split is needed, this function is used to allocate more pages.
521 
522   @retval RETURN_SUCCESS            The page entry is splitted.
523   @retval RETURN_UNSUPPORTED        The page entry does not support to be splitted.
524   @retval RETURN_OUT_OF_RESOURCES   No resource to split page entry.
525 **/
526 RETURN_STATUS
SplitPage(IN UINT64 * PageEntry,IN PAGE_ATTRIBUTE PageAttribute,IN PAGE_ATTRIBUTE SplitAttribute,IN PAGE_TABLE_LIB_ALLOCATE_PAGES AllocatePagesFunc)527 SplitPage (
528   IN  UINT64                            *PageEntry,
529   IN  PAGE_ATTRIBUTE                    PageAttribute,
530   IN  PAGE_ATTRIBUTE                    SplitAttribute,
531   IN  PAGE_TABLE_LIB_ALLOCATE_PAGES     AllocatePagesFunc
532   )
533 {
534   UINT64   BaseAddress;
535   UINT64   *NewPageEntry;
536   UINTN    Index;
537   UINT64   AddressEncMask;
538 
539   ASSERT (PageAttribute == Page2M || PageAttribute == Page1G);
540 
541   ASSERT (AllocatePagesFunc != NULL);
542 
543   // Make sure AddressEncMask is contained to smallest supported address field.
544   //
545   AddressEncMask = PcdGet64 (PcdPteMemoryEncryptionAddressOrMask) & PAGING_1G_ADDRESS_MASK_64;
546 
547   if (PageAttribute == Page2M) {
548     //
549     // Split 2M to 4K
550     //
551     ASSERT (SplitAttribute == Page4K);
552     if (SplitAttribute == Page4K) {
553       NewPageEntry = AllocatePagesFunc (1);
554       DEBUG ((DEBUG_VERBOSE, "Split - 0x%x\n", NewPageEntry));
555       if (NewPageEntry == NULL) {
556         return RETURN_OUT_OF_RESOURCES;
557       }
558       BaseAddress = *PageEntry & ~AddressEncMask & PAGING_2M_ADDRESS_MASK_64;
559       for (Index = 0; Index < SIZE_4KB / sizeof(UINT64); Index++) {
560         NewPageEntry[Index] = (BaseAddress + SIZE_4KB * Index) | AddressEncMask | ((*PageEntry) & PAGE_PROGATE_BITS);
561       }
562       (*PageEntry) = (UINT64)(UINTN)NewPageEntry | AddressEncMask | ((*PageEntry) & PAGE_ATTRIBUTE_BITS);
563       return RETURN_SUCCESS;
564     } else {
565       return RETURN_UNSUPPORTED;
566     }
567   } else if (PageAttribute == Page1G) {
568     //
569     // Split 1G to 2M
570     // No need support 1G->4K directly, we should use 1G->2M, then 2M->4K to get more compact page table.
571     //
572     ASSERT (SplitAttribute == Page2M || SplitAttribute == Page4K);
573     if ((SplitAttribute == Page2M || SplitAttribute == Page4K)) {
574       NewPageEntry = AllocatePagesFunc (1);
575       DEBUG ((DEBUG_VERBOSE, "Split - 0x%x\n", NewPageEntry));
576       if (NewPageEntry == NULL) {
577         return RETURN_OUT_OF_RESOURCES;
578       }
579       BaseAddress = *PageEntry & ~AddressEncMask  & PAGING_1G_ADDRESS_MASK_64;
580       for (Index = 0; Index < SIZE_4KB / sizeof(UINT64); Index++) {
581         NewPageEntry[Index] = (BaseAddress + SIZE_2MB * Index) | AddressEncMask | IA32_PG_PS | ((*PageEntry) & PAGE_PROGATE_BITS);
582       }
583       (*PageEntry) = (UINT64)(UINTN)NewPageEntry | AddressEncMask | ((*PageEntry) & PAGE_ATTRIBUTE_BITS);
584       return RETURN_SUCCESS;
585     } else {
586       return RETURN_UNSUPPORTED;
587     }
588   } else {
589     return RETURN_UNSUPPORTED;
590   }
591 }
592 
593 /**
594  Check the WP status in CR0 register. This bit is used to lock or unlock write
595  access to pages marked as read-only.
596 
597   @retval TRUE    Write protection is enabled.
598   @retval FALSE   Write protection is disabled.
599 **/
600 BOOLEAN
IsReadOnlyPageWriteProtected(VOID)601 IsReadOnlyPageWriteProtected (
602   VOID
603   )
604 {
605   IA32_CR0  Cr0;
606   //
607   // To avoid unforseen consequences, don't touch paging settings in SMM mode
608   // in this driver.
609   //
610   if (!IsInSmm ()) {
611     Cr0.UintN = AsmReadCr0 ();
612     return (BOOLEAN) (Cr0.Bits.WP != 0);
613   }
614   return FALSE;
615 }
616 
617 /**
618  Disable Write Protect on pages marked as read-only.
619 **/
620 VOID
DisableReadOnlyPageWriteProtect(VOID)621 DisableReadOnlyPageWriteProtect (
622   VOID
623   )
624 {
625   IA32_CR0  Cr0;
626   //
627   // To avoid unforseen consequences, don't touch paging settings in SMM mode
628   // in this driver.
629   //
630   if (!IsInSmm ()) {
631     Cr0.UintN = AsmReadCr0 ();
632     Cr0.Bits.WP = 0;
633     AsmWriteCr0 (Cr0.UintN);
634   }
635 }
636 
637 /**
638  Enable Write Protect on pages marked as read-only.
639 **/
640 VOID
EnableReadOnlyPageWriteProtect(VOID)641 EnableReadOnlyPageWriteProtect (
642   VOID
643   )
644 {
645   IA32_CR0  Cr0;
646   //
647   // To avoid unforseen consequences, don't touch paging settings in SMM mode
648   // in this driver.
649   //
650   if (!IsInSmm ()) {
651     Cr0.UintN = AsmReadCr0 ();
652     Cr0.Bits.WP = 1;
653     AsmWriteCr0 (Cr0.UintN);
654   }
655 }
656 
657 /**
658   This function modifies the page attributes for the memory region specified by BaseAddress and
659   Length from their current attributes to the attributes specified by Attributes.
660 
661   Caller should make sure BaseAddress and Length is at page boundary.
662 
663   @param[in]  PagingContext     The paging context. NULL means get page table from current CPU context.
664   @param[in]  BaseAddress       The physical address that is the start address of a memory region.
665   @param[in]  Length            The size in bytes of the memory region.
666   @param[in]  Attributes        The bit mask of attributes to modify for the memory region.
667   @param[in]  PageAction        The page action.
668   @param[in]  AllocatePagesFunc If page split is needed, this function is used to allocate more pages.
669                                 NULL mean page split is unsupported.
670   @param[out] IsSplitted        TRUE means page table splitted. FALSE means page table not splitted.
671   @param[out] IsModified        TRUE means page table modified. FALSE means page table not modified.
672 
673   @retval RETURN_SUCCESS           The attributes were modified for the memory region.
674   @retval RETURN_ACCESS_DENIED     The attributes for the memory resource range specified by
675                                    BaseAddress and Length cannot be modified.
676   @retval RETURN_INVALID_PARAMETER Length is zero.
677                                    Attributes specified an illegal combination of attributes that
678                                    cannot be set together.
679   @retval RETURN_OUT_OF_RESOURCES  There are not enough system resources to modify the attributes of
680                                    the memory resource range.
681   @retval RETURN_UNSUPPORTED       The processor does not support one or more bytes of the memory
682                                    resource range specified by BaseAddress and Length.
683                                    The bit mask of attributes is not support for the memory resource
684                                    range specified by BaseAddress and Length.
685 **/
686 RETURN_STATUS
ConvertMemoryPageAttributes(IN PAGE_TABLE_LIB_PAGING_CONTEXT * PagingContext OPTIONAL,IN PHYSICAL_ADDRESS BaseAddress,IN UINT64 Length,IN UINT64 Attributes,IN PAGE_ACTION PageAction,IN PAGE_TABLE_LIB_ALLOCATE_PAGES AllocatePagesFunc OPTIONAL,OUT BOOLEAN * IsSplitted,OPTIONAL OUT BOOLEAN * IsModified OPTIONAL)687 ConvertMemoryPageAttributes (
688   IN  PAGE_TABLE_LIB_PAGING_CONTEXT     *PagingContext OPTIONAL,
689   IN  PHYSICAL_ADDRESS                  BaseAddress,
690   IN  UINT64                            Length,
691   IN  UINT64                            Attributes,
692   IN  PAGE_ACTION                       PageAction,
693   IN  PAGE_TABLE_LIB_ALLOCATE_PAGES     AllocatePagesFunc OPTIONAL,
694   OUT BOOLEAN                           *IsSplitted,  OPTIONAL
695   OUT BOOLEAN                           *IsModified   OPTIONAL
696   )
697 {
698   PAGE_TABLE_LIB_PAGING_CONTEXT     CurrentPagingContext;
699   UINT64                            *PageEntry;
700   PAGE_ATTRIBUTE                    PageAttribute;
701   UINTN                             PageEntryLength;
702   PAGE_ATTRIBUTE                    SplitAttribute;
703   RETURN_STATUS                     Status;
704   BOOLEAN                           IsEntryModified;
705   BOOLEAN                           IsWpEnabled;
706 
707   if ((BaseAddress & (SIZE_4KB - 1)) != 0) {
708     DEBUG ((DEBUG_ERROR, "BaseAddress(0x%lx) is not aligned!\n", BaseAddress));
709     return EFI_UNSUPPORTED;
710   }
711   if ((Length & (SIZE_4KB - 1)) != 0) {
712     DEBUG ((DEBUG_ERROR, "Length(0x%lx) is not aligned!\n", Length));
713     return EFI_UNSUPPORTED;
714   }
715   if (Length == 0) {
716     DEBUG ((DEBUG_ERROR, "Length is 0!\n"));
717     return RETURN_INVALID_PARAMETER;
718   }
719 
720   if ((Attributes & ~EFI_MEMORY_ATTRIBUTE_MASK) != 0) {
721     DEBUG ((DEBUG_ERROR, "Attributes(0x%lx) has unsupported bit\n", Attributes));
722     return EFI_UNSUPPORTED;
723   }
724 
725   if (PagingContext == NULL) {
726     GetCurrentPagingContext (&CurrentPagingContext);
727   } else {
728     CopyMem (&CurrentPagingContext, PagingContext, sizeof(CurrentPagingContext));
729   }
730   switch(CurrentPagingContext.MachineType) {
731   case IMAGE_FILE_MACHINE_I386:
732     if (CurrentPagingContext.ContextData.Ia32.PageTableBase == 0) {
733       if (Attributes == 0) {
734         return EFI_SUCCESS;
735       } else {
736         DEBUG ((DEBUG_ERROR, "PageTable is 0!\n"));
737         return EFI_UNSUPPORTED;
738       }
739     }
740     if ((CurrentPagingContext.ContextData.Ia32.Attributes & PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PAE) == 0) {
741       DEBUG ((DEBUG_ERROR, "Non-PAE Paging!\n"));
742       return EFI_UNSUPPORTED;
743     }
744     if ((BaseAddress + Length) > BASE_4GB) {
745       DEBUG ((DEBUG_ERROR, "Beyond 4GB memory in 32-bit mode!\n"));
746       return EFI_UNSUPPORTED;
747     }
748     break;
749   case IMAGE_FILE_MACHINE_X64:
750     ASSERT (CurrentPagingContext.ContextData.X64.PageTableBase != 0);
751     break;
752   default:
753     ASSERT(FALSE);
754     return EFI_UNSUPPORTED;
755     break;
756   }
757 
758 //  DEBUG ((DEBUG_ERROR, "ConvertMemoryPageAttributes(%x) - %016lx, %016lx, %02lx\n", IsSet, BaseAddress, Length, Attributes));
759 
760   if (IsSplitted != NULL) {
761     *IsSplitted = FALSE;
762   }
763   if (IsModified != NULL) {
764     *IsModified = FALSE;
765   }
766   if (AllocatePagesFunc == NULL) {
767     AllocatePagesFunc = AllocatePageTableMemory;
768   }
769 
770   //
771   // Make sure that the page table is changeable.
772   //
773   IsWpEnabled = IsReadOnlyPageWriteProtected ();
774   if (IsWpEnabled) {
775     DisableReadOnlyPageWriteProtect ();
776   }
777 
778   //
779   // Below logic is to check 2M/4K page to make sure we do not waste memory.
780   //
781   Status = EFI_SUCCESS;
782   while (Length != 0) {
783     PageEntry = GetPageTableEntry (&CurrentPagingContext, BaseAddress, &PageAttribute);
784     if (PageEntry == NULL) {
785       Status = RETURN_UNSUPPORTED;
786       goto Done;
787     }
788     PageEntryLength = PageAttributeToLength (PageAttribute);
789     SplitAttribute = NeedSplitPage (BaseAddress, Length, PageEntry, PageAttribute);
790     if (SplitAttribute == PageNone) {
791       ConvertPageEntryAttribute (&CurrentPagingContext, PageEntry, Attributes, PageAction, &IsEntryModified);
792       if (IsEntryModified) {
793         if (IsModified != NULL) {
794           *IsModified = TRUE;
795         }
796       }
797       //
798       // Convert success, move to next
799       //
800       BaseAddress += PageEntryLength;
801       Length -= PageEntryLength;
802     } else {
803       if (AllocatePagesFunc == NULL) {
804         Status = RETURN_UNSUPPORTED;
805         goto Done;
806       }
807       Status = SplitPage (PageEntry, PageAttribute, SplitAttribute, AllocatePagesFunc);
808       if (RETURN_ERROR (Status)) {
809         Status = RETURN_UNSUPPORTED;
810         goto Done;
811       }
812       if (IsSplitted != NULL) {
813         *IsSplitted = TRUE;
814       }
815       if (IsModified != NULL) {
816         *IsModified = TRUE;
817       }
818       //
819       // Just split current page
820       // Convert success in next around
821       //
822     }
823   }
824 
825 Done:
826   //
827   // Restore page table write protection, if any.
828   //
829   if (IsWpEnabled) {
830     EnableReadOnlyPageWriteProtect ();
831   }
832   return Status;
833 }
834 
835 /**
836   This function assigns the page attributes for the memory region specified by BaseAddress and
837   Length from their current attributes to the attributes specified by Attributes.
838 
839   Caller should make sure BaseAddress and Length is at page boundary.
840 
841   Caller need guarantee the TPL <= TPL_NOTIFY, if there is split page request.
842 
843   @param[in]  PagingContext     The paging context. NULL means get page table from current CPU context.
844   @param[in]  BaseAddress       The physical address that is the start address of a memory region.
845   @param[in]  Length            The size in bytes of the memory region.
846   @param[in]  Attributes        The bit mask of attributes to set for the memory region.
847   @param[in]  AllocatePagesFunc If page split is needed, this function is used to allocate more pages.
848                                 NULL mean page split is unsupported.
849 
850   @retval RETURN_SUCCESS           The attributes were cleared for the memory region.
851   @retval RETURN_ACCESS_DENIED     The attributes for the memory resource range specified by
852                                    BaseAddress and Length cannot be modified.
853   @retval RETURN_INVALID_PARAMETER Length is zero.
854                                    Attributes specified an illegal combination of attributes that
855                                    cannot be set together.
856   @retval RETURN_OUT_OF_RESOURCES  There are not enough system resources to modify the attributes of
857                                    the memory resource range.
858   @retval RETURN_UNSUPPORTED       The processor does not support one or more bytes of the memory
859                                    resource range specified by BaseAddress and Length.
860                                    The bit mask of attributes is not support for the memory resource
861                                    range specified by BaseAddress and Length.
862 **/
863 RETURN_STATUS
864 EFIAPI
AssignMemoryPageAttributes(IN PAGE_TABLE_LIB_PAGING_CONTEXT * PagingContext OPTIONAL,IN PHYSICAL_ADDRESS BaseAddress,IN UINT64 Length,IN UINT64 Attributes,IN PAGE_TABLE_LIB_ALLOCATE_PAGES AllocatePagesFunc OPTIONAL)865 AssignMemoryPageAttributes (
866   IN  PAGE_TABLE_LIB_PAGING_CONTEXT     *PagingContext OPTIONAL,
867   IN  PHYSICAL_ADDRESS                  BaseAddress,
868   IN  UINT64                            Length,
869   IN  UINT64                            Attributes,
870   IN  PAGE_TABLE_LIB_ALLOCATE_PAGES     AllocatePagesFunc OPTIONAL
871   )
872 {
873   RETURN_STATUS  Status;
874   BOOLEAN        IsModified;
875   BOOLEAN        IsSplitted;
876 
877 //  DEBUG((DEBUG_INFO, "AssignMemoryPageAttributes: 0x%lx - 0x%lx (0x%lx)\n", BaseAddress, Length, Attributes));
878   Status = ConvertMemoryPageAttributes (PagingContext, BaseAddress, Length, Attributes, PageActionAssign, AllocatePagesFunc, &IsSplitted, &IsModified);
879   if (!EFI_ERROR(Status)) {
880     if ((PagingContext == NULL) && IsModified) {
881       //
882       // Flush TLB as last step.
883       //
884       // Note: Since APs will always init CR3 register in HLT loop mode or do
885       // TLB flush in MWAIT loop mode, there's no need to flush TLB for them
886       // here.
887       //
888       CpuFlushTlb();
889     }
890   }
891 
892   return Status;
893 }
894 
895 /**
896  Check if Execute Disable feature is enabled or not.
897 **/
898 BOOLEAN
IsExecuteDisableEnabled(VOID)899 IsExecuteDisableEnabled (
900   VOID
901   )
902 {
903   MSR_CORE_IA32_EFER_REGISTER    MsrEfer;
904 
905   MsrEfer.Uint64 = AsmReadMsr64 (MSR_IA32_EFER);
906   return (MsrEfer.Bits.NXE == 1);
907 }
908 
909 /**
910   Update GCD memory space attributes according to current page table setup.
911 **/
912 VOID
RefreshGcdMemoryAttributesFromPaging(VOID)913 RefreshGcdMemoryAttributesFromPaging (
914   VOID
915   )
916 {
917   EFI_STATUS                          Status;
918   UINTN                               NumberOfDescriptors;
919   EFI_GCD_MEMORY_SPACE_DESCRIPTOR     *MemorySpaceMap;
920   PAGE_TABLE_LIB_PAGING_CONTEXT       PagingContext;
921   PAGE_ATTRIBUTE                      PageAttribute;
922   UINT64                              *PageEntry;
923   UINT64                              PageLength;
924   UINT64                              MemorySpaceLength;
925   UINT64                              Length;
926   UINT64                              BaseAddress;
927   UINT64                              PageStartAddress;
928   UINT64                              Attributes;
929   UINT64                              Capabilities;
930   UINT64                              NewAttributes;
931   UINTN                               Index;
932 
933   //
934   // Assuming that memory space map returned is sorted already; otherwise sort
935   // them in the order of lowest address to highest address.
936   //
937   Status = gDS->GetMemorySpaceMap (&NumberOfDescriptors, &MemorySpaceMap);
938   ASSERT_EFI_ERROR (Status);
939 
940   GetCurrentPagingContext (&PagingContext);
941 
942   Attributes      = 0;
943   NewAttributes   = 0;
944   BaseAddress     = 0;
945   PageLength      = 0;
946 
947   if (IsExecuteDisableEnabled ()) {
948     Capabilities = EFI_MEMORY_RO | EFI_MEMORY_RP | EFI_MEMORY_XP;
949   } else {
950     Capabilities = EFI_MEMORY_RO | EFI_MEMORY_RP;
951   }
952 
953   for (Index = 0; Index < NumberOfDescriptors; Index++) {
954     if (MemorySpaceMap[Index].GcdMemoryType == EfiGcdMemoryTypeNonExistent) {
955       continue;
956     }
957 
958     //
959     // Sync the actual paging related capabilities back to GCD service first.
960     // As a side effect (good one), this can also help to avoid unnecessary
961     // memory map entries due to the different capabilities of the same type
962     // memory, such as multiple RT_CODE and RT_DATA entries in memory map,
963     // which could cause boot failure of some old Linux distro (before v4.3).
964     //
965     Status = gDS->SetMemorySpaceCapabilities (
966                     MemorySpaceMap[Index].BaseAddress,
967                     MemorySpaceMap[Index].Length,
968                     MemorySpaceMap[Index].Capabilities | Capabilities
969                     );
970     if (EFI_ERROR (Status)) {
971       //
972       // If we cannot update the capabilities, we cannot update its
973       // attributes either. So just simply skip current block of memory.
974       //
975       DEBUG ((
976         DEBUG_WARN,
977         "Failed to update capability: [%lu] %016lx - %016lx (%016lx -> %016lx)\r\n",
978         (UINT64)Index, MemorySpaceMap[Index].BaseAddress,
979         MemorySpaceMap[Index].BaseAddress + MemorySpaceMap[Index].Length - 1,
980         MemorySpaceMap[Index].Capabilities,
981         MemorySpaceMap[Index].Capabilities | Capabilities
982         ));
983       continue;
984     }
985 
986     if (MemorySpaceMap[Index].BaseAddress >= (BaseAddress + PageLength)) {
987       //
988       // Current memory space starts at a new page. Resetting PageLength will
989       // trigger a retrieval of page attributes at new address.
990       //
991       PageLength = 0;
992     } else {
993       //
994       // In case current memory space is not adjacent to last one
995       //
996       PageLength -= (MemorySpaceMap[Index].BaseAddress - BaseAddress);
997     }
998 
999     //
1000     // Sync actual page attributes to GCD
1001     //
1002     BaseAddress       = MemorySpaceMap[Index].BaseAddress;
1003     MemorySpaceLength = MemorySpaceMap[Index].Length;
1004     while (MemorySpaceLength > 0) {
1005       if (PageLength == 0) {
1006         PageEntry = GetPageTableEntry (&PagingContext, BaseAddress, &PageAttribute);
1007         if (PageEntry == NULL) {
1008           break;
1009         }
1010 
1011         //
1012         // Note current memory space might start in the middle of a page
1013         //
1014         PageStartAddress  = (*PageEntry) & (UINT64)PageAttributeToMask(PageAttribute);
1015         PageLength        = PageAttributeToLength (PageAttribute) - (BaseAddress - PageStartAddress);
1016         Attributes        = GetAttributesFromPageEntry (PageEntry);
1017       }
1018 
1019       Length = MIN (PageLength, MemorySpaceLength);
1020       if (Attributes != (MemorySpaceMap[Index].Attributes &
1021                          EFI_MEMORY_ATTRIBUTE_MASK)) {
1022         NewAttributes = (MemorySpaceMap[Index].Attributes &
1023                          ~EFI_MEMORY_ATTRIBUTE_MASK) | Attributes;
1024         Status = gDS->SetMemorySpaceAttributes (
1025                         BaseAddress,
1026                         Length,
1027                         NewAttributes
1028                         );
1029         ASSERT_EFI_ERROR (Status);
1030         DEBUG ((
1031           DEBUG_VERBOSE,
1032           "Updated memory space attribute: [%lu] %016lx - %016lx (%016lx -> %016lx)\r\n",
1033           (UINT64)Index, BaseAddress, BaseAddress + Length - 1,
1034           MemorySpaceMap[Index].Attributes,
1035           NewAttributes
1036           ));
1037       }
1038 
1039       PageLength        -= Length;
1040       MemorySpaceLength -= Length;
1041       BaseAddress       += Length;
1042     }
1043   }
1044 
1045   FreePool (MemorySpaceMap);
1046 }
1047 
1048 /**
1049   Initialize a buffer pool for page table use only.
1050 
1051   To reduce the potential split operation on page table, the pages reserved for
1052   page table should be allocated in the times of PAGE_TABLE_POOL_UNIT_PAGES and
1053   at the boundary of PAGE_TABLE_POOL_ALIGNMENT. So the page pool is always
1054   initialized with number of pages greater than or equal to the given PoolPages.
1055 
1056   Once the pages in the pool are used up, this method should be called again to
1057   reserve at least another PAGE_TABLE_POOL_UNIT_PAGES. Usually this won't happen
1058   often in practice.
1059 
1060   @param[in] PoolPages      The least page number of the pool to be created.
1061 
1062   @retval TRUE    The pool is initialized successfully.
1063   @retval FALSE   The memory is out of resource.
1064 **/
1065 BOOLEAN
InitializePageTablePool(IN UINTN PoolPages)1066 InitializePageTablePool (
1067   IN  UINTN                           PoolPages
1068   )
1069 {
1070   VOID                      *Buffer;
1071   BOOLEAN                   IsModified;
1072 
1073   //
1074   // Do not allow re-entrance.
1075   //
1076   if (mPageTablePoolLock) {
1077     return FALSE;
1078   }
1079 
1080   mPageTablePoolLock = TRUE;
1081   IsModified = FALSE;
1082 
1083   //
1084   // Always reserve at least PAGE_TABLE_POOL_UNIT_PAGES, including one page for
1085   // header.
1086   //
1087   PoolPages += 1;   // Add one page for header.
1088   PoolPages = ((PoolPages - 1) / PAGE_TABLE_POOL_UNIT_PAGES + 1) *
1089               PAGE_TABLE_POOL_UNIT_PAGES;
1090   Buffer = AllocateAlignedPages (PoolPages, PAGE_TABLE_POOL_ALIGNMENT);
1091   if (Buffer == NULL) {
1092     DEBUG ((DEBUG_ERROR, "ERROR: Out of aligned pages\r\n"));
1093     goto Done;
1094   }
1095 
1096   DEBUG ((
1097     DEBUG_INFO,
1098     "Paging: added %lu pages to page table pool\r\n",
1099     (UINT64)PoolPages
1100     ));
1101 
1102   //
1103   // Link all pools into a list for easier track later.
1104   //
1105   if (mPageTablePool == NULL) {
1106     mPageTablePool = Buffer;
1107     mPageTablePool->NextPool = mPageTablePool;
1108   } else {
1109     ((PAGE_TABLE_POOL *)Buffer)->NextPool = mPageTablePool->NextPool;
1110     mPageTablePool->NextPool = Buffer;
1111     mPageTablePool = Buffer;
1112   }
1113 
1114   //
1115   // Reserve one page for pool header.
1116   //
1117   mPageTablePool->FreePages  = PoolPages - 1;
1118   mPageTablePool->Offset = EFI_PAGES_TO_SIZE (1);
1119 
1120   //
1121   // Mark the whole pool pages as read-only.
1122   //
1123   ConvertMemoryPageAttributes (
1124     NULL,
1125     (PHYSICAL_ADDRESS)(UINTN)Buffer,
1126     EFI_PAGES_TO_SIZE (PoolPages),
1127     EFI_MEMORY_RO,
1128     PageActionSet,
1129     AllocatePageTableMemory,
1130     NULL,
1131     &IsModified
1132     );
1133   ASSERT (IsModified == TRUE);
1134 
1135 Done:
1136   mPageTablePoolLock = FALSE;
1137   return IsModified;
1138 }
1139 
1140 /**
1141   This API provides a way to allocate memory for page table.
1142 
1143   This API can be called more than once to allocate memory for page tables.
1144 
1145   Allocates the number of 4KB pages and returns a pointer to the allocated
1146   buffer. The buffer returned is aligned on a 4KB boundary.
1147 
1148   If Pages is 0, then NULL is returned.
1149   If there is not enough memory remaining to satisfy the request, then NULL is
1150   returned.
1151 
1152   @param  Pages                 The number of 4 KB pages to allocate.
1153 
1154   @return A pointer to the allocated buffer or NULL if allocation fails.
1155 
1156 **/
1157 VOID *
1158 EFIAPI
AllocatePageTableMemory(IN UINTN Pages)1159 AllocatePageTableMemory (
1160   IN UINTN           Pages
1161   )
1162 {
1163   VOID                            *Buffer;
1164 
1165   if (Pages == 0) {
1166     return NULL;
1167   }
1168 
1169   //
1170   // Renew the pool if necessary.
1171   //
1172   if (mPageTablePool == NULL ||
1173       Pages > mPageTablePool->FreePages) {
1174     if (!InitializePageTablePool (Pages)) {
1175       return NULL;
1176     }
1177   }
1178 
1179   Buffer = (UINT8 *)mPageTablePool + mPageTablePool->Offset;
1180 
1181   mPageTablePool->Offset     += EFI_PAGES_TO_SIZE (Pages);
1182   mPageTablePool->FreePages  -= Pages;
1183 
1184   return Buffer;
1185 }
1186 
1187 /**
1188   Special handler for #DB exception, which will restore the page attributes
1189   (not-present). It should work with #PF handler which will set pages to
1190   'present'.
1191 
1192   @param ExceptionType  Exception type.
1193   @param SystemContext  Pointer to EFI_SYSTEM_CONTEXT.
1194 
1195 **/
1196 VOID
1197 EFIAPI
DebugExceptionHandler(IN EFI_EXCEPTION_TYPE ExceptionType,IN EFI_SYSTEM_CONTEXT SystemContext)1198 DebugExceptionHandler (
1199   IN EFI_EXCEPTION_TYPE   ExceptionType,
1200   IN EFI_SYSTEM_CONTEXT   SystemContext
1201   )
1202 {
1203   UINTN     CpuIndex;
1204   UINTN     PFEntry;
1205   BOOLEAN   IsWpEnabled;
1206 
1207   MpInitLibWhoAmI (&CpuIndex);
1208 
1209   //
1210   // Clear last PF entries
1211   //
1212   IsWpEnabled = IsReadOnlyPageWriteProtected ();
1213   if (IsWpEnabled) {
1214     DisableReadOnlyPageWriteProtect ();
1215   }
1216 
1217   for (PFEntry = 0; PFEntry < mPFEntryCount[CpuIndex]; PFEntry++) {
1218     if (mLastPFEntryPointer[CpuIndex][PFEntry] != NULL) {
1219       *mLastPFEntryPointer[CpuIndex][PFEntry] &= ~(UINT64)IA32_PG_P;
1220     }
1221   }
1222 
1223   if (IsWpEnabled) {
1224     EnableReadOnlyPageWriteProtect ();
1225   }
1226 
1227   //
1228   // Reset page fault exception count for next page fault.
1229   //
1230   mPFEntryCount[CpuIndex] = 0;
1231 
1232   //
1233   // Flush TLB
1234   //
1235   CpuFlushTlb ();
1236 
1237   //
1238   // Clear TF in EFLAGS
1239   //
1240   if (mPagingContext.MachineType == IMAGE_FILE_MACHINE_I386) {
1241     SystemContext.SystemContextIa32->Eflags &= (UINT32)~BIT8;
1242   } else {
1243     SystemContext.SystemContextX64->Rflags &= (UINT64)~BIT8;
1244   }
1245 }
1246 
1247 /**
1248   Special handler for #PF exception, which will set the pages which caused
1249   #PF to be 'present'. The attribute of those pages should be restored in
1250   the subsequent #DB handler.
1251 
1252   @param ExceptionType  Exception type.
1253   @param SystemContext  Pointer to EFI_SYSTEM_CONTEXT.
1254 
1255 **/
1256 VOID
1257 EFIAPI
PageFaultExceptionHandler(IN EFI_EXCEPTION_TYPE ExceptionType,IN EFI_SYSTEM_CONTEXT SystemContext)1258 PageFaultExceptionHandler (
1259   IN EFI_EXCEPTION_TYPE   ExceptionType,
1260   IN EFI_SYSTEM_CONTEXT   SystemContext
1261   )
1262 {
1263   EFI_STATUS                      Status;
1264   UINT64                          PFAddress;
1265   PAGE_TABLE_LIB_PAGING_CONTEXT   PagingContext;
1266   PAGE_ATTRIBUTE                  PageAttribute;
1267   UINT64                          Attributes;
1268   UINT64                          *PageEntry;
1269   UINTN                           Index;
1270   UINTN                           CpuIndex;
1271   UINTN                           PageNumber;
1272   BOOLEAN                         NonStopMode;
1273 
1274   PFAddress = AsmReadCr2 () & ~EFI_PAGE_MASK;
1275   if (PFAddress < BASE_4KB) {
1276     NonStopMode = NULL_DETECTION_NONSTOP_MODE ? TRUE : FALSE;
1277   } else {
1278     NonStopMode = HEAP_GUARD_NONSTOP_MODE ? TRUE : FALSE;
1279   }
1280 
1281   if (NonStopMode) {
1282     MpInitLibWhoAmI (&CpuIndex);
1283     GetCurrentPagingContext (&PagingContext);
1284     //
1285     // Memory operation cross page boundary, like "rep mov" instruction, will
1286     // cause infinite loop between this and Debug Trap handler. We have to make
1287     // sure that current page and the page followed are both in PRESENT state.
1288     //
1289     PageNumber = 2;
1290     while (PageNumber > 0) {
1291       PageEntry = GetPageTableEntry (&PagingContext, PFAddress, &PageAttribute);
1292       ASSERT(PageEntry != NULL);
1293 
1294       if (PageEntry != NULL) {
1295         Attributes = GetAttributesFromPageEntry (PageEntry);
1296         if ((Attributes & EFI_MEMORY_RP) != 0) {
1297           Attributes &= ~EFI_MEMORY_RP;
1298           Status = AssignMemoryPageAttributes (&PagingContext, PFAddress,
1299                                                EFI_PAGE_SIZE, Attributes, NULL);
1300           if (!EFI_ERROR(Status)) {
1301             Index = mPFEntryCount[CpuIndex];
1302             //
1303             // Re-retrieve page entry because above calling might update page
1304             // table due to table split.
1305             //
1306             PageEntry = GetPageTableEntry (&PagingContext, PFAddress, &PageAttribute);
1307             mLastPFEntryPointer[CpuIndex][Index++] = PageEntry;
1308             mPFEntryCount[CpuIndex] = Index;
1309           }
1310         }
1311       }
1312 
1313       PFAddress += EFI_PAGE_SIZE;
1314       --PageNumber;
1315     }
1316   }
1317 
1318   //
1319   // Initialize the serial port before dumping.
1320   //
1321   SerialPortInitialize ();
1322   //
1323   // Display ExceptionType, CPU information and Image information
1324   //
1325   DumpCpuContext (ExceptionType, SystemContext);
1326   if (NonStopMode) {
1327     //
1328     // Set TF in EFLAGS
1329     //
1330     if (mPagingContext.MachineType == IMAGE_FILE_MACHINE_I386) {
1331       SystemContext.SystemContextIa32->Eflags |= (UINT32)BIT8;
1332     } else {
1333       SystemContext.SystemContextX64->Rflags |= (UINT64)BIT8;
1334     }
1335   } else {
1336     CpuDeadLoop ();
1337   }
1338 }
1339 
1340 /**
1341   Initialize the Page Table lib.
1342 **/
1343 VOID
InitializePageTableLib(VOID)1344 InitializePageTableLib (
1345   VOID
1346   )
1347 {
1348   PAGE_TABLE_LIB_PAGING_CONTEXT     CurrentPagingContext;
1349   UINT32                            *Attributes;
1350   UINTN                             *PageTableBase;
1351 
1352   GetCurrentPagingContext (&CurrentPagingContext);
1353 
1354   GetPagingDetails (&CurrentPagingContext.ContextData, &PageTableBase, &Attributes);
1355 
1356   //
1357   // Reserve memory of page tables for future uses, if paging is enabled.
1358   //
1359   if ((*PageTableBase != 0) &&
1360       (*Attributes & PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PAE) != 0) {
1361     DisableReadOnlyPageWriteProtect ();
1362     InitializePageTablePool (1);
1363     EnableReadOnlyPageWriteProtect ();
1364   }
1365 
1366   if (HEAP_GUARD_NONSTOP_MODE || NULL_DETECTION_NONSTOP_MODE) {
1367     mPFEntryCount = (UINTN *)AllocateZeroPool (sizeof (UINTN) * mNumberOfProcessors);
1368     ASSERT (mPFEntryCount != NULL);
1369 
1370     mLastPFEntryPointer = (UINT64 *(*)[MAX_PF_ENTRY_COUNT])
1371                           AllocateZeroPool (sizeof (mLastPFEntryPointer[0]) * mNumberOfProcessors);
1372     ASSERT (mLastPFEntryPointer != NULL);
1373   }
1374 
1375   DEBUG ((DEBUG_INFO, "CurrentPagingContext:\n"));
1376   DEBUG ((DEBUG_INFO, "  MachineType   - 0x%x\n", CurrentPagingContext.MachineType));
1377   DEBUG ((DEBUG_INFO, "  PageTableBase - 0x%Lx\n", (UINT64)*PageTableBase));
1378   DEBUG ((DEBUG_INFO, "  Attributes    - 0x%x\n", *Attributes));
1379 
1380   return ;
1381 }
1382 
1383