]> git.proxmox.com Git - mirror_edk2.git/blobdiff - OvmfPkg/Virtio10Dxe/Virtio10.c
OvmfPkg: Virtio10Dxe: non-transitional driver for virtio-1.0 PCI devices
[mirror_edk2.git] / OvmfPkg / Virtio10Dxe / Virtio10.c
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