OvmfPkg: Virtio10Dxe: non-transitional driver for virtio-1.0 PCI devices
authorLaszlo Ersek <lersek@redhat.com>
Fri, 11 Mar 2016 23:11:55 +0000 (00:11 +0100)
committerLaszlo Ersek <lersek@redhat.com>
Wed, 6 Apr 2016 17:21:51 +0000 (19:21 +0200)
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>
OvmfPkg/Virtio10Dxe/Virtio10.c [new file with mode: 0644]
OvmfPkg/Virtio10Dxe/Virtio10.h [new file with mode: 0644]
OvmfPkg/Virtio10Dxe/Virtio10.inf [new file with mode: 0644]

diff --git a/OvmfPkg/Virtio10Dxe/Virtio10.c b/OvmfPkg/Virtio10Dxe/Virtio10.c
new file mode 100644 (file)
index 0000000..06f0699
--- /dev/null
@@ -0,0 +1,1061 @@
+/** @file\r
+  A non-transitional driver for VirtIo 1.0 PCI devices.\r
+\r
+  Copyright (C) 2016, Red Hat, Inc.\r
+\r
+  This program and the accompanying materials are licensed and made available\r
+  under the terms and conditions of the BSD License which accompanies this\r
+  distribution. The full text of the license may be found at\r
+  http://opensource.org/licenses/bsd-license.php\r
+\r
+  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT\r
+  WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
+**/\r
+\r
+#include <IndustryStandard/Pci.h>\r
+#include <IndustryStandard/Virtio.h>\r
+#include <Protocol/PciIo.h>\r
+#include <Protocol/VirtioDevice.h>\r
+#include <Library/BaseMemoryLib.h>\r
+#include <Library/DebugLib.h>\r
+#include <Library/MemoryAllocationLib.h>\r
+#include <Library/UefiBootServicesTableLib.h>\r
+#include <Library/UefiLib.h>\r
+\r
+#include "Virtio10.h"\r
+\r
+\r
+//\r
+// Utility functions\r
+//\r
+\r
+/**\r
+  Transfer data between the caller and a register in a virtio-1.0 register\r
+  block.\r
+\r
+  @param[in]     PciIo        The EFI_PCI_IO_PROTOCOL instance that represents\r
+                              the device.\r
+\r
+  @param[in]     Config       The "fat pointer" structure that identifies the\r
+                              register block to access.\r
+\r
+  @param[in]     Write        TRUE if the register should be written, FALSE if\r
+                              the register should be read.\r
+\r
+  @param[in]     FieldOffset  The offset of the register within the register\r
+                              block.\r
+\r
+  @param[in]     FieldSize    The size of the register within the register\r
+                              block. Can be one of 1, 2, 4 and 8. Accesses to\r
+                              8-byte registers are broken up into two 4-byte\r
+                              accesses.\r
+\r
+  @param[in,out] Buffer       When Write is TRUE, the register is written with\r
+                              data from Buffer. When Write is FALSE, the caller\r
+                              receives the register value into Buffer.\r
+\r
+  @retval  EFI_SUCCESS            Register access successful.\r
+\r
+  @retval  EFI_INVALID_PARAMETER  The register block pointed-to by Config\r
+                                  doesn't exist; or FieldOffset and FieldSize\r
+                                  would overflow the register block; or\r
+                                  FieldSize is invalid.\r
+\r
+  @return                         Error codes from\r
+                                  EFI_PCI_IO_PROTOCOL.(Io|Mem).(Read|Write)\r
+                                  member functions.\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+Virtio10Transfer (\r
+  IN     EFI_PCI_IO_PROTOCOL *PciIo,\r
+  IN     VIRTIO_1_0_CONFIG   *Config,\r
+  IN     BOOLEAN             Write,\r
+  IN     UINTN               FieldOffset,\r
+  IN     UINTN               FieldSize,\r
+  IN OUT VOID                *Buffer\r
+  )\r
+{\r
+  UINTN                      Count;\r
+  EFI_PCI_IO_PROTOCOL_WIDTH  Width;\r
+  EFI_PCI_IO_PROTOCOL_ACCESS *BarType;\r
+  EFI_PCI_IO_PROTOCOL_IO_MEM Access;\r
+\r
+  if (!Config->Exists ||\r
+      FieldSize > Config->Length ||\r
+      FieldOffset > Config->Length - FieldSize) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  Count = 1;\r
+  switch (FieldSize) {\r
+    case 1:\r
+      Width = EfiPciIoWidthUint8;\r
+      break;\r
+\r
+    case 2:\r
+      Width = EfiPciIoWidthUint16;\r
+      break;\r
+\r
+    case 8:\r
+      Count = 2;\r
+      //\r
+      // fall through\r
+      //\r
+\r
+    case 4:\r
+      Width = EfiPciIoWidthUint32;\r
+      break;\r
+\r
+    default:\r
+      return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  BarType = (Config->BarType == Virtio10BarTypeMem) ? &PciIo->Mem : &PciIo->Io;\r
+  Access = Write ? BarType->Write : BarType->Read;\r
+\r
+  return Access (PciIo, Width, Config->Bar, Config->Offset + FieldOffset,\r
+           Count, Buffer);\r
+}\r
+\r
+\r
+/**\r
+  Determine if a PCI BAR is IO or MMIO.\r
+\r
+  @param[in]  PciIo     The EFI_PCI_IO_PROTOCOL instance that represents the\r
+                        device.\r
+\r
+  @param[in]  BarIndex  The number of the BAR whose type the caller is\r
+                        interested in.\r
+\r
+  @param[out] BarType   On output, a VIRTIO_1_0_BAR_TYPE value that gives the\r
+                        type of the BAR.\r
+\r
+  @retval EFI_SUCCESS      The BAR type has been recognized and stored in\r
+                           BarType.\r
+\r
+  @retval EFI_UNSUPPORTED  The BAR type couldn't be identified.\r
+\r
+  @return                  Error codes from\r
+                           EFI_PCI_IO_PROTOCOL.GetBarAttributes().\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+GetBarType (\r
+  IN  EFI_PCI_IO_PROTOCOL *PciIo,\r
+  IN  UINT8               BarIndex,\r
+  OUT VIRTIO_1_0_BAR_TYPE *BarType\r
+  )\r
+{\r
+  EFI_STATUS Status;\r
+  VOID       *Resources;\r
+\r
+  Status = PciIo->GetBarAttributes (PciIo, BarIndex, NULL, &Resources);\r
+  if (EFI_ERROR (Status)) {\r
+    return Status;\r
+  }\r
+\r
+  Status = EFI_UNSUPPORTED;\r
+\r
+  if (*(UINT8 *)Resources == ACPI_QWORD_ADDRESS_SPACE_DESCRIPTOR) {\r
+    EFI_ACPI_QWORD_ADDRESS_SPACE_DESCRIPTOR *Descriptor;\r
+\r
+    Descriptor = Resources;\r
+    switch (Descriptor->ResType) {\r
+    case ACPI_ADDRESS_SPACE_TYPE_MEM:\r
+      *BarType = Virtio10BarTypeMem;\r
+      Status = EFI_SUCCESS;\r
+      break;\r
+\r
+    case ACPI_ADDRESS_SPACE_TYPE_IO:\r
+      *BarType = Virtio10BarTypeIo;\r
+      Status = EFI_SUCCESS;\r
+      break;\r
+\r
+    default:\r
+      break;\r
+    }\r
+  }\r
+\r
+  FreePool (Resources);\r
+  return Status;\r
+}\r
+\r
+\r
+/**\r
+  Read a slice from PCI config space at the given offset, then advance the\r
+  offset.\r
+\r
+  @param [in]     PciIo   The EFI_PCI_IO_PROTOCOL instance that represents the\r
+                          device.\r
+\r
+  @param [in,out] Offset  On input, the offset in PCI config space to start\r
+                          reading from. On output, the offset of the first byte\r
+                          that was not read. On error, Offset is not modified.\r
+\r
+  @param [in]     Size    The number of bytes to read.\r
+\r
+  @param [out]    Buffer  On output, the bytes read from PCI config space are\r
+                          stored in this object.\r
+\r
+  @retval EFI_SUCCESS  Size bytes have been transferred from PCI config space\r
+                       (from Offset) to Buffer, and Offset has been incremented\r
+                       by Size.\r
+\r
+  @return              Error codes from PciIo->Pci.Read().\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+ReadConfigSpace (\r
+  IN     EFI_PCI_IO_PROTOCOL *PciIo,\r
+  IN OUT UINT32              *Offset,\r
+  IN     UINTN               Size,\r
+     OUT VOID                *Buffer\r
+  )\r
+{\r
+  EFI_STATUS Status;\r
+\r
+  Status = PciIo->Pci.Read (PciIo, EfiPciIoWidthUint8, *Offset, Size, Buffer);\r
+  if (EFI_ERROR (Status)) {\r
+    return Status;\r
+  }\r
+  *Offset += (UINT32)Size;\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+\r
+/*\r
+  Traverse the PCI capabilities list of a virtio-1.0 device, and capture the\r
+  locations of the interesting virtio-1.0 register blocks.\r
+\r
+  @param[in,out] Device         The VIRTIO_1_0_DEV structure that identifies\r
+                                the device. On input, the caller is responsible\r
+                                that the Device->PciIo member be live, and that\r
+                                the CommonConfig, NotifyConfig,\r
+                                NotifyOffsetMultiplier and SpecificConfig\r
+                                members be zeroed. On output,  said members\r
+                                will have been updated from the PCI\r
+                                capabilities found.\r
+\r
+  @param[in]     CapabilityPtr  The offset of the first capability in PCI\r
+                                config space, taken from the standard PCI\r
+                                device header.\r
+\r
+  @retval EFI_SUCCESS  Traversal successful.\r
+\r
+  @return              Error codes from the ReadConfigSpace() and GetBarType()\r
+                       helper functions.\r
+*/\r
+STATIC\r
+EFI_STATUS\r
+ParseCapabilities (\r
+  IN OUT VIRTIO_1_0_DEV *Device,\r
+  IN     UINT8          CapabilityPtr\r
+  )\r
+{\r
+  UINT32              Offset;\r
+  VIRTIO_PCI_CAP_LINK CapLink;\r
+\r
+  for (Offset = CapabilityPtr & 0xFC;\r
+       Offset > 0;\r
+       Offset = CapLink.CapNext & 0xFC\r
+       ) {\r
+    EFI_STATUS        Status;\r
+    UINT8             CapLen;\r
+    VIRTIO_PCI_CAP    VirtIoCap;\r
+    VIRTIO_1_0_CONFIG *ParsedConfig;\r
+\r
+    //\r
+    // Read capability identifier and link to next capability.\r
+    //\r
+    Status = ReadConfigSpace (Device->PciIo, &Offset, sizeof CapLink,\r
+               &CapLink);\r
+    if (EFI_ERROR (Status)) {\r
+      return Status;\r
+    }\r
+    if (CapLink.CapId != 0x09) {\r
+      //\r
+      // Not a vendor-specific capability, move to the next one.\r
+      //\r
+      continue;\r
+    }\r
+\r
+    //\r
+    // Big enough to accommodate a VIRTIO_PCI_CAP structure?\r
+    //\r
+    Status = ReadConfigSpace (Device->PciIo, &Offset, sizeof CapLen, &CapLen);\r
+    if (EFI_ERROR (Status)) {\r
+      return Status;\r
+    }\r
+    if (CapLen < sizeof CapLink + sizeof CapLen + sizeof VirtIoCap) {\r
+      //\r
+      // Too small, move to next.\r
+      //\r
+      continue;\r
+    }\r
+\r
+    //\r
+    // Read interesting part of capability.\r
+    //\r
+    Status = ReadConfigSpace (Device->PciIo, &Offset, sizeof VirtIoCap,\r
+               &VirtIoCap);\r
+    if (EFI_ERROR (Status)) {\r
+      return Status;\r
+    }\r
+    switch (VirtIoCap.ConfigType) {\r
+    case VIRTIO_PCI_CAP_COMMON_CFG:\r
+      ParsedConfig = &Device->CommonConfig;\r
+      break;\r
+    case VIRTIO_PCI_CAP_NOTIFY_CFG:\r
+      ParsedConfig = &Device->NotifyConfig;\r
+      break;\r
+    case VIRTIO_PCI_CAP_DEVICE_CFG:\r
+      ParsedConfig = &Device->SpecificConfig;\r
+      break;\r
+    default:\r
+      //\r
+      // Capability is not interesting.\r
+      //\r
+      continue;\r
+    }\r
+\r
+    //\r
+    // Save the location of the register block into ParsedConfig.\r
+    //\r
+    Status = GetBarType (Device->PciIo, VirtIoCap.Bar, &ParsedConfig->BarType);\r
+    if (EFI_ERROR (Status)) {\r
+      return Status;\r
+    }\r
+    ParsedConfig->Bar    = VirtIoCap.Bar;\r
+    ParsedConfig->Offset = VirtIoCap.Offset;\r
+    ParsedConfig->Length = VirtIoCap.Length;\r
+\r
+    if (VirtIoCap.ConfigType == VIRTIO_PCI_CAP_NOTIFY_CFG) {\r
+      //\r
+      // This capability has an additional field called NotifyOffsetMultiplier;\r
+      // parse it too.\r
+      //\r
+      if (CapLen < sizeof CapLink + sizeof CapLen + sizeof VirtIoCap +\r
+                   sizeof Device->NotifyOffsetMultiplier) {\r
+        //\r
+        // Too small, move to next.\r
+        //\r
+        continue;\r
+      }\r
+\r
+      Status = ReadConfigSpace (Device->PciIo, &Offset,\r
+                 sizeof Device->NotifyOffsetMultiplier,\r
+                 &Device->NotifyOffsetMultiplier);\r
+      if (EFI_ERROR (Status)) {\r
+        return Status;\r
+      }\r
+    }\r
+\r
+    //\r
+    // Capability parsed successfully.\r
+    //\r
+    ParsedConfig->Exists = TRUE;\r
+  }\r
+\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+\r
+/**\r
+  Accumulate the BAR type of a virtio-1.0 register block into a UINT64\r
+  attribute map, such that the latter is suitable for enabling IO / MMIO\r
+  decoding with EFI_PCI_IO_PROTOCOL.Attributes().\r
+\r
+  @param[in]     Config      The "fat pointer" structure that identifies the\r
+                             register block. It is allowed for the register\r
+                             block not to exist.\r
+\r
+  @param[in,out] Attributes  On output, if the register block exists,\r
+                             EFI_PCI_IO_ATTRIBUTE_MEMORY or\r
+                             EFI_PCI_IO_ATTRIBUTE_IO is OR-ed into Attributes,\r
+                             according to the register block's BAR type.\r
+**/\r
+STATIC\r
+VOID\r
+UpdateAttributes (\r
+  IN     VIRTIO_1_0_CONFIG *Config,\r
+  IN OUT UINT64            *Attributes\r
+  )\r
+{\r
+  if (Config->Exists) {\r
+    *Attributes |= (Config->BarType == Virtio10BarTypeMem) ?\r
+                     EFI_PCI_IO_ATTRIBUTE_MEMORY:\r
+                     EFI_PCI_IO_ATTRIBUTE_IO;\r
+  }\r
+}\r
+\r
+\r
+//\r
+// VIRTIO_DEVICE_PROTOCOL member functions\r
+//\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+Virtio10GetDeviceFeatures (\r
+  IN VIRTIO_DEVICE_PROTOCOL *This,\r
+  OUT UINT64                *DeviceFeatures\r
+  )\r
+{\r
+  VIRTIO_1_0_DEV *Dev;\r
+  UINT32         Selector;\r
+  UINT32         Features32[2];\r
+\r
+  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);\r
+\r
+  for (Selector = 0; Selector < 2; ++Selector) {\r
+    EFI_STATUS Status;\r
+\r
+    //\r
+    // Select the low or high half of the features.\r
+    //\r
+    Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE,\r
+               OFFSET_OF (VIRTIO_PCI_COMMON_CFG, DeviceFeatureSelect),\r
+               sizeof Selector, &Selector);\r
+    if (EFI_ERROR (Status)) {\r
+      return Status;\r
+    }\r
+\r
+    //\r
+    // Fetch that half.\r
+    //\r
+    Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, FALSE,\r
+               OFFSET_OF (VIRTIO_PCI_COMMON_CFG, DeviceFeature),\r
+               sizeof Features32[Selector], &Features32[Selector]);\r
+    if (EFI_ERROR (Status)) {\r
+      return Status;\r
+    }\r
+  }\r
+\r
+  *DeviceFeatures = LShiftU64 (Features32[1], 32) | Features32[0];\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+Virtio10SetGuestFeatures (\r
+  IN VIRTIO_DEVICE_PROTOCOL  *This,\r
+  IN UINT64                   Features\r
+  )\r
+{\r
+  VIRTIO_1_0_DEV *Dev;\r
+  UINT32         Selector;\r
+  UINT32         Features32[2];\r
+\r
+  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);\r
+\r
+  Features32[0] = (UINT32)Features;\r
+  Features32[1] = (UINT32)RShiftU64 (Features, 32);\r
+\r
+  for (Selector = 0; Selector < 2; ++Selector) {\r
+    EFI_STATUS Status;\r
+\r
+    //\r
+    // Select the low or high half of the features.\r
+    //\r
+    Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE,\r
+               OFFSET_OF (VIRTIO_PCI_COMMON_CFG, DriverFeatureSelect),\r
+               sizeof Selector, &Selector);\r
+    if (EFI_ERROR (Status)) {\r
+      return Status;\r
+    }\r
+\r
+    //\r
+    // Write that half.\r
+    //\r
+    Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE,\r
+               OFFSET_OF (VIRTIO_PCI_COMMON_CFG, DriverFeature),\r
+               sizeof Features32[Selector], &Features32[Selector]);\r
+    if (EFI_ERROR (Status)) {\r
+      return Status;\r
+    }\r
+  }\r
+\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+Virtio10SetQueueAddress (\r
+  IN VIRTIO_DEVICE_PROTOCOL  *This,\r
+  IN VRING                   *Ring\r
+  )\r
+{\r
+  VIRTIO_1_0_DEV *Dev;\r
+  EFI_STATUS     Status;\r
+  UINT64         Address;\r
+  UINT16         Enable;\r
+\r
+  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);\r
+\r
+  Address = (UINTN)Ring->Desc;\r
+  Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE,\r
+             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueDesc),\r
+             sizeof Address, &Address);\r
+  if (EFI_ERROR (Status)) {\r
+    return Status;\r
+  }\r
+\r
+  Address = (UINTN)Ring->Avail.Flags;\r
+  Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE,\r
+             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueAvail),\r
+             sizeof Address, &Address);\r
+  if (EFI_ERROR (Status)) {\r
+    return Status;\r
+  }\r
+\r
+  Address = (UINTN)Ring->Used.Flags;\r
+  Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE,\r
+             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueUsed),\r
+             sizeof Address, &Address);\r
+  if (EFI_ERROR (Status)) {\r
+    return Status;\r
+  }\r
+\r
+  Enable = 1;\r
+  Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE,\r
+             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueEnable),\r
+             sizeof Enable, &Enable);\r
+  return Status;\r
+}\r
+\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+Virtio10SetQueueSel (\r
+  IN VIRTIO_DEVICE_PROTOCOL  *This,\r
+  IN UINT16                   Index\r
+  )\r
+{\r
+  VIRTIO_1_0_DEV *Dev;\r
+  EFI_STATUS     Status;\r
+\r
+  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);\r
+\r
+  Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE,\r
+             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueSelect),\r
+             sizeof Index, &Index);\r
+  return Status;\r
+}\r
+\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+Virtio10SetQueueNotify (\r
+  IN VIRTIO_DEVICE_PROTOCOL  *This,\r
+  IN UINT16                   Index\r
+  )\r
+{\r
+  VIRTIO_1_0_DEV *Dev;\r
+  EFI_STATUS     Status;\r
+  UINT16         SavedQueueSelect;\r
+  UINT16         NotifyOffset;\r
+\r
+  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);\r
+\r
+  //\r
+  // Read NotifyOffset first. NotifyOffset is queue specific, so we have\r
+  // to stash & restore the current queue selector around it.\r
+  //\r
+  // So, start with saving the current queue selector.\r
+  //\r
+  Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, FALSE,\r
+             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueSelect),\r
+             sizeof SavedQueueSelect, &SavedQueueSelect);\r
+  if (EFI_ERROR (Status)) {\r
+    return Status;\r
+  }\r
+\r
+  //\r
+  // Select the requested queue.\r
+  //\r
+  Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE,\r
+             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueSelect),\r
+             sizeof Index, &Index);\r
+  if (EFI_ERROR (Status)) {\r
+    return Status;\r
+  }\r
+\r
+  //\r
+  // Read the QueueNotifyOff field.\r
+  //\r
+  Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, FALSE,\r
+             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueNotifyOff),\r
+             sizeof NotifyOffset, &NotifyOffset);\r
+  if (EFI_ERROR (Status)) {\r
+    return Status;\r
+  }\r
+\r
+  //\r
+  // Re-select the original queue.\r
+  //\r
+  Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE,\r
+             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueSelect),\r
+             sizeof SavedQueueSelect, &SavedQueueSelect);\r
+  if (EFI_ERROR (Status)) {\r
+    return Status;\r
+  }\r
+\r
+  //\r
+  // We can now kick the queue.\r
+  //\r
+  Status = Virtio10Transfer (Dev->PciIo, &Dev->NotifyConfig, TRUE,\r
+             NotifyOffset * Dev->NotifyOffsetMultiplier,\r
+             sizeof Index, &Index);\r
+  return Status;\r
+}\r
+\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+Virtio10SetQueueAlign (\r
+  IN VIRTIO_DEVICE_PROTOCOL  *This,\r
+  IN UINT32                   Alignment\r
+  )\r
+{\r
+  return (Alignment == EFI_PAGE_SIZE) ? EFI_SUCCESS : EFI_UNSUPPORTED;\r
+}\r
+\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+Virtio10SetPageSize (\r
+  IN VIRTIO_DEVICE_PROTOCOL  *This,\r
+  IN UINT32                   PageSize\r
+  )\r
+{\r
+  return (PageSize == EFI_PAGE_SIZE) ? EFI_SUCCESS : EFI_UNSUPPORTED;\r
+}\r
+\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+Virtio10GetQueueNumMax (\r
+  IN  VIRTIO_DEVICE_PROTOCOL  *This,\r
+  OUT UINT16                  *QueueNumMax\r
+  )\r
+{\r
+  VIRTIO_1_0_DEV *Dev;\r
+  EFI_STATUS     Status;\r
+\r
+  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);\r
+\r
+  Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, FALSE,\r
+             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, QueueSize),\r
+             sizeof *QueueNumMax, QueueNumMax);\r
+  return Status;\r
+}\r
+\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+Virtio10SetQueueNum (\r
+  IN VIRTIO_DEVICE_PROTOCOL  *This,\r
+  IN UINT16                   QueueSize\r
+  )\r
+{\r
+  EFI_STATUS     Status;\r
+  UINT16         CurrentSize;\r
+\r
+  //\r
+  // This member function is required for VirtIo MMIO, and a no-op in\r
+  // VirtIo PCI 0.9.5. In VirtIo 1.0, drivers can theoretically use this\r
+  // member to reduce memory consumption, but none of our drivers do. So\r
+  // just check that they set the size that is already in effect.\r
+  //\r
+  Status = Virtio10GetQueueNumMax (This, &CurrentSize);\r
+  if (EFI_ERROR (Status)) {\r
+    return Status;\r
+  }\r
+  return (CurrentSize == QueueSize) ? EFI_SUCCESS : EFI_UNSUPPORTED;\r
+}\r
+\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+Virtio10GetDeviceStatus (\r
+  IN  VIRTIO_DEVICE_PROTOCOL  *This,\r
+  OUT UINT8                   *DeviceStatus\r
+  )\r
+{\r
+  VIRTIO_1_0_DEV *Dev;\r
+  EFI_STATUS     Status;\r
+\r
+  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);\r
+\r
+  Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, FALSE,\r
+             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, DeviceStatus),\r
+             sizeof *DeviceStatus, DeviceStatus);\r
+  return Status;\r
+}\r
+\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+Virtio10SetDeviceStatus (\r
+  IN VIRTIO_DEVICE_PROTOCOL  *This,\r
+  IN UINT8                   DeviceStatus\r
+  )\r
+{\r
+  VIRTIO_1_0_DEV *Dev;\r
+  EFI_STATUS     Status;\r
+\r
+  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);\r
+\r
+  Status = Virtio10Transfer (Dev->PciIo, &Dev->CommonConfig, TRUE,\r
+             OFFSET_OF (VIRTIO_PCI_COMMON_CFG, DeviceStatus),\r
+             sizeof DeviceStatus, &DeviceStatus);\r
+  return Status;\r
+}\r
+\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+Virtio10WriteDevice (\r
+  IN VIRTIO_DEVICE_PROTOCOL *This,\r
+  IN UINTN                  FieldOffset,\r
+  IN UINTN                  FieldSize,\r
+  IN UINT64                 Value\r
+  )\r
+{\r
+  VIRTIO_1_0_DEV *Dev;\r
+  EFI_STATUS     Status;\r
+\r
+  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);\r
+\r
+  Status = Virtio10Transfer (Dev->PciIo, &Dev->SpecificConfig, TRUE,\r
+             FieldOffset, FieldSize, &Value);\r
+  return Status;\r
+}\r
+\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+Virtio10ReadDevice (\r
+  IN  VIRTIO_DEVICE_PROTOCOL *This,\r
+  IN  UINTN                  FieldOffset,\r
+  IN  UINTN                  FieldSize,\r
+  IN  UINTN                  BufferSize,\r
+  OUT VOID                   *Buffer\r
+  )\r
+{\r
+  VIRTIO_1_0_DEV *Dev;\r
+  EFI_STATUS     Status;\r
+\r
+  if (FieldSize != BufferSize) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  Dev = VIRTIO_1_0_FROM_VIRTIO_DEVICE (This);\r
+\r
+  Status = Virtio10Transfer (Dev->PciIo, &Dev->SpecificConfig, FALSE,\r
+             FieldOffset, FieldSize, Buffer);\r
+  return Status;\r
+}\r
+\r
+\r
+STATIC CONST VIRTIO_DEVICE_PROTOCOL mVirtIoTemplate = {\r
+  VIRTIO_SPEC_REVISION (1, 0, 0),\r
+  0,                              // SubSystemDeviceId, filled in dynamically\r
+  Virtio10GetDeviceFeatures,\r
+  Virtio10SetGuestFeatures,\r
+  Virtio10SetQueueAddress,\r
+  Virtio10SetQueueSel,\r
+  Virtio10SetQueueNotify,\r
+  Virtio10SetQueueAlign,\r
+  Virtio10SetPageSize,\r
+  Virtio10GetQueueNumMax,\r
+  Virtio10SetQueueNum,\r
+  Virtio10GetDeviceStatus,\r
+  Virtio10SetDeviceStatus,\r
+  Virtio10WriteDevice,\r
+  Virtio10ReadDevice\r
+};\r
+\r
+\r
+//\r
+// EFI_DRIVER_BINDING_PROTOCOL member functions\r
+//\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+Virtio10BindingSupported (\r
+  IN EFI_DRIVER_BINDING_PROTOCOL *This,\r
+  IN EFI_HANDLE                  DeviceHandle,\r
+  IN EFI_DEVICE_PATH_PROTOCOL    *RemainingDevicePath\r
+  )\r
+{\r
+  EFI_STATUS          Status;\r
+  EFI_PCI_IO_PROTOCOL *PciIo;\r
+  PCI_TYPE00          Pci;\r
+\r
+  Status = gBS->OpenProtocol (DeviceHandle, &gEfiPciIoProtocolGuid,\r
+                  (VOID **)&PciIo, This->DriverBindingHandle,\r
+                  DeviceHandle, EFI_OPEN_PROTOCOL_BY_DRIVER);\r
+  if (EFI_ERROR (Status)) {\r
+    return Status;\r
+  }\r
+\r
+  Status = PciIo->Pci.Read (PciIo, EfiPciIoWidthUint32, 0,\r
+                        sizeof Pci / sizeof (UINT32), &Pci);\r
+  if (EFI_ERROR (Status)) {\r
+    goto CloseProtocol;\r
+  }\r
+\r
+  //\r
+  // Recognize non-transitional modern devices. Also, we'll have to parse the\r
+  // PCI capability list, so make sure the CapabilityPtr field will be valid.\r
+  //\r
+  if (Pci.Hdr.VendorId == VIRTIO_VENDOR_ID &&\r
+      Pci.Hdr.DeviceId >= 0x1040 &&\r
+      Pci.Hdr.DeviceId <= 0x107F &&\r
+      Pci.Hdr.RevisionID >= 0x01 &&\r
+      Pci.Device.SubsystemID >= 0x40 &&\r
+      (Pci.Hdr.Status & EFI_PCI_STATUS_CAPABILITY) != 0) {\r
+    Status = EFI_SUCCESS;\r
+  } else {\r
+    Status = EFI_UNSUPPORTED;\r
+  }\r
+\r
+CloseProtocol:\r
+  gBS->CloseProtocol (DeviceHandle, &gEfiPciIoProtocolGuid,\r
+         This->DriverBindingHandle, DeviceHandle);\r
+\r
+  return Status;\r
+}\r
+\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+Virtio10BindingStart (\r
+  IN EFI_DRIVER_BINDING_PROTOCOL *This,\r
+  IN EFI_HANDLE                  DeviceHandle,\r
+  IN EFI_DEVICE_PATH_PROTOCOL    *RemainingDevicePath\r
+  )\r
+{\r
+  VIRTIO_1_0_DEV *Device;\r
+  EFI_STATUS     Status;\r
+  PCI_TYPE00     Pci;\r
+  UINT64         SetAttributes;\r
+\r
+  Device = AllocateZeroPool (sizeof *Device);\r
+  if (Device == NULL) {\r
+    return EFI_OUT_OF_RESOURCES;\r
+  }\r
+\r
+  Device->Signature = VIRTIO_1_0_SIGNATURE;\r
+  CopyMem (&Device->VirtIo, &mVirtIoTemplate, sizeof mVirtIoTemplate);\r
+\r
+  Status = gBS->OpenProtocol (DeviceHandle, &gEfiPciIoProtocolGuid,\r
+                  (VOID **)&Device->PciIo, This->DriverBindingHandle,\r
+                  DeviceHandle, EFI_OPEN_PROTOCOL_BY_DRIVER);\r
+  if (EFI_ERROR (Status)) {\r
+    goto FreeDevice;\r
+  }\r
+\r
+  Status = Device->PciIo->Pci.Read (Device->PciIo, EfiPciIoWidthUint32, 0,\r
+                                sizeof Pci / sizeof (UINT32), &Pci);\r
+  if (EFI_ERROR (Status)) {\r
+    goto ClosePciIo;\r
+  }\r
+\r
+  Device->VirtIo.SubSystemDeviceId = Pci.Hdr.DeviceId - 0x1040;\r
+\r
+  Status = ParseCapabilities (Device, Pci.Device.CapabilityPtr);\r
+  if (EFI_ERROR (Status)) {\r
+    goto ClosePciIo;\r
+  }\r
+\r
+  Status = Device->PciIo->Attributes (Device->PciIo,\r
+                            EfiPciIoAttributeOperationGet, 0,\r
+                            &Device->OriginalPciAttributes);\r
+  if (EFI_ERROR (Status)) {\r
+    goto ClosePciIo;\r
+  }\r
+\r
+  SetAttributes = 0;\r
+  UpdateAttributes (&Device->CommonConfig, &SetAttributes);\r
+  UpdateAttributes (&Device->NotifyConfig, &SetAttributes);\r
+  UpdateAttributes (&Device->SpecificConfig, &SetAttributes);\r
+  Status = Device->PciIo->Attributes (Device->PciIo,\r
+                            EfiPciIoAttributeOperationEnable, SetAttributes,\r
+                            NULL);\r
+  if (EFI_ERROR (Status)) {\r
+    goto ClosePciIo;\r
+  }\r
+\r
+  Status = gBS->InstallProtocolInterface (&DeviceHandle,\r
+                  &gVirtioDeviceProtocolGuid, EFI_NATIVE_INTERFACE,\r
+                  &Device->VirtIo);\r
+  if (EFI_ERROR (Status)) {\r
+    goto RestorePciAttributes;\r
+  }\r
+\r
+  return EFI_SUCCESS;\r
+\r
+RestorePciAttributes:\r
+  Device->PciIo->Attributes (Device->PciIo, EfiPciIoAttributeOperationSet,\r
+                   Device->OriginalPciAttributes, NULL);\r
+\r
+ClosePciIo:\r
+  gBS->CloseProtocol (DeviceHandle, &gEfiPciIoProtocolGuid,\r
+         This->DriverBindingHandle, DeviceHandle);\r
+\r
+FreeDevice:\r
+  FreePool (Device);\r
+\r
+  return Status;\r
+}\r
+\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+Virtio10BindingStop (\r
+  IN EFI_DRIVER_BINDING_PROTOCOL *This,\r
+  IN EFI_HANDLE                  DeviceHandle,\r
+  IN UINTN                       NumberOfChildren,\r
+  IN EFI_HANDLE                  *ChildHandleBuffer\r
+  )\r
+{\r
+  EFI_STATUS             Status;\r
+  VIRTIO_DEVICE_PROTOCOL *VirtIo;\r
+  VIRTIO_1_0_DEV         *Device;\r
+\r
+  Status = gBS->OpenProtocol (DeviceHandle, &gVirtioDeviceProtocolGuid,\r
+                  (VOID **)&VirtIo, This->DriverBindingHandle,\r
+                  DeviceHandle, EFI_OPEN_PROTOCOL_GET_PROTOCOL);\r
+  if (EFI_ERROR (Status)) {\r
+    return Status;\r
+  }\r
+\r
+  Device = VIRTIO_1_0_FROM_VIRTIO_DEVICE (VirtIo);\r
+\r
+  Status = gBS->UninstallProtocolInterface (DeviceHandle,\r
+                  &gVirtioDeviceProtocolGuid, &Device->VirtIo);\r
+  if (EFI_ERROR (Status)) {\r
+    return Status;\r
+  }\r
+\r
+  Device->PciIo->Attributes (Device->PciIo, EfiPciIoAttributeOperationSet,\r
+                   Device->OriginalPciAttributes, NULL);\r
+  gBS->CloseProtocol (DeviceHandle, &gEfiPciIoProtocolGuid,\r
+         This->DriverBindingHandle, DeviceHandle);\r
+  FreePool (Device);\r
+\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+\r
+STATIC EFI_DRIVER_BINDING_PROTOCOL mDriverBinding = {\r
+  &Virtio10BindingSupported,\r
+  &Virtio10BindingStart,\r
+  &Virtio10BindingStop,\r
+  0x10, // Version\r
+  NULL, // ImageHandle, to be overwritten\r
+  NULL  // DriverBindingHandle, to be overwritten\r
+};\r
+\r
+\r
+//\r
+// EFI_COMPONENT_NAME_PROTOCOL and EFI_COMPONENT_NAME2_PROTOCOL\r
+// implementations\r
+//\r
+\r
+STATIC\r
+EFI_UNICODE_STRING_TABLE mDriverNameTable[] = {\r
+  { "eng;en", L"Virtio 1.0 PCI Driver" },\r
+  { NULL,     NULL                     }\r
+};\r
+\r
+STATIC\r
+EFI_COMPONENT_NAME_PROTOCOL mComponentName;\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+Virtio10GetDriverName (\r
+  IN  EFI_COMPONENT_NAME_PROTOCOL *This,\r
+  IN  CHAR8                       *Language,\r
+  OUT CHAR16                      **DriverName\r
+  )\r
+{\r
+  return LookupUnicodeString2 (\r
+           Language,\r
+           This->SupportedLanguages,\r
+           mDriverNameTable,\r
+           DriverName,\r
+           (BOOLEAN)(This == &mComponentName) // Iso639Language\r
+           );\r
+}\r
+\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+Virtio10GetDeviceName (\r
+  IN  EFI_COMPONENT_NAME_PROTOCOL *This,\r
+  IN  EFI_HANDLE                  DeviceHandle,\r
+  IN  EFI_HANDLE                  ChildHandle,\r
+  IN  CHAR8                       *Language,\r
+  OUT CHAR16                      **ControllerName\r
+  )\r
+{\r
+  return EFI_UNSUPPORTED;\r
+}\r
+\r
+STATIC\r
+EFI_COMPONENT_NAME_PROTOCOL mComponentName = {\r
+  &Virtio10GetDriverName,\r
+  &Virtio10GetDeviceName,\r
+  "eng"\r
+};\r
+\r
+STATIC\r
+EFI_COMPONENT_NAME2_PROTOCOL mComponentName2 = {\r
+  (EFI_COMPONENT_NAME2_GET_DRIVER_NAME)     &Virtio10GetDriverName,\r
+  (EFI_COMPONENT_NAME2_GET_CONTROLLER_NAME) &Virtio10GetDeviceName,\r
+  "en"\r
+};\r
+\r
+\r
+//\r
+// Entry point of this driver\r
+//\r
+\r
+EFI_STATUS\r
+EFIAPI\r
+Virtio10EntryPoint (\r
+  IN EFI_HANDLE       ImageHandle,\r
+  IN EFI_SYSTEM_TABLE *SystemTable\r
+  )\r
+{\r
+  return EfiLibInstallDriverBindingComponentName2 (\r
+           ImageHandle,\r
+           SystemTable,\r
+           &mDriverBinding,\r
+           ImageHandle,\r
+           &mComponentName,\r
+           &mComponentName2\r
+           );\r
+}\r
diff --git a/OvmfPkg/Virtio10Dxe/Virtio10.h b/OvmfPkg/Virtio10Dxe/Virtio10.h
new file mode 100644 (file)
index 0000000..2fbe27f
--- /dev/null
@@ -0,0 +1,56 @@
+/** @file\r
+  Private definitions of the VirtIo 1.0 driver.\r
+\r
+  Copyright (C) 2016, Red Hat, Inc.\r
+\r
+  This program and the accompanying materials are licensed and made available\r
+  under the terms and conditions of the BSD License which accompanies this\r
+  distribution. The full text of the license may be found at\r
+  http://opensource.org/licenses/bsd-license.php\r
+\r
+  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT\r
+  WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
+**/\r
+\r
+#ifndef _VIRTIO_1_0_DXE_H_\r
+#define _VIRTIO_1_0_DXE_H_\r
+\r
+#include <Protocol/PciIo.h>\r
+#include <Protocol/VirtioDevice.h>\r
+\r
+#define VIRTIO_1_0_SIGNATURE SIGNATURE_32 ('V', 'I', 'O', '1')\r
+\r
+//\r
+// Type of the PCI BAR that contains a VirtIo 1.0 config structure.\r
+//\r
+typedef enum {\r
+  Virtio10BarTypeMem,\r
+  Virtio10BarTypeIo\r
+} VIRTIO_1_0_BAR_TYPE;\r
+\r
+//\r
+// The type below defines the access to a VirtIo 1.0 config structure.\r
+//\r
+typedef struct {\r
+  BOOLEAN             Exists;  // The device exposes this structure\r
+  VIRTIO_1_0_BAR_TYPE BarType;\r
+  UINT8               Bar;\r
+  UINT32              Offset;  // Offset into BAR where structure starts\r
+  UINT32              Length;  // Length of structure in BAR.\r
+} VIRTIO_1_0_CONFIG;\r
+\r
+typedef struct {\r
+  UINT32                 Signature;\r
+  VIRTIO_DEVICE_PROTOCOL VirtIo;\r
+  EFI_PCI_IO_PROTOCOL    *PciIo;\r
+  UINT64                 OriginalPciAttributes;\r
+  VIRTIO_1_0_CONFIG      CommonConfig;           // Common settings\r
+  VIRTIO_1_0_CONFIG      NotifyConfig;           // Notifications\r
+  UINT32                 NotifyOffsetMultiplier;\r
+  VIRTIO_1_0_CONFIG      SpecificConfig;         // Device specific settings\r
+} VIRTIO_1_0_DEV;\r
+\r
+#define VIRTIO_1_0_FROM_VIRTIO_DEVICE(Device) \\r
+          CR (Device, VIRTIO_1_0_DEV, VirtIo, VIRTIO_1_0_SIGNATURE)\r
+\r
+#endif // _VIRTIO_1_0_DXE_H_\r
diff --git a/OvmfPkg/Virtio10Dxe/Virtio10.inf b/OvmfPkg/Virtio10Dxe/Virtio10.inf
new file mode 100644 (file)
index 0000000..f868d1c
--- /dev/null
@@ -0,0 +1,40 @@
+## @file\r
+# A non-transitional driver for VirtIo 1.0 PCI devices.\r
+#\r
+# Copyright (C) 2016, Red Hat, Inc.\r
+#\r
+# This program and the accompanying materials are licensed and made available\r
+# under the terms and conditions of the BSD License which accompanies this\r
+# distribution. The full text of the license may be found at\r
+# http://opensource.org/licenses/bsd-license.php\r
+#\r
+# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT\r
+# WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
+##\r
+\r
+[Defines]\r
+  INF_VERSION                    = 0x00010005\r
+  BASE_NAME                      = Virtio10\r
+  FILE_GUID                      = 0170F60C-1D40-4651-956D-F0BD9879D527\r
+  MODULE_TYPE                    = UEFI_DRIVER\r
+  VERSION_STRING                 = 1.0\r
+  ENTRY_POINT                    = Virtio10EntryPoint\r
+\r
+[Sources]\r
+  Virtio10.c\r
+\r
+[Packages]\r
+  MdePkg/MdePkg.dec\r
+  OvmfPkg/OvmfPkg.dec\r
+\r
+[LibraryClasses]\r
+  BaseMemoryLib\r
+  DebugLib\r
+  MemoryAllocationLib\r
+  UefiBootServicesTableLib\r
+  UefiDriverEntryPoint\r
+  UefiLib\r
+\r
+[Protocols]\r
+  gEfiPciIoProtocolGuid     ## TO_START\r
+  gVirtioDeviceProtocolGuid ## BY_START\r