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