//
// This file contains a 'Sample Driver' and is licensed as such
// under the terms of your license agreement with Intel or your
// vendor.  This file may be modified by the user, subject to
// the additional terms of the license agreement
//
/** @file
  Usb3 Debug Port library instance

  Copyright (c) 2013 - 2018, Intel Corporation. All rights reserved.<BR>
  This software and associated documentation (if any) is furnished
  under a license and may only be used or copied in accordance
  with the terms of the license. Except as permitted by such
  license, no part of this software or documentation may be
  reproduced, stored in a retrieval system, or transmitted in any
  form or by any means without the express written consent of
  Intel Corporation.

**/

#include <Base.h>

#include <PiDxe.h>
#include <Library/SerialPortLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Library/PciLib.h>
#include <Library/PcdLib.h>
#include <Library/TimerLib.h>
#include <Library/Usb3DebugPortParameterLib.h>
#include <Protocol/SmmBase2.h>
#include <Protocol/SmmAccess2.h>
#include "Usb3DebugPortLibInternal.h"

extern EFI_SMRAM_DESCRIPTOR mSmramCheckRanges[MAX_SMRAM_RANGE];
extern UINTN                mSmramCheckRangeCount;
extern BOOLEAN              mUsb3InSmm;
extern UINT64               mUsb3MmioSize;
extern BOOLEAN              mUsb3GetCapSuccess;

GUID                        gUsb3DbgGuid =  USB3_DBG_GUID;

USB3_DEBUG_PORT_CONTROLLER  mUsb3DebugPort;
USB3_DEBUG_PORT_INSTANCE    *mUsb3Instance = NULL;

/**
  Return XHCI MMIO base address.

**/
EFI_PHYSICAL_ADDRESS
GetXhciBaseAddress (
  VOID
  )
{
  UINT8                       Bus;
  UINT8                       Device;
  UINT8                       Function;
  EFI_PHYSICAL_ADDRESS        Address;
  UINT32                      Low;
  UINT32                      High;

  if (mUsb3DebugPort.Controller == 0) {
    mUsb3DebugPort.Controller = GetUsb3DebugPortController();
  }

  Bus = mUsb3DebugPort.PciAddress.Bus;
  Device = mUsb3DebugPort.PciAddress.Device;
  Function = mUsb3DebugPort.PciAddress.Function;

  Low = PciRead32 (PCI_LIB_ADDRESS(Bus, Device, Function, PCI_BASE_ADDRESSREG_OFFSET));
  High = PciRead32 (PCI_LIB_ADDRESS(Bus, Device, Function, PCI_BASE_ADDRESSREG_OFFSET + 4));
  Address = (EFI_PHYSICAL_ADDRESS) (LShiftU64 ((UINT64) High, 32) | Low);
  //
  // Mask other parts which are not part of base address
  //
  Address &= XHCI_BASE_ADDRESS_64_BIT_MASK;
  return Address;
}

/**
  Return XHCI debug instance address.

**/
USB3_DEBUG_PORT_INSTANCE *
GetUsb3DebugPortInstance (
  VOID
  )
{
  USB3_DEBUG_PORT_INSTANCE               *Instance;
  EFI_PHYSICAL_ADDRESS                   XhcMmioBase;
  UINT64                                 CapabilityPointer;
  UINT32                                 Capability;
  BOOLEAN                                Flag;
  UINT8                                  Bus;
  UINT8                                  Device;
  UINT8                                  Function;
  UINT16                                 Command;
  USB3_DEBUG_PORT_CONTROLLER             UsbDebugPort;

  Instance = NULL;

  XhcMmioBase = GetXhciBaseAddress ();

  if ((XhcMmioBase == 0) || (XhcMmioBase == XHCI_BASE_ADDRESS_64_BIT_MASK)) {
    return NULL;
  }

  if (mUsb3Instance != NULL) {
    FixUsb3InstanceResource (mUsb3Instance, XhcMmioBase);
    return mUsb3Instance;
  }

  Command = GetXhciPciCommand ();

  UsbDebugPort.Controller = GetUsb3DebugPortController();
  Bus      = UsbDebugPort.PciAddress.Bus;
  Device   = UsbDebugPort.PciAddress.Device;
  Function = UsbDebugPort.PciAddress.Function;

  //
  // Set Command Register
  //
  if ((Command & EFI_PCI_COMMAND_MEMORY_SPACE) == 0) {
    PciWrite16(PCI_LIB_ADDRESS(Bus, Device, Function, PCI_COMMAND_OFFSET), Command | EFI_PCI_COMMAND_MEMORY_SPACE);
    PciRead16(PCI_LIB_ADDRESS(Bus, Device, Function, PCI_COMMAND_OFFSET));
  }

  //
  // Calculate capability offset from HCCPARAMS [16:31], in 32-bit words
  //
  CapabilityPointer = XhcMmioBase + (MmioRead32 ((UINTN)(XhcMmioBase + XHC_HCCPARAMS_OFFSET)) >> 16) * 4;

  //
  // Search XHCI debug capability
  //
  Flag = FALSE;
  Capability = MmioRead32 ((UINTN)CapabilityPointer);
  while (TRUE) {
    if ((Capability & XHC_CAPABILITY_ID_MASK) == PCI_CAPABILITY_ID_DEBUG_PORT) {
      Flag = TRUE;
      break;
    }
    if ((((Capability & XHC_NEXT_CAPABILITY_MASK) >> 8) & XHC_CAPABILITY_ID_MASK) == 0) {
      //
      // Reach the end of list, quit
      //
      break;
    }
    CapabilityPointer += ((Capability & XHC_NEXT_CAPABILITY_MASK) >> 8) * 4;
    Capability = MmioRead32 ((UINTN)CapabilityPointer);
  }

  if (Flag) {
    Instance = (USB3_DEBUG_PORT_INSTANCE *)(UINTN) MmioRead32 ((UINTN) (CapabilityPointer + XHC_DC_DCDDI2));

    if (Instance != NULL) {
      FixUsb3InstanceResource (Instance, XhcMmioBase);
    }
  }

  //
  // Restore Command Register
  //
  PciWrite16(PCI_LIB_ADDRESS (Bus, Device, Function, PCI_COMMAND_OFFSET), Command);

  return Instance;
}


/**
  Initialize USB3 debug port.

  This method invokes various internal functions to facilitate
  detection and initialization of USB3 debug port.

  @retval RETURN_SUCCESS        The USB3 debug port was initialized.
**/
RETURN_STATUS
EFIAPI
USB3Initialize (
  VOID
  )
{
  //
  // Leave it empty, we assume PEI phase already do initialization
  //
  return RETURN_SUCCESS;
}

/**
  Initialize USB3 debug port.

  This method invokes various internal functions to facilitate
  detection and initialization of USB3 debug port.

  @retval RETURN_SUCCESS        The serial device was initialized.
**/
RETURN_STATUS
EFIAPI
USB3InitializeReal (
  VOID
  )
{
  USB3_DEBUG_PORT_INSTANCE    UsbDbg;
  USB3_DEBUG_PORT_INSTANCE    *Instance;
  EFI_PHYSICAL_ADDRESS        Address;
  EFI_STATUS                  Status;

  if ((gST == NULL) || (gBS == NULL)) {
    //
    // gST and gBS have not been initialized yet
    //
    return EFI_DEVICE_ERROR;
  }

  Status = EfiGetSystemConfigurationTable (&gUsb3DbgGuid, (VOID **) &mUsb3Instance);
  if (!EFI_ERROR (Status)) {
    return RETURN_SUCCESS;
  }

  //
  // It is first time to run DXE instance, copy Instance from Hob to ACPINvs
  // NOTE: Hob is not ready at this time, so copy it from XHCI register.
  //
  Instance = GetUsb3DebugPortInstance ();
  if (Instance == NULL) {
    //
    // Initialize USB debug
    //
    SetMem (&UsbDbg, sizeof(UsbDbg), 0);
    DiscoverUsb3DebugPort (&UsbDbg);
    if (UsbDbg.DebugSupport) {
      InitializeUsb3DebugPort (&UsbDbg);
    }
    Instance = &UsbDbg;
  }
  Address = SIZE_4GB;
  Status = gBS->AllocatePages (
                  AllocateMaxAddress,
                  EfiACPIMemoryNVS,
                  EFI_SIZE_TO_PAGES (sizeof (USB3_DEBUG_PORT_INSTANCE)),
                  &Address
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  CopyMem (
    (VOID *)(UINTN)Address,
    Instance,
    sizeof (USB3_DEBUG_PORT_INSTANCE)
    );
  mUsb3Instance = (USB3_DEBUG_PORT_INSTANCE *)(UINTN)Address;

  Status = gBS->InstallConfigurationTable (&gUsb3DbgGuid, mUsb3Instance);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (mUsb3Instance->DebugSupport) {
    SaveUsb3InstanceAddress (mUsb3Instance);
  }

  return RETURN_SUCCESS;
}

/**
  Calculate the size of XHCI MMIO space.

  @retval     TURE         The XHCI MMIO is in SMRAM ranges.
  @retval     FALSE        The XHCI MMIO is out of SMRAM ranges.
**/
UINT64
CalculateMmioSize (
  VOID
  )
{
  UINT8                       Bus;
  UINT8                       Device;
  UINT8                       Function;
  UINT32                      Value;
  UINT32                      Mask;
  UINT64                      MmioSize;
  UINT16                      Command;
  USB3_DEBUG_PORT_CONTROLLER  UsbDebugPort;
  EFI_PHYSICAL_ADDRESS        XhcMmioBase;

  UsbDebugPort.Controller = GetUsb3DebugPortController();
  Bus      = UsbDebugPort.PciAddress.Bus;
  Device   = UsbDebugPort.PciAddress.Device;
  Function = UsbDebugPort.PciAddress.Function;

  Mask     = 0xFFFFFFF0;
  MmioSize = 0;

  XhcMmioBase = GetXhciBaseAddress ();

  //
  // Disable MSE
  //
  Command = PciRead16 (PCI_LIB_ADDRESS(Bus, Device, Function, PCI_COMMAND_OFFSET));
  PciWrite16 (PCI_LIB_ADDRESS (Bus, Device, Function, PCI_COMMAND_OFFSET), Command & ~(EFI_PCI_COMMAND_MEMORY_SPACE));

  //
  // Get Mmio Size
  //
  PciWrite32 (PCI_LIB_ADDRESS(Bus, Device, Function, PCI_BASE_ADDRESSREG_OFFSET), 0xFFFFFFFF);
  Value    = PciRead32 (PCI_LIB_ADDRESS(Bus, Device, Function, PCI_BASE_ADDRESSREG_OFFSET));

  switch (Value & 0x07) {
    case 0x0:
      //
      // Memory space: anywhere in 32 bit address space
      //
      MmioSize = (~(Value & Mask)) + 1;
      break;
    case 0x4:
      //
      // Memory space: anywhere in 64 bit address space
      //
      MmioSize = Value & Mask;
      PciWrite32 (PCI_LIB_ADDRESS(Bus, Device, Function, PCI_BASE_ADDRESSREG_OFFSET + 4), 0xFFFFFFFF);
      Value    = PciRead32 (PCI_LIB_ADDRESS(Bus, Device, Function, PCI_BASE_ADDRESSREG_OFFSET + 4));
      //
      // Fix the length to support some spefic 64 bit BAR
      //
      Value |= ((UINT32)(-1) << HighBitSet32 (Value));
      //
      // Calculate the size of 64bit bar
      //
      MmioSize  |= LShiftU64 ((UINT64) Value, 32);
      MmioSize  = (~(MmioSize)) + 1;
      break;
    default:
      //
      // Unknown BAR type
      //
      MmioSize = (~(Value & Mask)) + 1;
      break;
  };


  //
  // Restore MMIO address
  //
  PciWrite32 (PCI_LIB_ADDRESS(Bus, Device, Function, PCI_BASE_ADDRESSREG_OFFSET), (UINT32)XhcMmioBase);
  PciWrite32 (PCI_LIB_ADDRESS(Bus, Device, Function, PCI_BASE_ADDRESSREG_OFFSET + 4), (UINT32) (XhcMmioBase >> 32));

  PciWrite16 (PCI_LIB_ADDRESS (Bus, Device, Function, PCI_COMMAND_OFFSET), Command | EFI_PCI_COMMAND_MEMORY_SPACE);

  return MmioSize;
}

/**
  The constructor function initialize USB3 debug port.

  @param  ImageHandle   The firmware allocated handle for the EFI image.
  @param  SystemTable   A pointer to the EFI System Table.

  @retval EFI_SUCCESS   The constructor always returns EFI_SUCCESS.

**/
EFI_STATUS
EFIAPI
Usb3DebugPortLibDxeConstructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_SMM_BASE2_PROTOCOL        *SmmBase;
  EFI_SMM_ACCESS2_PROTOCOL      *SmmAccess;
  UINTN                         Size;
  EFI_STATUS                    Status;

  //
  // Do real initialization here, because we need copy data from Hob to ACPINvs.
  // We must do it in constructor because it depends on UefiBootServicesTableLib.
  //
  if (FeaturePcdGet (PcdStatusCodeUseUsb3Serial)) {
    USB3InitializeReal ();
  }

  mUsb3MmioSize = CalculateMmioSize ();

  if (gBS != NULL) {
    SmmBase = NULL;
    Status = gBS->LocateProtocol (&gEfiSmmBase2ProtocolGuid, NULL, (VOID **)&SmmBase);
    if (!EFI_ERROR (Status)) {
      SmmBase->InSmm(SmmBase, &mUsb3InSmm);
    }

    if (mUsb3InSmm) {
      //
      // Get SMRAM information
      //
      SmmAccess = NULL;
      Status = gBS->LocateProtocol (&gEfiSmmAccess2ProtocolGuid, NULL, (VOID **)&SmmAccess);
      if (!EFI_ERROR (Status)) {
        Size = sizeof (mSmramCheckRanges);

        Status = SmmAccess->GetCapabilities (SmmAccess, &Size, mSmramCheckRanges);
        if (!EFI_ERROR (Status)) {
          mSmramCheckRangeCount = Size / sizeof (EFI_SMRAM_DESCRIPTOR);
        }
      }
    }
  }

  return EFI_SUCCESS;
}

/**
  Allocate aligned memory for XHC's usage.

  @param BufferSize     The size, in bytes, of the Buffer.

  @return A pointer to the allocated buffer or NULL if allocation fails.

**/
VOID*
AllocateAlignBuffer (
  IN UINTN                    BufferSize
  )
{
  VOID                    *Buf;
  EFI_PHYSICAL_ADDRESS    Address;
  EFI_STATUS              Status;

  Buf = NULL;

  if (gBS != NULL) {
    Address = 0xFFFFFFFF;
    Status = gBS->AllocatePages (
                    AllocateMaxAddress,
                    EfiACPIMemoryNVS,
                    EFI_SIZE_TO_PAGES (BufferSize),
                    &Address
                    );
    if (!EFI_ERROR (Status)) {
      Buf = (VOID *)(UINTN)Address;
    }
  }

  return Buf;
}

/**
  Check whether AllocatePages in permanent memory is ready.

  @retval TRUE  AllocatePages in permanent memory is ready.
  @retval FALSE AllocatePages in permanent memory is not ready.

**/
BOOLEAN
IsAllocatePagesReady (
  VOID
  )
{
  if (gBS != NULL) {
    return TRUE;
  }

  return FALSE;
}

