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