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