/** @file
  Scan the entire PCI bus for root bridges to support coreboot UEFI payload.

  Copyright (c) 2016, Intel Corporation. All rights reserved.<BR>

  This program and the accompanying materials are licensed and made available
  under the terms and conditions of the BSD License which accompanies this
  distribution.  The full text of the license may be found at
  http://opensource.org/licenses/bsd-license.php.

  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT
  WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include <PiDxe.h>
#include <IndustryStandard/Pci.h>
#include <Protocol/PciHostBridgeResourceAllocation.h>
#include <Protocol/PciRootBridgeIo.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PciHostBridgeLib.h>
#include <Library/PciLib.h>
#include "PciHostBridge.h"

/**
  Adjust the collected PCI resource.

  @param[in]  Io               IO aperture.

  @param[in]  Mem              MMIO aperture.

  @param[in]  MemAbove4G       MMIO aperture above 4G.

  @param[in]  PMem             Prefetchable MMIO aperture.

  @param[in]  PMemAbove4G      Prefetchable MMIO aperture above 4G.
**/
VOID
AdjustRootBridgeResource (
  IN  PCI_ROOT_BRIDGE_APERTURE *Io,
  IN  PCI_ROOT_BRIDGE_APERTURE *Mem,
  IN  PCI_ROOT_BRIDGE_APERTURE *MemAbove4G,
  IN  PCI_ROOT_BRIDGE_APERTURE *PMem,
  IN  PCI_ROOT_BRIDGE_APERTURE *PMemAbove4G
)
{
  UINT64  Mask;

  //
  // For now try to downgrade everything into MEM32 since
  // - coreboot does not assign resource above 4GB
  // - coreboot might allocate interleaved MEM32 and PMEM32 resource
  //   in some cases
  //
  if (PMem->Base < Mem->Base) {
    Mem->Base = PMem->Base;
  }

  if (PMem->Limit > Mem->Limit) {
    Mem->Limit = PMem->Limit;
  }

  PMem->Base  = MAX_UINT64;
  PMem->Limit = 0;

  if (MemAbove4G->Base < 0x100000000ULL) {
    if (MemAbove4G->Base < Mem->Base) {
      Mem->Base  = MemAbove4G->Base;
    }
    if (MemAbove4G->Limit > Mem->Limit) {
      Mem->Limit = MemAbove4G->Limit;
    }
    MemAbove4G->Base  = MAX_UINT64;
    MemAbove4G->Limit = 0;
  }

  if (PMemAbove4G->Base < 0x100000000ULL) {
    if (PMemAbove4G->Base < Mem->Base) {
      Mem->Base  = PMemAbove4G->Base;
    }
    if (PMemAbove4G->Limit > Mem->Limit) {
      Mem->Limit = PMemAbove4G->Limit;
    }
    PMemAbove4G->Base  = MAX_UINT64;
    PMemAbove4G->Limit = 0;
  }

  //
  // Align IO  resource at 4K  boundary
  //
  Mask        = 0xFFFULL;
  Io->Limit   = ((Io->Limit + Mask) & ~Mask) - 1;
  if (Io->Base != MAX_UINT64) {
    Io->Base &= ~Mask;
  }

  //
  // Align MEM resource at 1MB boundary
  //
  Mask        = 0xFFFFFULL;
  Mem->Limit  = ((Mem->Limit + Mask) & ~Mask) - 1;
  if (Mem->Base != MAX_UINT64) {
    Mem->Base &= ~Mask;
  }
}

/**
  Probe a bar is existed or not.

  @param[in]    Address           PCI address for the BAR.
  @param[out]   OriginalValue     The original bar value returned.
  @param[out]   Value             The probed bar value returned.
**/
STATIC
VOID
PcatPciRootBridgeBarExisted (
  IN  UINT64                         Address,
  OUT UINT32                         *OriginalValue,
  OUT UINT32                         *Value
)
{
  UINTN   PciAddress;

  PciAddress = (UINTN)Address;

  //
  // Preserve the original value
  //
  *OriginalValue = PciRead32 (PciAddress);

  //
  // Disable timer interrupt while the BAR is probed
  //
  DisableInterrupts ();

  PciWrite32 (PciAddress, 0xFFFFFFFF);
  *Value = PciRead32 (PciAddress);
  PciWrite32 (PciAddress, *OriginalValue);

  //
  // Enable interrupt
  //
  EnableInterrupts ();
}

/**
  Parse PCI bar and collect the assigned PCI resouce information.

  @param[in]  Command          Supported attributes.

  @param[in]  Bus              PCI bus number.

  @param[in]  Device           PCI device number.

  @param[in]  Function         PCI function number.

  @param[in]  BarOffsetBase    PCI bar start offset.

  @param[in]  BarOffsetEnd     PCI bar end offset.

  @param[in]  Io               IO aperture.

  @param[in]  Mem              MMIO aperture.

  @param[in]  MemAbove4G       MMIO aperture above 4G.

  @param[in]  PMem             Prefetchable MMIO aperture.

  @param[in]  PMemAbove4G      Prefetchable MMIO aperture above 4G.
**/
STATIC
VOID
PcatPciRootBridgeParseBars (
  IN UINT16                         Command,
  IN UINTN                          Bus,
  IN UINTN                          Device,
  IN UINTN                          Function,
  IN UINTN                          BarOffsetBase,
  IN UINTN                          BarOffsetEnd,
  IN PCI_ROOT_BRIDGE_APERTURE       *Io,
  IN PCI_ROOT_BRIDGE_APERTURE       *Mem,
  IN PCI_ROOT_BRIDGE_APERTURE       *MemAbove4G,
  IN PCI_ROOT_BRIDGE_APERTURE       *PMem,
  IN PCI_ROOT_BRIDGE_APERTURE       *PMemAbove4G

)
{
  UINT32                            OriginalValue;
  UINT32                            Value;
  UINT32                            OriginalUpperValue;
  UINT32                            UpperValue;
  UINT64                            Mask;
  UINTN                             Offset;
  UINTN                             LowBit;
  UINT64                            Base;
  UINT64                            Length;
  UINT64                            Limit;
  PCI_ROOT_BRIDGE_APERTURE          *MemAperture;

  for (Offset = BarOffsetBase; Offset < BarOffsetEnd; Offset += sizeof (UINT32)) {
    PcatPciRootBridgeBarExisted (
      PCI_LIB_ADDRESS (Bus, Device, Function, Offset),
      &OriginalValue, &Value
    );
    if (Value == 0) {
      continue;
    }
    if ((Value & BIT0) == BIT0) {
      //
      // IO Bar
      //
      if (Command & EFI_PCI_COMMAND_IO_SPACE) {
        Mask = 0xfffffffc;
        Base = OriginalValue & Mask;
        Length = ((~(Value & Mask)) & Mask) + 0x04;
        if (!(Value & 0xFFFF0000)) {
          Length &= 0x0000FFFF;
        }
        Limit = Base + Length - 1;

        if ((Base > 0) && (Base < Limit)) {
          if (Io->Base > Base) {
            Io->Base = Base;
          }
          if (Io->Limit < Limit) {
            Io->Limit = Limit;
          }
        }
      }
    } else {
      //
      // Mem Bar
      //
      if (Command & EFI_PCI_COMMAND_MEMORY_SPACE) {

        Mask = 0xfffffff0;
        Base = OriginalValue & Mask;
        Length = Value & Mask;

        if ((Value & (BIT1 | BIT2)) == 0) {
          //
          // 32bit
          //
          Length = ((~Length) + 1) & 0xffffffff;

          if ((Value & BIT3) == BIT3) {
            MemAperture = PMem;
          } else {
            MemAperture = Mem;
          }
        } else {
          //
          // 64bit
          //
          Offset += 4;
          PcatPciRootBridgeBarExisted (
            PCI_LIB_ADDRESS (Bus, Device, Function, Offset),
            &OriginalUpperValue,
            &UpperValue
          );

          Base = Base | LShiftU64 ((UINT64) OriginalUpperValue, 32);
          Length = Length | LShiftU64 ((UINT64) UpperValue, 32);
          if (Length != 0) {
            LowBit = LowBitSet64 (Length);
            Length = LShiftU64 (1ULL, LowBit);
          }

          if ((Value & BIT3) == BIT3) {
            MemAperture = PMemAbove4G;
          } else {
            MemAperture = MemAbove4G;
          }
        }

        Limit = Base + Length - 1;
        if ((Base > 0) && (Base < Limit)) {
          if (MemAperture->Base > Base) {
            MemAperture->Base = Base;
          }
          if (MemAperture->Limit < Limit) {
            MemAperture->Limit = Limit;
          }
        }
      }
    }
  }
}

/**
  Scan for all root bridges in platform.

  @param[out] NumberOfRootBridges  Number of root bridges detected

  @retval     Pointer to the allocated PCI_ROOT_BRIDGE structure array.
**/
PCI_ROOT_BRIDGE *
ScanForRootBridges (
  OUT UINTN      *NumberOfRootBridges
)
{
  UINTN      PrimaryBus;
  UINTN      SubBus;
  UINT8      Device;
  UINT8      Function;
  UINTN      NumberOfDevices;
  UINTN      Address;
  PCI_TYPE01 Pci;
  UINT64     Attributes;
  UINT64     Base;
  UINT64     Limit;
  UINT64     Value;
  PCI_ROOT_BRIDGE_APERTURE Io, Mem, MemAbove4G, PMem, PMemAbove4G, *MemAperture;
  PCI_ROOT_BRIDGE *RootBridges;
  UINTN      BarOffsetEnd;


  *NumberOfRootBridges = 0;
  RootBridges = NULL;

  //
  // After scanning all the PCI devices on the PCI root bridge's primary bus,
  // update the Primary Bus Number for the next PCI root bridge to be this PCI
  // root bridge's subordinate bus number + 1.
  //
  for (PrimaryBus = 0; PrimaryBus <= PCI_MAX_BUS; PrimaryBus = SubBus + 1) {
    SubBus = PrimaryBus;
    Attributes = 0;
    Io.Base = Mem.Base = MemAbove4G.Base = PMem.Base = PMemAbove4G.Base = MAX_UINT64;
    Io.Limit = Mem.Limit = MemAbove4G.Limit = PMem.Limit = PMemAbove4G.Limit = 0;
    //
    // Scan all the PCI devices on the primary bus of the PCI root bridge
    //
    for (Device = 0, NumberOfDevices = 0; Device <= PCI_MAX_DEVICE; Device++) {

      for (Function = 0; Function <= PCI_MAX_FUNC; Function++) {

        //
        // Compute the PCI configuration address of the PCI device to probe
        //
        Address = PCI_LIB_ADDRESS (PrimaryBus, Device, Function, 0);

        //
        // Read the Vendor ID from the PCI Configuration Header
        //
        if (PciRead16 (Address) == MAX_UINT16) {
          if (Function == 0) {
            //
            // If the PCI Configuration Read fails, or a PCI device does not
            // exist, then skip this entire PCI device
            //
            break;
          } else {
            //
            // If PCI function != 0, VendorId == 0xFFFF, we continue to search
            // PCI function.
            //
            continue;
          }
        }

        //
        // Read the entire PCI Configuration Header
        //
        PciReadBuffer (Address, sizeof (Pci), &Pci);

        //
        // Increment the number of PCI device found on the primary bus of the
        // PCI root bridge
        //
        NumberOfDevices++;

        //
        // Look for devices with the VGA Palette Snoop enabled in the COMMAND
        // register of the PCI Config Header
        //
        if ((Pci.Hdr.Command & EFI_PCI_COMMAND_VGA_PALETTE_SNOOP) != 0) {
          Attributes |= EFI_PCI_ATTRIBUTE_VGA_PALETTE_IO;
          Attributes |= EFI_PCI_ATTRIBUTE_VGA_PALETTE_IO_16;
        }

        BarOffsetEnd = 0;

        //
        // PCI-PCI Bridge
        //
        if (IS_PCI_BRIDGE (&Pci)) {
          //
          // Get the Bus range that the PPB is decoding
          //
          if (Pci.Bridge.SubordinateBus > SubBus) {
            //
            // If the suborinate bus number of the PCI-PCI bridge is greater
            // than the PCI root bridge's current subordinate bus number,
            // then update the PCI root bridge's subordinate bus number
            //
            SubBus = Pci.Bridge.SubordinateBus;
          }

          //
          // Get the I/O range that the PPB is decoding
          //
          Value = Pci.Bridge.IoBase & 0x0f;
          Base = ((UINT32) Pci.Bridge.IoBase & 0xf0) << 8;
          Limit = (((UINT32) Pci.Bridge.IoLimit & 0xf0) << 8) | 0x0fff;
          if (Value == BIT0) {
            Base |= ((UINT32) Pci.Bridge.IoBaseUpper16 << 16);
            Limit |= ((UINT32) Pci.Bridge.IoLimitUpper16 << 16);
          }
          if ((Base > 0) && (Base < Limit)) {
            if (Io.Base > Base) {
              Io.Base = Base;
            }
            if (Io.Limit < Limit) {
              Io.Limit = Limit;
            }
          }

          //
          // Get the Memory range that the PPB is decoding
          //
          Base = ((UINT32) Pci.Bridge.MemoryBase & 0xfff0) << 16;
          Limit = (((UINT32) Pci.Bridge.MemoryLimit & 0xfff0) << 16) | 0xfffff;
          if ((Base > 0) && (Base < Limit)) {
            if (Mem.Base > Base) {
              Mem.Base = Base;
            }
            if (Mem.Limit < Limit) {
              Mem.Limit = Limit;
            }
          }

          //
          // Get the Prefetchable Memory range that the PPB is decoding
          //
          Value = Pci.Bridge.PrefetchableMemoryBase & 0x0f;
          Base = ((UINT32) Pci.Bridge.PrefetchableMemoryBase & 0xfff0) << 16;
          Limit = (((UINT32) Pci.Bridge.PrefetchableMemoryLimit & 0xfff0)
                   << 16) | 0xfffff;
          MemAperture = &PMem;
          if (Value == BIT0) {
            Base |= LShiftU64 (Pci.Bridge.PrefetchableBaseUpper32, 32);
            Limit |= LShiftU64 (Pci.Bridge.PrefetchableLimitUpper32, 32);
            MemAperture = &PMemAbove4G;
          }
          if ((Base > 0) && (Base < Limit)) {
            if (MemAperture->Base > Base) {
              MemAperture->Base = Base;
            }
            if (MemAperture->Limit < Limit) {
              MemAperture->Limit = Limit;
            }
          }

          //
          // Look at the PPB Configuration for legacy decoding attributes
          //
          if ((Pci.Bridge.BridgeControl & EFI_PCI_BRIDGE_CONTROL_ISA)
              == EFI_PCI_BRIDGE_CONTROL_ISA) {
            Attributes |= EFI_PCI_ATTRIBUTE_ISA_IO;
            Attributes |= EFI_PCI_ATTRIBUTE_ISA_IO_16;
            Attributes |= EFI_PCI_ATTRIBUTE_ISA_MOTHERBOARD_IO;
          }
          if ((Pci.Bridge.BridgeControl & EFI_PCI_BRIDGE_CONTROL_VGA)
              == EFI_PCI_BRIDGE_CONTROL_VGA) {
            Attributes |= EFI_PCI_ATTRIBUTE_VGA_PALETTE_IO;
            Attributes |= EFI_PCI_ATTRIBUTE_VGA_MEMORY;
            Attributes |= EFI_PCI_ATTRIBUTE_VGA_IO;
            if ((Pci.Bridge.BridgeControl & EFI_PCI_BRIDGE_CONTROL_VGA_16)
                != 0) {
              Attributes |= EFI_PCI_ATTRIBUTE_VGA_PALETTE_IO_16;
              Attributes |= EFI_PCI_ATTRIBUTE_VGA_IO_16;
            }
          }

          BarOffsetEnd = OFFSET_OF (PCI_TYPE01, Bridge.Bar[2]);
        } else {
          //
          // Parse the BARs of the PCI device to get what I/O Ranges, Memory
          // Ranges, and Prefetchable Memory Ranges the device is decoding
          //
          if ((Pci.Hdr.HeaderType & HEADER_LAYOUT_CODE) == HEADER_TYPE_DEVICE) {
            BarOffsetEnd = OFFSET_OF (PCI_TYPE00, Device.Bar[6]);
          }
        }

        PcatPciRootBridgeParseBars (
          Pci.Hdr.Command,
          PrimaryBus,
          Device,
          Function,
          OFFSET_OF (PCI_TYPE00, Device.Bar),
          BarOffsetEnd,
          &Io,
          &Mem, &MemAbove4G,
          &PMem, &PMemAbove4G
        );

        //
        // See if the PCI device is an IDE controller
        //
        if (IS_CLASS2 (&Pci, PCI_CLASS_MASS_STORAGE,
                       PCI_CLASS_MASS_STORAGE_IDE)) {
          if (Pci.Hdr.ClassCode[0] & 0x80) {
            Attributes |= EFI_PCI_ATTRIBUTE_IDE_PRIMARY_IO;
            Attributes |= EFI_PCI_ATTRIBUTE_IDE_SECONDARY_IO;
          }
          if (Pci.Hdr.ClassCode[0] & 0x01) {
            Attributes |= EFI_PCI_ATTRIBUTE_IDE_PRIMARY_IO;
          }
          if (Pci.Hdr.ClassCode[0] & 0x04) {
            Attributes |= EFI_PCI_ATTRIBUTE_IDE_SECONDARY_IO;
          }
        }

        //
        // See if the PCI device is a legacy VGA controller or
        // a standard VGA controller
        //
        if (IS_CLASS2 (&Pci, PCI_CLASS_OLD, PCI_CLASS_OLD_VGA) ||
            IS_CLASS2 (&Pci, PCI_CLASS_DISPLAY, PCI_CLASS_DISPLAY_VGA)
           ) {
          Attributes |= EFI_PCI_ATTRIBUTE_VGA_PALETTE_IO;
          Attributes |= EFI_PCI_ATTRIBUTE_VGA_PALETTE_IO_16;
          Attributes |= EFI_PCI_ATTRIBUTE_VGA_MEMORY;
          Attributes |= EFI_PCI_ATTRIBUTE_VGA_IO;
          Attributes |= EFI_PCI_ATTRIBUTE_VGA_IO_16;
        }

        //
        // See if the PCI Device is a PCI - ISA or PCI - EISA
        // or ISA_POSITIVIE_DECODE Bridge device
        //
        if (Pci.Hdr.ClassCode[2] == PCI_CLASS_BRIDGE) {
          if (Pci.Hdr.ClassCode[1] == PCI_CLASS_BRIDGE_ISA ||
              Pci.Hdr.ClassCode[1] == PCI_CLASS_BRIDGE_EISA ||
              Pci.Hdr.ClassCode[1] == PCI_CLASS_BRIDGE_ISA_PDECODE) {
            Attributes |= EFI_PCI_ATTRIBUTE_ISA_IO;
            Attributes |= EFI_PCI_ATTRIBUTE_ISA_IO_16;
            Attributes |= EFI_PCI_ATTRIBUTE_ISA_MOTHERBOARD_IO;
          }
        }

        //
        // If this device is not a multi function device, then skip the rest
        // of this PCI device
        //
        if (Function == 0 && !IS_PCI_MULTI_FUNC (&Pci)) {
          break;
        }
      }
    }

    //
    // If at least one PCI device was found on the primary bus of this PCI
    // root bridge, then the PCI root bridge exists.
    //
    if (NumberOfDevices > 0) {
      RootBridges = ReallocatePool (
                      (*NumberOfRootBridges) * sizeof (PCI_ROOT_BRIDGE),
                      (*NumberOfRootBridges + 1) * sizeof (PCI_ROOT_BRIDGE),
                      RootBridges
                    );
      ASSERT (RootBridges != NULL);

      AdjustRootBridgeResource (&Io, &Mem, &MemAbove4G, &PMem, &PMemAbove4G);

      InitRootBridge (
        Attributes, Attributes, 0,
        (UINT8) PrimaryBus, (UINT8) SubBus,
        &Io, &Mem, &MemAbove4G, &PMem, &PMemAbove4G,
        &RootBridges[*NumberOfRootBridges]
      );
      RootBridges[*NumberOfRootBridges].ResourceAssigned = TRUE;
      //
      // Increment the index for the next PCI Root Bridge
      //
      (*NumberOfRootBridges)++;
    }
  }

  return RootBridges;
}
