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