/*++

Copyright (c) 2006, Intel Corporation. All rights reserved.<BR>
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:
  PeLoader.c

Abstract:

Revision History:

--*/
#include "EfiLdr.h"
#include "Debug.h"
#include "Support.h"

EFI_STATUS
EfiLdrPeCoffLoadPeRelocate (
  IN EFILDR_LOADED_IMAGE      *Image,
  IN EFI_IMAGE_DATA_DIRECTORY *RelocDir,
  IN UINTN                     Adjust,
  IN UINTN                    *NumberOfMemoryMapEntries,
  IN EFI_MEMORY_DESCRIPTOR    *EfiMemoryDescriptor
  );

EFI_STATUS
EfiLdrPeCoffImageRead (
    IN VOID                 *FHand,
    IN UINTN                Offset,
    IN OUT UINTN            ReadSize,
    OUT VOID                *Buffer
    );

VOID *
EfiLdrPeCoffImageAddress (
  IN EFILDR_LOADED_IMAGE     *Image,
  IN UINTN                   Address
  );


EFI_STATUS
EfiLdrPeCoffSetImageType (
  IN OUT EFILDR_LOADED_IMAGE      *Image,
  IN UINTN                        ImageType
  );

EFI_STATUS
EfiLdrPeCoffCheckImageMachineType (
  IN UINT16           MachineType
  );

EFI_STATUS
EfiLdrGetPeImageInfo (
  IN  VOID                    *FHand,
  OUT UINT64                  *ImageBase,
  OUT UINT32                  *ImageSize
  )
{
  EFI_STATUS                        Status;
  EFI_IMAGE_DOS_HEADER              DosHdr;
  EFI_IMAGE_OPTIONAL_HEADER_UNION   PeHdr;

  ZeroMem (&DosHdr, sizeof(DosHdr));
  ZeroMem (&PeHdr, sizeof(PeHdr));

  //
  // Read image headers
  //

  EfiLdrPeCoffImageRead (FHand, 0, sizeof(DosHdr), &DosHdr);
  if (DosHdr.e_magic != EFI_IMAGE_DOS_SIGNATURE) {
    return EFI_UNSUPPORTED;
  }

  EfiLdrPeCoffImageRead (FHand, DosHdr.e_lfanew, sizeof(PeHdr), &PeHdr);

  if (PeHdr.Pe32.Signature != EFI_IMAGE_NT_SIGNATURE) {
    return EFI_UNSUPPORTED;
  }
    
  //
  // Verify machine type
  //

  Status = EfiLdrPeCoffCheckImageMachineType (PeHdr.Pe32.FileHeader.Machine);
  if (EFI_ERROR(Status)) {
    return Status;
  }

  if (PeHdr.Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
    *ImageBase = (UINT32)PeHdr.Pe32.OptionalHeader.ImageBase;
  } else if (PeHdr.Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
    *ImageBase = PeHdr.Pe32Plus.OptionalHeader.ImageBase;
  } else {
    return EFI_UNSUPPORTED;
  }
  
  *ImageSize = PeHdr.Pe32.OptionalHeader.SizeOfImage;

  return EFI_SUCCESS;
}

EFI_STATUS
EfiLdrPeCoffLoadPeImage (
  IN VOID                     *FHand,
  IN EFILDR_LOADED_IMAGE      *Image,
  IN UINTN                    *NumberOfMemoryMapEntries,
  IN EFI_MEMORY_DESCRIPTOR    *EfiMemoryDescriptor
  )
{
  EFI_IMAGE_DOS_HEADER            DosHdr;
  EFI_IMAGE_OPTIONAL_HEADER_UNION PeHdr;
  EFI_IMAGE_SECTION_HEADER        *FirstSection;
  EFI_IMAGE_SECTION_HEADER        *Section;
  UINTN                           Index;
  EFI_STATUS                      Status;
  UINT8                           *Base;
  UINT8                           *End;
  EFI_IMAGE_DATA_DIRECTORY        *DirectoryEntry;
  UINTN                           DirCount;
  EFI_IMAGE_DEBUG_DIRECTORY_ENTRY TempDebugEntry;
  EFI_IMAGE_DEBUG_DIRECTORY_ENTRY *DebugEntry;
  UINTN                           CodeViewSize;
  UINTN                           CodeViewOffset;
  UINTN                           CodeViewFileOffset;
  UINTN                           OptionalHeaderSize;
  UINTN                           PeHeaderSize;
  UINT32                          NumberOfRvaAndSizes;
  EFI_IMAGE_DATA_DIRECTORY        *DataDirectory;
  UINT64                          ImageBase;
  CHAR8                           PrintBuffer[256];

  ZeroMem (&DosHdr, sizeof(DosHdr));
  ZeroMem (&PeHdr, sizeof(PeHdr));

  //
  // Read image headers
  //

  EfiLdrPeCoffImageRead (FHand, 0, sizeof(DosHdr), &DosHdr);
  if (DosHdr.e_magic != EFI_IMAGE_DOS_SIGNATURE) {
    AsciiSPrint (PrintBuffer, 256, "PeCoffLoadPeImage: Dos header signature not found\n");
    PrintString (PrintBuffer);
    PrintHeader ('F');
    return EFI_UNSUPPORTED;
  }

  EfiLdrPeCoffImageRead (FHand, DosHdr.e_lfanew, sizeof(PeHdr), &PeHdr);

  if (PeHdr.Pe32.Signature != EFI_IMAGE_NT_SIGNATURE) {
    AsciiSPrint (PrintBuffer, 256, "PeCoffLoadPeImage: PE image header signature not found\n");
    PrintString (PrintBuffer);
    PrintHeader ('G');
    return EFI_UNSUPPORTED;
  }
    
  //
  // Set the image subsystem type
  //

  Status = EfiLdrPeCoffSetImageType (Image, PeHdr.Pe32.OptionalHeader.Subsystem);
  if (EFI_ERROR(Status)) {
    AsciiSPrint (PrintBuffer, 256, "PeCoffLoadPeImage: Subsystem type not known\n");
    PrintString (PrintBuffer);
    PrintHeader ('H');
    return Status;
  }

  //
  // Verify machine type
  //

  Status = EfiLdrPeCoffCheckImageMachineType (PeHdr.Pe32.FileHeader.Machine);
  if (EFI_ERROR(Status)) {
    AsciiSPrint (PrintBuffer, 256, "PeCoffLoadPeImage: Incorrect machine type\n");
    PrintString (PrintBuffer);
    PrintHeader ('I');
    return Status;
  }

  //
  // Compute the amount of memory needed to load the image and 
  // allocate it.  This will include all sections plus the codeview debug info.
  // Since the codeview info is actually outside of the image, we calculate
  // its size seperately and add it to the total.
  //
  // Memory starts off as data
  //

  CodeViewSize       = 0;
  CodeViewFileOffset = 0;
  if (PeHdr.Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
    DirectoryEntry = (EFI_IMAGE_DATA_DIRECTORY *)&(PeHdr.Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_DEBUG]);
  } else if (PeHdr.Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
    DirectoryEntry = (EFI_IMAGE_DATA_DIRECTORY *)&(PeHdr.Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_DEBUG]);
  } else {
    return EFI_UNSUPPORTED;
  }
  for (DirCount = 0; 
       (DirCount < DirectoryEntry->Size / sizeof (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY)) && (CodeViewSize == 0);
       DirCount++) {
    Status = EfiLdrPeCoffImageRead (
               FHand, 
               DirectoryEntry->VirtualAddress + DirCount * sizeof (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY),
               sizeof (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY),
               &TempDebugEntry
               );
    if (!EFI_ERROR (Status)) {
      if (TempDebugEntry.Type == EFI_IMAGE_DEBUG_TYPE_CODEVIEW) {
        CodeViewSize = TempDebugEntry.SizeOfData;
        CodeViewFileOffset = TempDebugEntry.FileOffset;
      }
    }
  }
    
  CodeViewOffset = PeHdr.Pe32.OptionalHeader.SizeOfImage + PeHdr.Pe32.OptionalHeader.SectionAlignment;
  Image->NoPages = EFI_SIZE_TO_PAGES (CodeViewOffset + CodeViewSize);

  //
  // Compute the amount of memory needed to load the image and 
  // allocate it.  Memory starts off as data
  //

  Image->ImageBasePage = (EFI_PHYSICAL_ADDRESS)FindSpace (Image->NoPages, NumberOfMemoryMapEntries, EfiMemoryDescriptor, EfiRuntimeServicesCode, EFI_MEMORY_WB);
  if (Image->ImageBasePage == 0) {
    return EFI_OUT_OF_RESOURCES;
  }

  if (EFI_ERROR(Status)) {
    PrintHeader ('J');
    return Status;
  }

  AsciiSPrint (PrintBuffer, 256, "LoadPe: new image base %lx\n", Image->ImageBasePage);
  PrintString (PrintBuffer);
  Image->Info.ImageBase = (VOID *)(UINTN)Image->ImageBasePage;
  Image->Info.ImageSize = (Image->NoPages << EFI_PAGE_SHIFT) - 1;
  Image->ImageBase      = (UINT8 *)(UINTN)Image->ImageBasePage;
  Image->ImageEof       = Image->ImageBase + Image->Info.ImageSize;
  Image->ImageAdjust    = Image->ImageBase;

  //
  // Copy the Image header to the base location
  //
  Status = EfiLdrPeCoffImageRead (
             FHand, 
             0, 
             PeHdr.Pe32.OptionalHeader.SizeOfHeaders, 
             Image->ImageBase
             );

  if (EFI_ERROR(Status)) {
    PrintHeader ('K');
    return Status;
  }

  //
  // Load each directory of the image into memory... 
  //  Save the address of the Debug directory for later
  //
  if (PeHdr.Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
    NumberOfRvaAndSizes = PeHdr.Pe32.OptionalHeader.NumberOfRvaAndSizes;
    DataDirectory = PeHdr.Pe32.OptionalHeader.DataDirectory;
  } else {
    NumberOfRvaAndSizes = PeHdr.Pe32Plus.OptionalHeader.NumberOfRvaAndSizes;
    DataDirectory = PeHdr.Pe32Plus.OptionalHeader.DataDirectory;
  }
  DebugEntry = NULL;
  for (Index = 0; Index < NumberOfRvaAndSizes; Index++) {
    if ((DataDirectory[Index].VirtualAddress != 0) && (DataDirectory[Index].Size != 0)) {
      Status = EfiLdrPeCoffImageRead (
                 FHand,
                 DataDirectory[Index].VirtualAddress,
                 DataDirectory[Index].Size,
                 Image->ImageBase + DataDirectory[Index].VirtualAddress
                 );
      if (EFI_ERROR(Status)) {
        return Status;
      }
      if (Index == EFI_IMAGE_DIRECTORY_ENTRY_DEBUG) {
        DebugEntry = (EFI_IMAGE_DEBUG_DIRECTORY_ENTRY *) (Image->ImageBase + DataDirectory[Index].VirtualAddress);
      }
    }
  }

  //
  // Load each section of the image
  //

  // BUGBUG: change this to use the in memory copy
  if (PeHdr.Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
    OptionalHeaderSize = sizeof(EFI_IMAGE_OPTIONAL_HEADER32);
    PeHeaderSize       = sizeof(EFI_IMAGE_NT_HEADERS32);
  } else {
    OptionalHeaderSize = sizeof(EFI_IMAGE_OPTIONAL_HEADER64);
    PeHeaderSize       = sizeof(EFI_IMAGE_NT_HEADERS64);
  }
  FirstSection = (EFI_IMAGE_SECTION_HEADER *) (
                   Image->ImageBase +
                   DosHdr.e_lfanew + 
                   PeHeaderSize + 
                   PeHdr.Pe32.FileHeader.SizeOfOptionalHeader - 
                   OptionalHeaderSize
                   );

  Section = FirstSection;
  for (Index=0; Index < PeHdr.Pe32.FileHeader.NumberOfSections; Index += 1) {

    //
    // Compute sections address
    //

    Base = EfiLdrPeCoffImageAddress (Image, (UINTN)Section->VirtualAddress);
    End = EfiLdrPeCoffImageAddress (Image, (UINTN)(Section->VirtualAddress + Section->Misc.VirtualSize));
        
    if (EFI_ERROR(Status) || !Base || !End) {
//      DEBUG((D_LOAD|D_ERROR, "LoadPe: Section %d was not loaded\n", Index));
    PrintHeader ('L');
      return EFI_LOAD_ERROR;
    }

//    DEBUG((D_LOAD, "LoadPe: Section %d, loaded at %x\n", Index, Base));

    //
    // Read the section
    //
 
    if (Section->SizeOfRawData) {
      Status = EfiLdrPeCoffImageRead (FHand, Section->PointerToRawData, Section->SizeOfRawData, Base);
      if (EFI_ERROR(Status)) {
PrintHeader ('M');
        return Status;
      }
    }

    //
    // If raw size is less then virt size, zero fill the remaining
    //

    if (Section->SizeOfRawData < Section->Misc.VirtualSize) {
      ZeroMem (
        Base + Section->SizeOfRawData, 
        Section->Misc.VirtualSize - Section->SizeOfRawData
        );
    }

    //
    // Next Section
    //

    Section += 1;
  }

  //
  // Copy in CodeView information if it exists
  //
  if (CodeViewSize != 0) {
    Status = EfiLdrPeCoffImageRead (FHand, CodeViewFileOffset, CodeViewSize, Image->ImageBase + CodeViewOffset);
    DebugEntry->RVA = (UINT32) (CodeViewOffset);
  }

  //
  // Apply relocations only if needed
  //
  if (PeHdr.Pe32.OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
    ImageBase = (UINT64)PeHdr.Pe32.OptionalHeader.ImageBase;
  } else {
    ImageBase = PeHdr.Pe32Plus.OptionalHeader.ImageBase;
  }
  if ((UINTN)(Image->ImageBase) != (UINTN) (ImageBase)) {
    Status = EfiLdrPeCoffLoadPeRelocate (
               Image,
               &DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC],
               (UINTN) Image->ImageBase - (UINTN)ImageBase,
               NumberOfMemoryMapEntries,
               EfiMemoryDescriptor
               );

    if (EFI_ERROR(Status)) {
      PrintHeader ('N');
      return Status;
    }
  }

  //
  // Use exported EFI specific interface if present, else use the image's entry point
  //
  Image->EntryPoint = (EFI_IMAGE_ENTRY_POINT)(UINTN)
                        (EfiLdrPeCoffImageAddress(
                           Image, 
                           PeHdr.Pe32.OptionalHeader.AddressOfEntryPoint
                           ));

  return Status;
}

EFI_STATUS
EfiLdrPeCoffLoadPeRelocate (
  IN EFILDR_LOADED_IMAGE      *Image,
  IN EFI_IMAGE_DATA_DIRECTORY *RelocDir,
  IN UINTN                     Adjust,
  IN UINTN                    *NumberOfMemoryMapEntries,
  IN EFI_MEMORY_DESCRIPTOR    *EfiMemoryDescriptor
  )
{
  EFI_IMAGE_BASE_RELOCATION   *RelocBase;
  EFI_IMAGE_BASE_RELOCATION   *RelocBaseEnd;
  UINT16                      *Reloc;
  UINT16                      *RelocEnd;
  UINT8                       *Fixup;
  UINT8                       *FixupBase;
  UINT16                      *F16;
  UINT32                      *F32;
  UINT64                      *F64;
  UINT8                       *FixupData;
  UINTN                       NoFixupPages;

  //
  // Find the relocation block
  //

  RelocBase = EfiLdrPeCoffImageAddress (Image, RelocDir->VirtualAddress);
  RelocBaseEnd = EfiLdrPeCoffImageAddress (Image, RelocDir->VirtualAddress + RelocDir->Size);
  if (!RelocBase || !RelocBaseEnd) {
PrintHeader ('O');
    return EFI_LOAD_ERROR;
  }

  NoFixupPages = EFI_SIZE_TO_PAGES(RelocDir->Size / sizeof(UINT16) * sizeof(UINTN));
  Image->FixupData = (UINT8*) FindSpace (NoFixupPages, NumberOfMemoryMapEntries, EfiMemoryDescriptor, EfiRuntimeServicesData, EFI_MEMORY_WB);
  if (Image->FixupData == 0) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Run the whole relocation block
  //

  FixupData = Image->FixupData;
  while (RelocBase < RelocBaseEnd) {
           
    Reloc = (UINT16 *) ((UINT8 *) RelocBase + sizeof(EFI_IMAGE_BASE_RELOCATION));
    RelocEnd = (UINT16 *) ((UINT8 *) RelocBase + RelocBase->SizeOfBlock);
    FixupBase = EfiLdrPeCoffImageAddress (Image, RelocBase->VirtualAddress);
    if ((UINT8 *) RelocEnd < Image->ImageBase || (UINT8 *) RelocEnd > Image->ImageEof) {
PrintHeader ('P');
      return EFI_LOAD_ERROR;
    }

    //
    // Run this relocation record
    //

    while (Reloc < RelocEnd) {

      Fixup = FixupBase + (*Reloc & 0xFFF);
      switch ((*Reloc) >> 12) {

      case EFI_IMAGE_REL_BASED_ABSOLUTE:
        break;

      case EFI_IMAGE_REL_BASED_HIGH:
        F16 = (UINT16 *) Fixup;
        *F16  = (UINT16) (*F16 + (UINT16)(((UINT32)Adjust) >> 16));
        if (FixupData != NULL) {
          *(UINT16 *) FixupData = *F16;
          FixupData = FixupData + sizeof(UINT16);
        }
        break;

      case EFI_IMAGE_REL_BASED_LOW:
        F16 = (UINT16 *) Fixup;
        *F16 = (UINT16) (*F16 + (UINT16) Adjust);
        if (FixupData != NULL) {
          *(UINT16 *) FixupData = *F16;
          FixupData = FixupData + sizeof(UINT16);
        }
        break;

      case EFI_IMAGE_REL_BASED_HIGHLOW:
        F32 = (UINT32 *) Fixup;
        *F32 = *F32 + (UINT32) Adjust;
        if (FixupData != NULL) {
          FixupData = ALIGN_POINTER(FixupData, sizeof(UINT32));
          *(UINT32 *) FixupData = *F32;
          FixupData = FixupData + sizeof(UINT32);
        }
        break;

      case EFI_IMAGE_REL_BASED_DIR64:
        F64 = (UINT64 *) Fixup;
        *F64 = *F64 + (UINT64) Adjust;
        if (FixupData != NULL) {
          FixupData = ALIGN_POINTER(FixupData, sizeof(UINT64));
          *(UINT64 *) FixupData = *F64;
          FixupData = FixupData + sizeof(UINT64);
        }
        break;

      case EFI_IMAGE_REL_BASED_HIGHADJ:
        CpuDeadLoop();                 // BUGBUG: not done
        break;

      default:
//        DEBUG((D_LOAD|D_ERROR, "PeRelocate: unknown fixed type\n"));
PrintHeader ('Q');
        CpuDeadLoop();
        return EFI_LOAD_ERROR;
      }

      // Next reloc record
      Reloc += 1;
    }

    // next reloc block
    RelocBase = (EFI_IMAGE_BASE_RELOCATION *) RelocEnd;
  }

  //
  // Add Fixup data to whole Image (assume Fixup data just below the image), so that there is no hole in the descriptor.
  // Because only NoPages or ImageBasePage will be used in EfiLoader(), we update these 2 fields.
  //
  Image->NoPages += NoFixupPages;
  Image->ImageBasePage -= (NoFixupPages << EFI_PAGE_SHIFT);

  return EFI_SUCCESS;
}

EFI_STATUS
EfiLdrPeCoffImageRead (
  IN VOID                 *FHand,
  IN UINTN                Offset,
  IN OUT UINTN            ReadSize,
  OUT VOID                *Buffer
  )
{
  CopyMem (Buffer, (VOID *)((UINTN)FHand + Offset), ReadSize);

  return EFI_SUCCESS;
}

VOID *
EfiLdrPeCoffImageAddress (
  IN EFILDR_LOADED_IMAGE     *Image,
  IN UINTN                   Address
  )
{
  UINT8        *FixedAddress;

  FixedAddress = Image->ImageAdjust + Address;

  if ((FixedAddress < Image->ImageBase) || (FixedAddress > Image->ImageEof)) {
//    DEBUG((D_LOAD|D_ERROR, "PeCoffImageAddress: pointer is outside of image\n"));
    FixedAddress = NULL;
  }

//  DEBUG((
//    D_LOAD,
//    "PeCoffImageAddress: ImageBase %x, ImageEof %x, Address %x, FixedAddress %x\n", 
//    Image->ImageBase,
//    Image->ImageEof,
//    Address,
//    FixedAddress
//    ));
    return FixedAddress;
}


EFI_STATUS
EfiLdrPeCoffSetImageType (
  IN OUT EFILDR_LOADED_IMAGE      *Image,
  IN UINTN                        ImageType
  )
{
  EFI_MEMORY_TYPE                 CodeType;
  EFI_MEMORY_TYPE                 DataType;

  switch (ImageType) {
  case EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION:
    CodeType = EfiLoaderCode;
    DataType = EfiLoaderData;
    break;

  case EFI_IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER:
    CodeType = EfiBootServicesCode;
    DataType = EfiBootServicesData;
    break;

  case EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER:
    CodeType = EfiRuntimeServicesCode;
    DataType = EfiRuntimeServicesData;
    break;

  default:
    return EFI_INVALID_PARAMETER;
  }

  Image->Type = ImageType;
  Image->Info.ImageCodeType = CodeType;    
  Image->Info.ImageDataType = DataType;

  return EFI_SUCCESS;
}

EFI_STATUS
EfiLdrPeCoffCheckImageMachineType (
  IN UINT16           MachineType
  )
{
  EFI_STATUS          Status;

  Status = EFI_UNSUPPORTED;

#ifdef MDE_CPU_IA32
  if (MachineType == EFI_IMAGE_MACHINE_IA32) {
    Status = EFI_SUCCESS;
  }
#endif

#ifdef MDE_CPU_X64
  if (MachineType == EFI_IMAGE_MACHINE_X64) {
    Status = EFI_SUCCESS;
  }
#endif

#ifdef MDE_CPU_IPF
  if (MachineType == EFI_IMAGE_MACHINE_IA64) {
    Status = EFI_SUCCESS;
  }
#endif

  return Status;
}

