/** @file
  Copyright (c) 2016, Linaro, Ltd. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <PiDxe.h>

#include <Guid/NonDiscoverableDevice.h>

#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/DevicePathLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/NonDiscoverableDeviceRegistrationLib.h>
#include <Library/UefiBootServicesTableLib.h>

#include <Protocol/DevicePath.h>
#include <Protocol/NonDiscoverableDevice.h>

/**
  Get Guid form the type of non-discoverable device.

  @param[in]  Type    The type of non-discoverable device.

  @retval   Return the Guid.

**/
STATIC
CONST EFI_GUID *
GetGuidFromType (
  IN  NON_DISCOVERABLE_DEVICE_TYPE  Type
  )
{
  switch (Type) {
  case NonDiscoverableDeviceTypeAhci:
    return &gEdkiiNonDiscoverableAhciDeviceGuid;

  case NonDiscoverableDeviceTypeAmba:
    return &gEdkiiNonDiscoverableAmbaDeviceGuid;

  case NonDiscoverableDeviceTypeEhci:
    return &gEdkiiNonDiscoverableEhciDeviceGuid;

  case NonDiscoverableDeviceTypeNvme:
    return &gEdkiiNonDiscoverableNvmeDeviceGuid;

  case NonDiscoverableDeviceTypeOhci:
    return &gEdkiiNonDiscoverableOhciDeviceGuid;

  case NonDiscoverableDeviceTypeSdhci:
    return &gEdkiiNonDiscoverableSdhciDeviceGuid;

  case NonDiscoverableDeviceTypeUfs:
    return &gEdkiiNonDiscoverableUfsDeviceGuid;

  case NonDiscoverableDeviceTypeUhci:
    return &gEdkiiNonDiscoverableUhciDeviceGuid;

  case NonDiscoverableDeviceTypeXhci:
    return &gEdkiiNonDiscoverableXhciDeviceGuid;

  default:
    return NULL;
  }
}

#pragma pack (1)
typedef struct {
  VENDOR_DEVICE_PATH                  Vendor;
  UINT64                              BaseAddress;
  UINT8                               ResourceType;
  EFI_DEVICE_PATH_PROTOCOL            End;
} NON_DISCOVERABLE_DEVICE_PATH;
#pragma pack ()

/**
  Register a non-discoverable MMIO device.

  @param[in]      Type                The type of non-discoverable device
  @param[in]      DmaType             Whether the device is DMA coherent
  @param[in]      InitFunc            Initialization routine to be invoked when
                                      the device is enabled
  @param[in,out]  Handle              The handle onto which to install the
                                      non-discoverable device protocol.
                                      If Handle is NULL or *Handle is NULL, a
                                      new handle will be allocated.
  @param[in]      NumMmioResources    The number of UINTN base/size pairs that
                                      follow, each describing an MMIO region
                                      owned by the device
  @param[in]  ...                     The variable argument list which contains the
                                      info about MmioResources.

  @retval EFI_SUCCESS                 The registration succeeded.
  @retval EFI_INVALID_PARAMETER       An invalid argument was given
  @retval Other                       The registration failed.

**/
EFI_STATUS
EFIAPI
RegisterNonDiscoverableMmioDevice (
  IN      NON_DISCOVERABLE_DEVICE_TYPE      Type,
  IN      NON_DISCOVERABLE_DEVICE_DMA_TYPE  DmaType,
  IN      NON_DISCOVERABLE_DEVICE_INIT      InitFunc,
  IN OUT  EFI_HANDLE                        *Handle OPTIONAL,
  IN      UINTN                             NumMmioResources,
  ...
  )
{
  NON_DISCOVERABLE_DEVICE             *Device;
  NON_DISCOVERABLE_DEVICE_PATH        *DevicePath;
  EFI_HANDLE                          LocalHandle;
  EFI_STATUS                          Status;
  UINTN                               AllocSize;
  UINTN                               Index;
  VA_LIST                             Args;
  EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR   *Desc;
  EFI_ACPI_END_TAG_DESCRIPTOR         *End;
  UINTN                               Base, Size;

  if (Type >= NonDiscoverableDeviceTypeMax ||
      DmaType >= NonDiscoverableDeviceDmaTypeMax ||
      NumMmioResources == 0) {
    return EFI_INVALID_PARAMETER;
  }

  if (Handle == NULL) {
    Handle = &LocalHandle;
    LocalHandle = NULL;
  }

  AllocSize = sizeof *Device +
              NumMmioResources * sizeof (EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR) +
              sizeof (EFI_ACPI_END_TAG_DESCRIPTOR);
  Device = (NON_DISCOVERABLE_DEVICE *)AllocateZeroPool (AllocSize);
  if (Device == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Device->Type = GetGuidFromType (Type);
  ASSERT (Device->Type != NULL);

  Device->DmaType = DmaType;
  Device->Initialize = InitFunc;
  Device->Resources = (EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *)(Device + 1);

  VA_START (Args, NumMmioResources);
  for (Index = 0; Index < NumMmioResources; Index++) {
    Desc = &Device->Resources [Index];
    Base = VA_ARG (Args, UINTN);
    Size = VA_ARG (Args, UINTN);

    Desc->Desc                  = ACPI_ADDRESS_SPACE_DESCRIPTOR;
    Desc->Len                   = sizeof *Desc - 3;
    Desc->AddrRangeMin          = Base;
    Desc->AddrLen               = Size;
    Desc->AddrRangeMax          = Base + Size - 1;
    Desc->ResType               = ACPI_ADDRESS_SPACE_TYPE_MEM;
    Desc->AddrSpaceGranularity  = ((EFI_PHYSICAL_ADDRESS)Base + Size > SIZE_4GB) ? 64 : 32;
    Desc->AddrTranslationOffset = 0;
  }
  VA_END (Args);

  End = (EFI_ACPI_END_TAG_DESCRIPTOR *)&Device->Resources [NumMmioResources];

  End->Desc     = ACPI_END_TAG_DESCRIPTOR;
  End->Checksum = 0;

  DevicePath = (NON_DISCOVERABLE_DEVICE_PATH *)CreateDeviceNode (
                                                 HARDWARE_DEVICE_PATH,
                                                 HW_VENDOR_DP,
                                                 sizeof (*DevicePath));
  if (DevicePath == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto FreeDevice;
  }

  CopyGuid (&DevicePath->Vendor.Guid, &gEdkiiNonDiscoverableDeviceProtocolGuid);

  //
  // Use the base address and type of the first region to
  // make the device path unique
  //
  DevicePath->BaseAddress = Device->Resources [0].AddrRangeMin;
  DevicePath->ResourceType = Device->Resources [0].ResType;

  SetDevicePathNodeLength (&DevicePath->Vendor,
    sizeof (*DevicePath) - sizeof (DevicePath->End));
  SetDevicePathEndNode (&DevicePath->End);

  Status = gBS->InstallMultipleProtocolInterfaces (Handle,
                  &gEdkiiNonDiscoverableDeviceProtocolGuid, Device,
                  &gEfiDevicePathProtocolGuid, DevicePath,
                  NULL);
  if (EFI_ERROR (Status)) {
    goto FreeDevicePath;
  }
  return EFI_SUCCESS;

FreeDevicePath:
  FreePool (DevicePath);

FreeDevice:
  FreePool (Device);

  return Status;
}
