/*++

Copyright (c) 2006, Intel Corporation                                                         
All rights reserved. This program and the accompanying materials                          
are licensed and made available under the terms and conditions of the BSD License         
which accompanies this distribution.  The full text of the license may be found at        
http://opensource.org/licenses/bsd-license.php                                            
                                                                                          
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,                     
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.             

Module Name:
  VirtualMemory.c
  
Abstract:

  x64 Virtual Memory Management Services in the form of an IA-32 driver.  
  Used to establish a 1:1 Virtual to Physical Mapping that is required to
  enter Long Mode (x64 64-bit mode).

  While we make a 1:1 mapping (identity mapping) for all physical pages 
  we still need to use the MTRR's to ensure that the cachability attirbutes
  for all memory regions is correct.

  The basic idea is to use 2MB page table entries where ever possible. If
  more granularity of cachability is required then 4K page tables are used.

  References:
    1) IA-32 Intel(R) Atchitecture Software Developer's Manual Volume 1:Basic Architecture, Intel
    2) IA-32 Intel(R) Atchitecture Software Developer's Manual Volume 2:Instruction Set Reference, Intel
    3) IA-32 Intel(R) Atchitecture Software Developer's Manual Volume 3:System Programmer's Guide, Intel
  
--*/  

#include "VirtualMemory.h"

x64_MTRR_VARIABLE_RANGE     *mMTRRVariableRange;
x64_MTRR_FIXED_RANGE        mMTRRFixedRange;


//
// Physial memory limit values for each of the 11 fixed MTRRs
//
UINTN mFixedRangeLimit[] = {
  0x7FFFF,  // Fixed MTRR  #0 describes 0x00000..0x7FFFF
  0x9FFFF,  // Fixed MTRR  #1 describes 0x80000..0x9FFFF
  0xBFFFF,  // Fixed MTRR  #2 describes 0xA0000..0xBFFFF
  0xC7FFF,  // Fixed MTRR  #3 describes 0xC0000..0xC7FFF
  0xCFFFF,  // Fixed MTRR  #4 describes 0xC8000..0xCFFFF
  0xD7FFF,  // Fixed MTRR  #5 describes 0xD0000..0xD7FFF
  0xDFFFF,  // Fixed MTRR  #6 describes 0xD8000..0xDFFFF
  0xE7FFF,  // Fixed MTRR  #7 describes 0xE0000..0xE7FFF
  0xEFFFF,  // Fixed MTRR  #8 describes 0xE8000..0xEFFFF
  0xF7FFF,  // Fixed MTRR  #9 describes 0xF0000..0xF7FFF
  0xFFFFF   // Fixed MTRR #10 describes 0xF8000..0xFFFFF
};

//
// The size, in bits, of each of the 11 fixed MTRR.
//
UINTN mFixedRangeShift[] = {
  16,   // Fixed MTRR  #0 describes 8, 64 KB ranges
  14,   // Fixed MTRR  #1 describes 8, 16 KB ranges
  14,   // Fixed MTRR  #2 describes 8, 16 KB ranges
  12,   // Fixed MTRR  #3 describes 8, 4 KB ranges
  12,   // Fixed MTRR  #4 describes 8, 4 KB ranges
  12,   // Fixed MTRR  #5 describes 8, 4 KB ranges
  12,   // Fixed MTRR  #6 describes 8, 4 KB ranges
  12,   // Fixed MTRR  #7 describes 8, 4 KB ranges
  12,   // Fixed MTRR  #8 describes 8, 4 KB ranges
  12,   // Fixed MTRR  #9 describes 8, 4 KB ranges
  12    // Fixed MTRR #10 describes 8, 4 KB ranges
};


UINTN mPowerOf2[] = {
  1,
  2,
  4,
  8,
  16,
  32,
  64,
  128,
  256,
  512
};

x64_MTRR_MEMORY_TYPE
EfiGetMTRRMemoryType (
  IN  EFI_PHYSICAL_ADDRESS      Address
  )
/*++

Routine Description:

  Retrieves the memory type from the MTRR that describes a physical address.

Arguments:

  VariableRange - Set of Variable MTRRs

  FixedRange    - Set of Fixed MTRRs

  Address       - The physical address for which the MTRR memory type is being retrieved

Returns:

  The MTRR Memory Type for the physical memory specified by Address.

--*/
{
  UINTN                   Index;
  UINTN                   TypeIndex;
  BOOLEAN                 Found;
  x64_MTRR_MEMORY_TYPE  VariableType;
  EFI_PHYSICAL_ADDRESS    MaskBase;
  EFI_PHYSICAL_ADDRESS    PhysMask;

  //
  // If the MTRRs are disabled, then return the Uncached Memory Type
  //
  if (mMTRRFixedRange.DefaultType.Bits.E == 0) {
    return Uncached;
  }

  //
  // If the CPU supports Fixed MTRRs and the Fixed MTRRs are enabled, then 
  // see if Address falls into one of the Fixed MTRRs
  //
  if (mMTRRFixedRange.Capabilities.Bits.FIX && mMTRRFixedRange.DefaultType.Bits.FE) {
    //
    // Loop though 11 fixed MTRRs
    //
    for (Index = 0; Index < 11; Index++) {
      //
      // Check for a matching range
      //
      if (Address <= mFixedRangeLimit[Index]) {
        //
        // Compute the offset address into the MTRR bu subtrating the base address of the MTRR
        //
        if (Index > 0) {
          Address = Address - (mFixedRangeLimit[Index-1] + 1);
        }
        //
        // Retrieve the index into the MTRR to extract the memory type.  The range is 0..7
        //
        TypeIndex = (UINTN)RShiftU64 (Address, mFixedRangeShift[Index]);
        
        //
        // Retrieve and return the memory type for the matching range
        //
        return mMTRRFixedRange.Fixed[Index].Type[TypeIndex];
      }
    }
  }

  //
  // If Address was not found in a Fixed MTRR, then search the Variable MTRRs
  //
  for (Index = 0, Found = FALSE, VariableType = WriteBack; Index < mMTRRFixedRange.Capabilities.Bits.VCNT; Index++) {
    //
    // BugBug: __aullshr complier error
    //
    if ((mMTRRVariableRange[Index].PhysMask.Uint64 & 0x800) == 0x800) {    
    //if (mMTRRVariableRange[Index].PhysMask.Bits.Valid == 1) {
      PhysMask = mMTRRVariableRange[Index].PhysMask.Uint64 & ~0xfff;
      MaskBase = PhysMask & (mMTRRVariableRange[Index].PhysBase.Uint64 & ~0xfff);
      if (MaskBase == (PhysMask & Address)) {
        //
        // Check to see how many matches we find
        //
        Found = TRUE;
        if ((mMTRRVariableRange[Index].PhysBase.Bits.Type == Uncached) || (VariableType == Uncached)) {
          //
          // If any matching region uses UC, the memory region is UC
          //
          VariableType = Uncached;
        } else if ((mMTRRVariableRange[Index].PhysBase.Bits.Type == WriteThrough) || (VariableType == WriteThrough)){
          //
          // If it's WT and WB then set it to WT. If it's WT and other type it's undefined
          //
          VariableType = WriteThrough;
        } else {
          VariableType = mMTRRVariableRange[Index].PhysBase.Bits.Type;
        }
      }
    }
  }
  
  if (Found) {
    return VariableType;
  }

  //
  // Address was not found in the Fixed or Variable MTRRs, so return the default memory type
  //
  return mMTRRFixedRange.DefaultType.Bits.Type;
}


BOOLEAN
CanNotUse2MBPage (
  IN  EFI_PHYSICAL_ADDRESS  BaseAddress
  )
/*++

Routine Description:
  Test to see if a 2MB aligned page has all the same attributes. If a 2MB page
  has more than one attibute type it needs to be split into multiple 4K pages.

Arguments:
  BaseAddress - 2MB aligned address to check out

Returns:
  TRUE  - This 2MB address range (BaseAddress) can NOT be mapped by a 2MB page
  FALSE - This 2MB address range can be mapped by a 2MB page

--*/
{
  UINTN                   Index;
  x64_MTRR_MEMORY_TYPE  MemoryType;
  x64_MTRR_MEMORY_TYPE  PreviousMemoryType;
  
  //
  // Address needs to be 2MB aligned
  //
  ASSERT ((BaseAddress & 0x1fffff) == 0);

  PreviousMemoryType = -1;
  for (Index = 0; Index < 512; Index++, BaseAddress += 0x1000) {
    MemoryType = EfiGetMTRRMemoryType (BaseAddress);
    if ((Index != 0) && (MemoryType != PreviousMemoryType)) {
      return TRUE;
    }

    PreviousMemoryType = MemoryType;
  }

  //
  // All the pages had the same type
  //
  return FALSE;
}




VOID
Convert2MBPageTo4KPages (  
  IN  x64_PAGE_TABLE_ENTRY_2M   *PageDirectoryEntry2MB, 
  IN  EFI_PHYSICAL_ADDRESS        PageAddress
  )
/*++

Routine Description:
 Convert a single 2MB page entry to 512 4K page entries. The attributes for 
 the 4K pages are read from the MTRR registers.

Arguments:
  PageDirectoryEntry2MB - Page directory entry for PageAddress
  PageAddress           - 2MB algined address of region to convert

Returns:
  None

--*/
{
  EFI_PHYSICAL_ADDRESS                          Address;
  x64_PAGE_DIRECTORY_ENTRY_4K                   *PageDirectoryEntry4k;
  x64_PAGE_TABLE_ENTRY_4K                       *PageTableEntry;
  UINTN                                         Index1;

  //
  // Allocate the page table entry for the 4K pages
  //
  PageTableEntry = (x64_PAGE_TABLE_ENTRY_4K *) AllocatePages (1);

  ASSERT (PageTableEntry != NULL);

  //
  // Convert PageDirectoryEntry2MB into a 4K Page Directory
  //
  PageDirectoryEntry4k = (x64_PAGE_DIRECTORY_ENTRY_4K *)PageDirectoryEntry2MB;
  PageDirectoryEntry2MB->Uint64 = (UINT64)PageTableEntry;
  PageDirectoryEntry2MB->Bits.ReadWrite = 1;
  PageDirectoryEntry2MB->Bits.Present = 1;
  
  //
  // Fill in the 4K page entries with the attributes from the MTRRs
  //
  for (Index1 = 0, Address = PageAddress; Index1 < 512; Index1++, PageTableEntry++, Address += 0x1000) {
    PageTableEntry->Uint64 = (UINT64)Address;
    PageTableEntry->Bits.ReadWrite = 1;
    PageTableEntry->Bits.Present = 1;
  }
}


EFI_PHYSICAL_ADDRESS
CreateIdentityMappingPageTables (
  IN UINT32                NumberOfProcessorPhysicalAddressBits
  )
/*++

Routine Description:

  Allocates and fills in the Page Directory and Page Table Entries to
  establish a 1:1 Virtual to Physical mapping for physical memory from
  0 to 4GB.  Memory above 4GB is not mapped.  The MTRRs are used to 
  determine the cachability of the physical memory regions

Arguments:

  NumberOfProcessorPhysicalAddressBits - Number of processor address bits to use.
                                         Limits the number of page table entries 
                                         to the physical address space.

Returns:
  EFI_OUT_OF_RESOURCES  There are not enough resources to allocate the Page Tables

  EFI_SUCCESS           The 1:1 Virtual to Physical identity mapping was created

--*/
{  
  EFI_PHYSICAL_ADDRESS                          PageAddress;
  UINTN                                         Index;
  UINTN                                         MaxBitsSupported;
  UINTN                                         Index1;
  UINTN                                         Index2;
  x64_PAGE_MAP_AND_DIRECTORY_POINTER_2MB_4K     *PageMapLevel4Entry;
  x64_PAGE_MAP_AND_DIRECTORY_POINTER_2MB_4K     *PageMap;
  x64_PAGE_MAP_AND_DIRECTORY_POINTER_2MB_4K     *PageDirectoryPointerEntry;
  x64_PAGE_TABLE_ENTRY_2M                       *PageDirectoryEntry2MB;


  //
  //  Page Table structure 4 level 4K, 3 level 2MB.
  //
  //                   PageMapLevel4Entry        : bits 47-39
  //                   PageDirectoryPointerEntry : bits 38-30
  //  Page Table 2MB : PageDirectoryEntry2M      : bits 29-21
  //  Page Table 4K  : PageDirectoryEntry4K      : bits 29 - 21
  //                   PageTableEntry            : bits 20 - 12
  //
  // Strategy is to map every thing in the processor address space using 
  //  2MB pages. If more  granularity is required the 2MB page will get 
  //  converted to set of 4K pages. 
  //

  //
  // By architecture only one PageMapLevel4 exists - so lets allocate storgage for it.
  //
  PageMap = PageMapLevel4Entry = (x64_PAGE_MAP_AND_DIRECTORY_POINTER_2MB_4K *) AllocatePages (1);
  ASSERT (PageMap != NULL);
  PageAddress = 0;

  //
  // The number of page-map Level-4 Offset entries is based on the number of 
  // physical address bits. Less than equal to 38 bits only takes one entry.
  // 512 entries represents 48 address bits. 
  //
  if (NumberOfProcessorPhysicalAddressBits <= 38) {
    MaxBitsSupported = 1;
  } else {
    MaxBitsSupported = mPowerOf2[NumberOfProcessorPhysicalAddressBits - 39];
  }

  for (Index = 0; Index < MaxBitsSupported; Index++, PageMapLevel4Entry++) {
    //
    // Each PML4 entry points to a page of Page Directory Pointer entires.
    //  So lets allocate space for them and fill them in in the Index1 loop.
    //  
    PageDirectoryPointerEntry = (x64_PAGE_MAP_AND_DIRECTORY_POINTER_2MB_4K *) AllocatePages (1);
    ASSERT (PageDirectoryPointerEntry != NULL);

    //
    // Make a PML4 Entry
    //
    PageMapLevel4Entry->Uint64 = (UINT64)(UINTN)PageDirectoryPointerEntry;
    PageMapLevel4Entry->Bits.ReadWrite = 1;
    PageMapLevel4Entry->Bits.Present = 1;

    for (Index1 = 0; Index1 < 512; Index1++, PageDirectoryPointerEntry++) {
      //
      // Each Directory Pointer entries points to a page of Page Directory entires.
      //  So lets allocate space for them and fill them in in the Index2 loop.
      //       
      PageDirectoryEntry2MB = (x64_PAGE_TABLE_ENTRY_2M *) AllocatePages (1);
      ASSERT (PageDirectoryEntry2MB != NULL);

      //
      // Fill in a Page Directory Pointer Entries
      //
      PageDirectoryPointerEntry->Uint64 = (UINT64)(UINTN)PageDirectoryEntry2MB;
      PageDirectoryPointerEntry->Bits.ReadWrite = 1;
      PageDirectoryPointerEntry->Bits.Present = 1;

      for (Index2 = 0; Index2 < 512; Index2++, PageDirectoryEntry2MB++, PageAddress += 0x200000) {
        //
        // Fill in the Page Directory entries
        //
        PageDirectoryEntry2MB->Uint64 = (UINT64)PageAddress;
        PageDirectoryEntry2MB->Bits.ReadWrite = 1;
        PageDirectoryEntry2MB->Bits.Present = 1;
        PageDirectoryEntry2MB->Bits.MustBe1 = 1;

        if (CanNotUse2MBPage (PageAddress)) {
          //
          // Check to see if all 2MB has the same mapping. If not convert
          //  to 4K pages by adding the 4th level of page table entries
          //
          Convert2MBPageTo4KPages (PageDirectoryEntry2MB, PageAddress);
        }
      }
    }
  }

  //
  // For the PML4 entries we are not using fill in a null entry.
  //  for now we just copy the first entry.
  //
  for (; Index < 512; Index++, PageMapLevel4Entry++) {
  //    EfiCopyMem (PageMapLevel4Entry, PageMap, sizeof (x64_PAGE_MAP_AND_DIRECTORY_POINTER_2MB_4K));
     CopyMem (PageMapLevel4Entry,
              PageMap,
              sizeof (x64_PAGE_MAP_AND_DIRECTORY_POINTER_2MB_4K)
              );
  }

  return (EFI_PHYSICAL_ADDRESS)PageMap;
}

