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 283 // Now we processed the table move to the next entry 284 BlockEntry++; 285 } else if (EntryType == BlockEntryType) { 286 // We have found the BlockEntry attached to the address. We save its start address (the start 287 // address might be before the 'BaseAdress') and attributes 288 *BaseAddress = *BaseAddress & ~(TT_ADDRESS_AT_LEVEL(TableLevel) - 1); 289 *RegionLength = 0; 290 *RegionAttributes = *BlockEntry & TT_ATTRIBUTES_MASK; 291 } else { 292 // We have an 'Invalid' entry 293 return EFI_UNSUPPORTED; 294 } 295 296 while (BlockEntry <= LastBlockEntry) { 297 if ((*BlockEntry & TT_ATTRIBUTES_MASK) == *RegionAttributes) { 298 *RegionLength = *RegionLength + TT_BLOCK_ENTRY_SIZE_AT_LEVEL(TableLevel); 299 } else { 300 // In case we have found the end of the region we return success 301 return EFI_SUCCESS; 302 } 303 BlockEntry++; 304 } 305 306 // If we have reached the end of the TranslationTable and we have not found the end of the region then 307 // we return EFI_NOT_FOUND. 308 // The caller will continue to look for the memory region at its level 309 return EFI_NOT_FOUND; 310 } 311 312 EFI_STATUS 313 GetMemoryRegion ( 314 IN OUT UINTN *BaseAddress, 315 OUT UINTN *RegionLength, 316 OUT UINTN *RegionAttributes 317 ) 318 { 319 EFI_STATUS Status; 320 UINT64 *TranslationTable; 321 UINTN TableLevel; 322 UINTN EntryCount; 323 UINTN T0SZ; 324 325 ASSERT ((BaseAddress != NULL) && (RegionLength != NULL) && (RegionAttributes != NULL)); 326 327 TranslationTable = ArmGetTTBR0BaseAddress (); 328 329 T0SZ = ArmGetTCR () & TCR_T0SZ_MASK; 330 // Get the Table info from T0SZ 331 GetRootTranslationTableInfo (T0SZ, &TableLevel, &EntryCount); 332 333 Status = GetMemoryRegionRec (TranslationTable, TableLevel, 334 (UINTN*)TT_LAST_BLOCK_ADDRESS(TranslationTable, EntryCount), 335 BaseAddress, RegionLength, RegionAttributes); 336 337 // If the region continues up to the end of the root table then GetMemoryRegionRec() 338 // will return EFI_NOT_FOUND 339 if (Status == EFI_NOT_FOUND) { 340 return EFI_SUCCESS; 341 } else { 342 return Status; 343 } 344 } 345