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