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