/** @file
  Library class to work with PCI capabilities in PCI config space.

  Provides functions to parse capabilities lists, and to locate, describe, read
  and write capabilities. PCI config space access is abstracted away.

  Copyright (C) 2018, Red Hat, Inc.

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

#ifndef __PCI_CAP_LIB_H__
#define __PCI_CAP_LIB_H__

#include <Uefi/UefiBaseType.h>

//
// Base structure for representing a PCI device -- down to the PCI function
// level -- for the purposes of this library class. This is a forward
// declaration that is completed below. Concrete implementations are supposed
// to inherit and extend this type.
//
typedef struct PCI_CAP_DEV PCI_CAP_DEV;

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

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

  PCI_CAP_DEV_READ_CONFIG returns an unspecified error if accessing Size bytes
  from SourceOffset exceeds the config space limit of the PCI device. Fewer
  than Size bytes may have been read in this case.

  @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.

  @return                 Unspecified error codes. Fewer than Size bytes may
                          have been read.
**/
typedef
RETURN_STATUS
(EFIAPI *PCI_CAP_DEV_READ_CONFIG) (
  IN  PCI_CAP_DEV *PciDevice,
  IN  UINT16      SourceOffset,
  OUT VOID        *DestinationBuffer,
  IN  UINT16      Size
  );

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

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

  PCI_CAP_DEV_WRITE_CONFIG returns an unspecified error if accessing Size bytes
  at DestinationOffset exceeds the config space limit of the PCI device. Fewer
  than Size bytes may have been written in this case.

  @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.

  @return                 Unspecified error codes. Fewer than Size bytes may
                          have been written.
**/
typedef
RETURN_STATUS
(EFIAPI *PCI_CAP_DEV_WRITE_CONFIG) (
  IN PCI_CAP_DEV *PciDevice,
  IN UINT16      DestinationOffset,
  IN VOID        *SourceBuffer,
  IN UINT16      Size
  );

//
// Complete the PCI_CAP_DEV type here. The base abstraction only requires
// config space accessors.
//
struct PCI_CAP_DEV {
  PCI_CAP_DEV_READ_CONFIG  ReadConfig;
  PCI_CAP_DEV_WRITE_CONFIG WriteConfig;
};

//
// Opaque data structure representing parsed PCI Capabilities Lists.
//
typedef struct PCI_CAP_LIST PCI_CAP_LIST;

//
// Opaque data structure representing a PCI Capability in a parsed Capability
// List.
//
typedef struct PCI_CAP PCI_CAP;

//
// Distinguishes whether a Capability ID is 8-bit wide and interpreted in
// normal config space, or 16-bit wide and interpreted in extended config
// space. Capability ID definitions are relative to domain.
//
typedef enum {
  PciCapNormal,
  PciCapExtended
} PCI_CAP_DOMAIN;

//
// Public data structure that PciCapGetInfo() fills in about a PCI_CAP object.
//
typedef struct {
  PCI_CAP_DOMAIN Domain;
  UINT16         CapId;
  //
  // The capability identified by Domain and CapId may have multiple instances
  // in config space. NumInstances provides the total count of occurrences of
  // the capability. It is always positive.
  //
  UINT16 NumInstances;
  //
  // Instance is the serial number, in capabilities list traversal order (not
  // necessarily config space offset order), of the one capability instance
  // that PciCapGetInfo() is reporting about. Instance is always smaller than
  // NumInstances.
  //
  UINT16 Instance;
  //
  // The offset in config space at which the capability header of the
  // capability instance starts.
  //
  UINT16 Offset;
  //
  // The deduced maximum size of the capability instance, including the
  // capability header. This hint is an upper bound, calculated -- without
  // regard to the internal structure of the capability -- from (a) the next
  // lowest offset in configuration space that is known to be used by another
  // capability, and (b) from the end of the config space identified by Domain,
  // whichever is lower.
  //
  UINT16 MaxSizeHint;
  //
  // The version number of the capability instance. Always zero when Domain is
  // PciCapNormal.
  //
  UINT8 Version;
} PCI_CAP_INFO;


/**
  Parse the capabilities lists (both normal and extended, as applicable) of a
  PCI device.

  If the PCI device has no capabilities, that per se will not fail
  PciCapListInit(); an empty capabilities list will be represented.

  If the PCI device is found to be PCI Express, then an attempt will be made to
  parse the extended capabilities list as well. If the first extended config
  space access -- via PciDevice->ReadConfig() with SourceOffset=0x100 and
  Size=4 -- fails, that per se will not fail PciCapListInit(); the device will
  be assumed to have no extended capabilities.

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

  @param[out] CapList   Opaque data structure that holds an in-memory
                        representation of the parsed capabilities lists of
                        PciDevice.

  @retval RETURN_SUCCESS           The capabilities lists have been parsed from
                                   config space.

  @retval RETURN_OUT_OF_RESOURCES  Memory allocation failed.

  @retval RETURN_DEVICE_ERROR      A loop or some other kind of invalid pointer
                                   was detected in the capabilities lists of
                                   PciDevice.

  @return                          Error codes propagated from
                                   PciDevice->ReadConfig().
**/
RETURN_STATUS
EFIAPI
PciCapListInit (
  IN  PCI_CAP_DEV  *PciDevice,
  OUT PCI_CAP_LIST **CapList
  );


/**
  Free the resources used by CapList.

  @param[in] CapList  The PCI_CAP_LIST object to free, originally produced by
                      PciCapListInit().
**/
VOID
EFIAPI
PciCapListUninit (
  IN PCI_CAP_LIST *CapList
  );


/**
  Locate a capability instance in the parsed capabilities lists.

  @param[in] CapList   The PCI_CAP_LIST object produced by PciCapListInit().

  @param[in] Domain    Distinguishes whether CapId is 8-bit wide and
                       interpreted in normal config space, or 16-bit wide and
                       interpreted in extended config space. Capability ID
                       definitions are relative to domain.

  @param[in] CapId     Capability identifier to look up.

  @param[in] Instance  Domain and CapId may identify a multi-instance
                       capability. When Instance is zero, the first instance of
                       the capability is located (in list traversal order --
                       which may not mean increasing config space offset
                       order). Higher Instance values locate subsequent
                       instances of the same capability (in list traversal
                       order).

  @param[out] Cap      The capability instance that matches the search
                       criteria. Cap is owned by CapList and becomes invalid
                       when CapList is freed with PciCapListUninit().
                       PciCapListFindCap() may be called with Cap set to NULL,
                       in order to test the existence of a specific capability
                       instance.

  @retval RETURN_SUCCESS    The capability instance identified by (Domain,
                            CapId, Instance) has been found.

  @retval RETURN_NOT_FOUND  The requested (Domain, CapId, Instance) capability
                            instance does not exist.
**/
RETURN_STATUS
EFIAPI
PciCapListFindCap (
  IN  PCI_CAP_LIST   *CapList,
  IN  PCI_CAP_DOMAIN Domain,
  IN  UINT16         CapId,
  IN  UINT16         Instance,
  OUT PCI_CAP        **Cap    OPTIONAL
  );


/**
  Locate the first instance of the capability given by (Domain, CapId) such
  that the instance's Version is greater than or equal to MinVersion.

  This is a convenience function that may save client code calls to
  PciCapListFindCap() and PciCapGetInfo().

  @param[in] CapList     The PCI_CAP_LIST object produced by PciCapListInit().

  @param[in] Domain      Distinguishes whether CapId is 8-bit wide and
                         interpreted in normal config space, or 16-bit wide and
                         interpreted in extended config space. Capability ID
                         definitions are relative to domain.

  @param[in] CapId       Capability identifier to look up.

  @param[in] MinVersion  The minimum version that the capability instance is
                         required to have. Note that all capability instances
                         in Domain=PciCapNormal have Version=0.

  @param[out] Cap        The first capability instance that matches the search
                         criteria. Cap is owned by CapList and becomes invalid
                         when CapList is freed with PciCapListUninit().
                         PciCapListFindCapVersion() may be called with Cap set
                         to NULL, in order just to test whether the search
                         criteria are satisfiable.

  @retval RETURN_SUCCESS    The first capability instance matching (Domain,
                            CapId, MinVersion) has been located.

  @retval RETURN_NOT_FOUND  No capability instance matches (Domain, CapId,
                            MinVersion).
**/
RETURN_STATUS
EFIAPI
PciCapListFindCapVersion (
  IN  PCI_CAP_LIST   *CapList,
  IN  PCI_CAP_DOMAIN Domain,
  IN  UINT16         CapId,
  IN  UINT8          MinVersion,
  OUT PCI_CAP        **Cap      OPTIONAL
  );


/**
  Get information about a PCI Capability instance.

  @param[in] Cap    The capability instance to get info about, located with
                    PciCapListFindCap*().

  @param[out] Info  A PCI_CAP_INFO structure that describes the properties of
                    Cap.

  @retval RETURN_SUCCESS  Fields of Info have been set.

  @return                 Unspecified error codes, if filling in Info failed
                          for some reason.
**/
RETURN_STATUS
EFIAPI
PciCapGetInfo (
  IN  PCI_CAP      *Cap,
  OUT PCI_CAP_INFO *Info
  );


/**
  Read a slice of a capability instance.

  The function performs as few config space accesses as possible (without
  attempting 64-bit wide accesses). PciCapRead() performs bounds checking on
  SourceOffsetInCap and Size, and only invokes PciDevice->ReadConfig() if the
  requested transfer falls within Cap.

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

  @param[in] Cap                 The capability instance to read, located with
                                 PciCapListFindCap*().

  @param[in] SourceOffsetInCap   Source offset relative to the capability
                                 header to start reading from. A zero value
                                 refers to the first byte of the capability
                                 header.

  @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 Cap to
                                  DestinationBuffer.

  @retval RETURN_BAD_BUFFER_SIZE  Reading Size bytes starting from
                                  SourceOffsetInCap would not (entirely) be
                                  contained within Cap, as suggested by
                                  PCI_CAP_INFO.MaxSizeHint. No bytes have been
                                  read.

  @return                         Error codes propagated from
                                  PciDevice->ReadConfig(). Fewer than Size
                                  bytes may have been read.
**/
RETURN_STATUS
EFIAPI
PciCapRead (
  IN  PCI_CAP_DEV *PciDevice,
  IN  PCI_CAP     *Cap,
  IN  UINT16      SourceOffsetInCap,
  OUT VOID        *DestinationBuffer,
  IN  UINT16      Size
  );


/**
  Write a slice of a capability instance.

  The function performs as few config space accesses as possible (without
  attempting 64-bit wide accesses). PciCapWrite() performs bounds checking on
  DestinationOffsetInCap and Size, and only invokes PciDevice->WriteConfig() if
  the requested transfer falls within Cap.

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

  @param[in] Cap                     The capability instance to write, located
                                     with PciCapListFindCap*().

  @param[in] DestinationOffsetInCap  Destination offset relative to the
                                     capability header to start writing at. A
                                     zero value refers to the first byte of the
                                     capability header.

  @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 Cap.

  @retval RETURN_BAD_BUFFER_SIZE  Writing Size bytes starting at
                                  DestinationOffsetInCap would not (entirely)
                                  be contained within Cap, as suggested by
                                  PCI_CAP_INFO.MaxSizeHint. No bytes have been
                                  written.

  @return                         Error codes propagated from
                                  PciDevice->WriteConfig(). Fewer than Size
                                  bytes may have been written.
**/
RETURN_STATUS
EFIAPI
PciCapWrite (
  IN PCI_CAP_DEV *PciDevice,
  IN PCI_CAP     *Cap,
  IN UINT16      DestinationOffsetInCap,
  IN VOID        *SourceBuffer,
  IN UINT16      Size
  );

#endif // __PCI_CAP_LIB_H__
