/** @file
  Debug Port Library implementation based on usb3 debug port.

  Copyright (c) 2014 - 2018, Intel Corporation. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <Base.h>
#include <PiDxe.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/HobLib.h>
#include <Protocol/PciIo.h>
#include <Protocol/IoMmu.h>
#include <Protocol/DxeSmmReadyToLock.h>
#include "DebugCommunicationLibUsb3Internal.h"

GUID  gUsb3DbgGuid =  USB3_DBG_GUID;

USB3_DEBUG_PORT_HANDLE  mUsb3Instance         = { USB3DBG_UNINITIALIZED };
EFI_PHYSICAL_ADDRESS    mUsb3InstanceAddr     = 0;
EFI_PHYSICAL_ADDRESS    *mUsb3InstanceAddrPtr = NULL;
EFI_PCI_IO_PROTOCOL     *mUsb3PciIo           = NULL;

/**
  Creates a named event that can be signaled.

  This function creates an event using NotifyTpl, NotifyFunction.
  If Name is NULL, then ASSERT().
  If NotifyTpl is not a legal TPL value, then ASSERT().
  If NotifyFunction is NULL, then ASSERT().

  @param  Name                  Supplies the GUID name of the event.
  @param  NotifyTpl             Supplies the task priority level of the event notifications.
  @param  NotifyFunction        Supplies the function to notify when the event is signaled.
  @param  Event                 A pointer to the event created.

  @retval EFI_SUCCESS           A named event was created.
  @retval EFI_OUT_OF_RESOURCES  There are not enough resource to create the named event.

**/
EFI_STATUS
EFIAPI
Usb3NamedEventListen (
  IN CONST EFI_GUID    *Name,
  IN EFI_TPL           NotifyTpl,
  IN EFI_EVENT_NOTIFY  NotifyFunction,
  IN EFI_EVENT         *Event
  )
{
  EFI_STATUS  Status;
  VOID        *RegistrationLocal;

  ASSERT (Name != NULL);
  ASSERT (NotifyFunction != NULL);
  ASSERT (NotifyTpl <= TPL_HIGH_LEVEL);

  //
  // Create event
  //
  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  NotifyTpl,
                  NotifyFunction,
                  NULL,
                  Event
                  );
  ASSERT_EFI_ERROR (Status);

  //
  // Register for an installation of protocol interface
  //
  Status = gBS->RegisterProtocolNotify (
                  (EFI_GUID *)Name,
                  *Event,
                  &RegistrationLocal
                  );
  ASSERT_EFI_ERROR (Status);

  return Status;
}

/**
  USB3 map one DMA buffer.

  @param PciIo          Pointer to PciIo for USB3 debug port.
  @param Address        DMA buffer address to be mapped.
  @param NumberOfBytes  Number of bytes to be mapped.

**/
VOID
Usb3MapOneDmaBuffer (
  IN EFI_PCI_IO_PROTOCOL   *PciIo,
  IN EFI_PHYSICAL_ADDRESS  Address,
  IN UINTN                 NumberOfBytes
  )
{
  EFI_STATUS            Status;
  VOID                  *HostAddress;
  EFI_PHYSICAL_ADDRESS  DeviceAddress;
  VOID                  *Mapping;

  HostAddress = (VOID *)(UINTN)Address;
  Status      = PciIo->Map (
                         PciIo,
                         EfiPciIoOperationBusMasterCommonBuffer,
                         HostAddress,
                         &NumberOfBytes,
                         &DeviceAddress,
                         &Mapping
                         );
  ASSERT_EFI_ERROR (Status);
  ASSERT (DeviceAddress == ((EFI_PHYSICAL_ADDRESS)(UINTN)HostAddress));
}

/**
  USB3 map DMA buffers.

  @param Instance       Pointer to USB3 debug port instance.
  @param PciIo          Pointer to PciIo for USB3 debug port.

**/
VOID
Usb3MapDmaBuffers (
  IN USB3_DEBUG_PORT_HANDLE  *Instance,
  IN EFI_PCI_IO_PROTOCOL     *PciIo
  )
{
  Usb3MapOneDmaBuffer (
    PciIo,
    Instance->UrbIn.Data,
    XHCI_DEBUG_DEVICE_MAX_PACKET_SIZE * 2 + USB3_DEBUG_PORT_WRITE_MAX_PACKET_SIZE
    );

  Usb3MapOneDmaBuffer (
    PciIo,
    Instance->TransferRingIn.RingSeg0,
    sizeof (TRB_TEMPLATE) * TR_RING_TRB_NUMBER
    );

  Usb3MapOneDmaBuffer (
    PciIo,
    Instance->TransferRingOut.RingSeg0,
    sizeof (TRB_TEMPLATE) * TR_RING_TRB_NUMBER
    );

  Usb3MapOneDmaBuffer (
    PciIo,
    Instance->EventRing.EventRingSeg0,
    sizeof (TRB_TEMPLATE) * EVENT_RING_TRB_NUMBER
    );

  Usb3MapOneDmaBuffer (
    PciIo,
    Instance->EventRing.ERSTBase,
    sizeof (EVENT_RING_SEG_TABLE_ENTRY) * ERST_NUMBER
    );

  Usb3MapOneDmaBuffer (
    PciIo,
    Instance->DebugCapabilityContext,
    sizeof (XHC_DC_CONTEXT)
    );

  Usb3MapOneDmaBuffer (
    PciIo,
    ((XHC_DC_CONTEXT *)(UINTN)Instance->DebugCapabilityContext)->DbcInfoContext.String0DescAddress,
    STRING0_DESC_LEN + MANU_DESC_LEN + PRODUCT_DESC_LEN + SERIAL_DESC_LEN
    );
}

/**
  Invoke a notification event

  @param[in] Event              Event whose notification function is being invoked.
  @param[in] Context            The pointer to the notification function's context,
                                which is implementation-dependent.

**/
VOID
EFIAPI
Usb3DxeSmmReadyToLockNotify (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  USB3_DEBUG_PORT_HANDLE  *Instance;

  DEBUG ((DEBUG_INFO, "%a()\n", __FUNCTION__));

  Instance = GetUsb3DebugPortInstance ();
  ASSERT (Instance != NULL);

  Instance->InNotify = TRUE;

  //
  // For the case that the USB3 debug port instance and DMA buffers are
  // from PEI HOB with IOMMU enabled.
  // Reinitialize USB3 debug port with granted DXE DMA buffer accessible
  // by SMM environment.
  //
  InitializeUsbDebugHardware (Instance);

  //
  // Wait some time for host to be ready after re-initialization.
  //
  MicroSecondDelay (1000000);

  Instance->InNotify = FALSE;
  gBS->CloseEvent (Event);
}

/**
  USB3 get IOMMU protocol.

  @return Pointer to IOMMU protocol.

**/
EDKII_IOMMU_PROTOCOL *
Usb3GetIoMmu (
  VOID
  )
{
  EFI_STATUS            Status;
  EDKII_IOMMU_PROTOCOL  *IoMmu;

  IoMmu  = NULL;
  Status = gBS->LocateProtocol (
                  &gEdkiiIoMmuProtocolGuid,
                  NULL,
                  (VOID **)&IoMmu
                  );
  if (!EFI_ERROR (Status) && (IoMmu != NULL)) {
    return IoMmu;
  }

  return NULL;
}

/**
  Invoke a notification event

  @param[in] Event              Event whose notification function is being invoked.
  @param[in] Context            The pointer to the notification function's context,
                                which is implementation-dependent.

**/
VOID
EFIAPI
Usb3PciIoNotify (
  IN  EFI_EVENT  Event,
  IN  VOID       *Context
  )
{
  EFI_STATUS              Status;
  UINTN                   PciIoHandleCount;
  EFI_HANDLE              *PciIoHandleBuffer;
  UINTN                   Index;
  EFI_PCI_IO_PROTOCOL     *PciIo;
  UINTN                   PciSegment;
  UINTN                   PciBusNumber;
  UINTN                   PciDeviceNumber;
  UINTN                   PciFunctionNumber;
  UINT32                  PciAddress;
  USB3_DEBUG_PORT_HANDLE  *Instance;
  EFI_EVENT               SmmReadyToLockEvent;

  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  &gEfiPciIoProtocolGuid,
                  NULL,
                  &PciIoHandleCount,
                  &PciIoHandleBuffer
                  );
  if (!EFI_ERROR (Status) &&
      (PciIoHandleBuffer != NULL) &&
      (PciIoHandleCount != 0))
  {
    for (Index = 0; Index < PciIoHandleCount; Index++) {
      Status = gBS->HandleProtocol (
                      PciIoHandleBuffer[Index],
                      &gEfiPciIoProtocolGuid,
                      (VOID **)&PciIo
                      );
      ASSERT_EFI_ERROR (Status);
      Status = PciIo->GetLocation (PciIo, &PciSegment, &PciBusNumber, &PciDeviceNumber, &PciFunctionNumber);
      ASSERT_EFI_ERROR (Status);
      PciAddress = (UINT32)((PciBusNumber << 20) | (PciDeviceNumber << 15) | (PciFunctionNumber << 12));
      if (PciAddress == PcdGet32 (PcdUsbXhciPciAddress)) {
        //
        // Found the PciIo for USB3 debug port.
        //
        DEBUG ((DEBUG_INFO, "%a()\n", __FUNCTION__));
        if (Usb3GetIoMmu () != NULL) {
          Instance = GetUsb3DebugPortInstance ();
          ASSERT (Instance != NULL);
          if (Instance->Ready) {
            Instance->InNotify = TRUE;
            Usb3MapDmaBuffers (Instance, PciIo);
            Instance->InNotify = FALSE;

            if (Instance->FromHob) {
              mUsb3PciIo = PciIo;
              Usb3NamedEventListen (
                &gEfiDxeSmmReadyToLockProtocolGuid,
                TPL_NOTIFY,
                Usb3DxeSmmReadyToLockNotify,
                &SmmReadyToLockEvent
                );
            }
          }
        }

        gBS->CloseEvent (Event);
        break;
      }
    }

    gBS->FreePool (PciIoHandleBuffer);
  }
}

/**
  Return USB3 debug instance address pointer.

**/
EFI_PHYSICAL_ADDRESS *
GetUsb3DebugPortInstanceAddrPtr (
  VOID
  )
{
  if (mUsb3InstanceAddrPtr == NULL) {
    //
    // Use the local variables temporarily.
    //
    mUsb3InstanceAddr    = (EFI_PHYSICAL_ADDRESS)(UINTN)&mUsb3Instance;
    mUsb3InstanceAddrPtr = &mUsb3InstanceAddr;
  }

  return mUsb3InstanceAddrPtr;
}

/**
  Allocates pages that are suitable for an OperationBusMasterCommonBuffer or
  OperationBusMasterCommonBuffer64 mapping.

  @param PciIo                  Pointer to PciIo for USB3 debug port.
  @param Pages                  The number of pages to allocate.
  @param Address                A pointer to store the base system memory address of the
                                allocated range.

  @retval EFI_SUCCESS           The requested memory pages were allocated.
  @retval EFI_UNSUPPORTED       Attributes is unsupported. The only legal attribute bits are
                                MEMORY_WRITE_COMBINE and MEMORY_CACHED.
  @retval EFI_INVALID_PARAMETER One or more parameters are invalid.
  @retval EFI_OUT_OF_RESOURCES  The memory pages could not be allocated.

**/
EFI_STATUS
Usb3AllocateDmaBuffer (
  IN EFI_PCI_IO_PROTOCOL  *PciIo,
  IN UINTN                Pages,
  OUT VOID                **Address
  )
{
  EFI_STATUS  Status;

  *Address = NULL;
  Status   = PciIo->AllocateBuffer (
                      PciIo,
                      AllocateAnyPages,
                      EfiRuntimeServicesData,
                      Pages,
                      Address,
                      0
                      );
  if (!EFI_ERROR (Status)) {
    Usb3MapOneDmaBuffer (
      PciIo,
      (EFI_PHYSICAL_ADDRESS)(UINTN)*Address,
      EFI_PAGES_TO_SIZE (Pages)
      );
  }

  return Status;
}

/**
  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
  )
{
  EFI_PHYSICAL_ADDRESS  TmpAddr;
  EFI_STATUS            Status;
  VOID                  *Buf;

  Buf = NULL;

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

  return Buf;
}

/**
  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
DebugCommunicationUsb3DxeConstructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_PHYSICAL_ADDRESS    *AddrPtr;
  USB3_DEBUG_PORT_HANDLE  *Instance;
  EFI_PHYSICAL_ADDRESS    Address;
  EFI_STATUS              Status;
  EFI_EVENT               Event;

  Status = EfiGetSystemConfigurationTable (&gUsb3DbgGuid, (VOID **)&AddrPtr);
  if (EFI_ERROR (Status) || (AddrPtr == NULL)) {
    //
    // Instead of using local variables, install system configuration table for
    // the local instance and the buffer to save instance address pointer.
    //
    Address = SIZE_4GB;
    Status  = gBS->AllocatePages (
                     AllocateMaxAddress,
                     EfiACPIMemoryNVS,
                     EFI_SIZE_TO_PAGES (sizeof (EFI_PHYSICAL_ADDRESS) + sizeof (USB3_DEBUG_PORT_HANDLE)),
                     &Address
                     );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    AddrPtr = (EFI_PHYSICAL_ADDRESS *)(UINTN)Address;
    ZeroMem (AddrPtr, sizeof (EFI_PHYSICAL_ADDRESS) + sizeof (USB3_DEBUG_PORT_HANDLE));
    Instance = (USB3_DEBUG_PORT_HANDLE *)(AddrPtr + 1);
    CopyMem (Instance, &mUsb3Instance, sizeof (USB3_DEBUG_PORT_HANDLE));
    *AddrPtr = (EFI_PHYSICAL_ADDRESS)(UINTN)Instance;

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

  if (mUsb3InstanceAddrPtr != NULL) {
    *AddrPtr = *mUsb3InstanceAddrPtr;
  }

  mUsb3InstanceAddrPtr = AddrPtr;

  Instance = GetUsb3DebugPortInstance ();
  ASSERT (Instance != NULL);

  if (Instance->PciIoEvent == 0) {
    Status = Usb3NamedEventListen (
               &gEfiPciIoProtocolGuid,
               TPL_NOTIFY,
               Usb3PciIoNotify,
               &Event
               );
    if (!EFI_ERROR (Status)) {
      Instance->PciIoEvent = (EFI_PHYSICAL_ADDRESS)(UINTN)Event;
    }
  }

  return EFI_SUCCESS;
}

/**
  The destructor function.

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

  @retval EFI_SUCCESS   The destructor always returns EFI_SUCCESS.

**/
EFI_STATUS
EFIAPI
DebugCommunicationUsb3DxeDestructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  USB3_DEBUG_PORT_HANDLE  *Instance;

  Instance = GetUsb3DebugPortInstance ();
  ASSERT (Instance != NULL);

  if (Instance->PciIoEvent != 0) {
    //
    // Close the event created.
    //
    gBS->CloseEvent ((EFI_EVENT)(UINTN)Instance->PciIoEvent);
    Instance->PciIoEvent = 0;
  }

  return EFI_SUCCESS;
}
