]> git.proxmox.com Git - mirror_edk2.git/blame - ArmPkg/Drivers/CpuDxe/AArch64/Mmu.c
ArmPkg: Apply uncrustify changes
[mirror_edk2.git] / ArmPkg / Drivers / CpuDxe / AArch64 / Mmu.c
CommitLineData
25402f5d
HL
1/*++\r
2\r
3Copyright (c) 2009, Hewlett-Packard Company. All rights reserved.<BR>\r
4Portions copyright (c) 2010, Apple Inc. All rights reserved.<BR>\r
a387f2ee 5Portions copyright (c) 2011-2021, Arm Limited. All rights reserved.<BR>\r
b7a09b71 6Copyright (c) 2017, Intel Corporation. All rights reserved.<BR>\r
25402f5d 7\r
4059386c 8SPDX-License-Identifier: BSD-2-Clause-Patent\r
25402f5d
HL
9\r
10\r
11--*/\r
12\r
5b53eaff 13#include <Library/MemoryAllocationLib.h>\r
25402f5d
HL
14#include "CpuDxe.h"\r
15\r
429309e0 16#define INVALID_ENTRY ((UINT32)~0)\r
25402f5d 17\r
f45e254f
AB
18#define MIN_T0SZ 16\r
19#define BITS_PER_LEVEL 9\r
20\r
21STATIC\r
22VOID\r
23GetRootTranslationTableInfo (\r
429309e0
MK
24 IN UINTN T0SZ,\r
25 OUT UINTN *RootTableLevel,\r
26 OUT UINTN *RootTableEntryCount\r
f45e254f
AB
27 )\r
28{\r
429309e0
MK
29 *RootTableLevel = (T0SZ - MIN_T0SZ) / BITS_PER_LEVEL;\r
30 *RootTableEntryCount = TT_ENTRY_COUNT >> (T0SZ - MIN_T0SZ) % BITS_PER_LEVEL;\r
f45e254f
AB
31}\r
32\r
49188b2a
AB
33STATIC\r
34UINT64\r
35PageAttributeToGcdAttribute (\r
429309e0 36 IN UINT64 PageAttributes\r
49188b2a
AB
37 )\r
38{\r
39 UINT64 GcdAttributes;\r
40\r
41 switch (PageAttributes & TT_ATTR_INDX_MASK) {\r
429309e0
MK
42 case TT_ATTR_INDX_DEVICE_MEMORY:\r
43 GcdAttributes = EFI_MEMORY_UC;\r
44 break;\r
45 case TT_ATTR_INDX_MEMORY_NON_CACHEABLE:\r
46 GcdAttributes = EFI_MEMORY_WC;\r
47 break;\r
48 case TT_ATTR_INDX_MEMORY_WRITE_THROUGH:\r
49 GcdAttributes = EFI_MEMORY_WT;\r
50 break;\r
51 case TT_ATTR_INDX_MEMORY_WRITE_BACK:\r
52 GcdAttributes = EFI_MEMORY_WB;\r
53 break;\r
54 default:\r
55 DEBUG ((\r
56 DEBUG_ERROR,\r
57 "PageAttributeToGcdAttribute: PageAttributes:0x%lX not supported.\n",\r
58 PageAttributes\r
59 ));\r
60 ASSERT (0);\r
61 // The Global Coherency Domain (GCD) value is defined as a bit set.\r
62 // Returning 0 means no attribute has been set.\r
63 GcdAttributes = 0;\r
49188b2a
AB
64 }\r
65\r
66 // Determine protection attributes\r
67 if (((PageAttributes & TT_AP_MASK) == TT_AP_NO_RO) ||\r
429309e0
MK
68 ((PageAttributes & TT_AP_MASK) == TT_AP_RO_RO))\r
69 {\r
49188b2a
AB
70 // Read only cases map to write-protect\r
71 GcdAttributes |= EFI_MEMORY_RO;\r
72 }\r
73\r
74 // Process eXecute Never attribute\r
75 if ((PageAttributes & (TT_PXN_MASK | TT_UXN_MASK)) != 0) {\r
76 GcdAttributes |= EFI_MEMORY_XP;\r
77 }\r
78\r
79 return GcdAttributes;\r
80}\r
81\r
25402f5d
HL
82STATIC\r
83UINT64\r
84GetFirstPageAttribute (\r
85 IN UINT64 *FirstLevelTableAddress,\r
429309e0 86 IN UINTN TableLevel\r
25402f5d
HL
87 )\r
88{\r
429309e0 89 UINT64 FirstEntry;\r
25402f5d
HL
90\r
91 // Get the first entry of the table\r
92 FirstEntry = *FirstLevelTableAddress;\r
93\r
429309e0 94 if ((TableLevel != 3) && ((FirstEntry & TT_TYPE_MASK) == TT_TYPE_TABLE_ENTRY)) {\r
25402f5d 95 // Only valid for Levels 0, 1 and 2\r
25402f5d
HL
96\r
97 // Get the attribute of the subsequent table\r
429309e0 98 return GetFirstPageAttribute ((UINT64 *)(FirstEntry & TT_ADDRESS_MASK_DESCRIPTION_TABLE), TableLevel + 1);\r
25402f5d
HL
99 } else if (((FirstEntry & TT_TYPE_MASK) == TT_TYPE_BLOCK_ENTRY) ||\r
100 ((TableLevel == 3) && ((FirstEntry & TT_TYPE_MASK) == TT_TYPE_BLOCK_ENTRY_LEVEL3)))\r
101 {\r
102 return FirstEntry & TT_ATTR_INDX_MASK;\r
103 } else {\r
ee026ea7 104 return INVALID_ENTRY;\r
25402f5d
HL
105 }\r
106}\r
107\r
108STATIC\r
109UINT64\r
110GetNextEntryAttribute (\r
429309e0 111 IN UINT64 *TableAddress,\r
25402f5d
HL
112 IN UINTN EntryCount,\r
113 IN UINTN TableLevel,\r
114 IN UINT64 BaseAddress,\r
429309e0
MK
115 IN OUT UINT32 *PrevEntryAttribute,\r
116 IN OUT UINT64 *StartGcdRegion\r
25402f5d
HL
117 )\r
118{\r
429309e0
MK
119 UINTN Index;\r
120 UINT64 Entry;\r
121 UINT32 EntryAttribute;\r
122 UINT32 EntryType;\r
123 EFI_STATUS Status;\r
124 UINTN NumberOfDescriptors;\r
25402f5d
HL
125 EFI_GCD_MEMORY_SPACE_DESCRIPTOR *MemorySpaceMap;\r
126\r
127 // Get the memory space map from GCD\r
128 MemorySpaceMap = NULL;\r
429309e0 129 Status = gDS->GetMemorySpaceMap (&NumberOfDescriptors, &MemorySpaceMap);\r
25402f5d
HL
130 ASSERT_EFI_ERROR (Status);\r
131\r
132 // We cannot get more than 3-level page table\r
133 ASSERT (TableLevel <= 3);\r
134\r
135 // While the top level table might not contain TT_ENTRY_COUNT entries;\r
136 // the subsequent ones should be filled up\r
137 for (Index = 0; Index < EntryCount; Index++) {\r
429309e0
MK
138 Entry = TableAddress[Index];\r
139 EntryType = Entry & TT_TYPE_MASK;\r
25402f5d
HL
140 EntryAttribute = Entry & TT_ATTR_INDX_MASK;\r
141\r
142 // If Entry is a Table Descriptor type entry then go through the sub-level table\r
143 if ((EntryType == TT_TYPE_BLOCK_ENTRY) ||\r
429309e0
MK
144 ((TableLevel == 3) && (EntryType == TT_TYPE_BLOCK_ENTRY_LEVEL3)))\r
145 {\r
ee026ea7
LL
146 if ((*PrevEntryAttribute == INVALID_ENTRY) || (EntryAttribute != *PrevEntryAttribute)) {\r
147 if (*PrevEntryAttribute != INVALID_ENTRY) {\r
25402f5d 148 // Update GCD with the last region\r
429309e0
MK
149 SetGcdMemorySpaceAttributes (\r
150 MemorySpaceMap,\r
151 NumberOfDescriptors,\r
152 *StartGcdRegion,\r
153 (BaseAddress + (Index * TT_ADDRESS_AT_LEVEL (TableLevel))) - *StartGcdRegion,\r
154 PageAttributeToGcdAttribute (*PrevEntryAttribute)\r
155 );\r
25402f5d
HL
156 }\r
157\r
158 // Start of the new region\r
429309e0 159 *StartGcdRegion = BaseAddress + (Index * TT_ADDRESS_AT_LEVEL (TableLevel));\r
25402f5d
HL
160 *PrevEntryAttribute = EntryAttribute;\r
161 } else {\r
162 continue;\r
163 }\r
164 } else if (EntryType == TT_TYPE_TABLE_ENTRY) {\r
165 // Table Entry type is only valid for Level 0, 1, 2\r
166 ASSERT (TableLevel < 3);\r
167\r
168 // Increase the level number and scan the sub-level table\r
429309e0
MK
169 GetNextEntryAttribute (\r
170 (UINT64 *)(Entry & TT_ADDRESS_MASK_DESCRIPTION_TABLE),\r
171 TT_ENTRY_COUNT,\r
172 TableLevel + 1,\r
173 (BaseAddress + (Index * TT_ADDRESS_AT_LEVEL (TableLevel))),\r
174 PrevEntryAttribute,\r
175 StartGcdRegion\r
176 );\r
25402f5d 177 } else {\r
ee026ea7 178 if (*PrevEntryAttribute != INVALID_ENTRY) {\r
25402f5d 179 // Update GCD with the last region\r
429309e0
MK
180 SetGcdMemorySpaceAttributes (\r
181 MemorySpaceMap,\r
182 NumberOfDescriptors,\r
183 *StartGcdRegion,\r
184 (BaseAddress + (Index * TT_ADDRESS_AT_LEVEL (TableLevel))) - *StartGcdRegion,\r
185 PageAttributeToGcdAttribute (*PrevEntryAttribute)\r
186 );\r
25402f5d
HL
187\r
188 // Start of the new region\r
429309e0 189 *StartGcdRegion = BaseAddress + (Index * TT_ADDRESS_AT_LEVEL (TableLevel));\r
ee026ea7 190 *PrevEntryAttribute = INVALID_ENTRY;\r
25402f5d
HL
191 }\r
192 }\r
193 }\r
194\r
5b53eaff
OM
195 FreePool (MemorySpaceMap);\r
196\r
429309e0 197 return BaseAddress + (EntryCount * TT_ADDRESS_AT_LEVEL (TableLevel));\r
25402f5d
HL
198}\r
199\r
200EFI_STATUS\r
201SyncCacheConfig (\r
429309e0 202 IN EFI_CPU_ARCH_PROTOCOL *CpuProtocol\r
25402f5d
HL
203 )\r
204{\r
429309e0
MK
205 EFI_STATUS Status;\r
206 UINT32 PageAttribute;\r
207 UINT64 *FirstLevelTableAddress;\r
208 UINTN TableLevel;\r
209 UINTN TableCount;\r
210 UINTN NumberOfDescriptors;\r
211 EFI_GCD_MEMORY_SPACE_DESCRIPTOR *MemorySpaceMap;\r
212 UINTN Tcr;\r
213 UINTN T0SZ;\r
214 UINT64 BaseAddressGcdRegion;\r
215 UINT64 EndAddressGcdRegion;\r
25402f5d
HL
216\r
217 // This code assumes MMU is enabled and filed with section translations\r
218 ASSERT (ArmMmuEnabled ());\r
219\r
220 //\r
221 // Get the memory space map from GCD\r
222 //\r
223 MemorySpaceMap = NULL;\r
429309e0 224 Status = gDS->GetMemorySpaceMap (&NumberOfDescriptors, &MemorySpaceMap);\r
25402f5d
HL
225 ASSERT_EFI_ERROR (Status);\r
226\r
227 // The GCD implementation maintains its own copy of the state of memory space attributes. GCD needs\r
228 // to know what the initial memory space attributes are. The CPU Arch. Protocol does not provide a\r
229 // GetMemoryAttributes function for GCD to get this so we must resort to calling GCD (as if we were\r
230 // a client) to update its copy of the attributes. This is bad architecture and should be replaced\r
231 // with a way for GCD to query the CPU Arch. driver of the existing memory space attributes instead.\r
232\r
233 // Obtain page table base\r
429309e0 234 FirstLevelTableAddress = (UINT64 *)(ArmGetTTBR0BaseAddress ());\r
25402f5d
HL
235\r
236 // Get Translation Control Register value\r
237 Tcr = ArmGetTCR ();\r
238 // Get Address Region Size\r
239 T0SZ = Tcr & TCR_T0SZ_MASK;\r
240\r
241 // Get the level of the first table for the indicated Address Region Size\r
242 GetRootTranslationTableInfo (T0SZ, &TableLevel, &TableCount);\r
243\r
244 // First Attribute of the Page Tables\r
245 PageAttribute = GetFirstPageAttribute (FirstLevelTableAddress, TableLevel);\r
246\r
247 // We scan from the start of the memory map (ie: at the address 0x0)\r
248 BaseAddressGcdRegion = 0x0;\r
429309e0
MK
249 EndAddressGcdRegion = GetNextEntryAttribute (\r
250 FirstLevelTableAddress,\r
251 TableCount,\r
252 TableLevel,\r
253 BaseAddressGcdRegion,\r
254 &PageAttribute,\r
255 &BaseAddressGcdRegion\r
256 );\r
25402f5d 257\r
047c0cbb 258 // Update GCD with the last region if valid\r
ee026ea7 259 if (PageAttribute != INVALID_ENTRY) {\r
429309e0
MK
260 SetGcdMemorySpaceAttributes (\r
261 MemorySpaceMap,\r
262 NumberOfDescriptors,\r
263 BaseAddressGcdRegion,\r
264 EndAddressGcdRegion - BaseAddressGcdRegion,\r
265 PageAttributeToGcdAttribute (PageAttribute)\r
266 );\r
047c0cbb 267 }\r
25402f5d 268\r
5b53eaff
OM
269 FreePool (MemorySpaceMap);\r
270\r
25402f5d
HL
271 return EFI_SUCCESS;\r
272}\r
2e969d2e
OM
273\r
274UINT64\r
275EfiAttributeToArmAttribute (\r
429309e0 276 IN UINT64 EfiAttributes\r
2e969d2e
OM
277 )\r
278{\r
429309e0 279 UINT64 ArmAttributes;\r
2e969d2e
OM
280\r
281 switch (EfiAttributes & EFI_MEMORY_CACHETYPE_MASK) {\r
429309e0
MK
282 case EFI_MEMORY_UC:\r
283 if (ArmReadCurrentEL () == AARCH64_EL2) {\r
284 ArmAttributes = TT_ATTR_INDX_DEVICE_MEMORY | TT_XN_MASK;\r
285 } else {\r
286 ArmAttributes = TT_ATTR_INDX_DEVICE_MEMORY | TT_UXN_MASK | TT_PXN_MASK;\r
287 }\r
288\r
289 break;\r
290 case EFI_MEMORY_WC:\r
291 ArmAttributes = TT_ATTR_INDX_MEMORY_NON_CACHEABLE;\r
292 break;\r
293 case EFI_MEMORY_WT:\r
294 ArmAttributes = TT_ATTR_INDX_MEMORY_WRITE_THROUGH | TT_SH_INNER_SHAREABLE;\r
295 break;\r
296 case EFI_MEMORY_WB:\r
297 ArmAttributes = TT_ATTR_INDX_MEMORY_WRITE_BACK | TT_SH_INNER_SHAREABLE;\r
298 break;\r
299 default:\r
300 ArmAttributes = TT_ATTR_INDX_MASK;\r
2e969d2e
OM
301 }\r
302\r
303 // Set the access flag to match the block attributes\r
304 ArmAttributes |= TT_AF;\r
305\r
306 // Determine protection attributes\r
a387f2ee 307 if ((EfiAttributes & EFI_MEMORY_RO) != 0) {\r
2e969d2e
OM
308 ArmAttributes |= TT_AP_RO_RO;\r
309 }\r
310\r
311 // Process eXecute Never attribute\r
a387f2ee 312 if ((EfiAttributes & EFI_MEMORY_XP) != 0) {\r
2e969d2e
OM
313 ArmAttributes |= TT_PXN_MASK;\r
314 }\r
315\r
316 return ArmAttributes;\r
317}\r
318\r
319// This function will recursively go down the page table to find the first block address linked to 'BaseAddress'.\r
320// And then the function will identify the size of the region that has the same page table attribute.\r
321EFI_STATUS\r
322GetMemoryRegionRec (\r
429309e0
MK
323 IN UINT64 *TranslationTable,\r
324 IN UINTN TableLevel,\r
325 IN UINT64 *LastBlockEntry,\r
326 IN OUT UINTN *BaseAddress,\r
327 OUT UINTN *RegionLength,\r
328 OUT UINTN *RegionAttributes\r
2e969d2e
OM
329 )\r
330{\r
429309e0
MK
331 EFI_STATUS Status;\r
332 UINT64 *NextTranslationTable;\r
333 UINT64 *BlockEntry;\r
334 UINT64 BlockEntryType;\r
335 UINT64 EntryType;\r
2e969d2e
OM
336\r
337 if (TableLevel != 3) {\r
338 BlockEntryType = TT_TYPE_BLOCK_ENTRY;\r
339 } else {\r
340 BlockEntryType = TT_TYPE_BLOCK_ENTRY_LEVEL3;\r
341 }\r
342\r
343 // Find the block entry linked to the Base Address\r
429309e0
MK
344 BlockEntry = (UINT64 *)TT_GET_ENTRY_FOR_ADDRESS (TranslationTable, TableLevel, *BaseAddress);\r
345 EntryType = *BlockEntry & TT_TYPE_MASK;\r
2e969d2e 346\r
01273724 347 if ((TableLevel < 3) && (EntryType == TT_TYPE_TABLE_ENTRY)) {\r
429309e0 348 NextTranslationTable = (UINT64 *)(*BlockEntry & TT_ADDRESS_MASK_DESCRIPTION_TABLE);\r
2e969d2e
OM
349\r
350 // The entry is a page table, so we go to the next level\r
351 Status = GetMemoryRegionRec (\r
429309e0
MK
352 NextTranslationTable, // Address of the next level page table\r
353 TableLevel + 1, // Next Page Table level\r
354 (UINTN *)TT_LAST_BLOCK_ADDRESS (NextTranslationTable, TT_ENTRY_COUNT),\r
355 BaseAddress,\r
356 RegionLength,\r
357 RegionAttributes\r
358 );\r
2e969d2e
OM
359\r
360 // In case of 'Success', it means the end of the block region has been found into the upper\r
361 // level translation table\r
429309e0 362 if (!EFI_ERROR (Status)) {\r
2e969d2e
OM
363 return EFI_SUCCESS;\r
364 }\r
c4149528
OM
365\r
366 // Now we processed the table move to the next entry\r
367 BlockEntry++;\r
2e969d2e
OM
368 } else if (EntryType == BlockEntryType) {\r
369 // We have found the BlockEntry attached to the address. We save its start address (the start\r
ff5fef14 370 // address might be before the 'BaseAddress') and attributes\r
429309e0 371 *BaseAddress = *BaseAddress & ~(TT_ADDRESS_AT_LEVEL (TableLevel) - 1);\r
2e969d2e
OM
372 *RegionLength = 0;\r
373 *RegionAttributes = *BlockEntry & TT_ATTRIBUTES_MASK;\r
374 } else {\r
375 // We have an 'Invalid' entry\r
376 return EFI_UNSUPPORTED;\r
377 }\r
378\r
379 while (BlockEntry <= LastBlockEntry) {\r
380 if ((*BlockEntry & TT_ATTRIBUTES_MASK) == *RegionAttributes) {\r
429309e0 381 *RegionLength = *RegionLength + TT_BLOCK_ENTRY_SIZE_AT_LEVEL (TableLevel);\r
2e969d2e
OM
382 } else {\r
383 // In case we have found the end of the region we return success\r
384 return EFI_SUCCESS;\r
385 }\r
429309e0 386\r
2e969d2e
OM
387 BlockEntry++;\r
388 }\r
389\r
390 // If we have reached the end of the TranslationTable and we have not found the end of the region then\r
391 // we return EFI_NOT_FOUND.\r
392 // The caller will continue to look for the memory region at its level\r
393 return EFI_NOT_FOUND;\r
394}\r
395\r
396EFI_STATUS\r
397GetMemoryRegion (\r
429309e0
MK
398 IN OUT UINTN *BaseAddress,\r
399 OUT UINTN *RegionLength,\r
400 OUT UINTN *RegionAttributes\r
2e969d2e
OM
401 )\r
402{\r
403 EFI_STATUS Status;\r
429309e0 404 UINT64 *TranslationTable;\r
2e969d2e
OM
405 UINTN TableLevel;\r
406 UINTN EntryCount;\r
407 UINTN T0SZ;\r
408\r
409 ASSERT ((BaseAddress != NULL) && (RegionLength != NULL) && (RegionAttributes != NULL));\r
410\r
411 TranslationTable = ArmGetTTBR0BaseAddress ();\r
412\r
413 T0SZ = ArmGetTCR () & TCR_T0SZ_MASK;\r
414 // Get the Table info from T0SZ\r
415 GetRootTranslationTableInfo (T0SZ, &TableLevel, &EntryCount);\r
416\r
429309e0
MK
417 Status = GetMemoryRegionRec (\r
418 TranslationTable,\r
419 TableLevel,\r
420 (UINTN *)TT_LAST_BLOCK_ADDRESS (TranslationTable, EntryCount),\r
421 BaseAddress,\r
422 RegionLength,\r
423 RegionAttributes\r
424 );\r
2e969d2e
OM
425\r
426 // If the region continues up to the end of the root table then GetMemoryRegionRec()\r
427 // will return EFI_NOT_FOUND\r
428 if (Status == EFI_NOT_FOUND) {\r
429 return EFI_SUCCESS;\r
430 } else {\r
431 return Status;\r
432 }\r
433}\r