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