]> git.proxmox.com Git - mirror_edk2.git/blame - ArmPkg/Library/ArmMmuLib/AArch64/ArmMmuLibCore.c
ArmPkg/ArmMmuLib: Disable and re-enable MMU only when needed
[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
191fa79b 4* Copyright (c) 2011-2020, ARM Limited. All rights reserved.\r
d7f03464 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
0487cac0
AB
21#include <Library/HobLib.h>\r
22\r
23STATIC\r
24VOID (\r
25 EFIAPI *mReplaceLiveEntryFunc\r
26 )(\r
27 IN UINT64 *Entry,\r
28 IN UINT64 Value,\r
29 IN UINT64 RegionStart,\r
30 IN BOOLEAN DisableMmu\r
31 ) = ArmReplaceLiveTranslationEntry;\r
d7f03464 32\r
d7f03464
AB
33STATIC\r
34UINT64\r
35ArmMemoryAttributeToPageAttribute (\r
36 IN ARM_MEMORY_REGION_ATTRIBUTES Attributes\r
37 )\r
38{\r
39 switch (Attributes) {\r
429309e0
MK
40 case ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK_NONSHAREABLE:\r
41 case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_WRITE_BACK_NONSHAREABLE:\r
42 return TT_ATTR_INDX_MEMORY_WRITE_BACK;\r
43\r
44 case ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK:\r
45 case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_WRITE_BACK:\r
46 return TT_ATTR_INDX_MEMORY_WRITE_BACK | TT_SH_INNER_SHAREABLE;\r
47\r
48 case ARM_MEMORY_REGION_ATTRIBUTE_WRITE_THROUGH:\r
49 case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_WRITE_THROUGH:\r
50 return TT_ATTR_INDX_MEMORY_WRITE_THROUGH | TT_SH_INNER_SHAREABLE;\r
51\r
52 // Uncached and device mappings are treated as outer shareable by default,\r
53 case ARM_MEMORY_REGION_ATTRIBUTE_UNCACHED_UNBUFFERED:\r
54 case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_UNCACHED_UNBUFFERED:\r
55 return TT_ATTR_INDX_MEMORY_NON_CACHEABLE;\r
56\r
57 default:\r
58 ASSERT (0);\r
59 case ARM_MEMORY_REGION_ATTRIBUTE_DEVICE:\r
60 case ARM_MEMORY_REGION_ATTRIBUTE_NONSECURE_DEVICE:\r
61 if (ArmReadCurrentEL () == AARCH64_EL2) {\r
62 return TT_ATTR_INDX_DEVICE_MEMORY | TT_XN_MASK;\r
63 } else {\r
64 return TT_ATTR_INDX_DEVICE_MEMORY | TT_UXN_MASK | TT_PXN_MASK;\r
65 }\r
d7f03464
AB
66 }\r
67}\r
68\r
e93cb72e
AB
69#define MIN_T0SZ 16\r
70#define BITS_PER_LEVEL 9\r
db0f8c2f
AB
71#define MAX_VA_BITS 48\r
72\r
73STATIC\r
74UINTN\r
75GetRootTableEntryCount (\r
429309e0 76 IN UINTN T0SZ\r
db0f8c2f
AB
77 )\r
78{\r
79 return TT_ENTRY_COUNT >> (T0SZ - MIN_T0SZ) % BITS_PER_LEVEL;\r
80}\r
d7f03464 81\r
991c5d89
AB
82STATIC\r
83UINTN\r
84GetRootTableLevel (\r
429309e0 85 IN UINTN T0SZ\r
d7f03464
AB
86 )\r
87{\r
991c5d89 88 return (T0SZ - MIN_T0SZ) / BITS_PER_LEVEL;\r
d7f03464
AB
89}\r
90\r
91STATIC\r
92VOID\r
191fa79b 93ReplaceTableEntry (\r
429309e0
MK
94 IN UINT64 *Entry,\r
95 IN UINT64 Value,\r
96 IN UINT64 RegionStart,\r
0487cac0 97 IN UINT64 BlockMask,\r
429309e0 98 IN BOOLEAN IsLiveBlockMapping\r
d7f03464
AB
99 )\r
100{\r
0487cac0
AB
101 BOOLEAN DisableMmu;\r
102\r
103 //\r
104 // Replacing a live block entry with a table entry (or vice versa) requires a\r
105 // break-before-make sequence as per the architecture. This means the mapping\r
106 // must be made invalid and cleaned from the TLBs first, and this is a bit of\r
107 // a hassle if the mapping in question covers the code that is actually doing\r
108 // the mapping and the unmapping, and so we only bother with this if actually\r
109 // necessary.\r
110 //\r
111\r
112 if (!IsLiveBlockMapping || !ArmMmuEnabled ()) {\r
113 // If the mapping is not a live block mapping, or the MMU is not on yet, we\r
114 // can simply overwrite the entry.\r
d7f03464 115 *Entry = Value;\r
191fa79b 116 ArmUpdateTranslationTableEntry (Entry, (VOID *)(UINTN)RegionStart);\r
d7f03464 117 } else {\r
0487cac0
AB
118 // If the mapping in question does not cover the code that updates the\r
119 // entry in memory, or the entry that we are intending to update, we can\r
120 // use an ordinary break before make. Otherwise, we will need to\r
121 // temporarily disable the MMU.\r
122 DisableMmu = FALSE;\r
123 if ((((RegionStart ^ (UINTN)ArmReplaceLiveTranslationEntry) & ~BlockMask) == 0) ||\r
124 (((RegionStart ^ (UINTN)Entry) & ~BlockMask) == 0))\r
125 {\r
126 DisableMmu = TRUE;\r
127 DEBUG ((DEBUG_WARN, "%a: splitting block entry with MMU disabled\n", __FUNCTION__));\r
128 }\r
129\r
130 ArmReplaceLiveTranslationEntry (Entry, Value, RegionStart, DisableMmu);\r
d7f03464
AB
131 }\r
132}\r
133\r
134STATIC\r
135VOID\r
191fa79b 136FreePageTablesRecursive (\r
d390920e
AB
137 IN UINT64 *TranslationTable,\r
138 IN UINTN Level\r
d7f03464
AB
139 )\r
140{\r
429309e0 141 UINTN Index;\r
d7f03464 142\r
d390920e
AB
143 ASSERT (Level <= 3);\r
144\r
145 if (Level < 3) {\r
146 for (Index = 0; Index < TT_ENTRY_COUNT; Index++) {\r
147 if ((TranslationTable[Index] & TT_TYPE_MASK) == TT_TYPE_TABLE_ENTRY) {\r
429309e0
MK
148 FreePageTablesRecursive (\r
149 (VOID *)(UINTN)(TranslationTable[Index] &\r
150 TT_ADDRESS_MASK_BLOCK_ENTRY),\r
151 Level + 1\r
152 );\r
d390920e 153 }\r
d7f03464
AB
154 }\r
155 }\r
429309e0 156\r
191fa79b 157 FreePages (TranslationTable, 1);\r
d7f03464
AB
158}\r
159\r
5fc89953
AB
160STATIC\r
161BOOLEAN\r
162IsBlockEntry (\r
163 IN UINT64 Entry,\r
164 IN UINTN Level\r
165 )\r
166{\r
167 if (Level == 3) {\r
168 return (Entry & TT_TYPE_MASK) == TT_TYPE_BLOCK_ENTRY_LEVEL3;\r
169 }\r
429309e0 170\r
5fc89953
AB
171 return (Entry & TT_TYPE_MASK) == TT_TYPE_BLOCK_ENTRY;\r
172}\r
173\r
174STATIC\r
175BOOLEAN\r
176IsTableEntry (\r
177 IN UINT64 Entry,\r
178 IN UINTN Level\r
179 )\r
180{\r
181 if (Level == 3) {\r
182 //\r
183 // TT_TYPE_TABLE_ENTRY aliases TT_TYPE_BLOCK_ENTRY_LEVEL3\r
184 // so we need to take the level into account as well.\r
185 //\r
186 return FALSE;\r
187 }\r
429309e0 188\r
5fc89953
AB
189 return (Entry & TT_TYPE_MASK) == TT_TYPE_TABLE_ENTRY;\r
190}\r
191\r
d7f03464 192STATIC\r
191fa79b
AB
193EFI_STATUS\r
194UpdateRegionMappingRecursive (\r
0487cac0
AB
195 IN UINT64 RegionStart,\r
196 IN UINT64 RegionEnd,\r
197 IN UINT64 AttributeSetMask,\r
198 IN UINT64 AttributeClearMask,\r
199 IN UINT64 *PageTable,\r
200 IN UINTN Level,\r
201 IN BOOLEAN TableIsLive\r
d7f03464
AB
202 )\r
203{\r
429309e0
MK
204 UINTN BlockShift;\r
205 UINT64 BlockMask;\r
206 UINT64 BlockEnd;\r
207 UINT64 *Entry;\r
208 UINT64 EntryValue;\r
209 VOID *TranslationTable;\r
210 EFI_STATUS Status;\r
0487cac0 211 BOOLEAN NextTableIsLive;\r
d7f03464 212\r
191fa79b 213 ASSERT (((RegionStart | RegionEnd) & EFI_PAGE_MASK) == 0);\r
d7f03464 214\r
191fa79b 215 BlockShift = (Level + 1) * BITS_PER_LEVEL + MIN_T0SZ;\r
429309e0
MK
216 BlockMask = MAX_UINT64 >> BlockShift;\r
217\r
218 DEBUG ((\r
219 DEBUG_VERBOSE,\r
220 "%a(%d): %llx - %llx set %lx clr %lx\n",\r
221 __FUNCTION__,\r
222 Level,\r
223 RegionStart,\r
224 RegionEnd,\r
225 AttributeSetMask,\r
226 AttributeClearMask\r
227 ));\r
228\r
229 for ( ; RegionStart < RegionEnd; RegionStart = BlockEnd) {\r
191fa79b 230 BlockEnd = MIN (RegionEnd, (RegionStart | BlockMask) + 1);\r
429309e0 231 Entry = &PageTable[(RegionStart >> (64 - BlockShift)) & (TT_ENTRY_COUNT - 1)];\r
d7f03464 232\r
191fa79b
AB
233 //\r
234 // If RegionStart or BlockEnd is not aligned to the block size at this\r
235 // level, we will have to create a table mapping in order to map less\r
236 // than a block, and recurse to create the block or page entries at\r
237 // the next level. No block mappings are allowed at all at level 0,\r
238 // so in that case, we have to recurse unconditionally.\r
239 //\r
0487cac0
AB
240 // One special case to take into account is any region that covers the page\r
241 // table itself: if we'd cover such a region with block mappings, we are\r
242 // more likely to end up in the situation later where we need to disable\r
243 // the MMU in order to update page table entries safely, so prefer page\r
244 // mappings in that particular case.\r
245 //\r
429309e0 246 if ((Level == 0) || (((RegionStart | BlockEnd) & BlockMask) != 0) ||\r
0487cac0 247 ((Level < 3) && (((UINT64)PageTable & ~BlockMask) == RegionStart)) ||\r
ab644cfa 248 IsTableEntry (*Entry, Level))\r
429309e0 249 {\r
191fa79b 250 ASSERT (Level < 3);\r
d7f03464 251\r
5fc89953 252 if (!IsTableEntry (*Entry, Level)) {\r
191fa79b
AB
253 //\r
254 // No table entry exists yet, so we need to allocate a page table\r
255 // for the next level.\r
256 //\r
674e127e 257 TranslationTable = AllocatePages (1);\r
d7f03464 258 if (TranslationTable == NULL) {\r
191fa79b 259 return EFI_OUT_OF_RESOURCES;\r
d7f03464
AB
260 }\r
261\r
748fea62
AB
262 if (!ArmMmuEnabled ()) {\r
263 //\r
264 // Make sure we are not inadvertently hitting in the caches\r
265 // when populating the page tables.\r
266 //\r
267 InvalidateDataCacheRange (TranslationTable, EFI_PAGE_SIZE);\r
268 }\r
269\r
f7079d1b
AB
270 ZeroMem (TranslationTable, EFI_PAGE_SIZE);\r
271\r
5fc89953 272 if (IsBlockEntry (*Entry, Level)) {\r
191fa79b
AB
273 //\r
274 // We are splitting an existing block entry, so we have to populate\r
275 // the new table with the attributes of the block entry it replaces.\r
276 //\r
429309e0
MK
277 Status = UpdateRegionMappingRecursive (\r
278 RegionStart & ~BlockMask,\r
279 (RegionStart | BlockMask) + 1,\r
280 *Entry & TT_ATTRIBUTES_MASK,\r
281 0,\r
282 TranslationTable,\r
0487cac0
AB
283 Level + 1,\r
284 FALSE\r
429309e0 285 );\r
191fa79b
AB
286 if (EFI_ERROR (Status)) {\r
287 //\r
288 // The range we passed to UpdateRegionMappingRecursive () is block\r
289 // aligned, so it is guaranteed that no further pages were allocated\r
290 // by it, and so we only have to free the page we allocated here.\r
291 //\r
292 FreePages (TranslationTable, 1);\r
293 return Status;\r
294 }\r
d7f03464 295 }\r
0487cac0
AB
296\r
297 NextTableIsLive = FALSE;\r
191fa79b
AB
298 } else {\r
299 TranslationTable = (VOID *)(UINTN)(*Entry & TT_ADDRESS_MASK_BLOCK_ENTRY);\r
0487cac0 300 NextTableIsLive = TableIsLive;\r
d7f03464 301 }\r
d7f03464 302\r
191fa79b
AB
303 //\r
304 // Recurse to the next level\r
305 //\r
429309e0
MK
306 Status = UpdateRegionMappingRecursive (\r
307 RegionStart,\r
308 BlockEnd,\r
309 AttributeSetMask,\r
310 AttributeClearMask,\r
311 TranslationTable,\r
0487cac0
AB
312 Level + 1,\r
313 NextTableIsLive\r
429309e0 314 );\r
191fa79b 315 if (EFI_ERROR (Status)) {\r
5fc89953 316 if (!IsTableEntry (*Entry, Level)) {\r
191fa79b
AB
317 //\r
318 // We are creating a new table entry, so on failure, we can free all\r
319 // allocations we made recursively, given that the whole subhierarchy\r
320 // has not been wired into the live page tables yet. (This is not\r
321 // possible for existing table entries, since we cannot revert the\r
322 // modifications we made to the subhierarchy it represents.)\r
323 //\r
d390920e 324 FreePageTablesRecursive (TranslationTable, Level + 1);\r
d7f03464 325 }\r
429309e0 326\r
191fa79b
AB
327 return Status;\r
328 }\r
d7f03464 329\r
5fc89953 330 if (!IsTableEntry (*Entry, Level)) {\r
191fa79b 331 EntryValue = (UINTN)TranslationTable | TT_TYPE_TABLE_ENTRY;\r
429309e0
MK
332 ReplaceTableEntry (\r
333 Entry,\r
334 EntryValue,\r
335 RegionStart,\r
0487cac0
AB
336 BlockMask,\r
337 TableIsLive && IsBlockEntry (*Entry, Level)\r
429309e0 338 );\r
d7f03464 339 }\r
191fa79b 340 } else {\r
429309e0 341 EntryValue = (*Entry & AttributeClearMask) | AttributeSetMask;\r
191fa79b
AB
342 EntryValue |= RegionStart;\r
343 EntryValue |= (Level == 3) ? TT_TYPE_BLOCK_ENTRY_LEVEL3\r
344 : TT_TYPE_BLOCK_ENTRY;\r
345\r
0487cac0 346 ReplaceTableEntry (Entry, EntryValue, RegionStart, BlockMask, FALSE);\r
d7f03464
AB
347 }\r
348 }\r
429309e0 349\r
191fa79b
AB
350 return EFI_SUCCESS;\r
351}\r
d7f03464 352\r
d7f03464 353STATIC\r
f49ea03d 354EFI_STATUS\r
d7f03464 355UpdateRegionMapping (\r
0487cac0
AB
356 IN UINT64 RegionStart,\r
357 IN UINT64 RegionLength,\r
358 IN UINT64 AttributeSetMask,\r
359 IN UINT64 AttributeClearMask,\r
360 IN BOOLEAN TableIsLive\r
d7f03464
AB
361 )\r
362{\r
429309e0 363 UINTN T0SZ;\r
191fa79b 364\r
a9e51860 365 if (((RegionStart | RegionLength) & EFI_PAGE_MASK) != 0) {\r
f49ea03d 366 return EFI_INVALID_PARAMETER;\r
d7f03464
AB
367 }\r
368\r
191fa79b 369 T0SZ = ArmGetTCR () & TCR_T0SZ_MASK;\r
d7f03464 370\r
429309e0
MK
371 return UpdateRegionMappingRecursive (\r
372 RegionStart,\r
373 RegionStart + RegionLength,\r
374 AttributeSetMask,\r
375 AttributeClearMask,\r
376 ArmGetTTBR0BaseAddress (),\r
0487cac0
AB
377 GetRootTableLevel (T0SZ),\r
378 TableIsLive\r
429309e0 379 );\r
d7f03464
AB
380}\r
381\r
382STATIC\r
f49ea03d 383EFI_STATUS\r
d7f03464
AB
384FillTranslationTable (\r
385 IN UINT64 *RootTable,\r
386 IN ARM_MEMORY_REGION_DESCRIPTOR *MemoryRegion\r
387 )\r
388{\r
389 return UpdateRegionMapping (\r
d7f03464
AB
390 MemoryRegion->VirtualBase,\r
391 MemoryRegion->Length,\r
392 ArmMemoryAttributeToPageAttribute (MemoryRegion->Attributes) | TT_AF,\r
0487cac0
AB
393 0,\r
394 FALSE\r
d7f03464
AB
395 );\r
396}\r
397\r
e0307a7d
AB
398STATIC\r
399UINT64\r
400GcdAttributeToPageAttribute (\r
429309e0 401 IN UINT64 GcdAttributes\r
e0307a7d
AB
402 )\r
403{\r
429309e0 404 UINT64 PageAttributes;\r
e0307a7d
AB
405\r
406 switch (GcdAttributes & EFI_MEMORY_CACHETYPE_MASK) {\r
429309e0
MK
407 case EFI_MEMORY_UC:\r
408 PageAttributes = TT_ATTR_INDX_DEVICE_MEMORY;\r
409 break;\r
410 case EFI_MEMORY_WC:\r
411 PageAttributes = TT_ATTR_INDX_MEMORY_NON_CACHEABLE;\r
412 break;\r
413 case EFI_MEMORY_WT:\r
414 PageAttributes = TT_ATTR_INDX_MEMORY_WRITE_THROUGH | TT_SH_INNER_SHAREABLE;\r
415 break;\r
416 case EFI_MEMORY_WB:\r
417 PageAttributes = TT_ATTR_INDX_MEMORY_WRITE_BACK | TT_SH_INNER_SHAREABLE;\r
418 break;\r
419 default:\r
420 PageAttributes = TT_ATTR_INDX_MASK;\r
421 break;\r
e0307a7d
AB
422 }\r
423\r
429309e0
MK
424 if (((GcdAttributes & EFI_MEMORY_XP) != 0) ||\r
425 ((GcdAttributes & EFI_MEMORY_CACHETYPE_MASK) == EFI_MEMORY_UC))\r
426 {\r
e0307a7d
AB
427 if (ArmReadCurrentEL () == AARCH64_EL2) {\r
428 PageAttributes |= TT_XN_MASK;\r
429 } else {\r
430 PageAttributes |= TT_UXN_MASK | TT_PXN_MASK;\r
431 }\r
432 }\r
433\r
434 if ((GcdAttributes & EFI_MEMORY_RO) != 0) {\r
017564d6 435 PageAttributes |= TT_AP_NO_RO;\r
e0307a7d
AB
436 }\r
437\r
438 return PageAttributes | TT_AF;\r
439}\r
440\r
f49ea03d 441EFI_STATUS\r
521f3ced 442ArmSetMemoryAttributes (\r
429309e0
MK
443 IN EFI_PHYSICAL_ADDRESS BaseAddress,\r
444 IN UINT64 Length,\r
445 IN UINT64 Attributes\r
d7f03464
AB
446 )\r
447{\r
429309e0
MK
448 UINT64 PageAttributes;\r
449 UINT64 PageAttributeMask;\r
e0307a7d 450\r
429309e0 451 PageAttributes = GcdAttributeToPageAttribute (Attributes);\r
e0307a7d
AB
452 PageAttributeMask = 0;\r
453\r
454 if ((Attributes & EFI_MEMORY_CACHETYPE_MASK) == 0) {\r
455 //\r
456 // No memory type was set in Attributes, so we are going to update the\r
457 // permissions only.\r
458 //\r
429309e0 459 PageAttributes &= TT_AP_MASK | TT_UXN_MASK | TT_PXN_MASK;\r
e0307a7d
AB
460 PageAttributeMask = ~(TT_ADDRESS_MASK_BLOCK_ENTRY | TT_AP_MASK |\r
461 TT_PXN_MASK | TT_XN_MASK);\r
462 }\r
d7f03464 463\r
429309e0
MK
464 return UpdateRegionMapping (\r
465 BaseAddress,\r
466 Length,\r
467 PageAttributes,\r
0487cac0
AB
468 PageAttributeMask,\r
469 TRUE\r
429309e0 470 );\r
d7f03464
AB
471}\r
472\r
473STATIC\r
f49ea03d 474EFI_STATUS\r
d7f03464 475SetMemoryRegionAttribute (\r
429309e0
MK
476 IN EFI_PHYSICAL_ADDRESS BaseAddress,\r
477 IN UINT64 Length,\r
478 IN UINT64 Attributes,\r
479 IN UINT64 BlockEntryMask\r
d7f03464
AB
480 )\r
481{\r
0487cac0
AB
482 return UpdateRegionMapping (\r
483 BaseAddress,\r
484 Length,\r
485 Attributes,\r
486 BlockEntryMask,\r
487 TRUE\r
488 );\r
d7f03464
AB
489}\r
490\r
f49ea03d 491EFI_STATUS\r
d7f03464 492ArmSetMemoryRegionNoExec (\r
429309e0
MK
493 IN EFI_PHYSICAL_ADDRESS BaseAddress,\r
494 IN UINT64 Length\r
d7f03464
AB
495 )\r
496{\r
429309e0 497 UINT64 Val;\r
d7f03464
AB
498\r
499 if (ArmReadCurrentEL () == AARCH64_EL1) {\r
500 Val = TT_PXN_MASK | TT_UXN_MASK;\r
501 } else {\r
502 Val = TT_XN_MASK;\r
503 }\r
504\r
505 return SetMemoryRegionAttribute (\r
506 BaseAddress,\r
507 Length,\r
508 Val,\r
429309e0
MK
509 ~TT_ADDRESS_MASK_BLOCK_ENTRY\r
510 );\r
d7f03464
AB
511}\r
512\r
f49ea03d 513EFI_STATUS\r
d7f03464 514ArmClearMemoryRegionNoExec (\r
429309e0
MK
515 IN EFI_PHYSICAL_ADDRESS BaseAddress,\r
516 IN UINT64 Length\r
d7f03464
AB
517 )\r
518{\r
429309e0 519 UINT64 Mask;\r
d7f03464
AB
520\r
521 // XN maps to UXN in the EL1&0 translation regime\r
522 Mask = ~(TT_ADDRESS_MASK_BLOCK_ENTRY | TT_PXN_MASK | TT_XN_MASK);\r
523\r
524 return SetMemoryRegionAttribute (\r
525 BaseAddress,\r
526 Length,\r
527 0,\r
429309e0
MK
528 Mask\r
529 );\r
d7f03464
AB
530}\r
531\r
f49ea03d 532EFI_STATUS\r
d7f03464 533ArmSetMemoryRegionReadOnly (\r
429309e0
MK
534 IN EFI_PHYSICAL_ADDRESS BaseAddress,\r
535 IN UINT64 Length\r
d7f03464
AB
536 )\r
537{\r
538 return SetMemoryRegionAttribute (\r
539 BaseAddress,\r
540 Length,\r
017564d6 541 TT_AP_NO_RO,\r
429309e0
MK
542 ~TT_ADDRESS_MASK_BLOCK_ENTRY\r
543 );\r
d7f03464
AB
544}\r
545\r
f49ea03d 546EFI_STATUS\r
d7f03464 547ArmClearMemoryRegionReadOnly (\r
429309e0
MK
548 IN EFI_PHYSICAL_ADDRESS BaseAddress,\r
549 IN UINT64 Length\r
d7f03464
AB
550 )\r
551{\r
552 return SetMemoryRegionAttribute (\r
553 BaseAddress,\r
554 Length,\r
017564d6 555 TT_AP_NO_RW,\r
429309e0
MK
556 ~(TT_ADDRESS_MASK_BLOCK_ENTRY | TT_AP_MASK)\r
557 );\r
d7f03464
AB
558}\r
559\r
f49ea03d 560EFI_STATUS\r
d7f03464
AB
561EFIAPI\r
562ArmConfigureMmu (\r
563 IN ARM_MEMORY_REGION_DESCRIPTOR *MemoryTable,\r
429309e0 564 OUT VOID **TranslationTableBase OPTIONAL,\r
d7f03464
AB
565 OUT UINTN *TranslationTableSize OPTIONAL\r
566 )\r
567{\r
429309e0
MK
568 VOID *TranslationTable;\r
569 UINTN MaxAddressBits;\r
570 UINT64 MaxAddress;\r
571 UINTN T0SZ;\r
572 UINTN RootTableEntryCount;\r
573 UINT64 TCR;\r
574 EFI_STATUS Status;\r
d7f03464 575\r
4249278a 576 if (MemoryTable == NULL) {\r
d7f03464 577 ASSERT (MemoryTable != NULL);\r
f49ea03d 578 return EFI_INVALID_PARAMETER;\r
d7f03464
AB
579 }\r
580\r
e36b243c
AB
581 //\r
582 // Limit the virtual address space to what we can actually use: UEFI\r
583 // mandates a 1:1 mapping, so no point in making the virtual address\r
584 // space larger than the physical address space. We also have to take\r
585 // into account the architectural limitations that result from UEFI's\r
586 // use of 4 KB pages.\r
587 //\r
db0f8c2f 588 MaxAddressBits = MIN (ArmGetPhysicalAddressBits (), MAX_VA_BITS);\r
429309e0 589 MaxAddress = LShiftU64 (1ULL, MaxAddressBits) - 1;\r
d7f03464 590\r
429309e0 591 T0SZ = 64 - MaxAddressBits;\r
db0f8c2f 592 RootTableEntryCount = GetRootTableEntryCount (T0SZ);\r
d7f03464
AB
593\r
594 //\r
595 // Set TCR that allows us to retrieve T0SZ in the subsequent functions\r
596 //\r
597 // Ideally we will be running at EL2, but should support EL1 as well.\r
598 // UEFI should not run at EL3.\r
599 if (ArmReadCurrentEL () == AARCH64_EL2) {\r
429309e0 600 // Note: Bits 23 and 31 are reserved(RES1) bits in TCR_EL2\r
d7f03464
AB
601 TCR = T0SZ | (1UL << 31) | (1UL << 23) | TCR_TG0_4KB;\r
602\r
603 // Set the Physical Address Size using MaxAddress\r
604 if (MaxAddress < SIZE_4GB) {\r
605 TCR |= TCR_PS_4GB;\r
606 } else if (MaxAddress < SIZE_64GB) {\r
607 TCR |= TCR_PS_64GB;\r
608 } else if (MaxAddress < SIZE_1TB) {\r
609 TCR |= TCR_PS_1TB;\r
610 } else if (MaxAddress < SIZE_4TB) {\r
611 TCR |= TCR_PS_4TB;\r
612 } else if (MaxAddress < SIZE_16TB) {\r
613 TCR |= TCR_PS_16TB;\r
614 } else if (MaxAddress < SIZE_256TB) {\r
615 TCR |= TCR_PS_256TB;\r
616 } else {\r
429309e0
MK
617 DEBUG ((\r
618 DEBUG_ERROR,\r
4249278a 619 "ArmConfigureMmu: The MaxAddress 0x%lX is not supported by this MMU configuration.\n",\r
429309e0
MK
620 MaxAddress\r
621 ));\r
d7f03464 622 ASSERT (0); // Bigger than 48-bit memory space are not supported\r
f49ea03d 623 return EFI_UNSUPPORTED;\r
d7f03464
AB
624 }\r
625 } else if (ArmReadCurrentEL () == AARCH64_EL1) {\r
626 // Due to Cortex-A57 erratum #822227 we must set TG1[1] == 1, regardless of EPD1.\r
627 TCR = T0SZ | TCR_TG0_4KB | TCR_TG1_4KB | TCR_EPD1;\r
628\r
629 // Set the Physical Address Size using MaxAddress\r
630 if (MaxAddress < SIZE_4GB) {\r
631 TCR |= TCR_IPS_4GB;\r
632 } else if (MaxAddress < SIZE_64GB) {\r
633 TCR |= TCR_IPS_64GB;\r
634 } else if (MaxAddress < SIZE_1TB) {\r
635 TCR |= TCR_IPS_1TB;\r
636 } else if (MaxAddress < SIZE_4TB) {\r
637 TCR |= TCR_IPS_4TB;\r
638 } else if (MaxAddress < SIZE_16TB) {\r
639 TCR |= TCR_IPS_16TB;\r
640 } else if (MaxAddress < SIZE_256TB) {\r
641 TCR |= TCR_IPS_256TB;\r
642 } else {\r
429309e0
MK
643 DEBUG ((\r
644 DEBUG_ERROR,\r
4249278a 645 "ArmConfigureMmu: The MaxAddress 0x%lX is not supported by this MMU configuration.\n",\r
429309e0
MK
646 MaxAddress\r
647 ));\r
d7f03464 648 ASSERT (0); // Bigger than 48-bit memory space are not supported\r
f49ea03d 649 return EFI_UNSUPPORTED;\r
d7f03464
AB
650 }\r
651 } else {\r
652 ASSERT (0); // UEFI is only expected to run at EL2 and EL1, not EL3.\r
f49ea03d 653 return EFI_UNSUPPORTED;\r
d7f03464
AB
654 }\r
655\r
35718840
AB
656 //\r
657 // Translation table walks are always cache coherent on ARMv8-A, so cache\r
658 // maintenance on page tables is never needed. Since there is a risk of\r
659 // loss of coherency when using mismatched attributes, and given that memory\r
660 // is mapped cacheable except for extraordinary cases (such as non-coherent\r
661 // DMA), have the page table walker perform cached accesses as well, and\r
05db766b 662 // assert below that matches the attributes we use for CPU accesses to\r
35718840
AB
663 // the region.\r
664 //\r
665 TCR |= TCR_SH_INNER_SHAREABLE |\r
666 TCR_RGN_OUTER_WRITE_BACK_ALLOC |\r
667 TCR_RGN_INNER_WRITE_BACK_ALLOC;\r
668\r
d7f03464
AB
669 // Set TCR\r
670 ArmSetTCR (TCR);\r
671\r
aa961dea
AB
672 // Allocate pages for translation table\r
673 TranslationTable = AllocatePages (1);\r
d7f03464 674 if (TranslationTable == NULL) {\r
f49ea03d 675 return EFI_OUT_OF_RESOURCES;\r
d7f03464 676 }\r
429309e0 677\r
4249278a
AB
678 //\r
679 // We set TTBR0 just after allocating the table to retrieve its location from\r
680 // the subsequent functions without needing to pass this value across the\r
681 // functions. The MMU is only enabled after the translation tables are\r
682 // populated.\r
683 //\r
d7f03464
AB
684 ArmSetTTBR0 (TranslationTable);\r
685\r
686 if (TranslationTableBase != NULL) {\r
687 *TranslationTableBase = TranslationTable;\r
688 }\r
689\r
690 if (TranslationTableSize != NULL) {\r
4249278a 691 *TranslationTableSize = RootTableEntryCount * sizeof (UINT64);\r
d7f03464
AB
692 }\r
693\r
748fea62
AB
694 //\r
695 // Make sure we are not inadvertently hitting in the caches\r
696 // when populating the page tables.\r
697 //\r
429309e0
MK
698 InvalidateDataCacheRange (\r
699 TranslationTable,\r
700 RootTableEntryCount * sizeof (UINT64)\r
701 );\r
4249278a 702 ZeroMem (TranslationTable, RootTableEntryCount * sizeof (UINT64));\r
d7f03464 703\r
d7f03464 704 while (MemoryTable->Length != 0) {\r
d7f03464 705 Status = FillTranslationTable (TranslationTable, MemoryTable);\r
f49ea03d 706 if (EFI_ERROR (Status)) {\r
4249278a 707 goto FreeTranslationTable;\r
d7f03464 708 }\r
429309e0 709\r
d7f03464
AB
710 MemoryTable++;\r
711 }\r
712\r
4249278a
AB
713 //\r
714 // EFI_MEMORY_UC ==> MAIR_ATTR_DEVICE_MEMORY\r
715 // EFI_MEMORY_WC ==> MAIR_ATTR_NORMAL_MEMORY_NON_CACHEABLE\r
716 // EFI_MEMORY_WT ==> MAIR_ATTR_NORMAL_MEMORY_WRITE_THROUGH\r
717 // EFI_MEMORY_WB ==> MAIR_ATTR_NORMAL_MEMORY_WRITE_BACK\r
718 //\r
719 ArmSetMAIR (\r
429309e0 720 MAIR_ATTR (TT_ATTR_INDX_DEVICE_MEMORY, MAIR_ATTR_DEVICE_MEMORY) |\r
4249278a
AB
721 MAIR_ATTR (TT_ATTR_INDX_MEMORY_NON_CACHEABLE, MAIR_ATTR_NORMAL_MEMORY_NON_CACHEABLE) |\r
722 MAIR_ATTR (TT_ATTR_INDX_MEMORY_WRITE_THROUGH, MAIR_ATTR_NORMAL_MEMORY_WRITE_THROUGH) |\r
429309e0 723 MAIR_ATTR (TT_ATTR_INDX_MEMORY_WRITE_BACK, MAIR_ATTR_NORMAL_MEMORY_WRITE_BACK)\r
4249278a 724 );\r
d7f03464
AB
725\r
726 ArmDisableAlignmentCheck ();\r
526f160f 727 ArmEnableStackAlignmentCheck ();\r
d7f03464
AB
728 ArmEnableInstructionCache ();\r
729 ArmEnableDataCache ();\r
730\r
731 ArmEnableMmu ();\r
f49ea03d 732 return EFI_SUCCESS;\r
d7f03464 733\r
4249278a 734FreeTranslationTable:\r
aa961dea 735 FreePages (TranslationTable, 1);\r
d7f03464
AB
736 return Status;\r
737}\r
738\r
739RETURN_STATUS\r
740EFIAPI\r
741ArmMmuBaseLibConstructor (\r
742 VOID\r
743 )\r
744{\r
429309e0 745 extern UINT32 ArmReplaceLiveTranslationEntrySize;\r
d7f03464
AB
746\r
747 //\r
748 // The ArmReplaceLiveTranslationEntry () helper function may be invoked\r
749 // with the MMU off so we have to ensure that it gets cleaned to the PoC\r
750 //\r
429309e0
MK
751 WriteBackDataCacheRange (\r
752 (VOID *)(UINTN)ArmReplaceLiveTranslationEntry,\r
753 ArmReplaceLiveTranslationEntrySize\r
754 );\r
d7f03464
AB
755\r
756 return RETURN_SUCCESS;\r
757}\r