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