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