/** @file

  Copyright (C) 2016, Linaro Ltd. All rights reserved.<BR>

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

**/

#include "NonDiscoverablePciDeviceIo.h"

#include <Protocol/DriverBinding.h>

#define MAX_NON_DISCOVERABLE_PCI_DEVICE_ID   (32 * 256)

STATIC UINTN               mUniqueIdCounter = 0;
EFI_CPU_ARCH_PROTOCOL      *mCpu;

//
// We only support the following device types
//
STATIC
CONST EFI_GUID * CONST
SupportedNonDiscoverableDevices[] = {
  &gEdkiiNonDiscoverableAhciDeviceGuid,
  &gEdkiiNonDiscoverableEhciDeviceGuid,
  &gEdkiiNonDiscoverableNvmeDeviceGuid,
  &gEdkiiNonDiscoverableOhciDeviceGuid,
  &gEdkiiNonDiscoverableSdhciDeviceGuid,
  &gEdkiiNonDiscoverableUfsDeviceGuid,
  &gEdkiiNonDiscoverableUhciDeviceGuid,
  &gEdkiiNonDiscoverableXhciDeviceGuid,
};

//
// Probe, start and stop functions of this driver, called by the DXE core for
// specific devices.
//
// The following specifications document these interfaces:
// - Driver Writer's Guide for UEFI 2.3.1 v1.01, 9 Driver Binding Protocol
// - UEFI Spec 2.3.1 + Errata C, 10.1 EFI Driver Binding Protocol
//
// The implementation follows:
// - Driver Writer's Guide for UEFI 2.3.1 v1.01
//   - 5.1.3.4 OpenProtocol() and CloseProtocol()
// - UEFI Spec 2.3.1 + Errata C
//   -  6.3 Protocol Handler Services
//

/**
  Supported function of Driver Binding protocol for this driver.
  Test to see if this driver supports ControllerHandle.

  @param This                   Protocol instance pointer.
  @param DeviceHandle           Handle of device to test.
  @param RemainingDevicePath    A pointer to the device path.
                                it should be ignored by device driver.

  @retval EFI_SUCCESS           This driver supports this device.
  @retval other                 This driver does not support this device.

**/
STATIC
EFI_STATUS
EFIAPI
NonDiscoverablePciDeviceSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL *This,
  IN EFI_HANDLE                  DeviceHandle,
  IN EFI_DEVICE_PATH_PROTOCOL    *RemainingDevicePath
  )
{
  NON_DISCOVERABLE_DEVICE             *Device;
  EFI_STATUS                          Status;
  EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR   *Desc;
  INTN                                Idx;

  Status = gBS->OpenProtocol (DeviceHandle,
                  &gEdkiiNonDiscoverableDeviceProtocolGuid, (VOID **)&Device,
                  This->DriverBindingHandle, DeviceHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = EFI_UNSUPPORTED;
  for (Idx = 0; Idx < ARRAY_SIZE (SupportedNonDiscoverableDevices); Idx++) {
    if (CompareGuid (Device->Type, SupportedNonDiscoverableDevices [Idx])) {
      Status = EFI_SUCCESS;
      break;
    }
  }

  if (EFI_ERROR (Status)) {
    goto CloseProtocol;
  }

  //
  // We only support MMIO devices, so iterate over the resources to ensure
  // that they only describe things that we can handle
  //
  for (Desc = Device->Resources; Desc->Desc != ACPI_END_TAG_DESCRIPTOR;
       Desc = (VOID *)((UINT8 *)Desc + Desc->Len + 3)) {
    if (Desc->Desc != ACPI_ADDRESS_SPACE_DESCRIPTOR ||
        Desc->ResType != ACPI_ADDRESS_SPACE_TYPE_MEM) {
      Status = EFI_UNSUPPORTED;
      break;
    }
  }

CloseProtocol:
  gBS->CloseProtocol (DeviceHandle, &gEdkiiNonDiscoverableDeviceProtocolGuid,
         This->DriverBindingHandle, DeviceHandle);

  return Status;
}

/**
  This routine is called right after the .Supported() called and
  Start this driver on ControllerHandle.

  @param This                   Protocol instance pointer.
  @param DeviceHandle           Handle of device to bind driver to.
  @param RemainingDevicePath    A pointer to the device path.
                                it should be ignored by device driver.

  @retval EFI_SUCCESS           This driver is added to this device.
  @retval other                 Some error occurs when binding this driver to this device.

**/
STATIC
EFI_STATUS
EFIAPI
NonDiscoverablePciDeviceStart (
  IN EFI_DRIVER_BINDING_PROTOCOL *This,
  IN EFI_HANDLE                  DeviceHandle,
  IN EFI_DEVICE_PATH_PROTOCOL    *RemainingDevicePath
  )
{
  NON_DISCOVERABLE_PCI_DEVICE   *Dev;
  EFI_STATUS                    Status;

  ASSERT (mUniqueIdCounter < MAX_NON_DISCOVERABLE_PCI_DEVICE_ID);
  if (mUniqueIdCounter >= MAX_NON_DISCOVERABLE_PCI_DEVICE_ID) {
    return EFI_OUT_OF_RESOURCES;
  }

  Dev = AllocateZeroPool (sizeof *Dev);
  if (Dev == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Status = gBS->OpenProtocol (DeviceHandle,
                  &gEdkiiNonDiscoverableDeviceProtocolGuid,
                  (VOID **)&Dev->Device, This->DriverBindingHandle,
                  DeviceHandle, EFI_OPEN_PROTOCOL_BY_DRIVER);
  if (EFI_ERROR (Status)) {
    goto FreeDev;
  }

  InitializePciIoProtocol (Dev);

  //
  // Setup complete, attempt to export the driver instance's
  // EFI_PCI_IO_PROTOCOL interface.
  //
  Dev->Signature = NON_DISCOVERABLE_PCI_DEVICE_SIG;
  Status = gBS->InstallProtocolInterface (&DeviceHandle, &gEfiPciIoProtocolGuid,
                  EFI_NATIVE_INTERFACE, &Dev->PciIo);
  if (EFI_ERROR (Status)) {
    goto CloseProtocol;
  }

  Dev->UniqueId = mUniqueIdCounter++;

  return EFI_SUCCESS;

CloseProtocol:
  gBS->CloseProtocol (DeviceHandle, &gEdkiiNonDiscoverableDeviceProtocolGuid,
         This->DriverBindingHandle, DeviceHandle);

FreeDev:
  FreePool (Dev);

  return Status;
}

/**
  Stop this driver on ControllerHandle.

  @param This               Protocol instance pointer.
  @param DeviceHandle       Handle of device to stop driver on.
  @param NumberOfChildren   Not used.
  @param ChildHandleBuffer  Not used.

  @retval EFI_SUCCESS   This driver is removed from this device.
  @retval other         Some error occurs when removing this driver from this device.

**/
STATIC
EFI_STATUS
EFIAPI
NonDiscoverablePciDeviceStop (
  IN EFI_DRIVER_BINDING_PROTOCOL *This,
  IN EFI_HANDLE                  DeviceHandle,
  IN UINTN                       NumberOfChildren,
  IN EFI_HANDLE                  *ChildHandleBuffer
  )
{
  EFI_STATUS                      Status;
  EFI_PCI_IO_PROTOCOL             *PciIo;
  NON_DISCOVERABLE_PCI_DEVICE     *Dev;

  Status = gBS->OpenProtocol (DeviceHandle, &gEfiPciIoProtocolGuid,
                  (VOID **)&PciIo, This->DriverBindingHandle, DeviceHandle,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Dev = NON_DISCOVERABLE_PCI_DEVICE_FROM_PCI_IO (PciIo);

  //
  // Handle Stop() requests for in-use driver instances gracefully.
  //
  Status = gBS->UninstallProtocolInterface (DeviceHandle,
                  &gEfiPciIoProtocolGuid, &Dev->PciIo);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  gBS->CloseProtocol (DeviceHandle, &gEdkiiNonDiscoverableDeviceProtocolGuid,
         This->DriverBindingHandle, DeviceHandle);

  FreePool (Dev);

  return EFI_SUCCESS;
}


//
// The static object that groups the Supported() (ie. probe), Start() and
// Stop() functions of the driver together. Refer to UEFI Spec 2.3.1 + Errata
// C, 10.1 EFI Driver Binding Protocol.
//
STATIC EFI_DRIVER_BINDING_PROTOCOL gDriverBinding = {
  &NonDiscoverablePciDeviceSupported,
  &NonDiscoverablePciDeviceStart,
  &NonDiscoverablePciDeviceStop,
  0x10, // Version, must be in [0x10 .. 0xFFFFFFEF] for IHV-developed drivers
  NULL,
  NULL
};

/**
  Entry point of this driver.

  @param  ImageHandle     Image handle this driver.
  @param  SystemTable     Pointer to the System Table.

  @retval EFI_SUCCESS     The entry point is executed successfully.
  @retval other           Some error occurred when executing this entry point.

**/
EFI_STATUS
EFIAPI
NonDiscoverablePciDeviceDxeEntryPoint (
  IN EFI_HANDLE       ImageHandle,
  IN EFI_SYSTEM_TABLE *SystemTable
  )
{
  EFI_STATUS      Status;

  Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **)&mCpu);
  ASSERT_EFI_ERROR(Status);

  return EfiLibInstallDriverBindingComponentName2 (
           ImageHandle,
           SystemTable,
           &gDriverBinding,
           ImageHandle,
           &gComponentName,
           &gComponentName2
           );
}
