/** @file
Module produces Device I/O on top of PCI Root Bridge I/O for Segment 0 only.
This is a valid assumption because many of the EFI 1.02/EFI 1.10 systems that may have provided 
Device I/O were single segment platforms.  The goal of the ECP is to provide compatibility with the 
drivers/apps that may have used Device I/O.

Device I/O is on list of deprecated protocols for UEFI 2.0 and later.
This module module layers Device I/O on top of PCI Root Bridge I/O (Segment 0)
 Use if:
   There are no R8.x modules present that produces Device I/O
   EFI drivers included that consume Device I/O
   Platform required to support EFI drivers that consume Device I/O
   Platform required to support EFI applications that consume Device I/O

Copyright (c) 2008 Intel Corporation. <BR>
All rights reserved. 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/Pci22.h>
#include <Protocol/DeviceIo.h>
#include <Protocol/PciRootBridgeIo.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/UefiLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/DevicePathLib.h>


/**
  Perform reading memory mapped I/O space of device.

  @param  This                   A pointer to EFI_DEVICE_IO protocol instance.
  @param  Width                  Width of I/O operations.
  @param  Address                The base address of I/O operations.
  @param  Count                  The number of I/O operations to perform.  Bytes
                                 moves is Width size * Count, starting at Address.
  @param  Buffer                 The destination buffer to store results.

  @retval EFI_SUCCESS            The data was read from the device.
  @retval EFI_INVALID_PARAMETER  Width is invalid.
  @retval EFI_OUT_OF_RESOURCES   The request could not be completed due to lack of
                                 resources.

**/
EFI_STATUS
EFIAPI
DeviceIoMemRead (
  IN     EFI_DEVICE_IO_PROTOCOL   *This,
  IN     EFI_IO_WIDTH             Width,
  IN     UINT64                   Address,
  IN     UINTN                    Count,
  IN OUT VOID                     *Buffer
  );


/**
  Perform writing memory mapped I/O space of device.

  @param  This                   A pointer to EFI_DEVICE_IO protocol instance.
  @param  Width                  Width of I/O operations.
  @param  Address                The base address of I/O operations.
  @param  Count                  The number of I/O operations to perform.  Bytes
                                 moves is Width size * Count, starting at Address.
  @param  Buffer                 The source buffer of data to be written.

  @retval EFI_SUCCESS            The data was written to the device.
  @retval EFI_INVALID_PARAMETER  Width is invalid.
  @retval EFI_OUT_OF_RESOURCES   The request could not be completed due to lack of
                                 resources.

**/
EFI_STATUS
EFIAPI
DeviceIoMemWrite (
  IN     EFI_DEVICE_IO_PROTOCOL    *This,
  IN     EFI_IO_WIDTH              Width,
  IN     UINT64                    Address,
  IN     UINTN                     Count,
  IN OUT VOID                      *Buffer
  );

/**
  Perform reading I/O space of device.

  @param  This                   A pointer to EFI_DEVICE_IO protocol instance.
  @param  Width                  Width of I/O operations.
  @param  Address                The base address of I/O operations.
  @param  Count                  The number of I/O operations to perform.  Bytes
                                 moves is Width size * Count, starting at Address.
  @param  Buffer                 The destination buffer to store results.

  @retval EFI_SUCCESS            The data was read from the device.
  @retval EFI_INVALID_PARAMETER  Width is invalid.
  @retval EFI_OUT_OF_RESOURCES   The request could not be completed due to lack of
                                 resources.

**/
EFI_STATUS
EFIAPI
DeviceIoIoRead (
  IN     EFI_DEVICE_IO_PROTOCOL   *This,
  IN     EFI_IO_WIDTH             Width,
  IN     UINT64                   Address,
  IN     UINTN                    Count,
  IN OUT VOID                     *Buffer
  );

/**
  Perform writing I/O space of device.

  @param  This                   A pointer to EFI_DEVICE_IO protocol instance.
  @param  Width                  Width of I/O operations.
  @param  Address                The base address of I/O operations.
  @param  Count                  The number of I/O operations to perform.  Bytes
                                 moves is Width size * Count, starting at Address.
  @param  Buffer                 The source buffer of data to be written.

  @retval EFI_SUCCESS            The data was written to the device.
  @retval EFI_INVALID_PARAMETER  Width is invalid.
  @retval EFI_OUT_OF_RESOURCES   The request could not be completed due to lack of
                                 resources.

**/
EFI_STATUS
EFIAPI
DeviceIoIoWrite (
  IN     EFI_DEVICE_IO_PROTOCOL    *This,
  IN     EFI_IO_WIDTH              Width,
  IN     UINT64                    Address,
  IN     UINTN                     Count,
  IN OUT VOID                      *Buffer
  );

/**
  Perform reading PCI configuration space of device

  @param  This                   A pointer to EFI_DEVICE_IO protocol instance.
  @param  Width                  Width of I/O operations.
  @param  Address                The base address of I/O operations.
  @param  Count                  The number of I/O operations to perform.  Bytes
                                 moves is Width size * Count, starting at Address.
  @param  Buffer                 The destination buffer to store results.

  @retval EFI_SUCCESS            The data was read from the device.
  @retval EFI_INVALID_PARAMETER  Width is invalid.
  @retval EFI_OUT_OF_RESOURCES   The request could not be completed due to lack of
                                 resources.

**/
EFI_STATUS
EFIAPI
DeviceIoPciRead (
  IN     EFI_DEVICE_IO_PROTOCOL   *This,
  IN     EFI_IO_WIDTH             Width,
  IN     UINT64                   Address,
  IN     UINTN                    Count,
  IN OUT VOID                     *Buffer
  );

/**
  Perform writing PCI configuration space of device.

  @param  This                   A pointer to EFI_DEVICE_IO protocol instance.
  @param  Width                  Width of I/O operations.
  @param  Address                The base address of I/O operations.
  @param  Count                  The number of I/O operations to perform.  Bytes
                                 moves is Width size * Count, starting at Address.
  @param  Buffer                 The source buffer of data to be written.

  @retval EFI_SUCCESS            The data was written to the device.
  @retval EFI_INVALID_PARAMETER  Width is invalid.
  @retval EFI_OUT_OF_RESOURCES   The request could not be completed due to lack of
                                 resources.

**/
EFI_STATUS
EFIAPI
DeviceIoPciWrite (
  IN     EFI_DEVICE_IO_PROTOCOL    *This,
  IN     EFI_IO_WIDTH              Width,
  IN     UINT64                    Address,
  IN     UINTN                     Count,
  IN OUT VOID                      *Buffer
  );

/**
  Provides an EFI Device Path for a PCI device with the given PCI configuration space address.

  @param  This                   A pointer to the EFI_DEVICE_IO_INTERFACE instance.
  @param  Address                The PCI configuration space address of the device
                                 whose Device Path is going to be returned.
  @param  PciDevicePath          A pointer to the pointer for the EFI Device Path
                                 for PciAddress. Memory for the Device Path is
                                 allocated from the pool.

  @retval EFI_SUCCESS            The PciDevicePath returns a pointer to a valid EFI
                                 Device Path.
  @retval EFI_UNSUPPORTED        The PciAddress does not map to a valid EFI Device
                                 Path.
  @retval EFI_OUT_OF_RESOURCES   The request could not be completed due to a lack
                                 of resources.

**/
EFI_STATUS
EFIAPI
DeviceIoPciDevicePath (
  IN     EFI_DEVICE_IO_PROTOCOL        *This,
  IN     UINT64                        Address,
  IN OUT EFI_DEVICE_PATH_PROTOCOL      **PciDevicePath
  );

/**
  Provides the device-specific addresses needed to access system memory.

  @param  This                   A pointer to the EFI_DEVICE_IO_INTERFACE instance.
  @param  Operation              Indicates if the bus master is going to read or
                                 write to system memory.
  @param  HostAddress            The system memory address to map to the device.
  @param  NumberOfBytes          On input the number of bytes to map. On output the
                                 number of bytes that were mapped.
  @param  DeviceAddress          The resulting map address for the bus master
                                 device to use to access the hosts HostAddress.
  @param  Mapping                A resulting value to pass to Unmap().

  @retval EFI_SUCCESS            The range was mapped for the returned
                                 NumberOfBytes.
  @retval EFI_INVALID_PARAMETER  The Operation or HostAddress is undefined.
  @retval EFI_UNSUPPORTED        The HostAddress cannot be mapped as a common
                                 buffer.
  @retval EFI_DEVICE_ERROR       The system hardware could not map the requested
                                 address.
  @retval EFI_OUT_OF_RESOURCES   The request could not be completed due to a lack
                                 of resources.

**/
EFI_STATUS
EFIAPI
DeviceIoMap (
  IN     EFI_DEVICE_IO_PROTOCOL   *This,
  IN     EFI_IO_OPERATION_TYPE    Operation,
  IN     EFI_PHYSICAL_ADDRESS     *HostAddress,
  IN OUT UINTN                    *NumberOfBytes,
  OUT    EFI_PHYSICAL_ADDRESS     *DeviceAddress,
  OUT    VOID                     **Mapping
  );

/**
  Completes the Map() operation and releases any corresponding resources.

  @param  This                   A pointer to the EFI_DEVICE_IO_INTERFACE instance.
  @param  Mapping                The mapping value returned from Map().

  @retval EFI_SUCCESS            The range was unmapped.
  @retval EFI_DEVICE_ERROR       The data was not committed to the target system
                                 memory.

**/
EFI_STATUS
EFIAPI
DeviceIoUnmap (
  IN EFI_DEVICE_IO_PROTOCOL   *This,
  IN VOID                     *Mapping
  );

/**
  Allocates pages that are suitable for an EFIBusMasterCommonBuffer mapping.

  @param  This                   A pointer to the EFI_DEVICE_IO_INTERFACE instance.
  @param  Type                   The type allocation to perform.
  @param  MemoryType             The type of memory to allocate,
                                 EfiBootServicesData or EfiRuntimeServicesData.
  @param  Pages                  The number of pages to allocate.
  @param  PhysicalAddress        A pointer to store the base address of the
                                 allocated range.

  @retval EFI_SUCCESS            The requested memory pages were allocated.
  @retval EFI_OUT_OF_RESOURCES   The memory pages could not be allocated.
  @retval EFI_INVALID_PARAMETER  The requested memory type is invalid.
  @retval EFI_UNSUPPORTED        The requested PhysicalAddress is not supported on
                                 this platform.

**/
EFI_STATUS
EFIAPI
DeviceIoAllocateBuffer (
  IN     EFI_DEVICE_IO_PROTOCOL    *This,
  IN     EFI_ALLOCATE_TYPE         Type,
  IN     EFI_MEMORY_TYPE           MemoryType,
  IN     UINTN                     Pages,
  IN OUT EFI_PHYSICAL_ADDRESS      *PhysicalAddress
  );

/**
  Flushes any posted write data to the device.

  @param  This                   A pointer to the EFI_DEVICE_IO_INTERFACE instance.

  @retval EFI_SUCCESS            The buffers were flushed.
  @retval EFI_DEVICE_ERROR       The buffers were not flushed due to a hardware
                                 error.

**/
EFI_STATUS
EFIAPI
DeviceIoFlush (
  IN EFI_DEVICE_IO_PROTOCOL  *This
  );

/**
  Frees pages that were allocated with AllocateBuffer().

  @param  This                   A pointer to the EFI_DEVICE_IO_INTERFACE instance.
  @param  Pages                  The number of pages to free.
  @param  HostAddress            The base address of the range to free.

  @retval EFI_SUCCESS            The requested memory pages were freed.
  @retval EFI_NOT_FOUND          The requested memory pages were not allocated with
                                 AllocateBuffer().
  @retval EFI_INVALID_PARAMETER  HostAddress is not page aligned or Pages is
                                 invalid.

**/
EFI_STATUS
EFIAPI
DeviceIoFreeBuffer (
  IN EFI_DEVICE_IO_PROTOCOL   *This,
  IN UINTN                    Pages,
  IN EFI_PHYSICAL_ADDRESS     HostAddress
  );


#define DEVICE_IO_PRIVATE_DATA_SIGNATURE  SIGNATURE_32 ('D', 'e', 'I', 'O')

typedef struct {
  UINTN                           Signature;
  EFI_DEVICE_IO_PROTOCOL          DeviceIo;
  EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *PciRootBridgeIo;
  EFI_DEVICE_PATH_PROTOCOL        *DevicePath;
  UINT16                          PrimaryBus;
  UINT16                          SubordinateBus;
} DEVICE_IO_PRIVATE_DATA;

#define DEVICE_IO_PRIVATE_DATA_FROM_THIS(a) CR (a, DEVICE_IO_PRIVATE_DATA, DeviceIo, DEVICE_IO_PRIVATE_DATA_SIGNATURE)

#define MAX_COMMON_BUFFER                 0x00000000FFFFFFFF


EFI_EVENT  mPciRootBridgeIoRegistration;

//
// Device Io Volume Protocol template
//
DEVICE_IO_PRIVATE_DATA gDeviceIoPrivateDataTemplate = {
  DEVICE_IO_PRIVATE_DATA_SIGNATURE,
  {
    {
      DeviceIoMemRead,
      DeviceIoMemWrite
    },
    {
      DeviceIoIoRead,
      DeviceIoIoWrite  
    },
    {
      DeviceIoPciRead,
      DeviceIoPciWrite,  
    },
    DeviceIoMap,
    DeviceIoPciDevicePath,
    DeviceIoUnmap,
    DeviceIoAllocateBuffer,
    DeviceIoFlush,
    DeviceIoFreeBuffer
  },
  NULL, // PciRootBridgeIo
  NULL, // DevicePath
  0,    // PrimaryBus
  255   // SubordinateBus
};

VOID
EFIAPI
PciRootBridgeIoNotificationEvent (
  IN  EFI_EVENT       Event,
  IN  VOID            *Context
  )
{
  EFI_STATUS                     Status;
  UINTN                          BufferSize;
  EFI_HANDLE                     Handle;
  DEVICE_IO_PRIVATE_DATA         *Private;
  EFI_DEVICE_IO_PROTOCOL         *DeviceIo;
  EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *PciRootBridgeIo;

  while (TRUE) {
    BufferSize = sizeof (Handle);
    Status = gBS->LocateHandle (
                    ByRegisterNotify,
                    &gEfiPciRootBridgeIoProtocolGuid,
                    mPciRootBridgeIoRegistration,
                    &BufferSize,
                    &Handle
                    );
    if (EFI_ERROR (Status)) {
      //
      // Exit Path of While Loop....
      //
      break;
    }

    //
    // Skip this handle if the Device Io Protocol is already installed
    //
    Status = gBS->HandleProtocol (
                    Handle,
                    &gEfiDeviceIoProtocolGuid,
                    (VOID **)&DeviceIo
                    );
    if (!EFI_ERROR (Status)) {
      continue;
    }

    //
    // Retrieve the Pci Root Bridge IO Protocol
    //
    Status = gBS->HandleProtocol (
                    Handle,
                    &gEfiPciRootBridgeIoProtocolGuid,
                    (VOID **)&PciRootBridgeIo
                    );
    ASSERT_EFI_ERROR (Status);

    //
    // We only install Device IO for PCI bus in Segment 0.
    // See the file description at @file for details.
    //
    if (PciRootBridgeIo->SegmentNumber != 0) {
      continue;
    }

    //
    // Allocate private data structure
    //
    Private = AllocateCopyPool (sizeof (DEVICE_IO_PRIVATE_DATA), &gDeviceIoPrivateDataTemplate);
    if (Private == NULL) {
      continue;
    }

    Private->PciRootBridgeIo = PciRootBridgeIo;

    Status = gBS->HandleProtocol (
                    Handle,
                    &gEfiDevicePathProtocolGuid,
                    (VOID **) &Private->DevicePath
                    );

    //
    // Install Device Io onto same handle
    //
    Status = gBS->InstallMultipleProtocolInterfaces (
                    &Handle,
                    &gEfiDeviceIoProtocolGuid,
                    &Private->DeviceIo,
                    NULL
                    );
    ASSERT_EFI_ERROR (Status);
  }
}

/**
  The user Entry Point for DXE driver. The user code starts with this function
  as the real entry point for the image goes into a library that calls this 
  function.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.  
  @param[in] SystemTable    A pointer to the EFI System Table.
  
  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval other             Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
InitializeDeviceIo (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EfiCreateProtocolNotifyEvent (
    &gEfiPciRootBridgeIoProtocolGuid,
    TPL_CALLBACK,
    PciRootBridgeIoNotificationEvent,
    NULL,
    &mPciRootBridgeIoRegistration
    );
  return EFI_SUCCESS;
}


/**
  Perform reading memory mapped I/O space of device.

  @param  This                   A pointer to EFI_DEVICE_IO protocol instance.
  @param  Width                  Width of I/O operations.
  @param  Address                The base address of I/O operations.
  @param  Count                  The number of I/O operations to perform.  Bytes
                                 moves is Width size * Count, starting at Address.
  @param  Buffer                 The destination buffer to store results.

  @retval EFI_SUCCESS            The data was read from the device.
  @retval EFI_INVALID_PARAMETER  Width is invalid.
  @retval EFI_OUT_OF_RESOURCES   The request could not be completed due to lack of
                                 resources.

**/
EFI_STATUS
EFIAPI
DeviceIoMemRead (
  IN     EFI_DEVICE_IO_PROTOCOL   *This,
  IN     EFI_IO_WIDTH             Width,
  IN     UINT64                   Address,
  IN     UINTN                    Count,
  IN OUT VOID                     *Buffer
  )
{
  EFI_STATUS              Status;
  DEVICE_IO_PRIVATE_DATA  *Private;

  Private = DEVICE_IO_PRIVATE_DATA_FROM_THIS (This);

  if (Width > MMIO_COPY_UINT64) {
    return EFI_INVALID_PARAMETER;
  }
  if (Width >= MMIO_COPY_UINT8) {
    Width = (EFI_IO_WIDTH) (Width - MMIO_COPY_UINT8);
    Status = Private->PciRootBridgeIo->CopyMem (
                                         Private->PciRootBridgeIo,
                                         (EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH) Width,
                                         (UINT64) (UINTN) Buffer,
                                         Address,
                                         Count
                                         );
  } else {
    Status = Private->PciRootBridgeIo->Mem.Read (
                                             Private->PciRootBridgeIo,
                                             (EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH) Width,
                                             Address,
                                             Count,
                                             Buffer
                                             );
  }

  return Status;
}




/**
  Perform writing memory mapped I/O space of device.

  @param  This                   A pointer to EFI_DEVICE_IO protocol instance.
  @param  Width                  Width of I/O operations.
  @param  Address                The base address of I/O operations.
  @param  Count                  The number of I/O operations to perform.  Bytes
                                 moves is Width size * Count, starting at Address.
  @param  Buffer                 The source buffer of data to be written.

  @retval EFI_SUCCESS            The data was written to the device.
  @retval EFI_INVALID_PARAMETER  Width is invalid.
  @retval EFI_OUT_OF_RESOURCES   The request could not be completed due to lack of
                                 resources.

**/
EFI_STATUS
EFIAPI
DeviceIoMemWrite (
  IN     EFI_DEVICE_IO_PROTOCOL    *This,
  IN     EFI_IO_WIDTH              Width,
  IN     UINT64                    Address,
  IN     UINTN                     Count,
  IN OUT VOID                      *Buffer
  )
{
  EFI_STATUS              Status;
  DEVICE_IO_PRIVATE_DATA  *Private;

  Private = DEVICE_IO_PRIVATE_DATA_FROM_THIS (This);

  if (Width > MMIO_COPY_UINT64) {
    return EFI_INVALID_PARAMETER;
  }
  if (Width >= MMIO_COPY_UINT8) {
    Width = (EFI_IO_WIDTH) (Width - MMIO_COPY_UINT8);
    Status = Private->PciRootBridgeIo->CopyMem (
                                         Private->PciRootBridgeIo,
                                         (EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH) Width,
                                         Address,
                                         (UINT64) (UINTN) Buffer,
                                         Count
                                         );
  } else {
    Status = Private->PciRootBridgeIo->Mem.Write (
                                             Private->PciRootBridgeIo,
                                             (EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH) Width,
                                             Address,
                                             Count,
                                             Buffer
                                             );
  }

  return Status;
}


/**
  Perform reading I/O space of device.

  @param  This                   A pointer to EFI_DEVICE_IO protocol instance.
  @param  Width                  Width of I/O operations.
  @param  Address                The base address of I/O operations.
  @param  Count                  The number of I/O operations to perform.  Bytes
                                 moves is Width size * Count, starting at Address.
  @param  Buffer                 The destination buffer to store results.

  @retval EFI_SUCCESS            The data was read from the device.
  @retval EFI_INVALID_PARAMETER  Width is invalid.
  @retval EFI_OUT_OF_RESOURCES   The request could not be completed due to lack of
                                 resources.

**/
EFI_STATUS
EFIAPI
DeviceIoIoRead (
  IN     EFI_DEVICE_IO_PROTOCOL   *This,
  IN     EFI_IO_WIDTH             Width,
  IN     UINT64                   Address,
  IN     UINTN                    Count,
  IN OUT VOID                     *Buffer
  )
{
  EFI_STATUS              Status;
  DEVICE_IO_PRIVATE_DATA  *Private;

  Private = DEVICE_IO_PRIVATE_DATA_FROM_THIS (This);

  if (Width >= MMIO_COPY_UINT8) {
    return EFI_INVALID_PARAMETER;
  }

  Status = Private->PciRootBridgeIo->Io.Read (
                                          Private->PciRootBridgeIo,
                                          (EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH) Width,
                                          Address,
                                          Count,
                                          Buffer
                                          );

  return Status;
}


/**
  Perform writing I/O space of device.

  @param  This                   A pointer to EFI_DEVICE_IO protocol instance.
  @param  Width                  Width of I/O operations.
  @param  Address                The base address of I/O operations.
  @param  Count                  The number of I/O operations to perform.  Bytes
                                 moves is Width size * Count, starting at Address.
  @param  Buffer                 The source buffer of data to be written.

  @retval EFI_SUCCESS            The data was written to the device.
  @retval EFI_INVALID_PARAMETER  Width is invalid.
  @retval EFI_OUT_OF_RESOURCES   The request could not be completed due to lack of
                                 resources.

**/
EFI_STATUS
EFIAPI
DeviceIoIoWrite (
  IN     EFI_DEVICE_IO_PROTOCOL    *This,
  IN     EFI_IO_WIDTH              Width,
  IN     UINT64                    Address,
  IN     UINTN                     Count,
  IN OUT VOID                      *Buffer
  )
{
  EFI_STATUS              Status;
  DEVICE_IO_PRIVATE_DATA  *Private;

  Private = DEVICE_IO_PRIVATE_DATA_FROM_THIS (This);

  if (Width >= MMIO_COPY_UINT8) {
    return EFI_INVALID_PARAMETER;
  }

  Status = Private->PciRootBridgeIo->Io.Write (
                                          Private->PciRootBridgeIo,
                                          (EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH) Width,
                                          Address,
                                          Count,
                                          Buffer
                                          );

  return Status;
}


/**
  Perform reading PCI configuration space of device

  @param  This                   A pointer to EFI_DEVICE_IO protocol instance.
  @param  Width                  Width of I/O operations.
  @param  Address                The base address of I/O operations.
  @param  Count                  The number of I/O operations to perform.  Bytes
                                 moves is Width size * Count, starting at Address.
  @param  Buffer                 The destination buffer to store results.

  @retval EFI_SUCCESS            The data was read from the device.
  @retval EFI_INVALID_PARAMETER  Width is invalid.
  @retval EFI_OUT_OF_RESOURCES   The request could not be completed due to lack of
                                 resources.

**/
EFI_STATUS
EFIAPI
DeviceIoPciRead (
  IN     EFI_DEVICE_IO_PROTOCOL   *This,
  IN     EFI_IO_WIDTH             Width,
  IN     UINT64                   Address,
  IN     UINTN                    Count,
  IN OUT VOID                     *Buffer
  )
{
  EFI_STATUS              Status;
  DEVICE_IO_PRIVATE_DATA  *Private;

  Private = DEVICE_IO_PRIVATE_DATA_FROM_THIS (This);

  if (Width < 0 || Width >= MMIO_COPY_UINT8) {
    return EFI_INVALID_PARAMETER;
  }

  Status = Private->PciRootBridgeIo->Pci.Read (
                                           Private->PciRootBridgeIo,
                                           (EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH) Width,
                                           Address,
                                           Count,
                                           Buffer
                                           );

  return Status;
}


/**
  Perform writing PCI configuration space of device.

  @param  This                   A pointer to EFI_DEVICE_IO protocol instance.
  @param  Width                  Width of I/O operations.
  @param  Address                The base address of I/O operations.
  @param  Count                  The number of I/O operations to perform.  Bytes
                                 moves is Width size * Count, starting at Address.
  @param  Buffer                 The source buffer of data to be written.

  @retval EFI_SUCCESS            The data was written to the device.
  @retval EFI_INVALID_PARAMETER  Width is invalid.
  @retval EFI_OUT_OF_RESOURCES   The request could not be completed due to lack of
                                 resources.

**/
EFI_STATUS
EFIAPI
DeviceIoPciWrite (
  IN     EFI_DEVICE_IO_PROTOCOL    *This,
  IN     EFI_IO_WIDTH              Width,
  IN     UINT64                    Address,
  IN     UINTN                     Count,
  IN OUT VOID                      *Buffer
  )
{
  EFI_STATUS              Status;
  DEVICE_IO_PRIVATE_DATA  *Private;

  Private = DEVICE_IO_PRIVATE_DATA_FROM_THIS (This);

  if (Width < 0 || Width >= MMIO_COPY_UINT8) {
    return EFI_INVALID_PARAMETER;
  }

  Status = Private->PciRootBridgeIo->Pci.Write (
                                           Private->PciRootBridgeIo,
                                           (EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_WIDTH) Width,
                                           Address,
                                           Count,
                                           Buffer
                                           );

  return Status;
}


/**
  Append a PCI device path node to another device path.

  @param  Private                A pointer to DEVICE_IO_PRIVATE_DATA instance.
  @param  Bus                    PCI bus number of the device.
  @param  Device                 PCI device number of the device.
  @param  Function               PCI function number of the device.
  @param  DevicePath             Original device path which will be appended a PCI
                                 device path node.
  @param  BridgePrimaryBus       Primary bus number of the bridge.
  @param  BridgeSubordinateBus   Subordinate bus number of the bridge.

  @return Pointer to the appended PCI device path.

**/
EFI_DEVICE_PATH_PROTOCOL *
AppendPciDevicePath (
  IN     DEVICE_IO_PRIVATE_DATA    *Private,
  IN     UINT8                     Bus,
  IN     UINT8                     Device,
  IN     UINT8                     Function,
  IN     EFI_DEVICE_PATH_PROTOCOL  *DevicePath,
  IN OUT UINT16                    *BridgePrimaryBus,
  IN OUT UINT16                    *BridgeSubordinateBus
  )
{
  UINT16                    ThisBus;
  UINT8                     ThisDevice;
  UINT8                     ThisFunc;
  UINT64                    Address;
  PCI_TYPE01                PciBridge;
  PCI_TYPE01                *PciPtr;
  EFI_DEVICE_PATH_PROTOCOL  *ReturnDevicePath;
  PCI_DEVICE_PATH           PciNode;

  PciPtr = &PciBridge;
  for (ThisBus = *BridgePrimaryBus; ThisBus <= *BridgeSubordinateBus; ThisBus++) {
    for (ThisDevice = 0; ThisDevice <= PCI_MAX_DEVICE; ThisDevice++) {
      for (ThisFunc = 0; ThisFunc <= PCI_MAX_FUNC; ThisFunc++) {
        Address = EFI_PCI_ADDRESS (ThisBus, ThisDevice, ThisFunc, 0);
        ZeroMem (PciPtr, sizeof (PCI_TYPE01));
        Private->DeviceIo.Pci.Read (
                                &Private->DeviceIo,
                                IO_UINT32,
                                Address,
                                1,
                                &(PciPtr->Hdr.VendorId)
                                );
        if ((PciPtr->Hdr.VendorId == 0xffff) && (ThisFunc == 0)) {
          break;
        }
        if (PciPtr->Hdr.VendorId == 0xffff) {
          continue;
        } else {
          Private->DeviceIo.Pci.Read (
                                  &Private->DeviceIo,
                                  IO_UINT32,
                                  Address,
                                  sizeof (PCI_TYPE01) / sizeof (UINT32),
                                  PciPtr
                                  );
          if (IS_PCI_BRIDGE (PciPtr)) {
            if (Bus >= PciPtr->Bridge.SecondaryBus && Bus <= PciPtr->Bridge.SubordinateBus) {

              PciNode.Header.Type     = HARDWARE_DEVICE_PATH;
              PciNode.Header.SubType  = HW_PCI_DP;
              SetDevicePathNodeLength (&PciNode.Header, sizeof (PciNode));

              PciNode.Device        = ThisDevice;
              PciNode.Function      = ThisFunc;
              ReturnDevicePath      = AppendDevicePathNode (DevicePath, &PciNode.Header);

              *BridgePrimaryBus     = PciPtr->Bridge.SecondaryBus;
              *BridgeSubordinateBus = PciPtr->Bridge.SubordinateBus;
              return ReturnDevicePath;
            }
          }
          if (ThisFunc == 0 && !(PciPtr->Hdr.HeaderType & HEADER_TYPE_MULTI_FUNCTION)) {
            //
            // Skip sub functions, this is not a multi function device
            //
            ThisFunc = 8;
          }
        }
      }
    }
  }

  ZeroMem (&PciNode, sizeof (PciNode));
  PciNode.Header.Type     = HARDWARE_DEVICE_PATH;
  PciNode.Header.SubType  = HW_PCI_DP;
  SetDevicePathNodeLength (&PciNode.Header, sizeof (PciNode));
  PciNode.Device        = Device;
  PciNode.Function      = Function;

  ReturnDevicePath      = AppendDevicePathNode (DevicePath, &PciNode.Header);

  *BridgePrimaryBus     = 0xffff;
  *BridgeSubordinateBus = 0xffff;
  return ReturnDevicePath;
}


/**
  Provides an EFI Device Path for a PCI device with the given PCI configuration space address.

  @param  This                   A pointer to the EFI_DEVICE_IO_INTERFACE instance.
  @param  Address                The PCI configuration space address of the device
                                 whose Device Path is going to be returned.
  @param  PciDevicePath          A pointer to the pointer for the EFI Device Path
                                 for PciAddress. Memory for the Device Path is
                                 allocated from the pool.

  @retval EFI_SUCCESS            The PciDevicePath returns a pointer to a valid EFI
                                 Device Path.
  @retval EFI_UNSUPPORTED        The PciAddress does not map to a valid EFI Device
                                 Path.
  @retval EFI_OUT_OF_RESOURCES   The request could not be completed due to a lack
                                 of resources.

**/
EFI_STATUS
EFIAPI
DeviceIoPciDevicePath (
  IN     EFI_DEVICE_IO_PROTOCOL        *This,
  IN     UINT64                        Address,
  IN OUT EFI_DEVICE_PATH_PROTOCOL      **PciDevicePath
  )
{
  DEVICE_IO_PRIVATE_DATA  *Private;
  UINT16                  PrimaryBus;
  UINT16                  SubordinateBus;
  UINT8                   Bus;
  UINT8                   Device;
  UINT8                   Func;

  Private = DEVICE_IO_PRIVATE_DATA_FROM_THIS (This);

  Bus     = (UINT8) (((UINT32) Address >> 24) & 0xff);
  Device  = (UINT8) (((UINT32) Address >> 16) & 0xff);
  Func    = (UINT8) (((UINT32) Address >> 8) & 0xff);

  if (Bus < Private->PrimaryBus || Bus > Private->SubordinateBus) {
    return EFI_UNSUPPORTED;
  }

  *PciDevicePath  = Private->DevicePath;
  PrimaryBus      = Private->PrimaryBus;
  SubordinateBus  = Private->SubordinateBus;
  do {
    *PciDevicePath = AppendPciDevicePath (
                       Private,
                       Bus,
                       Device,
                       Func,
                       *PciDevicePath,
                       &PrimaryBus,
                       &SubordinateBus
                       );
    if (*PciDevicePath == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
  } while (PrimaryBus != 0xffff);

  return EFI_SUCCESS;
}


/**
  Provides the device-specific addresses needed to access system memory.

  @param  This                   A pointer to the EFI_DEVICE_IO_INTERFACE instance.
  @param  Operation              Indicates if the bus master is going to read or
                                 write to system memory.
  @param  HostAddress            The system memory address to map to the device.
  @param  NumberOfBytes          On input the number of bytes to map. On output the
                                 number of bytes that were mapped.
  @param  DeviceAddress          The resulting map address for the bus master
                                 device to use to access the hosts HostAddress.
  @param  Mapping                A resulting value to pass to Unmap().

  @retval EFI_SUCCESS            The range was mapped for the returned
                                 NumberOfBytes.
  @retval EFI_INVALID_PARAMETER  The Operation or HostAddress is undefined.
  @retval EFI_UNSUPPORTED        The HostAddress cannot be mapped as a common
                                 buffer.
  @retval EFI_DEVICE_ERROR       The system hardware could not map the requested
                                 address.
  @retval EFI_OUT_OF_RESOURCES   The request could not be completed due to a lack
                                 of resources.

**/
EFI_STATUS
EFIAPI
DeviceIoMap (
  IN     EFI_DEVICE_IO_PROTOCOL   *This,
  IN     EFI_IO_OPERATION_TYPE    Operation,
  IN     EFI_PHYSICAL_ADDRESS     *HostAddress,
  IN OUT UINTN                    *NumberOfBytes,
  OUT    EFI_PHYSICAL_ADDRESS     *DeviceAddress,
  OUT    VOID                     **Mapping
  )
{
  EFI_STATUS              Status;
  DEVICE_IO_PRIVATE_DATA  *Private;

  Private = DEVICE_IO_PRIVATE_DATA_FROM_THIS (This);

  if (Operation < 0 || Operation > EfiBusMasterCommonBuffer) {
    return EFI_INVALID_PARAMETER;
  }

  if (((UINTN) (*HostAddress) != (*HostAddress)) && Operation == EfiBusMasterCommonBuffer) {
    return EFI_UNSUPPORTED;
  }

  Status = Private->PciRootBridgeIo->Map (
                                       Private->PciRootBridgeIo,
                                       (EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL_OPERATION) Operation,
                                       (VOID *) (UINTN) (*HostAddress),
                                       NumberOfBytes,
                                       DeviceAddress,
                                       Mapping
                                       );

  return Status;
}


/**
  Completes the Map() operation and releases any corresponding resources.

  @param  This                   A pointer to the EFI_DEVICE_IO_INTERFACE instance.
  @param  Mapping                The mapping value returned from Map().

  @retval EFI_SUCCESS            The range was unmapped.
  @retval EFI_DEVICE_ERROR       The data was not committed to the target system
                                 memory.

**/
EFI_STATUS
EFIAPI
DeviceIoUnmap (
  IN EFI_DEVICE_IO_PROTOCOL   *This,
  IN VOID                     *Mapping
  )
{
  EFI_STATUS              Status;
  DEVICE_IO_PRIVATE_DATA  *Private;

  Private = DEVICE_IO_PRIVATE_DATA_FROM_THIS (This);

  Status = Private->PciRootBridgeIo->Unmap (
                                       Private->PciRootBridgeIo,
                                       Mapping
                                       );

  return Status;
}


/**
  Allocates pages that are suitable for an EFIBusMasterCommonBuffer mapping.

  @param  This                   A pointer to the EFI_DEVICE_IO_INTERFACE instance.
  @param  Type                   The type allocation to perform.
  @param  MemoryType             The type of memory to allocate,
                                 EfiBootServicesData or EfiRuntimeServicesData.
  @param  Pages                  The number of pages to allocate.
  @param  PhysicalAddress        A pointer to store the base address of the
                                 allocated range.

  @retval EFI_SUCCESS            The requested memory pages were allocated.
  @retval EFI_OUT_OF_RESOURCES   The memory pages could not be allocated.
  @retval EFI_INVALID_PARAMETER  The requested memory type is invalid.
  @retval EFI_UNSUPPORTED        The requested PhysicalAddress is not supported on
                                 this platform.

**/
EFI_STATUS
EFIAPI
DeviceIoAllocateBuffer (
  IN     EFI_DEVICE_IO_PROTOCOL    *This,
  IN     EFI_ALLOCATE_TYPE         Type,
  IN     EFI_MEMORY_TYPE           MemoryType,
  IN     UINTN                     Pages,
  IN OUT EFI_PHYSICAL_ADDRESS      *PhysicalAddress
  )
{
  EFI_STATUS              Status;
  EFI_PHYSICAL_ADDRESS    HostAddress;
  DEVICE_IO_PRIVATE_DATA  *Private;
  VOID                    *HostAddress2;

  Private = DEVICE_IO_PRIVATE_DATA_FROM_THIS (This);

  HostAddress = *PhysicalAddress;

  if ((MemoryType != EfiBootServicesData) && (MemoryType != EfiRuntimeServicesData)) {
    return EFI_INVALID_PARAMETER;
  }

  if ((Type >= MaxAllocateType) || (Type < AllocateAnyPages)) {
    return EFI_INVALID_PARAMETER;
  }

  if ((Type == AllocateAddress) && (HostAddress + EFI_PAGES_TO_SIZE (Pages) - 1 > MAX_COMMON_BUFFER)) {
    return EFI_UNSUPPORTED;
  }

  if ((AllocateAnyPages == Type) || (AllocateMaxAddress == Type && HostAddress > MAX_COMMON_BUFFER)) {
    Type        = AllocateMaxAddress;
    HostAddress = MAX_COMMON_BUFFER;
  }

  HostAddress2 = (VOID *) (UINTN) (HostAddress);
  Status = Private->PciRootBridgeIo->AllocateBuffer (
                                       Private->PciRootBridgeIo,
                                       Type,
                                       MemoryType,
                                       Pages,
                                       &HostAddress2,
                                       EFI_PCI_ATTRIBUTE_MEMORY_WRITE_COMBINE |
                                       EFI_PCI_ATTRIBUTE_MEMORY_CACHED
                                       );
                                                    
  if (EFI_ERROR (Status)) {
    return Status;
  }


  *PhysicalAddress = (EFI_PHYSICAL_ADDRESS) (UINTN) HostAddress2;

  return EFI_SUCCESS;
}


/**
  Flushes any posted write data to the device.

  @param  This                   A pointer to the EFI_DEVICE_IO_INTERFACE instance.

  @retval EFI_SUCCESS            The buffers were flushed.
  @retval EFI_DEVICE_ERROR       The buffers were not flushed due to a hardware
                                 error.

**/
EFI_STATUS
EFIAPI
DeviceIoFlush (
  IN EFI_DEVICE_IO_PROTOCOL  *This
  )
{
  EFI_STATUS              Status;
  DEVICE_IO_PRIVATE_DATA  *Private;

  Private = DEVICE_IO_PRIVATE_DATA_FROM_THIS (This);

  Status  = Private->PciRootBridgeIo->Flush (Private->PciRootBridgeIo);

  return Status;
}


/**
  Frees pages that were allocated with AllocateBuffer().

  @param  This                   A pointer to the EFI_DEVICE_IO_INTERFACE instance.
  @param  Pages                  The number of pages to free.
  @param  HostAddress            The base address of the range to free.

  @retval EFI_SUCCESS            The requested memory pages were freed.
  @retval EFI_NOT_FOUND          The requested memory pages were not allocated with
                                 AllocateBuffer().
  @retval EFI_INVALID_PARAMETER  HostAddress is not page aligned or Pages is
                                 invalid.

**/
EFI_STATUS
EFIAPI
DeviceIoFreeBuffer (
  IN EFI_DEVICE_IO_PROTOCOL   *This,
  IN UINTN                    Pages,
  IN EFI_PHYSICAL_ADDRESS     HostAddress
  )
{
  DEVICE_IO_PRIVATE_DATA  *Private;

  Private = DEVICE_IO_PRIVATE_DATA_FROM_THIS (This);

  if (((HostAddress & EFI_PAGE_MASK) != 0) || (Pages <= 0)) {
    return EFI_INVALID_PARAMETER;
  }

  return  Private->PciRootBridgeIo->FreeBuffer (
            Private->PciRootBridgeIo,
            Pages,
            (VOID *) (UINTN) HostAddress
            );

}

