]> git.proxmox.com Git - mirror_edk2.git/blame - ArmPkg/Library/ArmMmuLib/AArch64/ArmMmuLibCore.c
OvmfPkg/X86QemuLoadImageLib: fix "unused variable" error in X64 DXE builds
[mirror_edk2.git] / ArmPkg / Library / ArmMmuLib / AArch64 / ArmMmuLibCore.c
CommitLineData
d7f03464
AB
1/** @file\r
2* File managing the MMU for ARMv8 architecture\r
3*\r
4* Copyright (c) 2011-2014, ARM Limited. All rights reserved.\r
5* Copyright (c) 2016, Linaro Limited. All rights reserved.\r
b7a09b71 6* Copyright (c) 2017, Intel Corporation. All rights reserved.<BR>\r
d7f03464 7*\r
4059386c 8* SPDX-License-Identifier: BSD-2-Clause-Patent\r
d7f03464
AB
9*\r
10**/\r
11\r
12#include <Uefi.h>\r
13#include <Chipset/AArch64.h>\r
14#include <Library/BaseMemoryLib.h>\r
15#include <Library/CacheMaintenanceLib.h>\r
16#include <Library/MemoryAllocationLib.h>\r
17#include <Library/ArmLib.h>\r
18#include <Library/ArmMmuLib.h>\r
19#include <Library/BaseLib.h>\r
20#include <Library/DebugLib.h>\r
21\r
22// We use this index definition to define an invalid block entry\r
23#define TT_ATTR_INDX_INVALID ((UINT32)~0)\r
24\r
25STATIC\r
26UINT64\r
27ArmMemoryAttributeToPageAttribute (\r
28 IN ARM_MEMORY_REGION_ATTRIBUTES Attributes\r
29 )\r
30{\r
31 switch (Attributes) {\r
829633e3
PL
32 case ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK_NONSHAREABLE:\r
33 case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_WRITE_BACK_NONSHAREABLE:\r
34 return TT_ATTR_INDX_MEMORY_WRITE_BACK;\r
35\r
d7f03464
AB
36 case ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK:\r
37 case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_WRITE_BACK:\r
38 return TT_ATTR_INDX_MEMORY_WRITE_BACK | TT_SH_INNER_SHAREABLE;\r
39\r
40 case ARM_MEMORY_REGION_ATTRIBUTE_WRITE_THROUGH:\r
41 case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_WRITE_THROUGH:\r
42 return TT_ATTR_INDX_MEMORY_WRITE_THROUGH | TT_SH_INNER_SHAREABLE;\r
43\r
44 // Uncached and device mappings are treated as outer shareable by default,\r
45 case ARM_MEMORY_REGION_ATTRIBUTE_UNCACHED_UNBUFFERED:\r
46 case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_UNCACHED_UNBUFFERED:\r
47 return TT_ATTR_INDX_MEMORY_NON_CACHEABLE;\r
48\r
49 default:\r
50 ASSERT(0);\r
51 case ARM_MEMORY_REGION_ATTRIBUTE_DEVICE:\r
52 case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_DEVICE:\r
53 if (ArmReadCurrentEL () == AARCH64_EL2)\r
54 return TT_ATTR_INDX_DEVICE_MEMORY | TT_XN_MASK;\r
55 else\r
56 return TT_ATTR_INDX_DEVICE_MEMORY | TT_UXN_MASK | TT_PXN_MASK;\r
57 }\r
58}\r
59\r
60UINT64\r
61PageAttributeToGcdAttribute (\r
62 IN UINT64 PageAttributes\r
63 )\r
64{\r
65 UINT64 GcdAttributes;\r
66\r
67 switch (PageAttributes & TT_ATTR_INDX_MASK) {\r
68 case TT_ATTR_INDX_DEVICE_MEMORY:\r
69 GcdAttributes = EFI_MEMORY_UC;\r
70 break;\r
71 case TT_ATTR_INDX_MEMORY_NON_CACHEABLE:\r
72 GcdAttributes = EFI_MEMORY_WC;\r
73 break;\r
74 case TT_ATTR_INDX_MEMORY_WRITE_THROUGH:\r
75 GcdAttributes = EFI_MEMORY_WT;\r
76 break;\r
77 case TT_ATTR_INDX_MEMORY_WRITE_BACK:\r
78 GcdAttributes = EFI_MEMORY_WB;\r
79 break;\r
80 default:\r
81 DEBUG ((EFI_D_ERROR, "PageAttributeToGcdAttribute: PageAttributes:0x%lX not supported.\n", PageAttributes));\r
82 ASSERT (0);\r
83 // The Global Coherency Domain (GCD) value is defined as a bit set.\r
84 // Returning 0 means no attribute has been set.\r
85 GcdAttributes = 0;\r
86 }\r
87\r
88 // Determine protection attributes\r
89 if (((PageAttributes & TT_AP_MASK) == TT_AP_NO_RO) || ((PageAttributes & TT_AP_MASK) == TT_AP_RO_RO)) {\r
90 // Read only cases map to write-protect\r
b7a09b71 91 GcdAttributes |= EFI_MEMORY_RO;\r
d7f03464
AB
92 }\r
93\r
94 // Process eXecute Never attribute\r
95 if ((PageAttributes & (TT_PXN_MASK | TT_UXN_MASK)) != 0 ) {\r
96 GcdAttributes |= EFI_MEMORY_XP;\r
97 }\r
98\r
99 return GcdAttributes;\r
100}\r
101\r
e93cb72e
AB
102#define MIN_T0SZ 16\r
103#define BITS_PER_LEVEL 9\r
d7f03464
AB
104\r
105VOID\r
106GetRootTranslationTableInfo (\r
107 IN UINTN T0SZ,\r
108 OUT UINTN *TableLevel,\r
109 OUT UINTN *TableEntryCount\r
110 )\r
111{\r
d7f03464
AB
112 // Get the level of the root table\r
113 if (TableLevel) {\r
e93cb72e 114 *TableLevel = (T0SZ - MIN_T0SZ) / BITS_PER_LEVEL;\r
d7f03464
AB
115 }\r
116\r
d7f03464 117 if (TableEntryCount) {\r
e93cb72e 118 *TableEntryCount = 1UL << (BITS_PER_LEVEL - (T0SZ - MIN_T0SZ) % BITS_PER_LEVEL);\r
d7f03464
AB
119 }\r
120}\r
121\r
122STATIC\r
123VOID\r
124ReplaceLiveEntry (\r
125 IN UINT64 *Entry,\r
d5788777
AB
126 IN UINT64 Value,\r
127 IN UINT64 RegionStart\r
d7f03464
AB
128 )\r
129{\r
130 if (!ArmMmuEnabled ()) {\r
131 *Entry = Value;\r
132 } else {\r
d5788777 133 ArmReplaceLiveTranslationEntry (Entry, Value, RegionStart);\r
d7f03464
AB
134 }\r
135}\r
136\r
137STATIC\r
138VOID\r
139LookupAddresstoRootTable (\r
140 IN UINT64 MaxAddress,\r
141 OUT UINTN *T0SZ,\r
142 OUT UINTN *TableEntryCount\r
143 )\r
144{\r
145 UINTN TopBit;\r
146\r
147 // Check the parameters are not NULL\r
148 ASSERT ((T0SZ != NULL) && (TableEntryCount != NULL));\r
149\r
150 // Look for the highest bit set in MaxAddress\r
151 for (TopBit = 63; TopBit != 0; TopBit--) {\r
152 if ((1ULL << TopBit) & MaxAddress) {\r
153 // MaxAddress top bit is found\r
154 TopBit = TopBit + 1;\r
155 break;\r
156 }\r
157 }\r
158 ASSERT (TopBit != 0);\r
159\r
160 // Calculate T0SZ from the top bit of the MaxAddress\r
161 *T0SZ = 64 - TopBit;\r
162\r
163 // Get the Table info from T0SZ\r
164 GetRootTranslationTableInfo (*T0SZ, NULL, TableEntryCount);\r
165}\r
166\r
167STATIC\r
168UINT64*\r
169GetBlockEntryListFromAddress (\r
170 IN UINT64 *RootTable,\r
171 IN UINT64 RegionStart,\r
172 OUT UINTN *TableLevel,\r
173 IN OUT UINT64 *BlockEntrySize,\r
174 OUT UINT64 **LastBlockEntry\r
175 )\r
176{\r
177 UINTN RootTableLevel;\r
178 UINTN RootTableEntryCount;\r
179 UINT64 *TranslationTable;\r
180 UINT64 *BlockEntry;\r
181 UINT64 *SubTableBlockEntry;\r
182 UINT64 BlockEntryAddress;\r
183 UINTN BaseAddressAlignment;\r
184 UINTN PageLevel;\r
185 UINTN Index;\r
186 UINTN IndexLevel;\r
187 UINTN T0SZ;\r
188 UINT64 Attributes;\r
189 UINT64 TableAttributes;\r
190\r
191 // Initialize variable\r
192 BlockEntry = NULL;\r
193\r
194 // Ensure the parameters are valid\r
195 if (!(TableLevel && BlockEntrySize && LastBlockEntry)) {\r
196 ASSERT_EFI_ERROR (EFI_INVALID_PARAMETER);\r
197 return NULL;\r
198 }\r
199\r
200 // Ensure the Region is aligned on 4KB boundary\r
201 if ((RegionStart & (SIZE_4KB - 1)) != 0) {\r
202 ASSERT_EFI_ERROR (EFI_INVALID_PARAMETER);\r
203 return NULL;\r
204 }\r
205\r
206 // Ensure the required size is aligned on 4KB boundary and not 0\r
207 if ((*BlockEntrySize & (SIZE_4KB - 1)) != 0 || *BlockEntrySize == 0) {\r
208 ASSERT_EFI_ERROR (EFI_INVALID_PARAMETER);\r
209 return NULL;\r
210 }\r
211\r
212 T0SZ = ArmGetTCR () & TCR_T0SZ_MASK;\r
213 // Get the Table info from T0SZ\r
214 GetRootTranslationTableInfo (T0SZ, &RootTableLevel, &RootTableEntryCount);\r
215\r
216 // If the start address is 0x0 then we use the size of the region to identify the alignment\r
217 if (RegionStart == 0) {\r
218 // Identify the highest possible alignment for the Region Size\r
219 BaseAddressAlignment = LowBitSet64 (*BlockEntrySize);\r
220 } else {\r
221 // Identify the highest possible alignment for the Base Address\r
222 BaseAddressAlignment = LowBitSet64 (RegionStart);\r
223 }\r
224\r
225 // Identify the Page Level the RegionStart must belong to. Note that PageLevel\r
226 // should be at least 1 since block translations are not supported at level 0\r
227 PageLevel = MAX (3 - ((BaseAddressAlignment - 12) / 9), 1);\r
228\r
229 // If the required size is smaller than the current block size then we need to go to the page below.\r
230 // The PageLevel was calculated on the Base Address alignment but did not take in account the alignment\r
231 // of the allocation size\r
232 while (*BlockEntrySize < TT_BLOCK_ENTRY_SIZE_AT_LEVEL (PageLevel)) {\r
233 // It does not fit so we need to go a page level above\r
234 PageLevel++;\r
235 }\r
236\r
237 //\r
238 // Get the Table Descriptor for the corresponding PageLevel. We need to decompose RegionStart to get appropriate entries\r
239 //\r
240\r
241 TranslationTable = RootTable;\r
242 for (IndexLevel = RootTableLevel; IndexLevel <= PageLevel; IndexLevel++) {\r
243 BlockEntry = (UINT64*)TT_GET_ENTRY_FOR_ADDRESS (TranslationTable, IndexLevel, RegionStart);\r
244\r
245 if ((IndexLevel != 3) && ((*BlockEntry & TT_TYPE_MASK) == TT_TYPE_TABLE_ENTRY)) {\r
246 // Go to the next table\r
247 TranslationTable = (UINT64*)(*BlockEntry & TT_ADDRESS_MASK_DESCRIPTION_TABLE);\r
248\r
249 // If we are at the last level then update the last level to next level\r
250 if (IndexLevel == PageLevel) {\r
251 // Enter the next level\r
252 PageLevel++;\r
253 }\r
254 } else if ((*BlockEntry & TT_TYPE_MASK) == TT_TYPE_BLOCK_ENTRY) {\r
255 // If we are not at the last level then we need to split this BlockEntry\r
256 if (IndexLevel != PageLevel) {\r
257 // Retrieve the attributes from the block entry\r
258 Attributes = *BlockEntry & TT_ATTRIBUTES_MASK;\r
259\r
260 // Convert the block entry attributes into Table descriptor attributes\r
261 TableAttributes = TT_TABLE_AP_NO_PERMISSION;\r
262 if (Attributes & TT_NS) {\r
263 TableAttributes = TT_TABLE_NS;\r
264 }\r
265\r
266 // Get the address corresponding at this entry\r
267 BlockEntryAddress = RegionStart;\r
268 BlockEntryAddress = BlockEntryAddress >> TT_ADDRESS_OFFSET_AT_LEVEL(IndexLevel);\r
269 // Shift back to right to set zero before the effective address\r
270 BlockEntryAddress = BlockEntryAddress << TT_ADDRESS_OFFSET_AT_LEVEL(IndexLevel);\r
271\r
272 // Set the correct entry type for the next page level\r
273 if ((IndexLevel + 1) == 3) {\r
274 Attributes |= TT_TYPE_BLOCK_ENTRY_LEVEL3;\r
275 } else {\r
276 Attributes |= TT_TYPE_BLOCK_ENTRY;\r
277 }\r
278\r
279 // Create a new translation table\r
674e127e 280 TranslationTable = AllocatePages (1);\r
d7f03464
AB
281 if (TranslationTable == NULL) {\r
282 return NULL;\r
283 }\r
284\r
285 // Populate the newly created lower level table\r
286 SubTableBlockEntry = TranslationTable;\r
287 for (Index = 0; Index < TT_ENTRY_COUNT; Index++) {\r
288 *SubTableBlockEntry = Attributes | (BlockEntryAddress + (Index << TT_ADDRESS_OFFSET_AT_LEVEL(IndexLevel + 1)));\r
289 SubTableBlockEntry++;\r
290 }\r
291\r
292 // Fill the BlockEntry with the new TranslationTable\r
293 ReplaceLiveEntry (BlockEntry,\r
d5788777
AB
294 (UINTN)TranslationTable | TableAttributes | TT_TYPE_TABLE_ENTRY,\r
295 RegionStart);\r
d7f03464
AB
296 }\r
297 } else {\r
298 if (IndexLevel != PageLevel) {\r
299 //\r
300 // Case when we have an Invalid Entry and we are at a page level above of the one targetted.\r
301 //\r
302\r
303 // Create a new translation table\r
674e127e 304 TranslationTable = AllocatePages (1);\r
d7f03464
AB
305 if (TranslationTable == NULL) {\r
306 return NULL;\r
307 }\r
308\r
309 ZeroMem (TranslationTable, TT_ENTRY_COUNT * sizeof(UINT64));\r
310\r
311 // Fill the new BlockEntry with the TranslationTable\r
312 *BlockEntry = ((UINTN)TranslationTable & TT_ADDRESS_MASK_DESCRIPTION_TABLE) | TT_TYPE_TABLE_ENTRY;\r
313 }\r
314 }\r
315 }\r
316\r
317 // Expose the found PageLevel to the caller\r
318 *TableLevel = PageLevel;\r
319\r
320 // Now, we have the Table Level we can get the Block Size associated to this table\r
321 *BlockEntrySize = TT_BLOCK_ENTRY_SIZE_AT_LEVEL (PageLevel);\r
322\r
323 // The last block of the root table depends on the number of entry in this table,\r
324 // otherwise it is always the (TT_ENTRY_COUNT - 1)th entry in the table.\r
325 *LastBlockEntry = TT_LAST_BLOCK_ADDRESS(TranslationTable,\r
326 (PageLevel == RootTableLevel) ? RootTableEntryCount : TT_ENTRY_COUNT);\r
327\r
328 return BlockEntry;\r
329}\r
330\r
331STATIC\r
f49ea03d 332EFI_STATUS\r
d7f03464
AB
333UpdateRegionMapping (\r
334 IN UINT64 *RootTable,\r
335 IN UINT64 RegionStart,\r
336 IN UINT64 RegionLength,\r
337 IN UINT64 Attributes,\r
338 IN UINT64 BlockEntryMask\r
339 )\r
340{\r
341 UINT32 Type;\r
342 UINT64 *BlockEntry;\r
343 UINT64 *LastBlockEntry;\r
344 UINT64 BlockEntrySize;\r
345 UINTN TableLevel;\r
346\r
347 // Ensure the Length is aligned on 4KB boundary\r
348 if ((RegionLength == 0) || ((RegionLength & (SIZE_4KB - 1)) != 0)) {\r
349 ASSERT_EFI_ERROR (EFI_INVALID_PARAMETER);\r
f49ea03d 350 return EFI_INVALID_PARAMETER;\r
d7f03464
AB
351 }\r
352\r
353 do {\r
354 // Get the first Block Entry that matches the Virtual Address and also the information on the Table Descriptor\r
ff5fef14 355 // such as the size of the Block Entry and the address of the last BlockEntry of the Table Descriptor\r
d7f03464
AB
356 BlockEntrySize = RegionLength;\r
357 BlockEntry = GetBlockEntryListFromAddress (RootTable, RegionStart, &TableLevel, &BlockEntrySize, &LastBlockEntry);\r
358 if (BlockEntry == NULL) {\r
359 // GetBlockEntryListFromAddress() return NULL when it fails to allocate new pages from the Translation Tables\r
f49ea03d 360 return EFI_OUT_OF_RESOURCES;\r
d7f03464
AB
361 }\r
362\r
363 if (TableLevel != 3) {\r
364 Type = TT_TYPE_BLOCK_ENTRY;\r
365 } else {\r
366 Type = TT_TYPE_BLOCK_ENTRY_LEVEL3;\r
367 }\r
368\r
369 do {\r
370 // Fill the Block Entry with attribute and output block address\r
371 *BlockEntry &= BlockEntryMask;\r
372 *BlockEntry |= (RegionStart & TT_ADDRESS_MASK_BLOCK_ENTRY) | Attributes | Type;\r
373\r
d5788777
AB
374 ArmUpdateTranslationTableEntry (BlockEntry, (VOID *)RegionStart);\r
375\r
d7f03464
AB
376 // Go to the next BlockEntry\r
377 RegionStart += BlockEntrySize;\r
378 RegionLength -= BlockEntrySize;\r
379 BlockEntry++;\r
380\r
381 // Break the inner loop when next block is a table\r
382 // Rerun GetBlockEntryListFromAddress to avoid page table memory leak\r
76c23f9e 383 if (TableLevel != 3 && BlockEntry <= LastBlockEntry &&\r
d7f03464
AB
384 (*BlockEntry & TT_TYPE_MASK) == TT_TYPE_TABLE_ENTRY) {\r
385 break;\r
386 }\r
387 } while ((RegionLength >= BlockEntrySize) && (BlockEntry <= LastBlockEntry));\r
388 } while (RegionLength != 0);\r
389\r
f49ea03d 390 return EFI_SUCCESS;\r
d7f03464
AB
391}\r
392\r
393STATIC\r
f49ea03d 394EFI_STATUS\r
d7f03464
AB
395FillTranslationTable (\r
396 IN UINT64 *RootTable,\r
397 IN ARM_MEMORY_REGION_DESCRIPTOR *MemoryRegion\r
398 )\r
399{\r
400 return UpdateRegionMapping (\r
401 RootTable,\r
402 MemoryRegion->VirtualBase,\r
403 MemoryRegion->Length,\r
404 ArmMemoryAttributeToPageAttribute (MemoryRegion->Attributes) | TT_AF,\r
405 0\r
406 );\r
407}\r
408\r
e0307a7d
AB
409STATIC\r
410UINT64\r
411GcdAttributeToPageAttribute (\r
412 IN UINT64 GcdAttributes\r
413 )\r
414{\r
415 UINT64 PageAttributes;\r
416\r
417 switch (GcdAttributes & EFI_MEMORY_CACHETYPE_MASK) {\r
418 case EFI_MEMORY_UC:\r
419 PageAttributes = TT_ATTR_INDX_DEVICE_MEMORY;\r
420 break;\r
421 case EFI_MEMORY_WC:\r
422 PageAttributes = TT_ATTR_INDX_MEMORY_NON_CACHEABLE;\r
423 break;\r
424 case EFI_MEMORY_WT:\r
425 PageAttributes = TT_ATTR_INDX_MEMORY_WRITE_THROUGH | TT_SH_INNER_SHAREABLE;\r
426 break;\r
427 case EFI_MEMORY_WB:\r
428 PageAttributes = TT_ATTR_INDX_MEMORY_WRITE_BACK | TT_SH_INNER_SHAREABLE;\r
429 break;\r
430 default:\r
431 PageAttributes = TT_ATTR_INDX_MASK;\r
432 break;\r
433 }\r
434\r
435 if ((GcdAttributes & EFI_MEMORY_XP) != 0 ||\r
436 (GcdAttributes & EFI_MEMORY_CACHETYPE_MASK) == EFI_MEMORY_UC) {\r
437 if (ArmReadCurrentEL () == AARCH64_EL2) {\r
438 PageAttributes |= TT_XN_MASK;\r
439 } else {\r
440 PageAttributes |= TT_UXN_MASK | TT_PXN_MASK;\r
441 }\r
442 }\r
443\r
444 if ((GcdAttributes & EFI_MEMORY_RO) != 0) {\r
445 PageAttributes |= TT_AP_RO_RO;\r
446 }\r
447\r
448 return PageAttributes | TT_AF;\r
449}\r
450\r
f49ea03d 451EFI_STATUS\r
521f3ced 452ArmSetMemoryAttributes (\r
d7f03464
AB
453 IN EFI_PHYSICAL_ADDRESS BaseAddress,\r
454 IN UINT64 Length,\r
d9c0d991 455 IN UINT64 Attributes\r
d7f03464
AB
456 )\r
457{\r
f49ea03d 458 EFI_STATUS Status;\r
d7f03464 459 UINT64 *TranslationTable;\r
e0307a7d
AB
460 UINT64 PageAttributes;\r
461 UINT64 PageAttributeMask;\r
462\r
463 PageAttributes = GcdAttributeToPageAttribute (Attributes);\r
464 PageAttributeMask = 0;\r
465\r
466 if ((Attributes & EFI_MEMORY_CACHETYPE_MASK) == 0) {\r
467 //\r
468 // No memory type was set in Attributes, so we are going to update the\r
469 // permissions only.\r
470 //\r
471 PageAttributes &= TT_AP_MASK | TT_UXN_MASK | TT_PXN_MASK;\r
472 PageAttributeMask = ~(TT_ADDRESS_MASK_BLOCK_ENTRY | TT_AP_MASK |\r
473 TT_PXN_MASK | TT_XN_MASK);\r
474 }\r
d7f03464
AB
475\r
476 TranslationTable = ArmGetTTBR0BaseAddress ();\r
477\r
e0307a7d
AB
478 Status = UpdateRegionMapping (\r
479 TranslationTable,\r
480 BaseAddress,\r
481 Length,\r
482 PageAttributes,\r
483 PageAttributeMask);\r
f49ea03d 484 if (EFI_ERROR (Status)) {\r
d7f03464
AB
485 return Status;\r
486 }\r
487\r
f49ea03d 488 return EFI_SUCCESS;\r
d7f03464
AB
489}\r
490\r
491STATIC\r
f49ea03d 492EFI_STATUS\r
d7f03464
AB
493SetMemoryRegionAttribute (\r
494 IN EFI_PHYSICAL_ADDRESS BaseAddress,\r
495 IN UINT64 Length,\r
496 IN UINT64 Attributes,\r
497 IN UINT64 BlockEntryMask\r
498 )\r
499{\r
f49ea03d 500 EFI_STATUS Status;\r
d7f03464
AB
501 UINT64 *RootTable;\r
502\r
503 RootTable = ArmGetTTBR0BaseAddress ();\r
504\r
505 Status = UpdateRegionMapping (RootTable, BaseAddress, Length, Attributes, BlockEntryMask);\r
f49ea03d 506 if (EFI_ERROR (Status)) {\r
d7f03464
AB
507 return Status;\r
508 }\r
509\r
f49ea03d 510 return EFI_SUCCESS;\r
d7f03464
AB
511}\r
512\r
f49ea03d 513EFI_STATUS\r
d7f03464
AB
514ArmSetMemoryRegionNoExec (\r
515 IN EFI_PHYSICAL_ADDRESS BaseAddress,\r
516 IN UINT64 Length\r
517 )\r
518{\r
519 UINT64 Val;\r
520\r
521 if (ArmReadCurrentEL () == AARCH64_EL1) {\r
522 Val = TT_PXN_MASK | TT_UXN_MASK;\r
523 } else {\r
524 Val = TT_XN_MASK;\r
525 }\r
526\r
527 return SetMemoryRegionAttribute (\r
528 BaseAddress,\r
529 Length,\r
530 Val,\r
531 ~TT_ADDRESS_MASK_BLOCK_ENTRY);\r
532}\r
533\r
f49ea03d 534EFI_STATUS\r
d7f03464
AB
535ArmClearMemoryRegionNoExec (\r
536 IN EFI_PHYSICAL_ADDRESS BaseAddress,\r
537 IN UINT64 Length\r
538 )\r
539{\r
540 UINT64 Mask;\r
541\r
542 // XN maps to UXN in the EL1&0 translation regime\r
543 Mask = ~(TT_ADDRESS_MASK_BLOCK_ENTRY | TT_PXN_MASK | TT_XN_MASK);\r
544\r
545 return SetMemoryRegionAttribute (\r
546 BaseAddress,\r
547 Length,\r
548 0,\r
549 Mask);\r
550}\r
551\r
f49ea03d 552EFI_STATUS\r
d7f03464
AB
553ArmSetMemoryRegionReadOnly (\r
554 IN EFI_PHYSICAL_ADDRESS BaseAddress,\r
555 IN UINT64 Length\r
556 )\r
557{\r
558 return SetMemoryRegionAttribute (\r
559 BaseAddress,\r
560 Length,\r
561 TT_AP_RO_RO,\r
562 ~TT_ADDRESS_MASK_BLOCK_ENTRY);\r
563}\r
564\r
f49ea03d 565EFI_STATUS\r
d7f03464
AB
566ArmClearMemoryRegionReadOnly (\r
567 IN EFI_PHYSICAL_ADDRESS BaseAddress,\r
568 IN UINT64 Length\r
569 )\r
570{\r
571 return SetMemoryRegionAttribute (\r
572 BaseAddress,\r
573 Length,\r
574 TT_AP_RW_RW,\r
575 ~(TT_ADDRESS_MASK_BLOCK_ENTRY | TT_AP_MASK));\r
576}\r
577\r
f49ea03d 578EFI_STATUS\r
d7f03464
AB
579EFIAPI\r
580ArmConfigureMmu (\r
581 IN ARM_MEMORY_REGION_DESCRIPTOR *MemoryTable,\r
582 OUT VOID **TranslationTableBase OPTIONAL,\r
583 OUT UINTN *TranslationTableSize OPTIONAL\r
584 )\r
585{\r
586 VOID* TranslationTable;\r
d7f03464 587 UINT32 TranslationTableAttribute;\r
d7f03464 588 UINT64 MaxAddress;\r
d7f03464
AB
589 UINTN T0SZ;\r
590 UINTN RootTableEntryCount;\r
591 UINT64 TCR;\r
f49ea03d 592 EFI_STATUS Status;\r
d7f03464
AB
593\r
594 if(MemoryTable == NULL) {\r
595 ASSERT (MemoryTable != NULL);\r
f49ea03d 596 return EFI_INVALID_PARAMETER;\r
d7f03464
AB
597 }\r
598\r
e36b243c
AB
599 //\r
600 // Limit the virtual address space to what we can actually use: UEFI\r
601 // mandates a 1:1 mapping, so no point in making the virtual address\r
602 // space larger than the physical address space. We also have to take\r
603 // into account the architectural limitations that result from UEFI's\r
604 // use of 4 KB pages.\r
605 //\r
606 MaxAddress = MIN (LShiftU64 (1ULL, ArmGetPhysicalAddressBits ()) - 1,\r
1c36f028 607 MAX_ALLOC_ADDRESS);\r
d7f03464
AB
608\r
609 // Lookup the Table Level to get the information\r
610 LookupAddresstoRootTable (MaxAddress, &T0SZ, &RootTableEntryCount);\r
611\r
612 //\r
613 // Set TCR that allows us to retrieve T0SZ in the subsequent functions\r
614 //\r
615 // Ideally we will be running at EL2, but should support EL1 as well.\r
616 // UEFI should not run at EL3.\r
617 if (ArmReadCurrentEL () == AARCH64_EL2) {\r
618 //Note: Bits 23 and 31 are reserved(RES1) bits in TCR_EL2\r
619 TCR = T0SZ | (1UL << 31) | (1UL << 23) | TCR_TG0_4KB;\r
620\r
621 // Set the Physical Address Size using MaxAddress\r
622 if (MaxAddress < SIZE_4GB) {\r
623 TCR |= TCR_PS_4GB;\r
624 } else if (MaxAddress < SIZE_64GB) {\r
625 TCR |= TCR_PS_64GB;\r
626 } else if (MaxAddress < SIZE_1TB) {\r
627 TCR |= TCR_PS_1TB;\r
628 } else if (MaxAddress < SIZE_4TB) {\r
629 TCR |= TCR_PS_4TB;\r
630 } else if (MaxAddress < SIZE_16TB) {\r
631 TCR |= TCR_PS_16TB;\r
632 } else if (MaxAddress < SIZE_256TB) {\r
633 TCR |= TCR_PS_256TB;\r
634 } else {\r
635 DEBUG ((EFI_D_ERROR, "ArmConfigureMmu: The MaxAddress 0x%lX is not supported by this MMU configuration.\n", MaxAddress));\r
636 ASSERT (0); // Bigger than 48-bit memory space are not supported\r
f49ea03d 637 return EFI_UNSUPPORTED;\r
d7f03464
AB
638 }\r
639 } else if (ArmReadCurrentEL () == AARCH64_EL1) {\r
640 // Due to Cortex-A57 erratum #822227 we must set TG1[1] == 1, regardless of EPD1.\r
641 TCR = T0SZ | TCR_TG0_4KB | TCR_TG1_4KB | TCR_EPD1;\r
642\r
643 // Set the Physical Address Size using MaxAddress\r
644 if (MaxAddress < SIZE_4GB) {\r
645 TCR |= TCR_IPS_4GB;\r
646 } else if (MaxAddress < SIZE_64GB) {\r
647 TCR |= TCR_IPS_64GB;\r
648 } else if (MaxAddress < SIZE_1TB) {\r
649 TCR |= TCR_IPS_1TB;\r
650 } else if (MaxAddress < SIZE_4TB) {\r
651 TCR |= TCR_IPS_4TB;\r
652 } else if (MaxAddress < SIZE_16TB) {\r
653 TCR |= TCR_IPS_16TB;\r
654 } else if (MaxAddress < SIZE_256TB) {\r
655 TCR |= TCR_IPS_256TB;\r
656 } else {\r
657 DEBUG ((EFI_D_ERROR, "ArmConfigureMmu: The MaxAddress 0x%lX is not supported by this MMU configuration.\n", MaxAddress));\r
658 ASSERT (0); // Bigger than 48-bit memory space are not supported\r
f49ea03d 659 return EFI_UNSUPPORTED;\r
d7f03464
AB
660 }\r
661 } else {\r
662 ASSERT (0); // UEFI is only expected to run at EL2 and EL1, not EL3.\r
f49ea03d 663 return EFI_UNSUPPORTED;\r
d7f03464
AB
664 }\r
665\r
35718840
AB
666 //\r
667 // Translation table walks are always cache coherent on ARMv8-A, so cache\r
668 // maintenance on page tables is never needed. Since there is a risk of\r
669 // loss of coherency when using mismatched attributes, and given that memory\r
670 // is mapped cacheable except for extraordinary cases (such as non-coherent\r
671 // DMA), have the page table walker perform cached accesses as well, and\r
672 // assert below that that matches the attributes we use for CPU accesses to\r
673 // the region.\r
674 //\r
675 TCR |= TCR_SH_INNER_SHAREABLE |\r
676 TCR_RGN_OUTER_WRITE_BACK_ALLOC |\r
677 TCR_RGN_INNER_WRITE_BACK_ALLOC;\r
678\r
d7f03464
AB
679 // Set TCR\r
680 ArmSetTCR (TCR);\r
681\r
aa961dea
AB
682 // Allocate pages for translation table\r
683 TranslationTable = AllocatePages (1);\r
d7f03464 684 if (TranslationTable == NULL) {\r
f49ea03d 685 return EFI_OUT_OF_RESOURCES;\r
d7f03464
AB
686 }\r
687 // We set TTBR0 just after allocating the table to retrieve its location from the subsequent\r
688 // functions without needing to pass this value across the functions. The MMU is only enabled\r
689 // after the translation tables are populated.\r
690 ArmSetTTBR0 (TranslationTable);\r
691\r
692 if (TranslationTableBase != NULL) {\r
693 *TranslationTableBase = TranslationTable;\r
694 }\r
695\r
696 if (TranslationTableSize != NULL) {\r
aa961dea 697 *TranslationTableSize = RootTableEntryCount * sizeof(UINT64);\r
d7f03464
AB
698 }\r
699\r
aa961dea 700 ZeroMem (TranslationTable, RootTableEntryCount * sizeof(UINT64));\r
d7f03464
AB
701\r
702 TranslationTableAttribute = TT_ATTR_INDX_INVALID;\r
703 while (MemoryTable->Length != 0) {\r
35718840
AB
704\r
705 DEBUG_CODE_BEGIN ();\r
706 // Find the memory attribute for the Translation Table\r
707 if ((UINTN)TranslationTable >= MemoryTable->PhysicalBase &&\r
aa961dea 708 (UINTN)TranslationTable + EFI_PAGE_SIZE <= MemoryTable->PhysicalBase +\r
35718840
AB
709 MemoryTable->Length) {\r
710 TranslationTableAttribute = MemoryTable->Attributes;\r
711 }\r
712 DEBUG_CODE_END ();\r
d7f03464
AB
713\r
714 Status = FillTranslationTable (TranslationTable, MemoryTable);\r
f49ea03d 715 if (EFI_ERROR (Status)) {\r
d7f03464
AB
716 goto FREE_TRANSLATION_TABLE;\r
717 }\r
718 MemoryTable++;\r
719 }\r
720\r
35718840
AB
721 ASSERT (TranslationTableAttribute == ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK ||\r
722 TranslationTableAttribute == ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_WRITE_BACK);\r
d7f03464
AB
723\r
724 ArmSetMAIR (MAIR_ATTR(TT_ATTR_INDX_DEVICE_MEMORY, MAIR_ATTR_DEVICE_MEMORY) | // mapped to EFI_MEMORY_UC\r
725 MAIR_ATTR(TT_ATTR_INDX_MEMORY_NON_CACHEABLE, MAIR_ATTR_NORMAL_MEMORY_NON_CACHEABLE) | // mapped to EFI_MEMORY_WC\r
726 MAIR_ATTR(TT_ATTR_INDX_MEMORY_WRITE_THROUGH, MAIR_ATTR_NORMAL_MEMORY_WRITE_THROUGH) | // mapped to EFI_MEMORY_WT\r
727 MAIR_ATTR(TT_ATTR_INDX_MEMORY_WRITE_BACK, MAIR_ATTR_NORMAL_MEMORY_WRITE_BACK)); // mapped to EFI_MEMORY_WB\r
728\r
729 ArmDisableAlignmentCheck ();\r
526f160f 730 ArmEnableStackAlignmentCheck ();\r
d7f03464
AB
731 ArmEnableInstructionCache ();\r
732 ArmEnableDataCache ();\r
733\r
734 ArmEnableMmu ();\r
f49ea03d 735 return EFI_SUCCESS;\r
d7f03464
AB
736\r
737FREE_TRANSLATION_TABLE:\r
aa961dea 738 FreePages (TranslationTable, 1);\r
d7f03464
AB
739 return Status;\r
740}\r
741\r
742RETURN_STATUS\r
743EFIAPI\r
744ArmMmuBaseLibConstructor (\r
745 VOID\r
746 )\r
747{\r
748 extern UINT32 ArmReplaceLiveTranslationEntrySize;\r
749\r
750 //\r
751 // The ArmReplaceLiveTranslationEntry () helper function may be invoked\r
752 // with the MMU off so we have to ensure that it gets cleaned to the PoC\r
753 //\r
754 WriteBackDataCacheRange (ArmReplaceLiveTranslationEntry,\r
755 ArmReplaceLiveTranslationEntrySize);\r
756\r
757 return RETURN_SUCCESS;\r
758}\r