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>
8 SPDX-License-Identifier: BSD-2-Clause-Patent
13 #include <Library/MemoryAllocationLib.h>
16 #define TT_ATTR_INDX_INVALID ((UINT32)~0)
19 #define BITS_PER_LEVEL 9
23 GetRootTranslationTableInfo (
25 OUT UINTN
*RootTableLevel
,
26 OUT UINTN
*RootTableEntryCount
29 *RootTableLevel
= (T0SZ
- MIN_T0SZ
) / BITS_PER_LEVEL
;
30 *RootTableEntryCount
= TT_ENTRY_COUNT
>> (T0SZ
- MIN_T0SZ
) % BITS_PER_LEVEL
;
35 PageAttributeToGcdAttribute (
36 IN UINT64 PageAttributes
41 switch (PageAttributes
& TT_ATTR_INDX_MASK
) {
42 case TT_ATTR_INDX_DEVICE_MEMORY
:
43 GcdAttributes
= EFI_MEMORY_UC
;
45 case TT_ATTR_INDX_MEMORY_NON_CACHEABLE
:
46 GcdAttributes
= EFI_MEMORY_WC
;
48 case TT_ATTR_INDX_MEMORY_WRITE_THROUGH
:
49 GcdAttributes
= EFI_MEMORY_WT
;
51 case TT_ATTR_INDX_MEMORY_WRITE_BACK
:
52 GcdAttributes
= EFI_MEMORY_WB
;
56 "PageAttributeToGcdAttribute: PageAttributes:0x%lX not supported.\n",
59 // The Global Coherency Domain (GCD) value is defined as a bit set.
60 // Returning 0 means no attribute has been set.
64 // Determine protection attributes
65 if (((PageAttributes
& TT_AP_MASK
) == TT_AP_NO_RO
) ||
66 ((PageAttributes
& TT_AP_MASK
) == TT_AP_RO_RO
)) {
67 // Read only cases map to write-protect
68 GcdAttributes
|= EFI_MEMORY_RO
;
71 // Process eXecute Never attribute
72 if ((PageAttributes
& (TT_PXN_MASK
| TT_UXN_MASK
)) != 0) {
73 GcdAttributes
|= EFI_MEMORY_XP
;
81 GetFirstPageAttribute (
82 IN UINT64
*FirstLevelTableAddress
,
88 // Get the first entry of the table
89 FirstEntry
= *FirstLevelTableAddress
;
91 if ((TableLevel
!= 3) && (FirstEntry
& TT_TYPE_MASK
) == TT_TYPE_TABLE_ENTRY
) {
92 // Only valid for Levels 0, 1 and 2
94 // Get the attribute of the subsequent table
95 return GetFirstPageAttribute ((UINT64
*)(FirstEntry
& TT_ADDRESS_MASK_DESCRIPTION_TABLE
), TableLevel
+ 1);
96 } else if (((FirstEntry
& TT_TYPE_MASK
) == TT_TYPE_BLOCK_ENTRY
) ||
97 ((TableLevel
== 3) && ((FirstEntry
& TT_TYPE_MASK
) == TT_TYPE_BLOCK_ENTRY_LEVEL3
)))
99 return FirstEntry
& TT_ATTR_INDX_MASK
;
101 return TT_ATTR_INDX_INVALID
;
107 GetNextEntryAttribute (
108 IN UINT64
*TableAddress
,
111 IN UINT64 BaseAddress
,
112 IN OUT UINT32
*PrevEntryAttribute
,
113 IN OUT UINT64
*StartGcdRegion
118 UINT32 EntryAttribute
;
121 UINTN NumberOfDescriptors
;
122 EFI_GCD_MEMORY_SPACE_DESCRIPTOR
*MemorySpaceMap
;
124 // Get the memory space map from GCD
125 MemorySpaceMap
= NULL
;
126 Status
= gDS
->GetMemorySpaceMap (&NumberOfDescriptors
, &MemorySpaceMap
);
127 ASSERT_EFI_ERROR (Status
);
129 // We cannot get more than 3-level page table
130 ASSERT (TableLevel
<= 3);
132 // While the top level table might not contain TT_ENTRY_COUNT entries;
133 // the subsequent ones should be filled up
134 for (Index
= 0; Index
< EntryCount
; Index
++) {
135 Entry
= TableAddress
[Index
];
136 EntryType
= Entry
& TT_TYPE_MASK
;
137 EntryAttribute
= Entry
& TT_ATTR_INDX_MASK
;
139 // If Entry is a Table Descriptor type entry then go through the sub-level table
140 if ((EntryType
== TT_TYPE_BLOCK_ENTRY
) ||
141 ((TableLevel
== 3) && (EntryType
== TT_TYPE_BLOCK_ENTRY_LEVEL3
))) {
142 if ((*PrevEntryAttribute
== TT_ATTR_INDX_INVALID
) || (EntryAttribute
!= *PrevEntryAttribute
)) {
143 if (*PrevEntryAttribute
!= TT_ATTR_INDX_INVALID
) {
144 // Update GCD with the last region
145 SetGcdMemorySpaceAttributes (MemorySpaceMap
, NumberOfDescriptors
,
147 (BaseAddress
+ (Index
* TT_ADDRESS_AT_LEVEL(TableLevel
))) - *StartGcdRegion
,
148 PageAttributeToGcdAttribute (*PrevEntryAttribute
));
151 // Start of the new region
152 *StartGcdRegion
= BaseAddress
+ (Index
* TT_ADDRESS_AT_LEVEL(TableLevel
));
153 *PrevEntryAttribute
= EntryAttribute
;
157 } else if (EntryType
== TT_TYPE_TABLE_ENTRY
) {
158 // Table Entry type is only valid for Level 0, 1, 2
159 ASSERT (TableLevel
< 3);
161 // Increase the level number and scan the sub-level table
162 GetNextEntryAttribute ((UINT64
*)(Entry
& TT_ADDRESS_MASK_DESCRIPTION_TABLE
),
163 TT_ENTRY_COUNT
, TableLevel
+ 1,
164 (BaseAddress
+ (Index
* TT_ADDRESS_AT_LEVEL(TableLevel
))),
165 PrevEntryAttribute
, StartGcdRegion
);
167 if (*PrevEntryAttribute
!= TT_ATTR_INDX_INVALID
) {
168 // Update GCD with the last region
169 SetGcdMemorySpaceAttributes (MemorySpaceMap
, NumberOfDescriptors
,
171 (BaseAddress
+ (Index
* TT_ADDRESS_AT_LEVEL(TableLevel
))) - *StartGcdRegion
,
172 PageAttributeToGcdAttribute (*PrevEntryAttribute
));
174 // Start of the new region
175 *StartGcdRegion
= BaseAddress
+ (Index
* TT_ADDRESS_AT_LEVEL(TableLevel
));
176 *PrevEntryAttribute
= TT_ATTR_INDX_INVALID
;
181 FreePool (MemorySpaceMap
);
183 return BaseAddress
+ (EntryCount
* TT_ADDRESS_AT_LEVEL(TableLevel
));
188 IN EFI_CPU_ARCH_PROTOCOL
*CpuProtocol
192 UINT32 PageAttribute
= 0;
193 UINT64
*FirstLevelTableAddress
;
196 UINTN NumberOfDescriptors
;
197 EFI_GCD_MEMORY_SPACE_DESCRIPTOR
*MemorySpaceMap
;
200 UINT64 BaseAddressGcdRegion
;
201 UINT64 EndAddressGcdRegion
;
203 // This code assumes MMU is enabled and filed with section translations
204 ASSERT (ArmMmuEnabled ());
207 // Get the memory space map from GCD
209 MemorySpaceMap
= NULL
;
210 Status
= gDS
->GetMemorySpaceMap (&NumberOfDescriptors
, &MemorySpaceMap
);
211 ASSERT_EFI_ERROR (Status
);
213 // The GCD implementation maintains its own copy of the state of memory space attributes. GCD needs
214 // to know what the initial memory space attributes are. The CPU Arch. Protocol does not provide a
215 // GetMemoryAttributes function for GCD to get this so we must resort to calling GCD (as if we were
216 // a client) to update its copy of the attributes. This is bad architecture and should be replaced
217 // with a way for GCD to query the CPU Arch. driver of the existing memory space attributes instead.
219 // Obtain page table base
220 FirstLevelTableAddress
= (UINT64
*)(ArmGetTTBR0BaseAddress ());
222 // Get Translation Control Register value
224 // Get Address Region Size
225 T0SZ
= Tcr
& TCR_T0SZ_MASK
;
227 // Get the level of the first table for the indicated Address Region Size
228 GetRootTranslationTableInfo (T0SZ
, &TableLevel
, &TableCount
);
230 // First Attribute of the Page Tables
231 PageAttribute
= GetFirstPageAttribute (FirstLevelTableAddress
, TableLevel
);
233 // We scan from the start of the memory map (ie: at the address 0x0)
234 BaseAddressGcdRegion
= 0x0;
235 EndAddressGcdRegion
= GetNextEntryAttribute (FirstLevelTableAddress
,
236 TableCount
, TableLevel
,
237 BaseAddressGcdRegion
,
238 &PageAttribute
, &BaseAddressGcdRegion
);
240 // Update GCD with the last region if valid
241 if (PageAttribute
!= TT_ATTR_INDX_INVALID
) {
242 SetGcdMemorySpaceAttributes (MemorySpaceMap
, NumberOfDescriptors
,
243 BaseAddressGcdRegion
,
244 EndAddressGcdRegion
- BaseAddressGcdRegion
,
245 PageAttributeToGcdAttribute (PageAttribute
));
248 FreePool (MemorySpaceMap
);
254 EfiAttributeToArmAttribute (
255 IN UINT64 EfiAttributes
258 UINT64 ArmAttributes
;
260 switch (EfiAttributes
& EFI_MEMORY_CACHETYPE_MASK
) {
262 if (ArmReadCurrentEL () == AARCH64_EL2
) {
263 ArmAttributes
= TT_ATTR_INDX_DEVICE_MEMORY
| TT_XN_MASK
;
265 ArmAttributes
= TT_ATTR_INDX_DEVICE_MEMORY
| TT_UXN_MASK
| TT_PXN_MASK
;
269 ArmAttributes
= TT_ATTR_INDX_MEMORY_NON_CACHEABLE
;
272 ArmAttributes
= TT_ATTR_INDX_MEMORY_WRITE_THROUGH
| TT_SH_INNER_SHAREABLE
;
275 ArmAttributes
= TT_ATTR_INDX_MEMORY_WRITE_BACK
| TT_SH_INNER_SHAREABLE
;
278 ArmAttributes
= TT_ATTR_INDX_MASK
;
281 // Set the access flag to match the block attributes
282 ArmAttributes
|= TT_AF
;
284 // Determine protection attributes
285 if (EfiAttributes
& EFI_MEMORY_RO
) {
286 ArmAttributes
|= TT_AP_RO_RO
;
289 // Process eXecute Never attribute
290 if (EfiAttributes
& EFI_MEMORY_XP
) {
291 ArmAttributes
|= TT_PXN_MASK
;
294 return ArmAttributes
;
297 // This function will recursively go down the page table to find the first block address linked to 'BaseAddress'.
298 // And then the function will identify the size of the region that has the same page table attribute.
301 IN UINT64
*TranslationTable
,
303 IN UINT64
*LastBlockEntry
,
304 IN OUT UINTN
*BaseAddress
,
305 OUT UINTN
*RegionLength
,
306 OUT UINTN
*RegionAttributes
310 UINT64
*NextTranslationTable
;
312 UINT64 BlockEntryType
;
315 if (TableLevel
!= 3) {
316 BlockEntryType
= TT_TYPE_BLOCK_ENTRY
;
318 BlockEntryType
= TT_TYPE_BLOCK_ENTRY_LEVEL3
;
321 // Find the block entry linked to the Base Address
322 BlockEntry
= (UINT64
*)TT_GET_ENTRY_FOR_ADDRESS (TranslationTable
, TableLevel
, *BaseAddress
);
323 EntryType
= *BlockEntry
& TT_TYPE_MASK
;
325 if ((TableLevel
< 3) && (EntryType
== TT_TYPE_TABLE_ENTRY
)) {
326 NextTranslationTable
= (UINT64
*)(*BlockEntry
& TT_ADDRESS_MASK_DESCRIPTION_TABLE
);
328 // The entry is a page table, so we go to the next level
329 Status
= GetMemoryRegionRec (
330 NextTranslationTable
, // Address of the next level page table
331 TableLevel
+ 1, // Next Page Table level
332 (UINTN
*)TT_LAST_BLOCK_ADDRESS(NextTranslationTable
, TT_ENTRY_COUNT
),
333 BaseAddress
, RegionLength
, RegionAttributes
);
335 // In case of 'Success', it means the end of the block region has been found into the upper
336 // level translation table
337 if (!EFI_ERROR(Status
)) {
341 // Now we processed the table move to the next entry
343 } else if (EntryType
== BlockEntryType
) {
344 // We have found the BlockEntry attached to the address. We save its start address (the start
345 // address might be before the 'BaseAddress') and attributes
346 *BaseAddress
= *BaseAddress
& ~(TT_ADDRESS_AT_LEVEL(TableLevel
) - 1);
348 *RegionAttributes
= *BlockEntry
& TT_ATTRIBUTES_MASK
;
350 // We have an 'Invalid' entry
351 return EFI_UNSUPPORTED
;
354 while (BlockEntry
<= LastBlockEntry
) {
355 if ((*BlockEntry
& TT_ATTRIBUTES_MASK
) == *RegionAttributes
) {
356 *RegionLength
= *RegionLength
+ TT_BLOCK_ENTRY_SIZE_AT_LEVEL(TableLevel
);
358 // In case we have found the end of the region we return success
364 // If we have reached the end of the TranslationTable and we have not found the end of the region then
365 // we return EFI_NOT_FOUND.
366 // The caller will continue to look for the memory region at its level
367 return EFI_NOT_FOUND
;
372 IN OUT UINTN
*BaseAddress
,
373 OUT UINTN
*RegionLength
,
374 OUT UINTN
*RegionAttributes
378 UINT64
*TranslationTable
;
383 ASSERT ((BaseAddress
!= NULL
) && (RegionLength
!= NULL
) && (RegionAttributes
!= NULL
));
385 TranslationTable
= ArmGetTTBR0BaseAddress ();
387 T0SZ
= ArmGetTCR () & TCR_T0SZ_MASK
;
388 // Get the Table info from T0SZ
389 GetRootTranslationTableInfo (T0SZ
, &TableLevel
, &EntryCount
);
391 Status
= GetMemoryRegionRec (TranslationTable
, TableLevel
,
392 (UINTN
*)TT_LAST_BLOCK_ADDRESS(TranslationTable
, EntryCount
),
393 BaseAddress
, RegionLength
, RegionAttributes
);
395 // If the region continues up to the end of the root table then GetMemoryRegionRec()
396 // will return EFI_NOT_FOUND
397 if (Status
== EFI_NOT_FOUND
) {