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