/** @file
*
*  Copyright (c) 2011-2013, ARM Limited. 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.
*
**/

#include <PiDxe.h>
#include <Library/ArmLib.h>
#include <Library/CacheMaintenanceLib.h>
#include <Library/EblCmdLib.h>
#include <Library/BaseLib.h>
#include <Library/DxeServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiLib.h>
#include <Library/PcdLib.h>
#include <Library/EfiFileLib.h>
#include <Library/ArmDisassemblerLib.h>
#include <Library/PeCoffGetEntryPointLib.h>
#include <Library/PerformanceLib.h>
#include <Library/TimerLib.h>
#include <Library/BdsLib.h>

#include <Guid/DebugImageInfoTable.h>

#include <Protocol/DebugSupport.h>
#include <Protocol/LoadedImage.h>
#include <Protocol/DevicePathToText.h>

EFI_STATUS
EblDumpMmu (
  IN UINTN  Argc,
  IN CHAR8  **Argv
  );

EFI_STATUS
EblDumpFdt (
  IN UINTN  Argc,
  IN CHAR8  **Argv
  );

/**
  Simple arm disassembler via a library

  Argv[0] - symboltable
  Argv[1] - Optional qoted format string
  Argv[2] - Optional flag

  @param  Argc   Number of command arguments in Argv
  @param  Argv   Array of strings that represent the parsed command line.
                 Argv[0] is the command name

  @return EFI_SUCCESS

**/
EFI_STATUS
EblSymbolTable (
  IN UINTN  Argc,
  IN CHAR8  **Argv
  )
{
  EFI_STATUS                        Status;
  EFI_DEBUG_IMAGE_INFO_TABLE_HEADER *DebugImageTableHeader = NULL;
  EFI_DEBUG_IMAGE_INFO              *DebugTable;
  UINTN                             Entry;
  CHAR8                             *Format;
  CHAR8                             *Pdb;
  UINT32                            PeCoffSizeOfHeaders;
  UINT32                            ImageBase;
  BOOLEAN                           Elf;

  // Need to add lots of error checking on the passed in string
  // Default string is for RealView debugger
#if (__ARMCC_VERSION < 500000)
  Format = (Argc > 1) ? Argv[1] : "load /a /ni /np %a &0x%x";
#else
  Format = (Argc > 1) ? Argv[1] : "add-symbol-file %a 0x%x";
#endif
  Elf = (Argc > 2) ? FALSE : TRUE;

  Status = EfiGetSystemConfigurationTable (&gEfiDebugImageInfoTableGuid, (VOID **)&DebugImageTableHeader);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  DebugTable = DebugImageTableHeader->EfiDebugImageInfoTable;
  if (DebugTable == NULL) {
    return EFI_SUCCESS;
  }

  for (Entry = 0; Entry < DebugImageTableHeader->TableSize; Entry++, DebugTable++) {
    if (DebugTable->NormalImage != NULL) {
      if ((DebugTable->NormalImage->ImageInfoType == EFI_DEBUG_IMAGE_INFO_TYPE_NORMAL) && (DebugTable->NormalImage->LoadedImageProtocolInstance != NULL)) {
        ImageBase = (UINTN)DebugTable->NormalImage->LoadedImageProtocolInstance->ImageBase;
        PeCoffSizeOfHeaders = PeCoffGetSizeOfHeaders ((VOID *)(UINTN)ImageBase);
        Pdb = PeCoffLoaderGetPdbPointer (DebugTable->NormalImage->LoadedImageProtocolInstance->ImageBase);
        if (Pdb != NULL) {
          if (Elf) {
            // ELF and Mach-O images don't include the header so the linked address does not include header
            ImageBase += PeCoffSizeOfHeaders;
          }
          AsciiPrint (Format, Pdb, ImageBase);
          AsciiPrint ("\n");
        } else {
        }
      }
    }
  }

  return EFI_SUCCESS;
}


/**
  Simple arm disassembler via a library

  Argv[0] - disasm
  Argv[1] - Address to start disassembling from
  ARgv[2] - Number of instructions to disassembly (optional)

  @param  Argc   Number of command arguments in Argv
  @param  Argv   Array of strings that represent the parsed command line.
                 Argv[0] is the command name

  @return EFI_SUCCESS

**/
EFI_STATUS
EblDisassembler (
  IN UINTN  Argc,
  IN CHAR8  **Argv
  )
{
  UINT8   *Ptr, *CurrentAddress;
  UINT32  Address;
  UINT32  Count;
  CHAR8   Buffer[80];
  UINT32  ItBlock;

  if (Argc < 2) {
    return EFI_INVALID_PARAMETER;
  }

  Address = AsciiStrHexToUintn (Argv[1]);
  Count   = (Argc > 2) ? (UINT32)AsciiStrHexToUintn (Argv[2]) : 20;

  Ptr = (UINT8 *)(UINTN)Address;
  ItBlock = 0;
  do {
    CurrentAddress = Ptr;
    DisassembleInstruction (&Ptr, TRUE, TRUE, &ItBlock, Buffer, sizeof (Buffer));
    AsciiPrint ("0x%08x: %a\n", CurrentAddress, Buffer);
  } while (Count-- > 0);


  return EFI_SUCCESS;
}


CHAR8 *
ImageHandleToPdbFileName (
  IN  EFI_HANDLE    Handle
  )
{
  EFI_STATUS                  Status;
  EFI_LOADED_IMAGE_PROTOCOL   *LoadedImage;
  CHAR8                       *Pdb;
  CHAR8                       *StripLeading;

  Status = gBS->HandleProtocol (Handle, &gEfiLoadedImageProtocolGuid, (VOID **)&LoadedImage);
  if (EFI_ERROR (Status)) {
    return "";
  }

  Pdb = PeCoffLoaderGetPdbPointer (LoadedImage->ImageBase);
  StripLeading = AsciiStrStr (Pdb, "\\ARM\\");
  if (StripLeading == NULL) {
    StripLeading = AsciiStrStr (Pdb, "/ARM/");
    if (StripLeading == NULL) {
      return Pdb;
    }
  }
  // Hopefully we hacked off the unneeded part
  return (StripLeading + 5);
}


STATIC CHAR8 *mTokenList[] = {
  /*"SEC",*/
  "PEI",
  "DXE",
  /*"BDS",*/
  NULL
};

/**
  Simple arm disassembler via a library

  Argv[0] - disasm
  Argv[1] - Address to start disassembling from
  ARgv[2] - Number of instructions to disassembly (optional)

  @param  Argc   Number of command arguments in Argv
  @param  Argv   Array of strings that represent the parsed command line.
                 Argv[0] is the command name

  @return EFI_SUCCESS

**/
EFI_STATUS
EblPerformance (
  IN UINTN  Argc,
  IN CHAR8  **Argv
  )
{
  UINTN       Key;
  CONST VOID  *Handle;
  CONST CHAR8 *Token, *Module;
  UINT64      Start, Stop, TimeStamp;
  UINT64      Delta, TicksPerSecond, Milliseconds, Microseconds;
  UINTN       Index;
  BOOLEAN     CountUp;

  TicksPerSecond = GetPerformanceCounterProperties (&Start, &Stop);
  if (Start < Stop) {
    CountUp = TRUE;
  } else {
    CountUp = FALSE;
  }

  Key       = 0;
  do {
    Key = GetPerformanceMeasurement (Key, (CONST VOID **)&Handle, &Token, &Module, &Start, &Stop);
    if (Key != 0) {
      if (AsciiStriCmp ("StartImage:", Token) == 0) {
        if (Stop == 0) {
          // The entry for EBL is still running so the stop time will be zero. Skip it
          AsciiPrint ("   running     %a\n", ImageHandleToPdbFileName ((EFI_HANDLE)Handle));
        } else {
          Delta =  CountUp?(Stop - Start):(Start - Stop);
          Microseconds = DivU64x64Remainder (MultU64x32 (Delta, 1000000), TicksPerSecond, NULL);
          AsciiPrint ("%10ld us  %a\n", Microseconds, ImageHandleToPdbFileName ((EFI_HANDLE)Handle));
        }
      }
    }
  } while (Key != 0);

  AsciiPrint ("\n");

  TimeStamp = 0;
  Key       = 0;
  do {
    Key = GetPerformanceMeasurement (Key, (CONST VOID **)&Handle, &Token, &Module, &Start, &Stop);
    if (Key != 0) {
      for (Index = 0; mTokenList[Index] != NULL; Index++) {
        if (AsciiStriCmp (mTokenList[Index], Token) == 0) {
          Delta =  CountUp?(Stop - Start):(Start - Stop);
          TimeStamp += Delta;
          Milliseconds = DivU64x64Remainder (MultU64x32 (Delta, 1000), TicksPerSecond, NULL);
          AsciiPrint ("%6a %6ld ms\n", Token, Milliseconds);
          break;
        }
      }
    }
  } while (Key != 0);

  AsciiPrint ("Total Time = %ld ms\n\n", DivU64x64Remainder (MultU64x32 (TimeStamp, 1000), TicksPerSecond, NULL));

  return EFI_SUCCESS;
}

#define EFI_MEMORY_PORT_IO  0x4000000000000000ULL

EFI_STATUS
EblDumpGcd (
  IN UINTN  Argc,
  IN CHAR8  **Argv
  )
{
  EFI_STATUS                        Status;
  UINTN                           NumberOfDescriptors;
  UINTN i;
  EFI_GCD_MEMORY_SPACE_DESCRIPTOR *MemorySpaceMap;
  EFI_GCD_IO_SPACE_DESCRIPTOR *IoSpaceMap;

  Status = gDS->GetMemorySpaceMap(&NumberOfDescriptors,&MemorySpaceMap);
  if (EFI_ERROR (Status)) {
      return Status;
  }
  AsciiPrint ("    Address Range       Image     Device   Attributes\n");
  AsciiPrint ("__________________________________________________________\n");
  for (i=0; i < NumberOfDescriptors; i++) {
    AsciiPrint ("MEM %016lx - %016lx",(UINT64)MemorySpaceMap[i].BaseAddress,MemorySpaceMap[i].BaseAddress+MemorySpaceMap[i].Length-1);
    AsciiPrint (" %08x %08x",MemorySpaceMap[i].ImageHandle,MemorySpaceMap[i].DeviceHandle);

    if (MemorySpaceMap[i].Attributes & EFI_MEMORY_RUNTIME)
        AsciiPrint (" RUNTIME");
    if (MemorySpaceMap[i].Attributes & EFI_MEMORY_PORT_IO)
        AsciiPrint (" PORT_IO");

    if (MemorySpaceMap[i].Attributes & EFI_MEMORY_UC)
        AsciiPrint (" MEM_UC");
    if (MemorySpaceMap[i].Attributes & EFI_MEMORY_WC)
        AsciiPrint (" MEM_WC");
    if (MemorySpaceMap[i].Attributes & EFI_MEMORY_WT)
        AsciiPrint (" MEM_WT");
    if (MemorySpaceMap[i].Attributes & EFI_MEMORY_WB)
        AsciiPrint (" MEM_WB");
    if (MemorySpaceMap[i].Attributes & EFI_MEMORY_UCE)
        AsciiPrint (" MEM_UCE");
    if (MemorySpaceMap[i].Attributes & EFI_MEMORY_WP)
        AsciiPrint (" MEM_WP");
    if (MemorySpaceMap[i].Attributes & EFI_MEMORY_RP)
        AsciiPrint (" MEM_RP");
    if (MemorySpaceMap[i].Attributes & EFI_MEMORY_XP)
        AsciiPrint (" MEM_XP");

    switch (MemorySpaceMap[i].GcdMemoryType) {
      case EfiGcdMemoryTypeNonExistent:
        AsciiPrint (" TYPE_NONEXISTENT");
        break;
      case EfiGcdMemoryTypeReserved:
        AsciiPrint (" TYPE_RESERVED");
        break;
      case EfiGcdMemoryTypeSystemMemory:
        AsciiPrint (" TYPE_SYSMEM");
        break;
      case EfiGcdMemoryTypeMemoryMappedIo:
        AsciiPrint (" TYPE_MEMMAP");
        break;
      default:
        AsciiPrint (" TYPE_UNKNOWN");
        break;
    }

    AsciiPrint ("\n");
  }

  FreePool (MemorySpaceMap);

  Status = gDS->GetIoSpaceMap(&NumberOfDescriptors,&IoSpaceMap);
  if (EFI_ERROR (Status)) {
      return Status;
  }
  for (i=0; i < NumberOfDescriptors; i++) {
    AsciiPrint ("IO  %08lx - %08lx",IoSpaceMap[i].BaseAddress,IoSpaceMap[i].BaseAddress+IoSpaceMap[i].Length);
    AsciiPrint ("\t%08x %08x",IoSpaceMap[i].ImageHandle,IoSpaceMap[i].DeviceHandle);

    switch (IoSpaceMap[i].GcdIoType) {
      case EfiGcdIoTypeNonExistent:
        AsciiPrint (" TYPE_NONEXISTENT");
        break;
      case EfiGcdIoTypeReserved:
        AsciiPrint (" TYPE_RESERVED");
        break;
      case EfiGcdIoTypeIo:
        AsciiPrint (" TYPE_IO");
        break;
      default:
        AsciiPrint (" TYPE_UNKNOWN");
        break;
    }

    AsciiPrint ("\n");
  }

  FreePool (IoSpaceMap);

  return EFI_SUCCESS;
}

EFI_STATUS
EblDevicePaths (
  IN UINTN  Argc,
  IN CHAR8  **Argv
  )
{
  EFI_STATUS Status;
  UINTN                              HandleCount;
  EFI_HANDLE                         *HandleBuffer;
  UINTN                              Index;
  CHAR16*                            String;
  EFI_DEVICE_PATH_PROTOCOL*          DevicePathProtocol;
  EFI_DEVICE_PATH_TO_TEXT_PROTOCOL*  DevicePathToTextProtocol;

  BdsConnectAllDrivers();

  Status = gBS->LocateProtocol(&gEfiDevicePathToTextProtocolGuid, NULL, (VOID **)&DevicePathToTextProtocol);
  if (EFI_ERROR (Status)) {
    AsciiPrint ("Did not find the DevicePathToTextProtocol.\n");
    return EFI_SUCCESS;
  }

  Status = gBS->LocateHandleBuffer (ByProtocol, &gEfiDevicePathProtocolGuid, NULL, &HandleCount, &HandleBuffer);
  if (EFI_ERROR (Status)) {
    AsciiPrint ("No device path found\n");
    return EFI_SUCCESS;
  }

  for (Index = 0; Index < HandleCount; Index++) {
    Status = gBS->HandleProtocol (HandleBuffer[Index], &gEfiDevicePathProtocolGuid, (VOID **)&DevicePathProtocol);
    String = DevicePathToTextProtocol->ConvertDevicePathToText(DevicePathProtocol,TRUE,TRUE);
    Print (L"[0x%X] %s\n",(UINTN)HandleBuffer[Index], String);
  }

  return EFI_SUCCESS;
}

GLOBAL_REMOVE_IF_UNREFERENCED const EBL_COMMAND_TABLE mLibCmdTemplate[] =
{
  {
    "disasm address [count]",
    " disassemble count instructions",
    NULL,
    EblDisassembler
  },
  {
    "performance",
    " Display boot performance info",
    NULL,
    EblPerformance
  },
  {
    "symboltable [\"format string\"] [PECOFF]",
    " show symbol table commands for debugger",
    NULL,
    EblSymbolTable
  },
  {
    "dumpgcd",
    " dump Global Coherency Domain",
    NULL,
    EblDumpGcd
  },
  {
    "dumpmmu",
    " dump MMU Table",
    NULL,
    EblDumpMmu
  },
  {
    "devicepaths",
    " list all the Device Paths",
    NULL,
    EblDevicePaths
  },
  {
    "dumpfdt",
    " dump the current fdt or the one defined in the arguments",
    NULL,
    EblDumpFdt
  }
};


VOID
EblInitializeExternalCmd (
  VOID
  )
{
  EblAddCommands (mLibCmdTemplate, sizeof (mLibCmdTemplate)/sizeof (EBL_COMMAND_TABLE));
  return;
}