/**@file
  Hardware info library with types and accessors to parse information about
  PCI host bridges.

  Copyright 2021 - 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <Library/DebugLib.h>

#include "HardwareInfoPciHostBridgeLib.h"

#define IS_RANGE_INVALID(Start, Size, MaxValue)  (Start >= MaxValue || Size == 0)

/**
  Calculate the last (inclusive) address of a range.

  @param[in] Start  First address of the range
  @param[in] Size   Size of the range
  @return Last address of the range
**/
STATIC
UINT64
GetRangeEnd (
  IN  UINT64  Start,
  IN  UINT64  Size,
  IN  UINT64  MaxValue
  )
{
  if (IS_RANGE_INVALID (Start, Size, MaxValue)) {
    return 0;
  }

  return Start + Size - 1;
}

/**
  Internal helper to update LastAddress if the Limit address
  of the Mem aperture is higher than the provided value.

  @param[in]  Mem           Pointer to aperture whose limit is
                            to be compared against accumulative
                            last address.
  @param[out] LastAddress   Pointer to accumulative last address
                            to be updated if Limit is higher
**/
STATIC
VOID
UpdateLastAddressIfHigher (
  IN  PCI_ROOT_BRIDGE_APERTURE  *Mem,
  OUT UINT64                    *LastAddress
  )
{
  if (Mem->Limit > *LastAddress) {
    *LastAddress = Mem->Limit;
  }
}

EFI_STATUS
HardwareInfoPciHostBridgeLastMmioAddress (
  IN CONST  HOST_BRIDGE_INFO  *HostBridge,
  IN        UINTN             DataSize,
  IN        BOOLEAN           HighMem,
  OUT       UINT64            *LastMmioAddress
  )
{
  EFI_STATUS                Status;
  PCI_ROOT_BRIDGE_APERTURE  Mem;
  PCI_ROOT_BRIDGE_APERTURE  MemAbove4G;
  PCI_ROOT_BRIDGE_APERTURE  PMem;
  PCI_ROOT_BRIDGE_APERTURE  PMemAbove4G;

  if (LastMmioAddress == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Set the output to the lowest possible value so that if some step fails
  // the overall outcome reflects no information found
  //
  *LastMmioAddress = 0;

  Status = HardwareInfoPciHostBridgeGetApertures (
             HostBridge,
             DataSize,
             NULL,
             &Mem,
             &MemAbove4G,
             &PMem,
             &PMemAbove4G,
             NULL
             );

  //
  // Forward error to caller but ignore warnings given that, very likely,
  // the host bridge will have a PIO aperture we are explicitly
  // ignoring here since we care only about MMIO resources.
  //
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (HighMem) {
    UpdateLastAddressIfHigher (&MemAbove4G, LastMmioAddress);
    UpdateLastAddressIfHigher (&PMemAbove4G, LastMmioAddress);
  } else {
    UpdateLastAddressIfHigher (&Mem, LastMmioAddress);
    UpdateLastAddressIfHigher (&PMem, LastMmioAddress);
  }

  return EFI_SUCCESS;
}

/**
  Set the boundaries of an aperture to invalid values having
  size zero and start MaxValue (yields Start > Limit which
  depicts an invalid range)

  @param[in]  MaxValue    Max value of the aperture's range (depends
                          on the data type)
  @param[out] Aperture    Aperture object to invalidate
**/
STATIC
VOID
InvalidateRootBridgeAperture (
  OUT PCI_ROOT_BRIDGE_APERTURE  *Aperture
  )
{
  if (Aperture == NULL) {
    return;
  }

  Aperture->Base  = MAX_UINT64;
  Aperture->Limit = 0;
}

/**
  Fill a PCI ROOT BRIDGE APERTURE with the proper values calculated
  from the provided start and size.

  @param[in]  Start       Start address of the aperture
  @param[in]  Size        Size, in bytes, of the aperture
  @param[in]  MaxValue    Max value a valid address could take and which
                          represents an invalid start address.
  @param[out] Aperture    Pointer to the aperture to be filled

  @retval EFI_SUCCESS                 Aperture was filled successfully
  @retval EFI_INVALID_PARAMETER       Range depicted by Start and Size is
                                      valid but ignored because aperture
                                      pointer is NULL
  @retval EFI_WARN_BUFFER_TOO_SMALL   Aperture pointer is invalid but the
                                      range also is so no harm.
**/
STATIC
EFI_STATUS
FillHostBridgeAperture (
  IN  UINT64                    Start,
  IN  UINT64                    Size,
  IN  UINT64                    MaxValue,
  OUT PCI_ROOT_BRIDGE_APERTURE  *Aperture
  )
{
  UINT64  End;

  End = GetRangeEnd (Start, Size, MaxValue);

  if (Aperture == NULL) {
    if (!IS_RANGE_INVALID (Start, Size, MaxValue)) {
      //
      // Report an error to the caller since the range specified in
      // the host bridge's resources is non-empty but the provided
      // aperture pointer is null, thus the valid range is ignored.
      //
      return EFI_INVALID_PARAMETER;
    }

    return EFI_WARN_BUFFER_TOO_SMALL;
  }

  if (IS_RANGE_INVALID (Start, Size, MaxValue)) {
    //
    // Fill Aperture with invalid range values to signal the
    // absence of an address space (empty range)
    //
    InvalidateRootBridgeAperture (Aperture);
  } else {
    Aperture->Base  = Start;
    Aperture->Limit = End;
  }

  return EFI_SUCCESS;
}

/**
  Merge 2 ranges (normal and prefetchable) into a single aperture
  comprehending the addresses encompassed by both of them. If both
  ranges are not empty they must be contiguous for correctness.

  @param[in]  Start       Range start address
  @param[in]  Size        Range size in bytes
  @param[in]  PStart      Prefetchable range start address
  @param[in]  PSize       Prefetchable range size in bytes
  @param[in]  MaxValue    Max value a valid address could take and which
                          represents an invalid start address.
  @param[out] Aperture    Pointer to the aperture to be filled

  @retval EFI_SUCCESS                 Aperture was filled successfully
  @retval EFI_INVALID_PARAMETER       Either range depicted by Start, Size
                                      or PStart, PSize or both are valid
                                      but ignored because aperture pointer
                                      is NULL
  @retval EFI_WARN_BUFFER_TOO_SMALL   Aperture pointer is invalid but both
                                      ranges are too so no harm.
**/
STATIC
EFI_STATUS
MergeHostBridgeApertures (
  IN  UINT64                    Start,
  IN  UINT64                    Size,
  IN  UINT64                    PStart,
  IN  UINT64                    PSize,
  IN  UINT64                    MaxValue,
  OUT PCI_ROOT_BRIDGE_APERTURE  *Aperture
  )
{
  UINT64  PEnd;

  if (Aperture == NULL) {
    if (!IS_RANGE_INVALID (Start, Size, MaxValue) ||
        !IS_RANGE_INVALID (PStart, PSize, MaxValue))
    {
      //
      // Report an error to the caller since the range specified in
      // the host bridge's resources is non-empty but the provided
      // aperture pointer is null, thus the valid range is ignored.
      //
      return EFI_INVALID_PARAMETER;
    }

    return EFI_WARN_BUFFER_TOO_SMALL;
  }

  //
  // Start from an empty range (Limit < Base)
  //
  InvalidateRootBridgeAperture (Aperture);

  if (!IS_RANGE_INVALID (Start, Size, MaxValue)) {
    Aperture->Base  = Start;
    Aperture->Limit = Start + Size - 1;
  }

  if (!IS_RANGE_INVALID (PStart, PSize, MaxValue)) {
    PEnd = PStart + PSize - 1;

    if (PStart < Aperture->Base) {
      Aperture->Base = PStart;
    }

    if (PEnd > Aperture->Limit) {
      Aperture->Limit = PEnd;
    }
  }

  return EFI_SUCCESS;
}

EFI_STATUS
HardwareInfoPciHostBridgeGetBusNrRange (
  IN CONST  HOST_BRIDGE_INFO  *HostBridge,
  IN        UINTN             DataSize,
  OUT       UINTN             *BusNrStart,
  OUT       UINTN             *BusNrLast
  )
{
  if ((HostBridge == NULL) || (DataSize == 0) ||
      (BusNrStart == NULL) || (BusNrLast == NULL))
  {
    return EFI_INVALID_PARAMETER;
  }

  //
  // For now only version 0 is supported
  //
  if (HostBridge->Version != 0) {
    return EFI_INCOMPATIBLE_VERSION;
  }

  *BusNrStart = HostBridge->BusNrStart;
  *BusNrLast  = HostBridge->BusNrLast;

  return EFI_SUCCESS;
}

EFI_STATUS
HardwareInfoPciHostBridgeGetApertures (
  IN CONST  HOST_BRIDGE_INFO          *HostBridge,
  IN        UINTN                     DataSize,
  OUT       PCI_ROOT_BRIDGE_APERTURE  *Io,
  OUT       PCI_ROOT_BRIDGE_APERTURE  *Mem,
  OUT       PCI_ROOT_BRIDGE_APERTURE  *MemAbove4G,
  OUT       PCI_ROOT_BRIDGE_APERTURE  *PMem,
  OUT       PCI_ROOT_BRIDGE_APERTURE  *PMemAbove4G,
  OUT       PCI_ROOT_BRIDGE_APERTURE  *PcieConfig
  )
{
  EFI_STATUS  Status;
  BOOLEAN     StickyError;

  StickyError = FALSE;
  if ((HostBridge == NULL) || (DataSize == 0)) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // For now only version 0 is supported
  //
  if (HostBridge->Version != 0) {
    return EFI_INCOMPATIBLE_VERSION;
  }

  Status = FillHostBridgeAperture (
             HostBridge->IoStart,
             HostBridge->IoSize,
             MAX_UINT32,
             Io
             );
  StickyError |= EFI_ERROR (Status);

  Status = FillHostBridgeAperture (
             HostBridge->PcieConfigStart,
             HostBridge->PcieConfigSize,
             MAX_UINT64,
             PcieConfig
             );
  StickyError |= EFI_ERROR (Status);

  if (HostBridge->Flags.Bits.CombineMemPMem) {
    Status = MergeHostBridgeApertures (
               HostBridge->MemStart,
               HostBridge->MemSize,
               HostBridge->PMemStart,
               HostBridge->PMemSize,
               MAX_UINT32,
               Mem
               );
    StickyError |= EFI_ERROR (Status);

    Status = MergeHostBridgeApertures (
               HostBridge->MemAbove4GStart,
               HostBridge->MemAbove4GSize,
               HostBridge->PMemAbove4GStart,
               HostBridge->PMemAbove4GSize,
               MAX_UINT64,
               MemAbove4G
               );
    StickyError |= EFI_ERROR (Status);

    //
    // Invalidate unused apertures
    //
    InvalidateRootBridgeAperture (PMem);
    InvalidateRootBridgeAperture (PMemAbove4G);
  } else {
    Status = FillHostBridgeAperture (
               HostBridge->MemStart,
               HostBridge->MemSize,
               MAX_UINT32,
               Mem
               );
    StickyError |= EFI_ERROR (Status);

    Status = FillHostBridgeAperture (
               HostBridge->PMemStart,
               HostBridge->PMemSize,
               MAX_UINT32,
               PMem
               );
    StickyError |= EFI_ERROR (Status);

    Status = FillHostBridgeAperture (
               HostBridge->MemAbove4GStart,
               HostBridge->MemAbove4GSize,
               MAX_UINT64,
               MemAbove4G
               );
    StickyError |= EFI_ERROR (Status);

    Status = FillHostBridgeAperture (
               HostBridge->PMemAbove4GStart,
               HostBridge->PMemAbove4GSize,
               MAX_UINT64,
               PMem
               );
    StickyError |= EFI_ERROR (Status);
  }

  if (StickyError) {
    //
    // If any function returned an error it is due to a valid range
    // specified in the host bridge that was ignored due to a NULL
    // pointer. Translate it to a warning to allow for calling with
    // only a subset of the apertures.
    //
    return EFI_WARN_STALE_DATA;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
HardwareInfoPciHostBridgeGetFlags (
  IN CONST  HOST_BRIDGE_INFO  *HostBridge,
  IN        UINTN             DataSize,
  OUT       UINT64            *Attributes               OPTIONAL,
  OUT       BOOLEAN           *DmaAbove4G               OPTIONAL,
  OUT       BOOLEAN           *NoExtendedConfigSpace    OPTIONAL,
  OUT       BOOLEAN           *CombineMemPMem           OPTIONAL
  )
{
  if ((HostBridge == NULL) || (DataSize == 0)) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // For now only version 0 is supported
  //
  if (HostBridge->Version != 0) {
    return EFI_INCOMPATIBLE_VERSION;
  }

  if (Attributes) {
    *Attributes = HostBridge->Attributes;
  }

  if (DmaAbove4G) {
    *DmaAbove4G = !!HostBridge->Flags.Bits.DmaAbove4G;
  }

  if (NoExtendedConfigSpace) {
    *NoExtendedConfigSpace = !!HostBridge->Flags.Bits.NoExtendedConfigSpace;
  }

  if (CombineMemPMem) {
    *CombineMemPMem = !!HostBridge->Flags.Bits.CombineMemPMem;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
HardwareInfoPciHostBridgeGet (
  IN CONST  HOST_BRIDGE_INFO          *HostBridge,
  IN        UINTN                     DataSize,
  OUT       UINTN                     *BusNrStart,
  OUT       UINTN                     *BusNrLast,
  OUT       UINT64                    *Attributes               OPTIONAL,
  OUT       BOOLEAN                   *DmaAbove4G               OPTIONAL,
  OUT       BOOLEAN                   *NoExtendedConfigSpace    OPTIONAL,
  OUT       BOOLEAN                   *CombineMemPMem           OPTIONAL,
  OUT       PCI_ROOT_BRIDGE_APERTURE  *Io                       OPTIONAL,
  OUT       PCI_ROOT_BRIDGE_APERTURE  *Mem                      OPTIONAL,
  OUT       PCI_ROOT_BRIDGE_APERTURE  *MemAbove4G               OPTIONAL,
  OUT       PCI_ROOT_BRIDGE_APERTURE  *PMem                     OPTIONAL,
  OUT       PCI_ROOT_BRIDGE_APERTURE  *PMemAbove4G              OPTIONAL,
  OUT       PCI_ROOT_BRIDGE_APERTURE  *PcieConfig               OPTIONAL
  )
{
  EFI_STATUS  Status;

  Status = HardwareInfoPciHostBridgeGetBusNrRange (
             HostBridge,
             DataSize,
             BusNrStart,
             BusNrLast
             );

  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = HardwareInfoPciHostBridgeGetFlags (
             HostBridge,
             DataSize,
             Attributes,
             DmaAbove4G,
             NoExtendedConfigSpace,
             CombineMemPMem
             );

  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = HardwareInfoPciHostBridgeGetApertures (
             HostBridge,
             DataSize,
             Io,
             Mem,
             MemAbove4G,
             PMem,
             PMemAbove4G,
             PcieConfig
             );

  return Status;
}
