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