/** @file
  Plug a PciSegmentLib backend into PciCapLib, for config space access.

  Copyright (C) 2018, Red Hat, Inc.

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

#include <IndustryStandard/Pci23.h>

#include <Library/BaseLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PciSegmentLib.h>

#include "BasePciCapPciSegmentLib.h"

/**
  Read the config space of a given PCI device (both normal and extended).

  SegmentDevReadConfig() performs as few config space accesses as possible
  (without attempting 64-bit wide accesses).

  @param[in] PciDevice           Implementation-specific unique representation
                                 of the PCI device in the PCI hierarchy.

  @param[in] SourceOffset        Source offset in the config space of the PCI
                                 device to start reading from.

  @param[out] DestinationBuffer  Buffer to store the read data to.

  @param[in] Size                The number of bytes to transfer.

  @retval RETURN_SUCCESS      Size bytes have been transferred from config
                              space to DestinationBuffer.

  @retval RETURN_UNSUPPORTED  Accessing Size bytes from SourceOffset exceeds
                              the config space limit of the PCI device.
                              Although PCI_CAP_DEV_READ_CONFIG allows reading
                              fewer than Size bytes in this case,
                              SegmentDevReadConfig() will read none.
**/
STATIC
RETURN_STATUS
EFIAPI
SegmentDevReadConfig (
  IN  PCI_CAP_DEV  *PciDevice,
  IN  UINT16       SourceOffset,
  OUT VOID         *DestinationBuffer,
  IN  UINT16       Size
  )
{
  SEGMENT_DEV  *SegmentDev;
  UINT16       ConfigSpaceSize;
  UINT64       SourceAddress;

  SegmentDev      = SEGMENT_DEV_FROM_PCI_CAP_DEV (PciDevice);
  ConfigSpaceSize = (SegmentDev->MaxDomain == PciCapNormal ?
                     PCI_MAX_CONFIG_OFFSET : PCI_EXP_MAX_CONFIG_OFFSET);
  //
  // Note that all UINT16 variables below are promoted to INT32, and the
  // addition and the comparison is carried out in INT32.
  //
  if (SourceOffset + Size > ConfigSpaceSize) {
    return RETURN_UNSUPPORTED;
  }

  SourceAddress = PCI_SEGMENT_LIB_ADDRESS (
                    SegmentDev->SegmentNr,
                    SegmentDev->BusNr,
                    SegmentDev->DeviceNr,
                    SegmentDev->FunctionNr,
                    SourceOffset
                    );
  PciSegmentReadBuffer (SourceAddress, Size, DestinationBuffer);
  return RETURN_SUCCESS;
}

/**
  Write the config space of a given PCI device (both normal and extended).

  SegmentDevWriteConfig() performs as few config space accesses as possible
  (without attempting 64-bit wide accesses).

  @param[in] PciDevice          Implementation-specific unique representation
                                of the PCI device in the PCI hierarchy.

  @param[in] DestinationOffset  Destination offset in the config space of the
                                PCI device to start writing at.

  @param[in] SourceBuffer       Buffer to read the data to be stored from.

  @param[in] Size               The number of bytes to transfer.

  @retval RETURN_SUCCESS      Size bytes have been transferred from
                              SourceBuffer to config space.

  @retval RETURN_UNSUPPORTED  Accessing Size bytes at DestinationOffset exceeds
                              the config space limit of the PCI device.
                              Although PCI_CAP_DEV_WRITE_CONFIG allows writing
                              fewer than Size bytes in this case,
                              SegmentDevWriteConfig() will write none.
**/
STATIC
RETURN_STATUS
EFIAPI
SegmentDevWriteConfig (
  IN PCI_CAP_DEV  *PciDevice,
  IN UINT16       DestinationOffset,
  IN VOID         *SourceBuffer,
  IN UINT16       Size
  )
{
  SEGMENT_DEV  *SegmentDev;
  UINT16       ConfigSpaceSize;
  UINT64       DestinationAddress;

  SegmentDev      = SEGMENT_DEV_FROM_PCI_CAP_DEV (PciDevice);
  ConfigSpaceSize = (SegmentDev->MaxDomain == PciCapNormal ?
                     PCI_MAX_CONFIG_OFFSET : PCI_EXP_MAX_CONFIG_OFFSET);
  //
  // Note that all UINT16 variables below are promoted to INT32, and the
  // addition and the comparison is carried out in INT32.
  //
  if (DestinationOffset + Size > ConfigSpaceSize) {
    return RETURN_UNSUPPORTED;
  }

  DestinationAddress = PCI_SEGMENT_LIB_ADDRESS (
                         SegmentDev->SegmentNr,
                         SegmentDev->BusNr,
                         SegmentDev->DeviceNr,
                         SegmentDev->FunctionNr,
                         DestinationOffset
                         );
  PciSegmentWriteBuffer (DestinationAddress, Size, SourceBuffer);
  return RETURN_SUCCESS;
}

/**
  Create a PCI_CAP_DEV object from the PCI Segment:Bus:Device.Function
  quadruplet. The config space accessors are based upon PciSegmentLib.

  @param[in] MaxDomain   If MaxDomain is PciCapExtended, then
                         PciDevice->ReadConfig() and PciDevice->WriteConfig()
                         will delegate extended config space accesses too to
                         PciSegmentReadBuffer() and PciSegmentWriteBuffer(),
                         respectively. Otherwise, PciDevice->ReadConfig() and
                         PciDevice->WriteConfig() will reject accesses to
                         extended config space with RETURN_UNSUPPORTED, without
                         calling PciSegmentReadBuffer() or
                         PciSegmentWriteBuffer(). By setting MaxDomain to
                         PciCapNormal, the platform can prevent undefined
                         PciSegmentLib behavior when the PCI root bridge under
                         the PCI device at Segment:Bus:Device.Function doesn't
                         support extended config space.

  @param[in] Segment     16-bit wide segment number.

  @param[in] Bus         8-bit wide bus number.

  @param[in] Device      5-bit wide device number.

  @param[in] Function    3-bit wide function number.

  @param[out] PciDevice  The PCI_CAP_DEV object constructed as described above.
                         PciDevice can be passed to the PciCapLib APIs.

  @retval RETURN_SUCCESS            PciDevice has been constructed and output.

  @retval RETURN_INVALID_PARAMETER  Device or Function does not fit in the
                                    permitted number of bits.

  @retval RETURN_OUT_OF_RESOURCES   Memory allocation failed.
**/
RETURN_STATUS
EFIAPI
PciCapPciSegmentDeviceInit (
  IN  PCI_CAP_DOMAIN  MaxDomain,
  IN  UINT16          Segment,
  IN  UINT8           Bus,
  IN  UINT8           Device,
  IN  UINT8           Function,
  OUT PCI_CAP_DEV     **PciDevice
  )
{
  SEGMENT_DEV  *SegmentDev;

  if ((Device > PCI_MAX_DEVICE) || (Function > PCI_MAX_FUNC)) {
    return RETURN_INVALID_PARAMETER;
  }

  SegmentDev = AllocatePool (sizeof *SegmentDev);
  if (SegmentDev == NULL) {
    return RETURN_OUT_OF_RESOURCES;
  }

  SegmentDev->Signature              = SEGMENT_DEV_SIG;
  SegmentDev->MaxDomain              = MaxDomain;
  SegmentDev->SegmentNr              = Segment;
  SegmentDev->BusNr                  = Bus;
  SegmentDev->DeviceNr               = Device;
  SegmentDev->FunctionNr             = Function;
  SegmentDev->BaseDevice.ReadConfig  = SegmentDevReadConfig;
  SegmentDev->BaseDevice.WriteConfig = SegmentDevWriteConfig;

  *PciDevice = &SegmentDev->BaseDevice;
  return RETURN_SUCCESS;
}

/**
  Free the resources used by PciDevice.

  @param[in] PciDevice  The PCI_CAP_DEV object to free, originally produced by
                        PciCapPciSegmentDeviceInit().
**/
VOID
EFIAPI
PciCapPciSegmentDeviceUninit (
  IN PCI_CAP_DEV  *PciDevice
  )
{
  SEGMENT_DEV  *SegmentDev;

  SegmentDev = SEGMENT_DEV_FROM_PCI_CAP_DEV (PciDevice);
  FreePool (SegmentDev);
}
