]> git.proxmox.com Git - mirror_edk2.git/blame - UefiCpuPkg/CpuDxe/CpuPageTable.c
UefiCpuPkg/LocalApicLib: Exclude second SendIpi sequence on AMD processors.
[mirror_edk2.git] / UefiCpuPkg / CpuDxe / CpuPageTable.c
CommitLineData
22292ed3
JY
1/** @file\r
2 Page table management support.\r
3\r
4 Copyright (c) 2017, Intel Corporation. All rights reserved.<BR>\r
627dcba3
LD
5 Copyright (c) 2017, AMD Incorporated. All rights reserved.<BR>\r
6\r
22292ed3
JY
7 This program and the accompanying materials\r
8 are licensed and made available under the terms and conditions of the BSD License\r
9 which accompanies this distribution. The full text of the license may be found at\r
10 http://opensource.org/licenses/bsd-license.php\r
11\r
12 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,\r
13 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
14\r
15**/\r
16\r
17#include <Base.h>\r
18#include <Uefi.h>\r
19#include <Library/BaseLib.h>\r
20#include <Library/CpuLib.h>\r
21#include <Library/BaseMemoryLib.h>\r
22#include <Library/MemoryAllocationLib.h>\r
23#include <Library/DebugLib.h>\r
24#include <Library/UefiBootServicesTableLib.h>\r
25#include <Protocol/MpService.h>\r
c1cab54c
JW
26\r
27#include "CpuDxe.h"\r
22292ed3
JY
28#include "CpuPageTable.h"\r
29\r
30///\r
31/// Page Table Entry\r
32///\r
33#define IA32_PG_P BIT0\r
34#define IA32_PG_RW BIT1\r
35#define IA32_PG_U BIT2\r
36#define IA32_PG_WT BIT3\r
37#define IA32_PG_CD BIT4\r
38#define IA32_PG_A BIT5\r
39#define IA32_PG_D BIT6\r
40#define IA32_PG_PS BIT7\r
41#define IA32_PG_PAT_2M BIT12\r
42#define IA32_PG_PAT_4K IA32_PG_PS\r
43#define IA32_PG_PMNT BIT62\r
44#define IA32_PG_NX BIT63\r
45\r
46#define PAGE_ATTRIBUTE_BITS (IA32_PG_D | IA32_PG_A | IA32_PG_U | IA32_PG_RW | IA32_PG_P)\r
47//\r
48// Bits 1, 2, 5, 6 are reserved in the IA32 PAE PDPTE\r
49// X64 PAE PDPTE does not have such restriction\r
50//\r
51#define IA32_PAE_PDPTE_ATTRIBUTE_BITS (IA32_PG_P)\r
52\r
53#define PAGE_PROGATE_BITS (IA32_PG_NX | PAGE_ATTRIBUTE_BITS)\r
54\r
55#define PAGING_4K_MASK 0xFFF\r
56#define PAGING_2M_MASK 0x1FFFFF\r
57#define PAGING_1G_MASK 0x3FFFFFFF\r
58\r
59#define PAGING_PAE_INDEX_MASK 0x1FF\r
60\r
61#define PAGING_4K_ADDRESS_MASK_64 0x000FFFFFFFFFF000ull\r
62#define PAGING_2M_ADDRESS_MASK_64 0x000FFFFFFFE00000ull\r
63#define PAGING_1G_ADDRESS_MASK_64 0x000FFFFFC0000000ull\r
64\r
65typedef enum {\r
66 PageNone,\r
67 Page4K,\r
68 Page2M,\r
69 Page1G,\r
70} PAGE_ATTRIBUTE;\r
71\r
72typedef struct {\r
73 PAGE_ATTRIBUTE Attribute;\r
74 UINT64 Length;\r
75 UINT64 AddressMask;\r
76} PAGE_ATTRIBUTE_TABLE;\r
77\r
78typedef enum {\r
79 PageActionAssign,\r
80 PageActionSet,\r
81 PageActionClear,\r
82} PAGE_ACTION;\r
83\r
84PAGE_ATTRIBUTE_TABLE mPageAttributeTable[] = {\r
85 {Page4K, SIZE_4KB, PAGING_4K_ADDRESS_MASK_64},\r
86 {Page2M, SIZE_2MB, PAGING_2M_ADDRESS_MASK_64},\r
87 {Page1G, SIZE_1GB, PAGING_1G_ADDRESS_MASK_64},\r
88};\r
89\r
147fd35c
JW
90PAGE_TABLE_POOL *mPageTablePool = NULL;\r
91\r
22292ed3
JY
92/**\r
93 Return current paging context.\r
94\r
95 @param[in,out] PagingContext The paging context.\r
96**/\r
97VOID\r
98GetCurrentPagingContext (\r
99 IN OUT PAGE_TABLE_LIB_PAGING_CONTEXT *PagingContext\r
100 )\r
101{\r
102 UINT32 RegEax;\r
103 UINT32 RegEdx;\r
104\r
105 ZeroMem(PagingContext, sizeof(*PagingContext));\r
106 if (sizeof(UINTN) == sizeof(UINT64)) {\r
107 PagingContext->MachineType = IMAGE_FILE_MACHINE_X64;\r
108 } else {\r
109 PagingContext->MachineType = IMAGE_FILE_MACHINE_I386;\r
110 }\r
111 if ((AsmReadCr0 () & BIT31) != 0) {\r
112 PagingContext->ContextData.X64.PageTableBase = (AsmReadCr3 () & PAGING_4K_ADDRESS_MASK_64);\r
22292ed3
JY
113 } else {\r
114 PagingContext->ContextData.X64.PageTableBase = 0;\r
115 }\r
116\r
117 if ((AsmReadCr4 () & BIT4) != 0) {\r
118 PagingContext->ContextData.Ia32.Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PSE;\r
119 }\r
120 if ((AsmReadCr4 () & BIT5) != 0) {\r
121 PagingContext->ContextData.Ia32.Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PAE;\r
122 }\r
123 if ((AsmReadCr0 () & BIT16) != 0) {\r
124 PagingContext->ContextData.Ia32.Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_WP_ENABLE;\r
125 }\r
126\r
127 AsmCpuid (0x80000000, &RegEax, NULL, NULL, NULL);\r
128 if (RegEax > 0x80000000) {\r
129 AsmCpuid (0x80000001, NULL, NULL, NULL, &RegEdx);\r
130 if ((RegEdx & BIT20) != 0) {\r
131 // XD supported\r
01eb3f39
JF
132 if ((AsmReadMsr64 (0xC0000080) & BIT11) != 0) {\r
133 // XD activated\r
134 PagingContext->ContextData.Ia32.Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_XD_ACTIVATED;\r
22292ed3
JY
135 }\r
136 }\r
137 if ((RegEdx & BIT26) != 0) {\r
138 PagingContext->ContextData.Ia32.Attributes |= PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PAGE_1G_SUPPORT;\r
139 }\r
140 }\r
141}\r
142\r
143/**\r
144 Return length according to page attributes.\r
145\r
146 @param[in] PageAttributes The page attribute of the page entry.\r
147\r
148 @return The length of page entry.\r
149**/\r
150UINTN\r
151PageAttributeToLength (\r
152 IN PAGE_ATTRIBUTE PageAttribute\r
153 )\r
154{\r
155 UINTN Index;\r
156 for (Index = 0; Index < sizeof(mPageAttributeTable)/sizeof(mPageAttributeTable[0]); Index++) {\r
157 if (PageAttribute == mPageAttributeTable[Index].Attribute) {\r
158 return (UINTN)mPageAttributeTable[Index].Length;\r
159 }\r
160 }\r
161 return 0;\r
162}\r
163\r
164/**\r
165 Return address mask according to page attributes.\r
166\r
167 @param[in] PageAttributes The page attribute of the page entry.\r
168\r
169 @return The address mask of page entry.\r
170**/\r
171UINTN\r
172PageAttributeToMask (\r
173 IN PAGE_ATTRIBUTE PageAttribute\r
174 )\r
175{\r
176 UINTN Index;\r
177 for (Index = 0; Index < sizeof(mPageAttributeTable)/sizeof(mPageAttributeTable[0]); Index++) {\r
178 if (PageAttribute == mPageAttributeTable[Index].Attribute) {\r
179 return (UINTN)mPageAttributeTable[Index].AddressMask;\r
180 }\r
181 }\r
182 return 0;\r
183}\r
184\r
185/**\r
186 Return page table entry to match the address.\r
187\r
188 @param[in] PagingContext The paging context.\r
189 @param[in] Address The address to be checked.\r
190 @param[out] PageAttributes The page attribute of the page entry.\r
191\r
192 @return The page entry.\r
193**/\r
194VOID *\r
195GetPageTableEntry (\r
196 IN PAGE_TABLE_LIB_PAGING_CONTEXT *PagingContext,\r
197 IN PHYSICAL_ADDRESS Address,\r
198 OUT PAGE_ATTRIBUTE *PageAttribute\r
199 )\r
200{\r
201 UINTN Index1;\r
202 UINTN Index2;\r
203 UINTN Index3;\r
204 UINTN Index4;\r
205 UINT64 *L1PageTable;\r
206 UINT64 *L2PageTable;\r
207 UINT64 *L3PageTable;\r
208 UINT64 *L4PageTable;\r
627dcba3 209 UINT64 AddressEncMask;\r
22292ed3
JY
210\r
211 ASSERT (PagingContext != NULL);\r
212\r
213 Index4 = ((UINTN)RShiftU64 (Address, 39)) & PAGING_PAE_INDEX_MASK;\r
214 Index3 = ((UINTN)Address >> 30) & PAGING_PAE_INDEX_MASK;\r
215 Index2 = ((UINTN)Address >> 21) & PAGING_PAE_INDEX_MASK;\r
216 Index1 = ((UINTN)Address >> 12) & PAGING_PAE_INDEX_MASK;\r
217\r
627dcba3
LD
218 // Make sure AddressEncMask is contained to smallest supported address field.\r
219 //\r
220 AddressEncMask = PcdGet64 (PcdPteMemoryEncryptionAddressOrMask) & PAGING_1G_ADDRESS_MASK_64;\r
221\r
22292ed3
JY
222 if (PagingContext->MachineType == IMAGE_FILE_MACHINE_X64) {\r
223 L4PageTable = (UINT64 *)(UINTN)PagingContext->ContextData.X64.PageTableBase;\r
224 if (L4PageTable[Index4] == 0) {\r
225 *PageAttribute = PageNone;\r
226 return NULL;\r
227 }\r
228\r
627dcba3 229 L3PageTable = (UINT64 *)(UINTN)(L4PageTable[Index4] & ~AddressEncMask & PAGING_4K_ADDRESS_MASK_64);\r
22292ed3
JY
230 } else {\r
231 ASSERT((PagingContext->ContextData.Ia32.Attributes & PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PAE) != 0);\r
232 L3PageTable = (UINT64 *)(UINTN)PagingContext->ContextData.Ia32.PageTableBase;\r
233 }\r
234 if (L3PageTable[Index3] == 0) {\r
235 *PageAttribute = PageNone;\r
236 return NULL;\r
237 }\r
238 if ((L3PageTable[Index3] & IA32_PG_PS) != 0) {\r
239 // 1G\r
240 *PageAttribute = Page1G;\r
241 return &L3PageTable[Index3];\r
242 }\r
243\r
627dcba3 244 L2PageTable = (UINT64 *)(UINTN)(L3PageTable[Index3] & ~AddressEncMask & PAGING_4K_ADDRESS_MASK_64);\r
22292ed3
JY
245 if (L2PageTable[Index2] == 0) {\r
246 *PageAttribute = PageNone;\r
247 return NULL;\r
248 }\r
249 if ((L2PageTable[Index2] & IA32_PG_PS) != 0) {\r
250 // 2M\r
251 *PageAttribute = Page2M;\r
252 return &L2PageTable[Index2];\r
253 }\r
254\r
255 // 4k\r
627dcba3 256 L1PageTable = (UINT64 *)(UINTN)(L2PageTable[Index2] & ~AddressEncMask & PAGING_4K_ADDRESS_MASK_64);\r
22292ed3
JY
257 if ((L1PageTable[Index1] == 0) && (Address != 0)) {\r
258 *PageAttribute = PageNone;\r
259 return NULL;\r
260 }\r
261 *PageAttribute = Page4K;\r
262 return &L1PageTable[Index1];\r
263}\r
264\r
265/**\r
266 Return memory attributes of page entry.\r
267\r
268 @param[in] PageEntry The page entry.\r
269\r
270 @return Memory attributes of page entry.\r
271**/\r
272UINT64\r
273GetAttributesFromPageEntry (\r
274 IN UINT64 *PageEntry\r
275 )\r
276{\r
277 UINT64 Attributes;\r
278 Attributes = 0;\r
279 if ((*PageEntry & IA32_PG_P) == 0) {\r
280 Attributes |= EFI_MEMORY_RP;\r
281 }\r
282 if ((*PageEntry & IA32_PG_RW) == 0) {\r
283 Attributes |= EFI_MEMORY_RO;\r
284 }\r
285 if ((*PageEntry & IA32_PG_NX) != 0) {\r
286 Attributes |= EFI_MEMORY_XP;\r
287 }\r
288 return Attributes;\r
289}\r
290\r
291/**\r
292 Modify memory attributes of page entry.\r
293\r
294 @param[in] PagingContext The paging context.\r
295 @param[in] PageEntry The page entry.\r
296 @param[in] Attributes The bit mask of attributes to modify for the memory region.\r
297 @param[in] PageAction The page action.\r
298 @param[out] IsModified TRUE means page table modified. FALSE means page table not modified.\r
299**/\r
300VOID\r
301ConvertPageEntryAttribute (\r
302 IN PAGE_TABLE_LIB_PAGING_CONTEXT *PagingContext,\r
303 IN UINT64 *PageEntry,\r
304 IN UINT64 Attributes,\r
305 IN PAGE_ACTION PageAction,\r
306 OUT BOOLEAN *IsModified\r
307 )\r
308{\r
309 UINT64 CurrentPageEntry;\r
310 UINT64 NewPageEntry;\r
311\r
312 CurrentPageEntry = *PageEntry;\r
313 NewPageEntry = CurrentPageEntry;\r
314 if ((Attributes & EFI_MEMORY_RP) != 0) {\r
315 switch (PageAction) {\r
316 case PageActionAssign:\r
317 case PageActionSet:\r
318 NewPageEntry &= ~(UINT64)IA32_PG_P;\r
319 break;\r
320 case PageActionClear:\r
321 NewPageEntry |= IA32_PG_P;\r
322 break;\r
323 }\r
324 } else {\r
325 switch (PageAction) {\r
326 case PageActionAssign:\r
327 NewPageEntry |= IA32_PG_P;\r
328 break;\r
329 case PageActionSet:\r
330 case PageActionClear:\r
331 break;\r
332 }\r
333 }\r
334 if ((Attributes & EFI_MEMORY_RO) != 0) {\r
335 switch (PageAction) {\r
336 case PageActionAssign:\r
337 case PageActionSet:\r
338 NewPageEntry &= ~(UINT64)IA32_PG_RW;\r
339 break;\r
340 case PageActionClear:\r
341 NewPageEntry |= IA32_PG_RW;\r
342 break;\r
343 }\r
344 } else {\r
345 switch (PageAction) {\r
346 case PageActionAssign:\r
347 NewPageEntry |= IA32_PG_RW;\r
348 break;\r
349 case PageActionSet:\r
350 case PageActionClear:\r
351 break;\r
352 }\r
353 }\r
354 if ((PagingContext->ContextData.Ia32.Attributes & PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_XD_ACTIVATED) != 0) {\r
355 if ((Attributes & EFI_MEMORY_XP) != 0) {\r
356 switch (PageAction) {\r
357 case PageActionAssign:\r
358 case PageActionSet:\r
359 NewPageEntry |= IA32_PG_NX;\r
360 break;\r
361 case PageActionClear:\r
362 NewPageEntry &= ~IA32_PG_NX;\r
363 break;\r
364 }\r
365 } else {\r
366 switch (PageAction) {\r
367 case PageActionAssign:\r
368 NewPageEntry &= ~IA32_PG_NX;\r
369 break;\r
370 case PageActionSet:\r
371 case PageActionClear:\r
372 break;\r
373 }\r
374 }\r
375 }\r
376 *PageEntry = NewPageEntry;\r
377 if (CurrentPageEntry != NewPageEntry) {\r
378 *IsModified = TRUE;\r
827330cc
JW
379 DEBUG ((DEBUG_VERBOSE, "ConvertPageEntryAttribute 0x%lx", CurrentPageEntry));\r
380 DEBUG ((DEBUG_VERBOSE, "->0x%lx\n", NewPageEntry));\r
22292ed3
JY
381 } else {\r
382 *IsModified = FALSE;\r
383 }\r
384}\r
385\r
386/**\r
387 This function returns if there is need to split page entry.\r
388\r
389 @param[in] BaseAddress The base address to be checked.\r
390 @param[in] Length The length to be checked.\r
391 @param[in] PageEntry The page entry to be checked.\r
392 @param[in] PageAttribute The page attribute of the page entry.\r
393\r
394 @retval SplitAttributes on if there is need to split page entry.\r
395**/\r
396PAGE_ATTRIBUTE\r
397NeedSplitPage (\r
398 IN PHYSICAL_ADDRESS BaseAddress,\r
399 IN UINT64 Length,\r
400 IN UINT64 *PageEntry,\r
401 IN PAGE_ATTRIBUTE PageAttribute\r
402 )\r
403{\r
404 UINT64 PageEntryLength;\r
405\r
406 PageEntryLength = PageAttributeToLength (PageAttribute);\r
407\r
408 if (((BaseAddress & (PageEntryLength - 1)) == 0) && (Length >= PageEntryLength)) {\r
409 return PageNone;\r
410 }\r
411\r
412 if (((BaseAddress & PAGING_2M_MASK) != 0) || (Length < SIZE_2MB)) {\r
413 return Page4K;\r
414 }\r
415\r
416 return Page2M;\r
417}\r
418\r
419/**\r
420 This function splits one page entry to small page entries.\r
421\r
422 @param[in] PageEntry The page entry to be splitted.\r
423 @param[in] PageAttribute The page attribute of the page entry.\r
424 @param[in] SplitAttribute How to split the page entry.\r
425 @param[in] AllocatePagesFunc If page split is needed, this function is used to allocate more pages.\r
426\r
427 @retval RETURN_SUCCESS The page entry is splitted.\r
428 @retval RETURN_UNSUPPORTED The page entry does not support to be splitted.\r
429 @retval RETURN_OUT_OF_RESOURCES No resource to split page entry.\r
430**/\r
431RETURN_STATUS\r
432SplitPage (\r
433 IN UINT64 *PageEntry,\r
434 IN PAGE_ATTRIBUTE PageAttribute,\r
435 IN PAGE_ATTRIBUTE SplitAttribute,\r
436 IN PAGE_TABLE_LIB_ALLOCATE_PAGES AllocatePagesFunc\r
437 )\r
438{\r
439 UINT64 BaseAddress;\r
440 UINT64 *NewPageEntry;\r
441 UINTN Index;\r
627dcba3 442 UINT64 AddressEncMask;\r
22292ed3
JY
443\r
444 ASSERT (PageAttribute == Page2M || PageAttribute == Page1G);\r
445\r
446 ASSERT (AllocatePagesFunc != NULL);\r
447\r
627dcba3
LD
448 // Make sure AddressEncMask is contained to smallest supported address field.\r
449 //\r
450 AddressEncMask = PcdGet64 (PcdPteMemoryEncryptionAddressOrMask) & PAGING_1G_ADDRESS_MASK_64;\r
451\r
22292ed3
JY
452 if (PageAttribute == Page2M) {\r
453 //\r
454 // Split 2M to 4K\r
455 //\r
456 ASSERT (SplitAttribute == Page4K);\r
457 if (SplitAttribute == Page4K) {\r
458 NewPageEntry = AllocatePagesFunc (1);\r
459 DEBUG ((DEBUG_INFO, "Split - 0x%x\n", NewPageEntry));\r
460 if (NewPageEntry == NULL) {\r
461 return RETURN_OUT_OF_RESOURCES;\r
462 }\r
627dcba3 463 BaseAddress = *PageEntry & ~AddressEncMask & PAGING_2M_ADDRESS_MASK_64;\r
22292ed3 464 for (Index = 0; Index < SIZE_4KB / sizeof(UINT64); Index++) {\r
627dcba3 465 NewPageEntry[Index] = (BaseAddress + SIZE_4KB * Index) | AddressEncMask | ((*PageEntry) & PAGE_PROGATE_BITS);\r
22292ed3 466 }\r
fbe2c4b9 467 (*PageEntry) = (UINT64)(UINTN)NewPageEntry | AddressEncMask | ((*PageEntry) & PAGE_ATTRIBUTE_BITS);\r
22292ed3
JY
468 return RETURN_SUCCESS;\r
469 } else {\r
470 return RETURN_UNSUPPORTED;\r
471 }\r
472 } else if (PageAttribute == Page1G) {\r
473 //\r
474 // Split 1G to 2M\r
475 // No need support 1G->4K directly, we should use 1G->2M, then 2M->4K to get more compact page table.\r
476 //\r
477 ASSERT (SplitAttribute == Page2M || SplitAttribute == Page4K);\r
478 if ((SplitAttribute == Page2M || SplitAttribute == Page4K)) {\r
479 NewPageEntry = AllocatePagesFunc (1);\r
480 DEBUG ((DEBUG_INFO, "Split - 0x%x\n", NewPageEntry));\r
481 if (NewPageEntry == NULL) {\r
482 return RETURN_OUT_OF_RESOURCES;\r
483 }\r
627dcba3 484 BaseAddress = *PageEntry & ~AddressEncMask & PAGING_1G_ADDRESS_MASK_64;\r
22292ed3 485 for (Index = 0; Index < SIZE_4KB / sizeof(UINT64); Index++) {\r
627dcba3 486 NewPageEntry[Index] = (BaseAddress + SIZE_2MB * Index) | AddressEncMask | IA32_PG_PS | ((*PageEntry) & PAGE_PROGATE_BITS);\r
22292ed3 487 }\r
fbe2c4b9 488 (*PageEntry) = (UINT64)(UINTN)NewPageEntry | AddressEncMask | ((*PageEntry) & PAGE_ATTRIBUTE_BITS);\r
22292ed3
JY
489 return RETURN_SUCCESS;\r
490 } else {\r
491 return RETURN_UNSUPPORTED;\r
492 }\r
493 } else {\r
494 return RETURN_UNSUPPORTED;\r
495 }\r
496}\r
497\r
147fd35c
JW
498/**\r
499 Check the WP status in CR0 register. This bit is used to lock or unlock write\r
500 access to pages marked as read-only.\r
501\r
502 @retval TRUE Write protection is enabled.\r
503 @retval FALSE Write protection is disabled.\r
504**/\r
505BOOLEAN\r
506IsReadOnlyPageWriteProtected (\r
507 VOID\r
508 )\r
509{\r
510 return ((AsmReadCr0 () & BIT16) != 0);\r
511}\r
512\r
147fd35c
JW
513/**\r
514 Disable Write Protect on pages marked as read-only.\r
515**/\r
516VOID\r
517DisableReadOnlyPageWriteProtect (\r
518 VOID\r
519 )\r
520{\r
521 AsmWriteCr0 (AsmReadCr0() & ~BIT16);\r
147fd35c
JW
522}\r
523\r
524/**\r
525 Enable Write Protect on pages marked as read-only.\r
526**/\r
527VOID\r
528EnableReadOnlyPageWriteProtect (\r
529 VOID\r
530 )\r
531{\r
532 AsmWriteCr0 (AsmReadCr0() | BIT16);\r
147fd35c
JW
533}\r
534\r
22292ed3
JY
535/**\r
536 This function modifies the page attributes for the memory region specified by BaseAddress and\r
537 Length from their current attributes to the attributes specified by Attributes.\r
538\r
539 Caller should make sure BaseAddress and Length is at page boundary.\r
540\r
541 @param[in] PagingContext The paging context. NULL means get page table from current CPU context.\r
542 @param[in] BaseAddress The physical address that is the start address of a memory region.\r
543 @param[in] Length The size in bytes of the memory region.\r
544 @param[in] Attributes The bit mask of attributes to modify for the memory region.\r
545 @param[in] PageAction The page action.\r
546 @param[in] AllocatePagesFunc If page split is needed, this function is used to allocate more pages.\r
547 NULL mean page split is unsupported.\r
548 @param[out] IsSplitted TRUE means page table splitted. FALSE means page table not splitted.\r
549 @param[out] IsModified TRUE means page table modified. FALSE means page table not modified.\r
550\r
551 @retval RETURN_SUCCESS The attributes were modified for the memory region.\r
552 @retval RETURN_ACCESS_DENIED The attributes for the memory resource range specified by\r
553 BaseAddress and Length cannot be modified.\r
554 @retval RETURN_INVALID_PARAMETER Length is zero.\r
555 Attributes specified an illegal combination of attributes that\r
556 cannot be set together.\r
557 @retval RETURN_OUT_OF_RESOURCES There are not enough system resources to modify the attributes of\r
558 the memory resource range.\r
559 @retval RETURN_UNSUPPORTED The processor does not support one or more bytes of the memory\r
560 resource range specified by BaseAddress and Length.\r
561 The bit mask of attributes is not support for the memory resource\r
562 range specified by BaseAddress and Length.\r
563**/\r
564RETURN_STATUS\r
565ConvertMemoryPageAttributes (\r
566 IN PAGE_TABLE_LIB_PAGING_CONTEXT *PagingContext OPTIONAL,\r
567 IN PHYSICAL_ADDRESS BaseAddress,\r
568 IN UINT64 Length,\r
569 IN UINT64 Attributes,\r
570 IN PAGE_ACTION PageAction,\r
571 IN PAGE_TABLE_LIB_ALLOCATE_PAGES AllocatePagesFunc OPTIONAL,\r
572 OUT BOOLEAN *IsSplitted, OPTIONAL\r
573 OUT BOOLEAN *IsModified OPTIONAL\r
574 )\r
575{\r
576 PAGE_TABLE_LIB_PAGING_CONTEXT CurrentPagingContext;\r
577 UINT64 *PageEntry;\r
578 PAGE_ATTRIBUTE PageAttribute;\r
579 UINTN PageEntryLength;\r
580 PAGE_ATTRIBUTE SplitAttribute;\r
581 RETURN_STATUS Status;\r
582 BOOLEAN IsEntryModified;\r
147fd35c 583 BOOLEAN IsWpEnabled;\r
22292ed3
JY
584\r
585 if ((BaseAddress & (SIZE_4KB - 1)) != 0) {\r
586 DEBUG ((DEBUG_ERROR, "BaseAddress(0x%lx) is not aligned!\n", BaseAddress));\r
587 return EFI_UNSUPPORTED;\r
588 }\r
589 if ((Length & (SIZE_4KB - 1)) != 0) {\r
590 DEBUG ((DEBUG_ERROR, "Length(0x%lx) is not aligned!\n", Length));\r
591 return EFI_UNSUPPORTED;\r
592 }\r
593 if (Length == 0) {\r
594 DEBUG ((DEBUG_ERROR, "Length is 0!\n"));\r
595 return RETURN_INVALID_PARAMETER;\r
596 }\r
597\r
598 if ((Attributes & ~(EFI_MEMORY_RP | EFI_MEMORY_RO | EFI_MEMORY_XP)) != 0) {\r
599 DEBUG ((DEBUG_ERROR, "Attributes(0x%lx) has unsupported bit\n", Attributes));\r
600 return EFI_UNSUPPORTED;\r
601 }\r
602\r
603 if (PagingContext == NULL) {\r
604 GetCurrentPagingContext (&CurrentPagingContext);\r
605 } else {\r
606 CopyMem (&CurrentPagingContext, PagingContext, sizeof(CurrentPagingContext));\r
607 }\r
608 switch(CurrentPagingContext.MachineType) {\r
609 case IMAGE_FILE_MACHINE_I386:\r
610 if (CurrentPagingContext.ContextData.Ia32.PageTableBase == 0) {\r
22292ed3
JY
611 if (Attributes == 0) {\r
612 return EFI_SUCCESS;\r
613 } else {\r
c5719579 614 DEBUG ((DEBUG_ERROR, "PageTable is 0!\n"));\r
22292ed3
JY
615 return EFI_UNSUPPORTED;\r
616 }\r
617 }\r
618 if ((CurrentPagingContext.ContextData.Ia32.Attributes & PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PAE) == 0) {\r
619 DEBUG ((DEBUG_ERROR, "Non-PAE Paging!\n"));\r
620 return EFI_UNSUPPORTED;\r
621 }\r
4f10654e
JW
622 if ((BaseAddress + Length) > BASE_4GB) {\r
623 DEBUG ((DEBUG_ERROR, "Beyond 4GB memory in 32-bit mode!\n"));\r
624 return EFI_UNSUPPORTED;\r
625 }\r
22292ed3
JY
626 break;\r
627 case IMAGE_FILE_MACHINE_X64:\r
628 ASSERT (CurrentPagingContext.ContextData.X64.PageTableBase != 0);\r
629 break;\r
630 default:\r
631 ASSERT(FALSE);\r
632 return EFI_UNSUPPORTED;\r
633 break;\r
634 }\r
635\r
636// DEBUG ((DEBUG_ERROR, "ConvertMemoryPageAttributes(%x) - %016lx, %016lx, %02lx\n", IsSet, BaseAddress, Length, Attributes));\r
637\r
638 if (IsSplitted != NULL) {\r
639 *IsSplitted = FALSE;\r
640 }\r
641 if (IsModified != NULL) {\r
642 *IsModified = FALSE;\r
643 }\r
147fd35c
JW
644 if (AllocatePagesFunc == NULL) {\r
645 AllocatePagesFunc = AllocatePageTableMemory;\r
646 }\r
647\r
648 //\r
649 // Make sure that the page table is changeable.\r
650 //\r
651 IsWpEnabled = IsReadOnlyPageWriteProtected ();\r
652 if (IsWpEnabled) {\r
653 DisableReadOnlyPageWriteProtect ();\r
654 }\r
22292ed3
JY
655\r
656 //\r
657 // Below logic is to check 2M/4K page to make sure we donot waist memory.\r
658 //\r
147fd35c 659 Status = EFI_SUCCESS;\r
22292ed3
JY
660 while (Length != 0) {\r
661 PageEntry = GetPageTableEntry (&CurrentPagingContext, BaseAddress, &PageAttribute);\r
662 if (PageEntry == NULL) {\r
147fd35c
JW
663 Status = RETURN_UNSUPPORTED;\r
664 goto Done;\r
22292ed3
JY
665 }\r
666 PageEntryLength = PageAttributeToLength (PageAttribute);\r
667 SplitAttribute = NeedSplitPage (BaseAddress, Length, PageEntry, PageAttribute);\r
668 if (SplitAttribute == PageNone) {\r
669 ConvertPageEntryAttribute (&CurrentPagingContext, PageEntry, Attributes, PageAction, &IsEntryModified);\r
670 if (IsEntryModified) {\r
671 if (IsModified != NULL) {\r
672 *IsModified = TRUE;\r
673 }\r
674 }\r
675 //\r
676 // Convert success, move to next\r
677 //\r
678 BaseAddress += PageEntryLength;\r
679 Length -= PageEntryLength;\r
680 } else {\r
681 if (AllocatePagesFunc == NULL) {\r
147fd35c
JW
682 Status = RETURN_UNSUPPORTED;\r
683 goto Done;\r
22292ed3
JY
684 }\r
685 Status = SplitPage (PageEntry, PageAttribute, SplitAttribute, AllocatePagesFunc);\r
686 if (RETURN_ERROR (Status)) {\r
147fd35c
JW
687 Status = RETURN_UNSUPPORTED;\r
688 goto Done;\r
22292ed3
JY
689 }\r
690 if (IsSplitted != NULL) {\r
691 *IsSplitted = TRUE;\r
692 }\r
693 if (IsModified != NULL) {\r
694 *IsModified = TRUE;\r
695 }\r
696 //\r
697 // Just split current page\r
698 // Convert success in next around\r
699 //\r
700 }\r
701 }\r
702\r
147fd35c
JW
703Done:\r
704 //\r
705 // Restore page table write protection, if any.\r
706 //\r
707 if (IsWpEnabled) {\r
708 EnableReadOnlyPageWriteProtect ();\r
709 }\r
710 return Status;\r
22292ed3
JY
711}\r
712\r
713/**\r
714 This function assigns the page attributes for the memory region specified by BaseAddress and\r
715 Length from their current attributes to the attributes specified by Attributes.\r
716\r
717 Caller should make sure BaseAddress and Length is at page boundary.\r
718\r
719 Caller need guarentee the TPL <= TPL_NOTIFY, if there is split page request.\r
720\r
721 @param[in] PagingContext The paging context. NULL means get page table from current CPU context.\r
722 @param[in] BaseAddress The physical address that is the start address of a memory region.\r
723 @param[in] Length The size in bytes of the memory region.\r
724 @param[in] Attributes The bit mask of attributes to set for the memory region.\r
725 @param[in] AllocatePagesFunc If page split is needed, this function is used to allocate more pages.\r
726 NULL mean page split is unsupported.\r
727\r
728 @retval RETURN_SUCCESS The attributes were cleared for the memory region.\r
729 @retval RETURN_ACCESS_DENIED The attributes for the memory resource range specified by\r
730 BaseAddress and Length cannot be modified.\r
731 @retval RETURN_INVALID_PARAMETER Length is zero.\r
732 Attributes specified an illegal combination of attributes that\r
733 cannot be set together.\r
734 @retval RETURN_OUT_OF_RESOURCES There are not enough system resources to modify the attributes of\r
735 the memory resource range.\r
736 @retval RETURN_UNSUPPORTED The processor does not support one or more bytes of the memory\r
737 resource range specified by BaseAddress and Length.\r
738 The bit mask of attributes is not support for the memory resource\r
739 range specified by BaseAddress and Length.\r
740**/\r
741RETURN_STATUS\r
742EFIAPI\r
743AssignMemoryPageAttributes (\r
744 IN PAGE_TABLE_LIB_PAGING_CONTEXT *PagingContext OPTIONAL,\r
745 IN PHYSICAL_ADDRESS BaseAddress,\r
746 IN UINT64 Length,\r
747 IN UINT64 Attributes,\r
748 IN PAGE_TABLE_LIB_ALLOCATE_PAGES AllocatePagesFunc OPTIONAL\r
749 )\r
750{\r
751 RETURN_STATUS Status;\r
752 BOOLEAN IsModified;\r
753 BOOLEAN IsSplitted;\r
754\r
755// DEBUG((DEBUG_INFO, "AssignMemoryPageAttributes: 0x%lx - 0x%lx (0x%lx)\n", BaseAddress, Length, Attributes));\r
756 Status = ConvertMemoryPageAttributes (PagingContext, BaseAddress, Length, Attributes, PageActionAssign, AllocatePagesFunc, &IsSplitted, &IsModified);\r
757 if (!EFI_ERROR(Status)) {\r
758 if ((PagingContext == NULL) && IsModified) {\r
759 //\r
41a9c3fd
JW
760 // Flush TLB as last step.\r
761 //\r
762 // Note: Since APs will always init CR3 register in HLT loop mode or do\r
763 // TLB flush in MWAIT loop mode, there's no need to flush TLB for them\r
764 // here.\r
22292ed3
JY
765 //\r
766 CpuFlushTlb();\r
22292ed3
JY
767 }\r
768 }\r
769\r
770 return Status;\r
771}\r
772\r
768bd967
JW
773/**\r
774 Check if Execute Disable feature is enabled or not.\r
775**/\r
776BOOLEAN\r
777IsExecuteDisableEnabled (\r
778 VOID\r
779 )\r
780{\r
781 MSR_CORE_IA32_EFER_REGISTER MsrEfer;\r
782\r
783 MsrEfer.Uint64 = AsmReadMsr64 (MSR_IA32_EFER);\r
784 return (MsrEfer.Bits.NXE == 1);\r
785}\r
786\r
c1cab54c
JW
787/**\r
788 Update GCD memory space attributes according to current page table setup.\r
789**/\r
790VOID\r
791RefreshGcdMemoryAttributesFromPaging (\r
792 VOID\r
793 )\r
794{\r
795 EFI_STATUS Status;\r
796 UINTN NumberOfDescriptors;\r
797 EFI_GCD_MEMORY_SPACE_DESCRIPTOR *MemorySpaceMap;\r
798 PAGE_TABLE_LIB_PAGING_CONTEXT PagingContext;\r
799 PAGE_ATTRIBUTE PageAttribute;\r
800 UINT64 *PageEntry;\r
801 UINT64 PageLength;\r
802 UINT64 MemorySpaceLength;\r
803 UINT64 Length;\r
804 UINT64 BaseAddress;\r
805 UINT64 PageStartAddress;\r
806 UINT64 Attributes;\r
807 UINT64 Capabilities;\r
768bd967 808 UINT64 NewAttributes;\r
c1cab54c
JW
809 UINTN Index;\r
810\r
811 //\r
812 // Assuming that memory space map returned is sorted already; otherwise sort\r
813 // them in the order of lowest address to highest address.\r
814 //\r
815 Status = gDS->GetMemorySpaceMap (&NumberOfDescriptors, &MemorySpaceMap);\r
816 ASSERT_EFI_ERROR (Status);\r
817\r
818 GetCurrentPagingContext (&PagingContext);\r
819\r
768bd967
JW
820 Attributes = 0;\r
821 NewAttributes = 0;\r
822 BaseAddress = 0;\r
823 PageLength = 0;\r
824\r
825 if (IsExecuteDisableEnabled ()) {\r
826 Capabilities = EFI_MEMORY_RO | EFI_MEMORY_RP | EFI_MEMORY_XP;\r
827 } else {\r
828 Capabilities = EFI_MEMORY_RO | EFI_MEMORY_RP;\r
829 }\r
96207191 830\r
c1cab54c
JW
831 for (Index = 0; Index < NumberOfDescriptors; Index++) {\r
832 if (MemorySpaceMap[Index].GcdMemoryType == EfiGcdMemoryTypeNonExistent) {\r
833 continue;\r
834 }\r
835\r
768bd967
JW
836 //\r
837 // Sync the actual paging related capabilities back to GCD service first.\r
838 // As a side effect (good one), this can also help to avoid unnecessary\r
839 // memory map entries due to the different capabilities of the same type\r
840 // memory, such as multiple RT_CODE and RT_DATA entries in memory map,\r
841 // which could cause boot failure of some old Linux distro (before v4.3).\r
842 //\r
843 Status = gDS->SetMemorySpaceCapabilities (\r
844 MemorySpaceMap[Index].BaseAddress,\r
845 MemorySpaceMap[Index].Length,\r
846 MemorySpaceMap[Index].Capabilities | Capabilities\r
847 );\r
848 if (EFI_ERROR (Status)) {\r
849 //\r
850 // If we cannot udpate the capabilities, we cannot update its\r
851 // attributes either. So just simply skip current block of memory.\r
852 //\r
853 DEBUG ((\r
854 DEBUG_WARN,\r
855 "Failed to update capability: [%lu] %016lx - %016lx (%016lx -> %016lx)\r\n",\r
856 (UINT64)Index, MemorySpaceMap[Index].BaseAddress,\r
857 MemorySpaceMap[Index].BaseAddress + MemorySpaceMap[Index].Length - 1,\r
858 MemorySpaceMap[Index].Capabilities,\r
859 MemorySpaceMap[Index].Capabilities | Capabilities\r
860 ));\r
861 continue;\r
862 }\r
863\r
c1cab54c
JW
864 if (MemorySpaceMap[Index].BaseAddress >= (BaseAddress + PageLength)) {\r
865 //\r
866 // Current memory space starts at a new page. Resetting PageLength will\r
867 // trigger a retrieval of page attributes at new address.\r
868 //\r
869 PageLength = 0;\r
870 } else {\r
871 //\r
872 // In case current memory space is not adjacent to last one\r
873 //\r
874 PageLength -= (MemorySpaceMap[Index].BaseAddress - BaseAddress);\r
875 }\r
876\r
768bd967
JW
877 //\r
878 // Sync actual page attributes to GCD\r
879 //\r
c1cab54c
JW
880 BaseAddress = MemorySpaceMap[Index].BaseAddress;\r
881 MemorySpaceLength = MemorySpaceMap[Index].Length;\r
882 while (MemorySpaceLength > 0) {\r
883 if (PageLength == 0) {\r
884 PageEntry = GetPageTableEntry (&PagingContext, BaseAddress, &PageAttribute);\r
885 if (PageEntry == NULL) {\r
886 break;\r
887 }\r
888\r
889 //\r
890 // Note current memory space might start in the middle of a page\r
891 //\r
892 PageStartAddress = (*PageEntry) & (UINT64)PageAttributeToMask(PageAttribute);\r
893 PageLength = PageAttributeToLength (PageAttribute) - (BaseAddress - PageStartAddress);\r
894 Attributes = GetAttributesFromPageEntry (PageEntry);\r
c1cab54c
JW
895 }\r
896\r
897 Length = MIN (PageLength, MemorySpaceLength);\r
768bd967
JW
898 if (Attributes != (MemorySpaceMap[Index].Attributes &\r
899 EFI_MEMORY_PAGETYPE_MASK)) {\r
900 NewAttributes = (MemorySpaceMap[Index].Attributes &\r
901 ~EFI_MEMORY_PAGETYPE_MASK) | Attributes;\r
902 Status = gDS->SetMemorySpaceAttributes (\r
903 BaseAddress,\r
904 Length,\r
905 NewAttributes\r
906 );\r
907 ASSERT_EFI_ERROR (Status);\r
908 DEBUG ((\r
fbe2c4b9 909 DEBUG_VERBOSE,\r
768bd967
JW
910 "Updated memory space attribute: [%lu] %016lx - %016lx (%016lx -> %016lx)\r\n",\r
911 (UINT64)Index, BaseAddress, BaseAddress + Length - 1,\r
912 MemorySpaceMap[Index].Attributes,\r
913 NewAttributes\r
914 ));\r
c1cab54c
JW
915 }\r
916\r
917 PageLength -= Length;\r
918 MemorySpaceLength -= Length;\r
919 BaseAddress += Length;\r
920 }\r
921 }\r
922\r
923 FreePool (MemorySpaceMap);\r
924}\r
925\r
147fd35c
JW
926/**\r
927 Initialize a buffer pool for page table use only.\r
928\r
929 To reduce the potential split operation on page table, the pages reserved for\r
930 page table should be allocated in the times of PAGE_TABLE_POOL_UNIT_PAGES and\r
931 at the boundary of PAGE_TABLE_POOL_ALIGNMENT. So the page pool is always\r
932 initialized with number of pages greater than or equal to the given PoolPages.\r
933\r
934 Once the pages in the pool are used up, this method should be called again to\r
935 reserve at least another PAGE_TABLE_POOL_UNIT_PAGES. Usually this won't happen\r
936 often in practice.\r
937\r
938 @param[in] PoolPages The least page number of the pool to be created.\r
939\r
940 @retval TRUE The pool is initialized successfully.\r
941 @retval FALSE The memory is out of resource.\r
942**/\r
943BOOLEAN\r
944InitializePageTablePool (\r
945 IN UINTN PoolPages\r
946 )\r
947{\r
948 VOID *Buffer;\r
949 BOOLEAN IsModified;\r
950\r
951 //\r
952 // Always reserve at least PAGE_TABLE_POOL_UNIT_PAGES, including one page for\r
953 // header.\r
954 //\r
955 PoolPages += 1; // Add one page for header.\r
956 PoolPages = ((PoolPages - 1) / PAGE_TABLE_POOL_UNIT_PAGES + 1) *\r
957 PAGE_TABLE_POOL_UNIT_PAGES;\r
958 Buffer = AllocateAlignedPages (PoolPages, PAGE_TABLE_POOL_ALIGNMENT);\r
959 if (Buffer == NULL) {\r
960 DEBUG ((DEBUG_ERROR, "ERROR: Out of aligned pages\r\n"));\r
961 return FALSE;\r
962 }\r
963\r
964 //\r
965 // Link all pools into a list for easier track later.\r
966 //\r
967 if (mPageTablePool == NULL) {\r
968 mPageTablePool = Buffer;\r
969 mPageTablePool->NextPool = mPageTablePool;\r
970 } else {\r
971 ((PAGE_TABLE_POOL *)Buffer)->NextPool = mPageTablePool->NextPool;\r
972 mPageTablePool->NextPool = Buffer;\r
973 mPageTablePool = Buffer;\r
974 }\r
975\r
976 //\r
977 // Reserve one page for pool header.\r
978 //\r
979 mPageTablePool->FreePages = PoolPages - 1;\r
980 mPageTablePool->Offset = EFI_PAGES_TO_SIZE (1);\r
981\r
982 //\r
983 // Mark the whole pool pages as read-only.\r
984 //\r
985 ConvertMemoryPageAttributes (\r
986 NULL,\r
987 (PHYSICAL_ADDRESS)(UINTN)Buffer,\r
988 EFI_PAGES_TO_SIZE (PoolPages),\r
989 EFI_MEMORY_RO,\r
990 PageActionSet,\r
991 AllocatePageTableMemory,\r
992 NULL,\r
993 &IsModified\r
994 );\r
995 ASSERT (IsModified == TRUE);\r
996\r
997 return TRUE;\r
998}\r
999\r
1000/**\r
1001 This API provides a way to allocate memory for page table.\r
1002\r
1003 This API can be called more than once to allocate memory for page tables.\r
1004\r
1005 Allocates the number of 4KB pages and returns a pointer to the allocated\r
1006 buffer. The buffer returned is aligned on a 4KB boundary.\r
1007\r
1008 If Pages is 0, then NULL is returned.\r
1009 If there is not enough memory remaining to satisfy the request, then NULL is\r
1010 returned.\r
1011\r
1012 @param Pages The number of 4 KB pages to allocate.\r
1013\r
1014 @return A pointer to the allocated buffer or NULL if allocation fails.\r
1015\r
1016**/\r
1017VOID *\r
1018EFIAPI\r
1019AllocatePageTableMemory (\r
1020 IN UINTN Pages\r
1021 )\r
1022{\r
1023 VOID *Buffer;\r
1024\r
1025 if (Pages == 0) {\r
1026 return NULL;\r
1027 }\r
1028\r
1029 //\r
1030 // Renew the pool if necessary.\r
1031 //\r
1032 if (mPageTablePool == NULL ||\r
1033 Pages > mPageTablePool->FreePages) {\r
1034 if (!InitializePageTablePool (Pages)) {\r
1035 return NULL;\r
1036 }\r
1037 }\r
1038\r
1039 Buffer = (UINT8 *)mPageTablePool + mPageTablePool->Offset;\r
1040\r
1041 mPageTablePool->Offset += EFI_PAGES_TO_SIZE (Pages);\r
1042 mPageTablePool->FreePages -= Pages;\r
1043\r
1044 return Buffer;\r
1045}\r
1046\r
22292ed3
JY
1047/**\r
1048 Initialize the Page Table lib.\r
1049**/\r
1050VOID\r
1051InitializePageTableLib (\r
1052 VOID\r
1053 )\r
1054{\r
1055 PAGE_TABLE_LIB_PAGING_CONTEXT CurrentPagingContext;\r
1056\r
1057 GetCurrentPagingContext (&CurrentPagingContext);\r
147fd35c
JW
1058\r
1059 //\r
1060 // Reserve memory of page tables for future uses, if paging is enabled.\r
1061 //\r
1062 if (CurrentPagingContext.ContextData.X64.PageTableBase != 0 &&\r
1063 (CurrentPagingContext.ContextData.Ia32.Attributes &\r
1064 PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PAE) != 0) {\r
1065 DisableReadOnlyPageWriteProtect ();\r
1066 InitializePageTablePool (1);\r
1067 EnableReadOnlyPageWriteProtect ();\r
1068 }\r
1069\r
22292ed3
JY
1070 DEBUG ((DEBUG_INFO, "CurrentPagingContext:\n", CurrentPagingContext.MachineType));\r
1071 DEBUG ((DEBUG_INFO, " MachineType - 0x%x\n", CurrentPagingContext.MachineType));\r
1072 DEBUG ((DEBUG_INFO, " PageTableBase - 0x%x\n", CurrentPagingContext.ContextData.X64.PageTableBase));\r
1073 DEBUG ((DEBUG_INFO, " Attributes - 0x%x\n", CurrentPagingContext.ContextData.X64.Attributes));\r
1074\r
1075 return ;\r
1076}\r
1077\r