/** @file
  A non-transitional driver for VirtIo 1.0 PCI devices.

  Copyright (C) 2016, Red Hat, Inc.
  Copyright (C) 2017, AMD Inc, All rights reserved.<BR>

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

#include <IndustryStandard/Pci.h>
#include <IndustryStandard/Virtio.h>
#include <Protocol/PciIo.h>
#include <Protocol/PciRootBridgeIo.h>
#include <Protocol/VirtioDevice.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PciCapLib.h>
#include <Library/PciCapPciIoLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>

#include "Virtio10.h"

//
// Utility functions
//

/**
  Transfer data between the caller and a register in a virtio-1.0 register
  block.

  @param[in]     PciIo        The EFI_PCI_IO_PROTOCOL instance that represents
                              the device.

  @param[in]     Config       The "fat pointer" structure that identifies the
                              register block to access.

  @param[in]     Write        TRUE if the register should be written, FALSE if
                              the register should be read.

  @param[in]     FieldOffset  The offset of the register within the register
                              block.

  @param[in]     FieldSize    The size of the register within the register
                              block. Can be one of 1, 2, 4 and 8. Accesses to
                              8-byte registers are broken up into two 4-byte
                              accesses.

  @param[in,out] Buffer       When Write is TRUE, the register is written with
                              data from Buffer. When Write is FALSE, the caller
                              receives the register value into Buffer.

  @retval  EFI_SUCCESS            Register access successful.

  @retval  EFI_INVALID_PARAMETER  The register block pointed-to by Config
                                  doesn't exist; or FieldOffset and FieldSize
                                  would overflow the register block; or
                                  FieldSize is invalid.

  @return                         Error codes from
                                  EFI_PCI_IO_PROTOCOL.(Io|Mem).(Read|Write)
                                  member functions.
**/
STATIC
EFI_STATUS
Virtio10Transfer (
  IN     EFI_PCI_IO_PROTOCOL  *PciIo,
  IN     VIRTIO_1_0_CONFIG    *Config,
  IN     BOOLEAN              Write,
  IN     UINTN                FieldOffset,
  IN     UINTN                FieldSize,
  IN OUT VOID                 *Buffer
  )
{
  UINTN                       Count;
  EFI_PCI_IO_PROTOCOL_WIDTH   Width;
  EFI_PCI_IO_PROTOCOL_ACCESS  *BarType;
  EFI_PCI_IO_PROTOCOL_IO_MEM  Access;

  if (!Config->Exists ||
      (FieldSize > Config->Length) ||
      (FieldOffset > Config->Length - FieldSize))
  {
    return EFI_INVALID_PARAMETER;
  }

  Count = 1;
  switch (FieldSize) {
    case 1:
      Width = EfiPciIoWidthUint8;
      break;

    case 2:
      Width = EfiPciIoWidthUint16;
      break;

    case 8:
      Count = 2;
    //
    // fall through
    //

    case 4:
      Width = EfiPciIoWidthUint32;
      break;

    default:
      return EFI_INVALID_PARAMETER;
  }

  BarType = (Config->BarType == Virtio10BarTypeMem) ? &PciIo->Mem : &PciIo->Io;
  Access  = Write ? BarType->Write : BarType->Read;

  return Access (
           PciIo,
           Width,
           Config->Bar,
           Config->Offset + FieldOffset,
           Count,
           Buffer
           );
}

/**
  Determine if a PCI BAR is IO or MMIO.

  @param[in]  PciIo     The EFI_PCI_IO_PROTOCOL instance that represents the
                        device.

  @param[in]  BarIndex  The number of the BAR whose type the caller is
                        interested in.

  @param[out] BarType   On output, a VIRTIO_1_0_BAR_TYPE value that gives the
                        type of the BAR.

  @retval EFI_SUCCESS      The BAR type has been recognized and stored in
                           BarType.

  @retval EFI_UNSUPPORTED  The BAR type couldn't be identified.

  @return                  Error codes from
                           EFI_PCI_IO_PROTOCOL.GetBarAttributes().
**/
STATIC
EFI_STATUS
GetBarType (
  IN  EFI_PCI_IO_PROTOCOL  *PciIo,
  IN  UINT8                BarIndex,
  OUT VIRTIO_1_0_BAR_TYPE  *BarType
  )
{
  EFI_STATUS  Status;
  VOID        *Resources;

  Status = PciIo->GetBarAttributes (PciIo, BarIndex, NULL, &Resources);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = EFI_UNSUPPORTED;

  if (*(UINT8 *)Resources == ACPI_QWORD_ADDRESS_SPACE_DESCRIPTOR) {
    EFI_ACPI_QWORD_ADDRESS_SPACE_DESCRIPTOR  *Descriptor;

    Descriptor = Resources;
    switch (Descriptor->ResType) {
      case ACPI_ADDRESS_SPACE_TYPE_MEM:
        *BarType = Virtio10BarTypeMem;
        Status   = EFI_SUCCESS;
        break;

      case ACPI_ADDRESS_SPACE_TYPE_IO:
        *BarType = Virtio10BarTypeIo;
        Status   = EFI_SUCCESS;
        break;

      default:
        break;
    }
  }

  FreePool (Resources);
  return Status;
}

/*
  Traverse the PCI capabilities list of a virtio-1.0 device, and capture the
  locations of the interesting virtio-1.0 register blocks.

  @param[in,out] Device         The VIRTIO_1_0_DEV structure that identifies
                                the device. On input, the caller is responsible
                                that the Device->PciIo member be live, and that
                                the CommonConfig, NotifyConfig,
                                NotifyOffsetMultiplier and SpecificConfig
                                members be zeroed. On output,  said members
                                will have been updated from the PCI
                                capabilities found.

  @retval EFI_SUCCESS  Traversal successful.

  @return              Error codes from PciCapPciIoLib, PciCapLib, and the
                       GetBarType() helper function.
*/
STATIC
EFI_STATUS
ParseCapabilities (
  IN OUT VIRTIO_1_0_DEV  *Device
  )
{
  EFI_STATUS    Status;
  PCI_CAP_DEV   *PciDevice;
  PCI_CAP_LIST  *CapList;
  UINT16        VendorInstance;
  PCI_CAP       *VendorCap;

  Status = PciCapPciIoDeviceInit (Device->PciIo, &PciDevice);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = PciCapListInit (PciDevice, &CapList);
  if (EFI_ERROR (Status)) {
    goto UninitPciDevice;
  }

  for (VendorInstance = 0;
       !EFI_ERROR (
          PciCapListFindCap (
            CapList,
            PciCapNormal,
            EFI_PCI_CAPABILITY_ID_VENDOR,
            VendorInstance,
            &VendorCap
            )
          );
       VendorInstance++)
  {
    UINT8              CapLen;
    VIRTIO_PCI_CAP     VirtIoCap;
    VIRTIO_1_0_CONFIG  *ParsedConfig;

    //
    // Big enough to accommodate a VIRTIO_PCI_CAP structure?
    //
    Status = PciCapRead (
               PciDevice,
               VendorCap,
               OFFSET_OF (EFI_PCI_CAPABILITY_VENDOR_HDR, Length),
               &CapLen,
               sizeof CapLen
               );
    if (EFI_ERROR (Status)) {
      goto UninitCapList;
    }

    if (CapLen < sizeof VirtIoCap) {
      //
      // Too small, move to next.
      //
      continue;
    }

    //
    // Read interesting part of capability.
    //
    Status = PciCapRead (PciDevice, VendorCap, 0, &VirtIoCap, sizeof VirtIoCap);
    if (EFI_ERROR (Status)) {
      goto UninitCapList;
    }

    switch (VirtIoCap.ConfigType) {
      case VIRTIO_PCI_CAP_COMMON_CFG:
        ParsedConfig = &Device->CommonConfig;
        break;
      case VIRTIO_PCI_CAP_NOTIFY_CFG:
        ParsedConfig = &Device->NotifyConfig;
        break;
      case VIRTIO_PCI_CAP_DEVICE_CFG:
        ParsedConfig = &Device->SpecificConfig;
        break;
      default:
        //
        // Capability is not interesting.
        //
        continue;
    }

    //
    // Save the location of the register block into ParsedConfig.
    //
    Status = GetBarType (Device->PciIo, VirtIoCap.Bar, &ParsedConfig->BarType);
    if (EFI_ERROR (Status)) {
      goto UninitCapList;
    }

    ParsedConfig->Bar    = VirtIoCap.Bar;
    ParsedConfig->Offset = VirtIoCap.Offset;
    ParsedConfig->Length = VirtIoCap.Length;

    if (VirtIoCap.ConfigType == VIRTIO_PCI_CAP_NOTIFY_CFG) {
      //
      // This capability has an additional field called NotifyOffsetMultiplier;
      // parse it too.
      //
      if (CapLen < sizeof VirtIoCap + sizeof Device->NotifyOffsetMultiplier) {
        //
        // Too small, move to next.
        //
        continue;
      }

      Status = PciCapRead (
                 PciDevice,
                 VendorCap,
                 sizeof VirtIoCap,
                 &Device->NotifyOffsetMultiplier,
                 sizeof Device->NotifyOffsetMultiplier
                 );
      if (EFI_ERROR (Status)) {
        goto UninitCapList;
      }
    }

    //
    // Capability parsed successfully.
    //
    ParsedConfig->Exists = TRUE;
  }

  ASSERT_EFI_ERROR (Status);

UninitCapList:
  PciCapListUninit (CapList);

UninitPciDevice:
  PciCapPciIoDeviceUninit (PciDevice);

  return Status;
}

/**
  Accumulate the BAR type of a virtio-1.0 register block into a UINT64
  attribute map, such that the latter is suitable for enabling IO / MMIO
  decoding with EFI_PCI_IO_PROTOCOL.Attributes().

  @param[in]     Config      The "fat pointer" structure that identifies the
                             register block. It is allowed for the register
                             block not to exist.

  @param[in,out] Attributes  On output, if the register block exists,
                             EFI_PCI_IO_ATTRIBUTE_MEMORY or
                             EFI_PCI_IO_ATTRIBUTE_IO is OR-ed into Attributes,
                             according to the register block's BAR type.
**/
STATIC
VOID
UpdateAttributes (
  IN     VIRTIO_1_0_CONFIG  *Config,
  IN OUT UINT64             *Attributes
  )
{
  if (Config->Exists) {
    *Attributes |= (Config->BarType == Virtio10BarTypeMem) ?
                   EFI_PCI_IO_ATTRIBUTE_MEMORY :
                   EFI_PCI_IO_ATTRIBUTE_IO;
  }
}

//
// VIRTIO_DEVICE_PROTOCOL member functions
//

STATIC
EFI_STATUS
EFIAPI
Virtio10GetDeviceFeatures (
  IN VIRTIO_DEVICE_PROTOCOL  *This,
  OUT UINT64                 *DeviceFeatures
  )
{
  VIRTIO_1_0_DEV  *Dev;
  UINT32          Selector;
  UINT32          Features32[2];

  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);

  for (Selector = 0; Selector < 2; ++Selector) {
    EFI_STATUS  Status;

    //
    // Select the low or high half of the features.
    //
    Status = Virtio10Transfer (
               Dev->PciIo,
               &Dev->CommonConfig,
               TRUE,
               OFFSET_OF (VIRTIO_PCI_COMMON_CFG, DeviceFeatureSelect),
               sizeof Selector,
               &Selector
               );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    //
    // Fetch that half.
    //
    Status = Virtio10Transfer (
               Dev->PciIo,
               &Dev->CommonConfig,
               FALSE,
               OFFSET_OF (VIRTIO_PCI_COMMON_CFG, DeviceFeature),
               sizeof Features32[Selector],
               &Features32[Selector]
               );
    if (EFI_ERROR (Status)) {
      return Status;
    }
  }

  *DeviceFeatures = LShiftU64 (Features32[1], 32) | Features32[0];
  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
EFIAPI
Virtio10SetGuestFeatures (
  IN VIRTIO_DEVICE_PROTOCOL  *This,
  IN UINT64                  Features
  )
{
  VIRTIO_1_0_DEV  *Dev;
  UINT32          Selector;
  UINT32          Features32[2];

  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);

  Features32[0] = (UINT32)Features;
  Features32[1] = (UINT32)RShiftU64 (Features, 32);

  for (Selector = 0; Selector < 2; ++Selector) {
    EFI_STATUS  Status;

    //
    // Select the low or high half of the features.
    //
    Status = Virtio10Transfer (
               Dev->PciIo,
               &Dev->CommonConfig,
               TRUE,
               OFFSET_OF (VIRTIO_PCI_COMMON_CFG, DriverFeatureSelect),
               sizeof Selector,
               &Selector
               );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    //
    // Write that half.
    //
    Status = Virtio10Transfer (
               Dev->PciIo,
               &Dev->CommonConfig,
               TRUE,
               OFFSET_OF (VIRTIO_PCI_COMMON_CFG, DriverFeature),
               sizeof Features32[Selector],
               &Features32[Selector]
               );
    if (EFI_ERROR (Status)) {
      return Status;
    }
  }

  return EFI_SUCCESS;
}

STATIC
EFI_STATUS
EFIAPI
Virtio10SetQueueAddress (
  IN VIRTIO_DEVICE_PROTOCOL  *This,
  IN VRING                   *Ring,
  IN UINT64                  RingBaseShift
  )
{
  VIRTIO_1_0_DEV  *Dev;
  EFI_STATUS      Status;
  UINT64          Address;
  UINT16          Enable;

  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);

  Address  = (UINTN)Ring->Desc;
  Address += RingBaseShift;
  Status   = Virtio10Transfer (
               Dev->PciIo,
               &Dev->CommonConfig,
               TRUE,
               OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueDesc),
               sizeof Address,
               &Address
               );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Address  = (UINTN)Ring->Avail.Flags;
  Address += RingBaseShift;
  Status   = Virtio10Transfer (
               Dev->PciIo,
               &Dev->CommonConfig,
               TRUE,
               OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueAvail),
               sizeof Address,
               &Address
               );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Address  = (UINTN)Ring->Used.Flags;
  Address += RingBaseShift;
  Status   = Virtio10Transfer (
               Dev->PciIo,
               &Dev->CommonConfig,
               TRUE,
               OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueUsed),
               sizeof Address,
               &Address
               );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Enable = 1;
  Status = Virtio10Transfer (
             Dev->PciIo,
             &Dev->CommonConfig,
             TRUE,
             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueEnable),
             sizeof Enable,
             &Enable
             );
  return Status;
}

STATIC
EFI_STATUS
EFIAPI
Virtio10SetQueueSel (
  IN VIRTIO_DEVICE_PROTOCOL  *This,
  IN UINT16                  Index
  )
{
  VIRTIO_1_0_DEV  *Dev;
  EFI_STATUS      Status;

  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);

  Status = Virtio10Transfer (
             Dev->PciIo,
             &Dev->CommonConfig,
             TRUE,
             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueSelect),
             sizeof Index,
             &Index
             );
  return Status;
}

STATIC
EFI_STATUS
EFIAPI
Virtio10SetQueueNotify (
  IN VIRTIO_DEVICE_PROTOCOL  *This,
  IN UINT16                  Index
  )
{
  VIRTIO_1_0_DEV  *Dev;
  EFI_STATUS      Status;
  UINT16          SavedQueueSelect;
  UINT16          NotifyOffset;

  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);

  //
  // Read NotifyOffset first. NotifyOffset is queue specific, so we have
  // to stash & restore the current queue selector around it.
  //
  // So, start with saving the current queue selector.
  //
  Status = Virtio10Transfer (
             Dev->PciIo,
             &Dev->CommonConfig,
             FALSE,
             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueSelect),
             sizeof SavedQueueSelect,
             &SavedQueueSelect
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Select the requested queue.
  //
  Status = Virtio10Transfer (
             Dev->PciIo,
             &Dev->CommonConfig,
             TRUE,
             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueSelect),
             sizeof Index,
             &Index
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Read the QueueNotifyOff field.
  //
  Status = Virtio10Transfer (
             Dev->PciIo,
             &Dev->CommonConfig,
             FALSE,
             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueNotifyOff),
             sizeof NotifyOffset,
             &NotifyOffset
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Re-select the original queue.
  //
  Status = Virtio10Transfer (
             Dev->PciIo,
             &Dev->CommonConfig,
             TRUE,
             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueSelect),
             sizeof SavedQueueSelect,
             &SavedQueueSelect
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // We can now kick the queue.
  //
  Status = Virtio10Transfer (
             Dev->PciIo,
             &Dev->NotifyConfig,
             TRUE,
             NotifyOffset * Dev->NotifyOffsetMultiplier,
             sizeof Index,
             &Index
             );
  return Status;
}

STATIC
EFI_STATUS
EFIAPI
Virtio10SetQueueAlign (
  IN VIRTIO_DEVICE_PROTOCOL  *This,
  IN UINT32                  Alignment
  )
{
  return (Alignment == EFI_PAGE_SIZE) ? EFI_SUCCESS : EFI_UNSUPPORTED;
}

STATIC
EFI_STATUS
EFIAPI
Virtio10SetPageSize (
  IN VIRTIO_DEVICE_PROTOCOL  *This,
  IN UINT32                  PageSize
  )
{
  return (PageSize == EFI_PAGE_SIZE) ? EFI_SUCCESS : EFI_UNSUPPORTED;
}

STATIC
EFI_STATUS
EFIAPI
Virtio10GetQueueNumMax (
  IN  VIRTIO_DEVICE_PROTOCOL  *This,
  OUT UINT16                  *QueueNumMax
  )
{
  VIRTIO_1_0_DEV  *Dev;
  EFI_STATUS      Status;

  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);

  Status = Virtio10Transfer (
             Dev->PciIo,
             &Dev->CommonConfig,
             FALSE,
             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueSize),
             sizeof *QueueNumMax,
             QueueNumMax
             );
  return Status;
}

STATIC
EFI_STATUS
EFIAPI
Virtio10SetQueueNum (
  IN VIRTIO_DEVICE_PROTOCOL  *This,
  IN UINT16                  QueueSize
  )
{
  EFI_STATUS  Status;
  UINT16      CurrentSize;

  //
  // This member function is required for VirtIo MMIO, and a no-op in
  // VirtIo PCI 0.9.5. In VirtIo 1.0, drivers can theoretically use this
  // member to reduce memory consumption, but none of our drivers do. So
  // just check that they set the size that is already in effect.
  //
  Status = Virtio10GetQueueNumMax (This, &CurrentSize);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  return (CurrentSize == QueueSize) ? EFI_SUCCESS : EFI_UNSUPPORTED;
}

STATIC
EFI_STATUS
EFIAPI
Virtio10GetDeviceStatus (
  IN  VIRTIO_DEVICE_PROTOCOL  *This,
  OUT UINT8                   *DeviceStatus
  )
{
  VIRTIO_1_0_DEV  *Dev;
  EFI_STATUS      Status;

  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);

  Status = Virtio10Transfer (
             Dev->PciIo,
             &Dev->CommonConfig,
             FALSE,
             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, DeviceStatus),
             sizeof *DeviceStatus,
             DeviceStatus
             );
  return Status;
}

STATIC
EFI_STATUS
EFIAPI
Virtio10SetDeviceStatus (
  IN VIRTIO_DEVICE_PROTOCOL  *This,
  IN UINT8                   DeviceStatus
  )
{
  VIRTIO_1_0_DEV  *Dev;
  EFI_STATUS      Status;

  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);

  Status = Virtio10Transfer (
             Dev->PciIo,
             &Dev->CommonConfig,
             TRUE,
             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, DeviceStatus),
             sizeof DeviceStatus,
             &DeviceStatus
             );
  return Status;
}

STATIC
EFI_STATUS
EFIAPI
Virtio10WriteDevice (
  IN VIRTIO_DEVICE_PROTOCOL  *This,
  IN UINTN                   FieldOffset,
  IN UINTN                   FieldSize,
  IN UINT64                  Value
  )
{
  VIRTIO_1_0_DEV  *Dev;
  EFI_STATUS      Status;

  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);

  Status = Virtio10Transfer (
             Dev->PciIo,
             &Dev->SpecificConfig,
             TRUE,
             FieldOffset,
             FieldSize,
             &Value
             );
  return Status;
}

STATIC
EFI_STATUS
EFIAPI
Virtio10ReadDevice (
  IN  VIRTIO_DEVICE_PROTOCOL  *This,
  IN  UINTN                   FieldOffset,
  IN  UINTN                   FieldSize,
  IN  UINTN                   BufferSize,
  OUT VOID                    *Buffer
  )
{
  VIRTIO_1_0_DEV  *Dev;
  EFI_STATUS      Status;

  if (FieldSize != BufferSize) {
    return EFI_INVALID_PARAMETER;
  }

  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);

  Status = Virtio10Transfer (
             Dev->PciIo,
             &Dev->SpecificConfig,
             FALSE,
             FieldOffset,
             FieldSize,
             Buffer
             );
  return Status;
}

STATIC
EFI_STATUS
EFIAPI
Virtio10AllocateSharedPages (
  IN     VIRTIO_DEVICE_PROTOCOL  *This,
  IN     UINTN                   Pages,
  IN OUT VOID                    **HostAddress
  )
{
  VIRTIO_1_0_DEV  *Dev;
  EFI_STATUS      Status;

  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);

  Status = Dev->PciIo->AllocateBuffer (
                         Dev->PciIo,
                         AllocateAnyPages,
                         EfiBootServicesData,
                         Pages,
                         HostAddress,
                         EFI_PCI_ATTRIBUTE_MEMORY_CACHED
                         );
  return Status;
}

STATIC
VOID
EFIAPI
Virtio10FreeSharedPages (
  IN  VIRTIO_DEVICE_PROTOCOL  *This,
  IN  UINTN                   Pages,
  IN  VOID                    *HostAddress
  )
{
  VIRTIO_1_0_DEV  *Dev;

  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);

  Dev->PciIo->FreeBuffer (
                Dev->PciIo,
                Pages,
                HostAddress
                );
}

STATIC
EFI_STATUS
EFIAPI
Virtio10MapSharedBuffer (
  IN     VIRTIO_DEVICE_PROTOCOL  *This,
  IN     VIRTIO_MAP_OPERATION    Operation,
  IN     VOID                    *HostAddress,
  IN OUT UINTN                   *NumberOfBytes,
  OUT    EFI_PHYSICAL_ADDRESS    *DeviceAddress,
  OUT    VOID                    **Mapping
  )
{
  EFI_STATUS                     Status;
  VIRTIO_1_0_DEV                 *Dev;
  EFI_PCI_IO_PROTOCOL_OPERATION  PciIoOperation;

  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);

  //
  // Map VIRTIO_MAP_OPERATION to EFI_PCI_IO_PROTOCOL_OPERATION
  //
  switch (Operation) {
    case VirtioOperationBusMasterRead:
      PciIoOperation = EfiPciIoOperationBusMasterRead;
      break;
    case VirtioOperationBusMasterWrite:
      PciIoOperation = EfiPciIoOperationBusMasterWrite;
      break;
    case VirtioOperationBusMasterCommonBuffer:
      PciIoOperation = EfiPciIoOperationBusMasterCommonBuffer;
      break;
    default:
      return EFI_INVALID_PARAMETER;
  }

  Status = Dev->PciIo->Map (
                         Dev->PciIo,
                         PciIoOperation,
                         HostAddress,
                         NumberOfBytes,
                         DeviceAddress,
                         Mapping
                         );
  return Status;
}

STATIC
EFI_STATUS
EFIAPI
Virtio10UnmapSharedBuffer (
  IN  VIRTIO_DEVICE_PROTOCOL  *This,
  IN  VOID                    *Mapping
  )
{
  EFI_STATUS      Status;
  VIRTIO_1_0_DEV  *Dev;

  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);

  Status = Dev->PciIo->Unmap (
                         Dev->PciIo,
                         Mapping
                         );

  return Status;
}

STATIC CONST VIRTIO_DEVICE_PROTOCOL  mVirtIoTemplate = {
  VIRTIO_SPEC_REVISION (1,     0, 0),
  0,                           // SubSystemDeviceId, filled in dynamically
  Virtio10GetDeviceFeatures,
  Virtio10SetGuestFeatures,
  Virtio10SetQueueAddress,
  Virtio10SetQueueSel,
  Virtio10SetQueueNotify,
  Virtio10SetQueueAlign,
  Virtio10SetPageSize,
  Virtio10GetQueueNumMax,
  Virtio10SetQueueNum,
  Virtio10GetDeviceStatus,
  Virtio10SetDeviceStatus,
  Virtio10WriteDevice,
  Virtio10ReadDevice,
  Virtio10AllocateSharedPages,
  Virtio10FreeSharedPages,
  Virtio10MapSharedBuffer,
  Virtio10UnmapSharedBuffer
};

//
// EFI_DRIVER_BINDING_PROTOCOL member functions
//

STATIC
EFI_STATUS
EFIAPI
Virtio10BindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   DeviceHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
{
  EFI_STATUS           Status;
  EFI_PCI_IO_PROTOCOL  *PciIo;
  PCI_TYPE00           Pci;

  Status = gBS->OpenProtocol (
                  DeviceHandle,
                  &gEfiPciIoProtocolGuid,
                  (VOID **)&PciIo,
                  This->DriverBindingHandle,
                  DeviceHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = PciIo->Pci.Read (
                        PciIo,
                        EfiPciIoWidthUint32,
                        0,
                        sizeof Pci / sizeof (UINT32),
                        &Pci
                        );
  if (EFI_ERROR (Status)) {
    goto CloseProtocol;
  }

  Status = EFI_UNSUPPORTED;
  //
  // Recognize non-transitional modern devices. Also, we'll have to parse the
  // PCI capability list, so make sure the CapabilityPtr field will be valid.
  //
  if ((Pci.Hdr.VendorId == VIRTIO_VENDOR_ID) &&
      (Pci.Hdr.DeviceId >= 0x1040) &&
      (Pci.Hdr.DeviceId <= 0x107F) &&
      (Pci.Hdr.RevisionID >= 0x01) &&
      (Pci.Device.SubsystemID >= 0x40) &&
      ((Pci.Hdr.Status & EFI_PCI_STATUS_CAPABILITY) != 0))
  {
    //
    // The virtio-vga device is special. It can be driven both as a VGA device
    // with a linear framebuffer, and through its underlying, modern,
    // virtio-gpu-pci device, which has no linear framebuffer itself. For
    // compatibility with guest OSes that insist on inheriting a linear
    // framebuffer from the firmware, we should leave virtio-vga to
    // QemuVideoDxe, and support only virtio-gpu-pci here.
    //
    // Both virtio-vga and virtio-gpu-pci have DeviceId 0x1050, but only the
    // former has device class PCI_CLASS_DISPLAY_VGA.
    //
    if ((Pci.Hdr.DeviceId != 0x1050) || !IS_PCI_VGA (&Pci)) {
      Status = EFI_SUCCESS;
    }
  }

CloseProtocol:
  gBS->CloseProtocol (
         DeviceHandle,
         &gEfiPciIoProtocolGuid,
         This->DriverBindingHandle,
         DeviceHandle
         );

  return Status;
}

STATIC
EFI_STATUS
EFIAPI
Virtio10BindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   DeviceHandle,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
{
  VIRTIO_1_0_DEV  *Device;
  EFI_STATUS      Status;
  PCI_TYPE00      Pci;
  UINT64          SetAttributes;

  Device = AllocateZeroPool (sizeof *Device);
  if (Device == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Device->Signature = VIRTIO_1_0_SIGNATURE;
  CopyMem (&Device->VirtIo, &mVirtIoTemplate, sizeof mVirtIoTemplate);

  Status = gBS->OpenProtocol (
                  DeviceHandle,
                  &gEfiPciIoProtocolGuid,
                  (VOID **)&Device->PciIo,
                  This->DriverBindingHandle,
                  DeviceHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status)) {
    goto FreeDevice;
  }

  Status = Device->PciIo->Pci.Read (
                                Device->PciIo,
                                EfiPciIoWidthUint32,
                                0,
                                sizeof Pci / sizeof (UINT32),
                                &Pci
                                );
  if (EFI_ERROR (Status)) {
    goto ClosePciIo;
  }

  Device->VirtIo.SubSystemDeviceId = Pci.Hdr.DeviceId - 0x1040;

  Status = ParseCapabilities (Device);
  if (EFI_ERROR (Status)) {
    goto ClosePciIo;
  }

  Status = Device->PciIo->Attributes (
                            Device->PciIo,
                            EfiPciIoAttributeOperationGet,
                            0,
                            &Device->OriginalPciAttributes
                            );
  if (EFI_ERROR (Status)) {
    goto ClosePciIo;
  }

  SetAttributes = (EFI_PCI_IO_ATTRIBUTE_BUS_MASTER |
                   EFI_PCI_IO_ATTRIBUTE_DUAL_ADDRESS_CYCLE);
  UpdateAttributes (&Device->CommonConfig, &SetAttributes);
  UpdateAttributes (&Device->NotifyConfig, &SetAttributes);
  UpdateAttributes (&Device->SpecificConfig, &SetAttributes);
  Status = Device->PciIo->Attributes (
                            Device->PciIo,
                            EfiPciIoAttributeOperationEnable,
                            SetAttributes,
                            NULL
                            );
  if (EFI_ERROR (Status)) {
    goto ClosePciIo;
  }

  Status = gBS->InstallProtocolInterface (
                  &DeviceHandle,
                  &gVirtioDeviceProtocolGuid,
                  EFI_NATIVE_INTERFACE,
                  &Device->VirtIo
                  );
  if (EFI_ERROR (Status)) {
    goto RestorePciAttributes;
  }

  return EFI_SUCCESS;

RestorePciAttributes:
  Device->PciIo->Attributes (
                   Device->PciIo,
                   EfiPciIoAttributeOperationSet,
                   Device->OriginalPciAttributes,
                   NULL
                   );

ClosePciIo:
  gBS->CloseProtocol (
         DeviceHandle,
         &gEfiPciIoProtocolGuid,
         This->DriverBindingHandle,
         DeviceHandle
         );

FreeDevice:
  FreePool (Device);

  return Status;
}

STATIC
EFI_STATUS
EFIAPI
Virtio10BindingStop (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   DeviceHandle,
  IN UINTN                        NumberOfChildren,
  IN EFI_HANDLE                   *ChildHandleBuffer
  )
{
  EFI_STATUS              Status;
  VIRTIO_DEVICE_PROTOCOL  *VirtIo;
  VIRTIO_1_0_DEV          *Device;

  Status = gBS->OpenProtocol (
                  DeviceHandle,
                  &gVirtioDeviceProtocolGuid,
                  (VOID **)&VirtIo,
                  This->DriverBindingHandle,
                  DeviceHandle,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Device = VIRTIO_1_0_FROM_VIRTIO_DEVICE (VirtIo);

  Status = gBS->UninstallProtocolInterface (
                  DeviceHandle,
                  &gVirtioDeviceProtocolGuid,
                  &Device->VirtIo
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Device->PciIo->Attributes (
                   Device->PciIo,
                   EfiPciIoAttributeOperationSet,
                   Device->OriginalPciAttributes,
                   NULL
                   );
  gBS->CloseProtocol (
         DeviceHandle,
         &gEfiPciIoProtocolGuid,
         This->DriverBindingHandle,
         DeviceHandle
         );
  FreePool (Device);

  return EFI_SUCCESS;
}

STATIC EFI_DRIVER_BINDING_PROTOCOL  mDriverBinding = {
  &Virtio10BindingSupported,
  &Virtio10BindingStart,
  &Virtio10BindingStop,
  0x10, // Version
  NULL, // ImageHandle, to be overwritten
  NULL  // DriverBindingHandle, to be overwritten
};

//
// EFI_COMPONENT_NAME_PROTOCOL and EFI_COMPONENT_NAME2_PROTOCOL
// implementations
//

STATIC
EFI_UNICODE_STRING_TABLE  mDriverNameTable[] = {
  { "eng;en", L"Virtio 1.0 PCI Driver" },
  { NULL,     NULL                     }
};

STATIC
EFI_COMPONENT_NAME_PROTOCOL  mComponentName;

STATIC
EFI_STATUS
EFIAPI
Virtio10GetDriverName (
  IN  EFI_COMPONENT_NAME_PROTOCOL  *This,
  IN  CHAR8                        *Language,
  OUT CHAR16                       **DriverName
  )
{
  return LookupUnicodeString2 (
           Language,
           This->SupportedLanguages,
           mDriverNameTable,
           DriverName,
           (BOOLEAN)(This == &mComponentName) // Iso639Language
           );
}

STATIC
EFI_STATUS
EFIAPI
Virtio10GetDeviceName (
  IN  EFI_COMPONENT_NAME_PROTOCOL  *This,
  IN  EFI_HANDLE                   DeviceHandle,
  IN  EFI_HANDLE                   ChildHandle,
  IN  CHAR8                        *Language,
  OUT CHAR16                       **ControllerName
  )
{
  return EFI_UNSUPPORTED;
}

STATIC
EFI_COMPONENT_NAME_PROTOCOL  mComponentName = {
  &Virtio10GetDriverName,
  &Virtio10GetDeviceName,
  "eng"
};

STATIC
EFI_COMPONENT_NAME2_PROTOCOL  mComponentName2 = {
  (EFI_COMPONENT_NAME2_GET_DRIVER_NAME)&Virtio10GetDriverName,
  (EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME)&Virtio10GetDeviceName,
  "en"
};

//
// Entry point of this driver
//

EFI_STATUS
EFIAPI
Virtio10EntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  return EfiLibInstallDriverBindingComponentName2 (
           ImageHandle,
           SystemTable,
           &mDriverBinding,
           ImageHandle,
           &mComponentName,
           &mComponentName2
           );
}
