/** @file

Copyright (c) 2017, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <Uefi.h>
#include <PiDxe.h>
#include <Library/TestPointCheckLib.h>
#include <Library/TestPointLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/PciIo.h>
#include <Protocol/PciRootBridgeIo.h>
#include <Library/PciSegmentLib.h>
#include <Library/PciSegmentInfoLib.h>
#include <IndustryStandard/Pci.h>

#pragma pack(1)

//
// Data region after PCI configuration header(for cardbus bridge)
//
typedef struct {
  UINT16  SubVendorId;  // Subsystem Vendor ID
  UINT16  SubSystemId;  // Subsystem ID
  UINT32  LegacyBase;   // Optional 16-Bit PC Card Legacy
  // Mode Base Address
  //
  UINT32  Data[46];
} PCI_CARDBUS_DATA;

typedef union {
  PCI_DEVICE_HEADER_TYPE_REGION Device;
  PCI_BRIDGE_CONTROL_REGISTER   Bridge;
  PCI_CARDBUS_CONTROL_REGISTER  CardBus;
} NON_COMMON_UNION;

typedef struct {
  PCI_DEVICE_INDEPENDENT_REGION Common;
  NON_COMMON_UNION              NonCommon;
  UINT32                        Data[48];
} PCI_CONFIG_SPACE;

#pragma pack()

VOID
DumpPciDevice (
  IN UINT8                             Bus,
  IN UINT8                             Device,
  IN UINT8                             Function,
  IN PCI_TYPE00                        *PciData
  )
{
//DEBUG ((DEBUG_INFO, "  00/00/00 : [0000][0000] [00|00|00] 00000000 00000000 00000000 00000000 00000000 00000000 0000\n"));
  DEBUG ((DEBUG_INFO, "  %02x/%02x/%02x :",
    Bus,
    Device,
    Function
    ));
  DEBUG ((DEBUG_INFO, " [%04x][%04x]",
    PciData->Hdr.VendorId,
    PciData->Hdr.DeviceId
    ));
  DEBUG ((DEBUG_INFO, " [%02x|%02x|%02x]",
    PciData->Hdr.ClassCode[2],
    PciData->Hdr.ClassCode[1],
    PciData->Hdr.ClassCode[0]
    ));
  DEBUG ((DEBUG_INFO, " %08x %08x %08x %08x %08x %08x",
    PciData->Device.Bar[0],
    PciData->Device.Bar[1],
    PciData->Device.Bar[2],
    PciData->Device.Bar[3],
    PciData->Device.Bar[4],
    PciData->Device.Bar[5]
    ));
  DEBUG ((DEBUG_INFO, " %04x\n",
    PciData->Hdr.Command
    ));
}

VOID
DumpPciBridge (
  IN UINT8                             Bus,
  IN UINT8                             Device,
  IN UINT8                             Function,
  IN PCI_TYPE01                        *PciData
  )
{
//DEBUG ((DEBUG_INFO, "  00/00/00*: [0000][0000] [00|00|00] 00000000 00000000 [00|00|00] [00:00] [0000:0000] [0000:0000] [00000000:00000000] [0000:0000] 0000   0000\n"));
  DEBUG ((DEBUG_INFO, "  %02x/%02x/%02x*:",
    Bus,
    Device,
    Function
    ));
  DEBUG ((DEBUG_INFO, " [%04x][%04x]",
    PciData->Hdr.VendorId,
    PciData->Hdr.DeviceId
    ));
  DEBUG ((DEBUG_INFO, " [%02x|%02x|%02x]",
    PciData->Hdr.ClassCode[2],
    PciData->Hdr.ClassCode[1],
    PciData->Hdr.ClassCode[0]
    ));
  DEBUG ((DEBUG_INFO, " %08x %08x",
    PciData->Bridge.Bar[0],
    PciData->Bridge.Bar[1]
    ));
  DEBUG ((DEBUG_INFO, " [%02x|%02x|%02x]",
    PciData->Bridge.PrimaryBus,
    PciData->Bridge.SecondaryBus,
    PciData->Bridge.SubordinateBus
    ));
  DEBUG ((DEBUG_INFO, " [%02x:%02x] [%04x:%04x] [%04x:%04x]",
    PciData->Bridge.IoBase,
    PciData->Bridge.IoLimit,
    PciData->Bridge.MemoryBase,
    PciData->Bridge.MemoryLimit,
    PciData->Bridge.PrefetchableMemoryBase,
    PciData->Bridge.PrefetchableMemoryLimit
    ));
  DEBUG ((DEBUG_INFO, " [%08x:%08x] [%04x:%04x]",
    PciData->Bridge.PrefetchableBaseUpper32,
    PciData->Bridge.PrefetchableLimitUpper32,
    PciData->Bridge.IoBaseUpper16,
    PciData->Bridge.IoLimitUpper16
    ));
  DEBUG ((DEBUG_INFO, " %04x  ",
    PciData->Bridge.BridgeControl
    ));
  DEBUG ((DEBUG_INFO, " %04x\n",
    PciData->Hdr.Command
    ));
}

/**
  This function gets the protocol interface from the given handle, and
  obtains its address space descriptors.

  @param[in] Handle          The PCI_ROOT_BRIDIGE_IO_PROTOCOL handle.
  @param[out] IoDev          Handle used to access configuration space of PCI device.
  @param[out] Descriptors    Points to the address space descriptors.

  @retval EFI_SUCCESS     The command completed successfully
**/
EFI_STATUS
PciGetProtocolAndResource (
  IN  EFI_HANDLE                            Handle,
  OUT EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL       **IoDev,
  OUT EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR     **Descriptors
  )
{
  EFI_STATUS  Status;

  //
  // Get inferface from protocol
  //
  Status = gBS->HandleProtocol (
                Handle,
                &gEfiPciRootBridgeIoProtocolGuid,
                (VOID**)IoDev
               );

  if (EFI_ERROR (Status)) {
    return Status;
  }
  //
  // Call Configuration() to get address space descriptors
  //
  Status = (*IoDev)->Configuration (*IoDev, (VOID**)Descriptors);
  if (Status == EFI_UNSUPPORTED) {
    *Descriptors = NULL;
    return EFI_SUCCESS;

  } else {
    return Status;
  }
}

/**
  This function get the next bus range of given address space descriptors.
  It also moves the pointer backward a node, to get prepared to be called
  again.

  @param[in, out] Descriptors Points to current position of a serial of address space
                              descriptors.
  @param[out] MinBus          The lower range of bus number.
  @param[out] MaxBus          The upper range of bus number.
  @param[out] IsEnd           Meet end of the serial of descriptors.

  @retval EFI_SUCCESS     The command completed successfully.
**/
EFI_STATUS
PciGetNextBusRange (
  IN OUT EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR  **Descriptors,
  OUT    UINT16                             *MinBus,
  OUT    UINT16                             *MaxBus,
  OUT    BOOLEAN                            *IsEnd
  )
{
  *IsEnd = FALSE;

  //
  // When *Descriptors is NULL, Configuration() is not implemented, so assume
  // range is 0~PCI_MAX_BUS
  //
  if ((*Descriptors) == NULL) {
    *MinBus = 0;
    *MaxBus = PCI_MAX_BUS;
    return EFI_SUCCESS;
  }
  //
  // *Descriptors points to one or more address space descriptors, which
  // ends with a end tagged descriptor. Examine each of the descriptors,
  // if a bus typed one is found and its bus range covers bus, this handle
  // is the handle we are looking for.
  //

  while ((*Descriptors)->Desc != ACPI_END_TAG_DESCRIPTOR) {
    if ((*Descriptors)->ResType == ACPI_ADDRESS_SPACE_TYPE_BUS) {
      *MinBus = (UINT16) (*Descriptors)->AddrRangeMin;
      *MaxBus = (UINT16) (*Descriptors)->AddrRangeMax;
      (*Descriptors)++;
      return (EFI_SUCCESS);
    }

    (*Descriptors)++;
  }

  if ((*Descriptors)->Desc == ACPI_END_TAG_DESCRIPTOR) {
    *IsEnd = TRUE;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
TestPointCheckPciResource (
  VOID
  )
{
  UINT16                            Bus;
  UINT16                            Device;
  UINT16                            Func;
  UINT64                            Address;
  EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL   *IoDev;
  EFI_STATUS                        Status;
  PCI_TYPE00                        PciData;
  UINTN                             Index;
  EFI_HANDLE                        *HandleBuf;
  UINTN                             HandleCount;
  EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *Descriptors;
  UINT16                            MinBus;
  UINT16                            MaxBus;
  BOOLEAN                           IsEnd;

  DEBUG ((DEBUG_INFO, "==== TestPointCheckPciResource - Enter\n"));
  HandleBuf = NULL;
  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  &gEfiPciRootBridgeIoProtocolGuid,
                  NULL,
                  &HandleCount,
                  &HandleBuf
                  );
  if (EFI_ERROR (Status)) {
    goto Done ;
  }

  DEBUG ((DEBUG_INFO, "  B  D  F*    VID   DID   Class[CSP]   Bar0     Bar1    Bus[PSS]   Io[BL]  Memory[BL]"));
  DEBUG ((DEBUG_INFO, " PMemory[BL]    PMemoryU[BL]       IoU[BL]   BriCtl Command\n"));

  DEBUG ((DEBUG_INFO, "  B  D  F     VID   DID   Class[CSP]   Bar0     Bar1     Bar2     Bar3     Bar4     Bar5   Command\n"));

  for (Index = 0; Index < HandleCount; Index++) {
    Status = PciGetProtocolAndResource (
               HandleBuf[Index],
               &IoDev,
               &Descriptors
               );
    while (TRUE) {
      Status = PciGetNextBusRange (&Descriptors, &MinBus, &MaxBus, &IsEnd);
      if (EFI_ERROR (Status)) {
        goto Done;
      }

      if (IsEnd) {
        break;
      }

      for (Bus = MinBus; Bus <= MaxBus; Bus++) {
        //
        // For each devices, enumerate all functions it contains
        //
        for (Device = 0; Device <= PCI_MAX_DEVICE; Device++) {
          //
          // For each function, read its configuration space and print summary
          //
          for (Func = 0; Func <= PCI_MAX_FUNC; Func++) {
            Address = EFI_PCI_ADDRESS (Bus, Device, Func, 0);
            IoDev->Pci.Read (
                         IoDev,
                         EfiPciWidthUint16,
                         Address,
                         1,
                         &PciData.Hdr.VendorId
                         );

            //
            // If VendorId = 0xffff, there does not exist a device at this
            // location. For each device, if there is any function on it,
            // there must be 1 function at Function 0. So if Func = 0, there
            // will be no more functions in the same device, so we can break
            // loop to deal with the next device.
            //
            if (PciData.Hdr.VendorId == 0xffff && Func == 0) {
              break;
            }

            if (PciData.Hdr.VendorId != 0xffff) {
              IoDev->Pci.Read (
                           IoDev,
                           EfiPciWidthUint32,
                           Address,
                           sizeof (PciData) / sizeof (UINT32),
                           &PciData
                           );

              if (IS_PCI_BRIDGE(&PciData)) {
                // Bridge
                DumpPciBridge ((UINT8)Bus, (UINT8)Device, (UINT8)Func, (PCI_TYPE01 *)&PciData);
              } else if (IS_CARDBUS_BRIDGE(&PciData)) {
                // CardBus Bridge
              } else {
                // Device
                DumpPciDevice ((UINT8)Bus, (UINT8)Device, (UINT8)Func, &PciData);
              }

              //
              // If this is not a multi-function device, we can leave the loop
              // to deal with the next device.
              //
              if (Func == 0 && ((PciData.Hdr.HeaderType & HEADER_TYPE_MULTI_FUNCTION) == 0x00)) {
                break;
              }
            }
          }
        }
      }
      //
      // If Descriptor is NULL, Configuration() returns EFI_UNSUPPRORED,
      // we assume the bus range is 0~PCI_MAX_BUS. After enumerated all
      // devices on all bus, we can leave loop.
      //
      if (Descriptors == NULL) {
        break;
      }
    }
  }

Done:
  if (HandleBuf != NULL) {
    FreePool (HandleBuf);
  }

  DEBUG ((DEBUG_INFO, "==== TestPointCheckPciResource - Exit\n"));

  if (EFI_ERROR(Status)) {
    TestPointLibAppendErrorString (
      PLATFORM_TEST_POINT_ROLE_PLATFORM_IBV,
      NULL,
      TEST_POINT_BYTE3_PCI_ENUMERATION_DONE_RESOURCE_ALLOCATED_ERROR_CODE \
        TEST_POINT_PCI_ENUMERATION_DONE \
        TEST_POINT_BYTE3_PCI_ENUMERATION_DONE_RESOURCE_ALLOCATED_ERROR_STRING
      );
  }

  return Status;
}

EFI_STATUS
TestPointCheckPciBusMaster (
  VOID
  )
{
  UINTN             Segment;
  UINTN             SegmentCount;
  UINTN             Bus;
  UINTN             Device;
  UINTN             Function;
  UINT16            VendorId;
  UINT16            Command;
  UINT8             HeaderType;
  EFI_STATUS        Status;
  PCI_SEGMENT_INFO  *PciSegmentInfo;

  PciSegmentInfo = GetPciSegmentInfo (&SegmentCount);
  if (PciSegmentInfo == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Status = EFI_SUCCESS;
  for (Segment = 0; Segment < SegmentCount; Segment++) {
    for (Bus = PciSegmentInfo[Segment].StartBusNumber; Bus <= PciSegmentInfo[Segment].EndBusNumber; Bus++) {
      for (Device = 0; Device <= 0x1F; Device++) {
        for (Function = 0; Function <= 0x7; Function++) {
          VendorId = PciSegmentRead16 (PCI_SEGMENT_LIB_ADDRESS(PciSegmentInfo[Segment].SegmentNumber, Bus, Device, Function, PCI_VENDOR_ID_OFFSET));
          //
          // If VendorId = 0xffff, there does not exist a device at this
          // location. For each device, if there is any function on it,
          // there must be 1 function at Function 0. So if Func = 0, there
          // will be no more functions in the same device, so we can break
          // loop to deal with the next device.
          //
          if (VendorId == 0xffff && Function == 0) {
            break;
          }

          if (VendorId != 0xffff) {
            Command = PciSegmentRead16 (PCI_SEGMENT_LIB_ADDRESS(Segment, Bus, Device, Function, PCI_COMMAND_OFFSET));
            if ((Command & EFI_PCI_COMMAND_BUS_MASTER) != 0) {
              DEBUG ((DEBUG_INFO, "PCI BME enabled (S%04x.B%02x.D%02x.F%x - %04x)\n", Segment, Bus, Device, Function, Command));
              TestPointLibAppendErrorString (
                PLATFORM_TEST_POINT_ROLE_PLATFORM_IBV,
                NULL,
                TEST_POINT_BYTE3_PCI_ENUMERATION_DONE_BUS_MASTER_DISABLED_ERROR_CODE \
                  TEST_POINT_PCI_ENUMERATION_DONE \
                  TEST_POINT_BYTE3_PCI_ENUMERATION_DONE_BUS_MASTER_DISABLED_ERROR_STRING
                );
              Status = EFI_INVALID_PARAMETER;
            }

            //
            // If this is not a multi-function device, we can leave the loop
            // to deal with the next device.
            //
            HeaderType = PciSegmentRead8 (PCI_SEGMENT_LIB_ADDRESS(Segment, Bus, Device, Function, PCI_HEADER_TYPE_OFFSET));
            if (Function == 0 && ((HeaderType & HEADER_TYPE_MULTI_FUNCTION) == 0x00)) {
              break;
            }
          }
        }
      }
    }
  }

  return Status;
}
