diff mbox

[edk2,wave,3,v2,15/17] OvmfPkg: Virtio10Dxe: non-transitional driver for virtio-1.0 PCI devices

Message ID 1459946427-15771-16-git-send-email-lersek@redhat.com
State New
Headers show

Commit Message

Laszlo Ersek April 6, 2016, 12:40 p.m. UTC
This driver implements the VIRTIO_DEVICE_PROTOCOL for non-transitional PCI
devices, based on the virtio-1.0 specification (csprd05). Non-transitional
means that it only binds QEMU's virtio-xxx-pci devices that receive the
",disable-legacy=on,disable-modern=off" properties on the QEMU command
line. These devices have distinct PCI Device IDs from those that are bound
by VirtioPciDeviceDxe.

The central abstraction of this driver is the VIRTIO_1_0_CONFIG type. It
is practically a "fat pointer" to a register block. The pointed-to
register block
- may or may not exist (the latter being mostly useful for virtio-1.0
  devices that have no device-specific registers),
- lives in one of the device's BARs,
- lives in an IO or MMIO BAR,
- lives at an offset relative to the BAR start,
- has its size also maintained.

Such VIRTIO_1_0_CONFIG "fat pointers" (i.e., the locations of the register
blocks) are parsed from vendor capabilities that reside in the device's
standard PCI capabilities list (in PCI config space).

Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Laszlo Ersek <lersek@redhat.com>

Tested-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>

Acked-by: Jordan Justen <jordan.l.justen@intel.com>

---

Notes:
    v2:
    - no need to include <IndustryStandard/Virtio10.h> explicitly,
      <IndustryStandard/Virtio.h> exposes all versions [Jordan]

 OvmfPkg/Virtio10Dxe/Virtio10.inf |   40 +
 OvmfPkg/Virtio10Dxe/Virtio10.h   |   56 ++
 OvmfPkg/Virtio10Dxe/Virtio10.c   | 1061 ++++++++++++++++++++
 3 files changed, 1157 insertions(+)

-- 
1.8.3.1


_______________________________________________
edk2-devel mailing list
edk2-devel@lists.01.org
https://lists.01.org/mailman/listinfo/edk2-devel
diff mbox

Patch

diff --git a/OvmfPkg/Virtio10Dxe/Virtio10.inf b/OvmfPkg/Virtio10Dxe/Virtio10.inf
new file mode 100644
index 000000000000..f868d1cfc449
--- /dev/null
+++ b/OvmfPkg/Virtio10Dxe/Virtio10.inf
@@ -0,0 +1,40 @@ 
+## @file
+# A non-transitional driver for VirtIo 1.0 PCI devices.
+#
+# Copyright (C) 2016, Red Hat, Inc.
+#
+# 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.
+##
+
+[Defines]
+  INF_VERSION                    = 0x00010005
+  BASE_NAME                      = Virtio10
+  FILE_GUID                      = 0170F60C-1D40-4651-956D-F0BD9879D527
+  MODULE_TYPE                    = UEFI_DRIVER
+  VERSION_STRING                 = 1.0
+  ENTRY_POINT                    = Virtio10EntryPoint
+
+[Sources]
+  Virtio10.c
+
+[Packages]
+  MdePkg/MdePkg.dec
+  OvmfPkg/OvmfPkg.dec
+
+[LibraryClasses]
+  BaseMemoryLib
+  DebugLib
+  MemoryAllocationLib
+  UefiBootServicesTableLib
+  UefiDriverEntryPoint
+  UefiLib
+
+[Protocols]
+  gEfiPciIoProtocolGuid     ## TO_START
+  gVirtioDeviceProtocolGuid ## BY_START
diff --git a/OvmfPkg/Virtio10Dxe/Virtio10.h b/OvmfPkg/Virtio10Dxe/Virtio10.h
new file mode 100644
index 000000000000..2fbe27f1cfee
--- /dev/null
+++ b/OvmfPkg/Virtio10Dxe/Virtio10.h
@@ -0,0 +1,56 @@ 
+/** @file
+  Private definitions of the VirtIo 1.0 driver.
+
+  Copyright (C) 2016, Red Hat, Inc.
+
+  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.
+**/
+
+#ifndef _VIRTIO_1_0_DXE_H_
+#define _VIRTIO_1_0_DXE_H_
+
+#include <Protocol/PciIo.h>
+#include <Protocol/VirtioDevice.h>
+
+#define VIRTIO_1_0_SIGNATURE SIGNATURE_32 ('V', 'I', 'O', '1')
+
+//
+// Type of the PCI BAR that contains a VirtIo 1.0 config structure.
+//
+typedef enum {
+  Virtio10BarTypeMem,
+  Virtio10BarTypeIo
+} VIRTIO_1_0_BAR_TYPE;
+
+//
+// The type below defines the access to a VirtIo 1.0 config structure.
+//
+typedef struct {
+  BOOLEAN             Exists;  // The device exposes this structure
+  VIRTIO_1_0_BAR_TYPE BarType;
+  UINT8               Bar;
+  UINT32              Offset;  // Offset into BAR where structure starts
+  UINT32              Length;  // Length of structure in BAR.
+} VIRTIO_1_0_CONFIG;
+
+typedef struct {
+  UINT32                 Signature;
+  VIRTIO_DEVICE_PROTOCOL VirtIo;
+  EFI_PCI_IO_PROTOCOL    *PciIo;
+  UINT64                 OriginalPciAttributes;
+  VIRTIO_1_0_CONFIG      CommonConfig;           // Common settings
+  VIRTIO_1_0_CONFIG      NotifyConfig;           // Notifications
+  UINT32                 NotifyOffsetMultiplier;
+  VIRTIO_1_0_CONFIG      SpecificConfig;         // Device specific settings
+} VIRTIO_1_0_DEV;
+
+#define VIRTIO_1_0_FROM_VIRTIO_DEVICE(Device) \
+          CR (Device, VIRTIO_1_0_DEV, VirtIo, VIRTIO_1_0_SIGNATURE)
+
+#endif // _VIRTIO_1_0_DXE_H_
diff --git a/OvmfPkg/Virtio10Dxe/Virtio10.c b/OvmfPkg/Virtio10Dxe/Virtio10.c
new file mode 100644
index 000000000000..06f069907753
--- /dev/null
+++ b/OvmfPkg/Virtio10Dxe/Virtio10.c
@@ -0,0 +1,1061 @@ 
+/** @file
+  A non-transitional driver for VirtIo 1.0 PCI devices.
+
+  Copyright (C) 2016, Red Hat, Inc.
+
+  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 <IndustryStandard/Pci.h>
+#include <IndustryStandard/Virtio.h>
+#include <Protocol/PciIo.h>
+#include <Protocol/VirtioDevice.h>
+#include <Library/BaseMemoryLib.h>
+#include <Library/DebugLib.h>
+#include <Library/MemoryAllocationLib.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;
+}
+
+
+/**
+  Read a slice from PCI config space at the given offset, then advance the
+  offset.
+
+  @param [in]     PciIo   The EFI_PCI_IO_PROTOCOL instance that represents the
+                          device.
+
+  @param [in,out] Offset  On input, the offset in PCI config space to start
+                          reading from. On output, the offset of the first byte
+                          that was not read. On error, Offset is not modified.
+
+  @param [in]     Size    The number of bytes to read.
+
+  @param [out]    Buffer  On output, the bytes read from PCI config space are
+                          stored in this object.
+
+  @retval EFI_SUCCESS  Size bytes have been transferred from PCI config space
+                       (from Offset) to Buffer, and Offset has been incremented
+                       by Size.
+
+  @return              Error codes from PciIo->Pci.Read().
+**/
+STATIC
+EFI_STATUS
+ReadConfigSpace (
+  IN     EFI_PCI_IO_PROTOCOL *PciIo,
+  IN OUT UINT32              *Offset,
+  IN     UINTN               Size,
+     OUT VOID                *Buffer
+  )
+{
+  EFI_STATUS Status;
+
+  Status = PciIo->Pci.Read (PciIo, EfiPciIoWidthUint8, *Offset, Size, Buffer);
+  if (EFI_ERROR (Status)) {
+    return Status;
+  }
+  *Offset += (UINT32)Size;
+  return EFI_SUCCESS;
+}
+
+
+/*
+  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.
+
+  @param[in]     CapabilityPtr  The offset of the first capability in PCI
+                                config space, taken from the standard PCI
+                                device header.
+
+  @retval EFI_SUCCESS  Traversal successful.
+
+  @return              Error codes from the ReadConfigSpace() and GetBarType()
+                       helper functions.
+*/
+STATIC
+EFI_STATUS
+ParseCapabilities (
+  IN OUT VIRTIO_1_0_DEV *Device,
+  IN     UINT8          CapabilityPtr
+  )
+{
+  UINT32              Offset;
+  VIRTIO_PCI_CAP_LINK CapLink;
+
+  for (Offset = CapabilityPtr & 0xFC;
+       Offset > 0;
+       Offset = CapLink.CapNext & 0xFC
+       ) {
+    EFI_STATUS        Status;
+    UINT8             CapLen;
+    VIRTIO_PCI_CAP    VirtIoCap;
+    VIRTIO_1_0_CONFIG *ParsedConfig;
+
+    //
+    // Read capability identifier and link to next capability.
+    //
+    Status = ReadConfigSpace (Device->PciIo, &Offset, sizeof CapLink,
+               &CapLink);
+    if (EFI_ERROR (Status)) {
+      return Status;
+    }
+    if (CapLink.CapId != 0x09) {
+      //
+      // Not a vendor-specific capability, move to the next one.
+      //
+      continue;
+    }
+
+    //
+    // Big enough to accommodate a VIRTIO_PCI_CAP structure?
+    //
+    Status = ReadConfigSpace (Device->PciIo, &Offset, sizeof CapLen, &CapLen);
+    if (EFI_ERROR (Status)) {
+      return Status;
+    }
+    if (CapLen < sizeof CapLink + sizeof CapLen + sizeof VirtIoCap) {
+      //
+      // Too small, move to next.
+      //
+      continue;
+    }
+
+    //
+    // Read interesting part of capability.
+    //
+    Status = ReadConfigSpace (Device->PciIo, &Offset, sizeof VirtIoCap,
+               &VirtIoCap);
+    if (EFI_ERROR (Status)) {
+      return Status;
+    }
+    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)) {
+      return Status;
+    }
+    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 CapLink + sizeof CapLen + sizeof VirtIoCap +
+                   sizeof Device->NotifyOffsetMultiplier) {
+        //
+        // Too small, move to next.
+        //
+        continue;
+      }
+
+      Status = ReadConfigSpace (Device->PciIo, &Offset,
+                 sizeof Device->NotifyOffsetMultiplier,
+                 &Device->NotifyOffsetMultiplier);
+      if (EFI_ERROR (Status)) {
+        return Status;
+      }
+    }
+
+    //
+    // Capability parsed successfully.
+    //
+    ParsedConfig->Exists = TRUE;
+  }
+
+  return EFI_SUCCESS;
+}
+
+
+/**
+  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
+  )
+{
+  VIRTIO_1_0_DEV *Dev;
+  EFI_STATUS     Status;
+  UINT64         Address;
+  UINT16         Enable;
+
+  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);
+
+  Address = (UINTN)Ring->Desc;
+  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;
+  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;
+  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 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
+};
+
+
+//
+// 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;
+  }
+
+  //
+  // 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) {
+    Status = EFI_SUCCESS;
+  } else {
+    Status = EFI_UNSUPPORTED;
+  }
+
+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, Pci.Device.CapabilityPtr);
+  if (EFI_ERROR (Status)) {
+    goto ClosePciIo;
+  }
+
+  Status = Device->PciIo->Attributes (Device->PciIo,
+                            EfiPciIoAttributeOperationGet, 0,
+                            &Device->OriginalPciAttributes);
+  if (EFI_ERROR (Status)) {
+    goto ClosePciIo;
+  }
+
+  SetAttributes = 0;
+  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
+           );
+}