OvmfPkg/VirtioGpuDxe: introduce with Component Name 2 and Driver Binding

This patch adds the skeleton of the driver: it implements the Component
Name 2 Protocol and the Driver  Binding Protocol, in accordance with the
generic and GOP-specific requirements set forth in the UEFI spec and the
Driver Writers' Guide.

The basic idea is that VGPU_DEV abstracts the virtio GPU device, while the
single VGPU_GOP that we intend to support at this point stands for "head"
(aka "scanout") #0.

For now, the Virtio Device Protocol is only used for driver binding; no
actual virtio operations are done yet. Similarly, we use a "dummy" GOP
GUID and protocol structure (a plain UINT8 object) for now, so that
GOP-consuming drivers don't look at what we produce just yet.

The driver is a bit different from the other virtio device drivers written
thus far:

- It implements the GetControllerName() member of the Component Name 2
  Protocol. (Formatting helpful names is recommended by UEFI.) As a "best
  effort", we format the PCI BDF into the name (a PCI backend is not
  guaranteed by VIRTIO_DEVICE_PROTOCOL). It should provide a more friendly
  experience in the shell and elsewhere.

- This driver seeks to support all RemainingDevicePath cases:
  - NULL: produce all (= one) child handles (= VGPU_GOP heads) at once,
  - End of Device Path Node: produce no child handles,
  - specific ACPI ADR Node: check if it's supportable, and produce it
    (only one specific child controller is supported).
  This is one of the reasons for separating VGPU_GOP from VGPU_DEV.

The driver is a hybrid driver: it produces both child handles (one, to be
exact), but also installs a structure (VGPU_DEV) directly on the VirtIo
controller handle, using gEfiCallerIdGuid as protocol GUID. This is a
trick I've seen elsewhere in edk2 (for example, TerminalDxe), and it is
necessary for the following reason:

In EFI_COMPONENT_NAME2_PROTOCOL.GetControllerName(), we must be able to
"cast down" a VirtIo ControllerHandle to our own private data structure
(VGPU_DEV). That's only possible if we install the structure directly on
the VirtIo ControllerHandle (thereby rendering the driver a hybrid
driver), because a child controller with our GOP implementation on it may
not exist / be passed in there.

Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Ref: https://tianocore.acgmultimedia.com/show_bug.cgi?id=66
Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Reviewed-by: Jordan Justen <jordan.l.justen@intel.com>
diff --git a/OvmfPkg/VirtioGpuDxe/DriverBinding.c b/OvmfPkg/VirtioGpuDxe/DriverBinding.c
new file mode 100644
index 0000000..b902a07
--- /dev/null
+++ b/OvmfPkg/VirtioGpuDxe/DriverBinding.c
@@ -0,0 +1,831 @@
+/** @file

+

+  Implement the Driver Binding Protocol and the Component Name 2 Protocol for

+  the Virtio GPU hybrid 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.

+

+**/

+

+#include <Library/BaseMemoryLib.h>

+#include <Library/DevicePathLib.h>

+#include <Library/MemoryAllocationLib.h>

+#include <Library/PrintLib.h>

+#include <Library/UefiBootServicesTableLib.h>

+#include <Library/UefiLib.h>

+#include <Protocol/ComponentName2.h>

+#include <Protocol/DevicePath.h>

+#include <Protocol/DriverBinding.h>

+#include <Protocol/PciIo.h>

+

+#include "VirtioGpu.h"

+

+//

+// Dummy Graphics Output Protocol GUID: a temporary placeholder for the EFI

+// counterpart. It will be replaced with the real thing as soon as we implement

+// the EFI GOP. Refer to VGPU_GOP.Gop.

+//

+STATIC EFI_GUID mDummyGraphicsOutputProtocolGuid = {

+  0x4983f8dc, 0x2782, 0x415b,

+  { 0x91, 0xf5, 0x2c, 0xeb, 0x48, 0x4a, 0x0f, 0xe9 }

+};

+

+//

+// The device path node that describes the Video Output Device Attributes for

+// the single head (UEFI child handle) that we support.

+//

+// The ACPI_DISPLAY_ADR() macro corresponds to Table B-2, section "B.4.2 _DOD"

+// in the ACPI 3.0b spec, or more recently, to Table B-379, section "B.3.2

+// _DOD" in the ACPI 6.0 spec.

+//

+STATIC CONST ACPI_ADR_DEVICE_PATH mAcpiAdr = {

+  {                                         // Header

+    ACPI_DEVICE_PATH,                       //   Type

+    ACPI_ADR_DP,                            //   SubType

+    { sizeof mAcpiAdr, 0 },                 //   Length

+  },

+  ACPI_DISPLAY_ADR (                        // ADR

+    1,                                      //   DeviceIdScheme: use the ACPI

+                                            //     bit-field definitions

+    0,                                      //   HeadId

+    0,                                      //   NonVgaOutput

+    1,                                      //   BiosCanDetect

+    0,                                      //   VendorInfo

+    ACPI_ADR_DISPLAY_TYPE_EXTERNAL_DIGITAL, //   Type

+    0,                                      //   Port

+    0                                       //   Index

+    )

+};

+

+//

+// Component Name 2 Protocol implementation.

+//

+STATIC CONST EFI_UNICODE_STRING_TABLE mDriverNameTable[] = {

+  { "en", L"Virtio GPU Driver" },

+  { NULL, NULL                 }

+};

+

+STATIC

+EFI_STATUS

+EFIAPI

+VirtioGpuGetDriverName (

+  IN  EFI_COMPONENT_NAME2_PROTOCOL *This,

+  IN  CHAR8                        *Language,

+  OUT CHAR16                       **DriverName

+  )

+{

+  return LookupUnicodeString2 (Language, This->SupportedLanguages,

+           mDriverNameTable, DriverName, FALSE /* Iso639Language */);

+}

+

+STATIC

+EFI_STATUS

+EFIAPI

+VirtioGpuGetControllerName (

+  IN  EFI_COMPONENT_NAME2_PROTOCOL *This,

+  IN  EFI_HANDLE                   ControllerHandle,

+  IN  EFI_HANDLE                   ChildHandle       OPTIONAL,

+  IN  CHAR8                        *Language,

+  OUT CHAR16                       **ControllerName

+  )

+{

+  EFI_STATUS Status;

+  VGPU_DEV   *VgpuDev;

+

+  //

+  // Look up the VGPU_DEV "protocol interface" on ControllerHandle.

+  //

+  Status = gBS->OpenProtocol (ControllerHandle, &gEfiCallerIdGuid,

+                  (VOID **)&VgpuDev, gImageHandle, ControllerHandle,

+                  EFI_OPEN_PROTOCOL_GET_PROTOCOL);

+  if (EFI_ERROR (Status)) {

+    return Status;

+  }

+  //

+  // Sanity check: if we found gEfiCallerIdGuid on ControllerHandle, then we

+  // keep its Virtio Device Protocol interface open BY_DRIVER.

+  //

+  ASSERT_EFI_ERROR (EfiTestManagedDevice (ControllerHandle, gImageHandle,

+                      &gVirtioDeviceProtocolGuid));

+

+  if (ChildHandle == NULL) {

+    //

+    // The caller is querying the name of the VGPU_DEV controller.

+    //

+    return LookupUnicodeString2 (Language, This->SupportedLanguages,

+             VgpuDev->BusName, ControllerName, FALSE /* Iso639Language */);

+  }

+

+  //

+  // Otherwise, the caller is looking for the name of the GOP child controller.

+  // Check if it is asking about the GOP child controller that we manage. (The

+  // condition below covers the case when we haven't produced the GOP child

+  // controller yet, or we've destroyed it since.)

+  //

+  if (VgpuDev->Child == NULL || ChildHandle != VgpuDev->Child->GopHandle) {

+    return EFI_UNSUPPORTED;

+  }

+  //

+  // Sanity check: our GOP child controller keeps the VGPU_DEV controller's

+  // Virtio Device Protocol interface open BY_CHILD_CONTROLLER.

+  //

+  ASSERT_EFI_ERROR (EfiTestChildHandle (ControllerHandle, ChildHandle,

+                      &gVirtioDeviceProtocolGuid));

+

+  return LookupUnicodeString2 (Language, This->SupportedLanguages,

+           VgpuDev->Child->GopName, ControllerName,

+           FALSE /* Iso639Language */);

+}

+

+STATIC CONST EFI_COMPONENT_NAME2_PROTOCOL mComponentName2 = {

+  VirtioGpuGetDriverName,

+  VirtioGpuGetControllerName,

+  "en"                       // SupportedLanguages (RFC 4646)

+};

+

+//

+// Helper functions for the Driver Binding Protocol Implementation.

+//

+/**

+  Format the VGPU_DEV controller name, to be looked up and returned by

+  VirtioGpuGetControllerName().

+

+  @param[in] ControllerHandle  The handle that identifies the VGPU_DEV

+                               controller.

+

+  @param[in] AgentHandle       The handle of the agent that will attempt to

+                               temporarily open the PciIo protocol. This is the

+                               DriverBindingHandle member of the

+                               EFI_DRIVER_BINDING_PROTOCOL whose Start()

+                               function is calling this function.

+

+  @param[in] DevicePath        The device path that is installed on

+                               ControllerHandle.

+

+  @param[out] ControllerName   A dynamically allocated unicode string that

+                               unconditionally says "Virtio GPU Device", with a

+                               PCI Segment:Bus:Device.Function location

+                               optionally appended. The latter part is only

+                               produced if DevicePath contains at least one

+                               PciIo node; in that case, the most specific such

+                               node is used for retrieving the location info.

+                               The caller is responsible for freeing

+                               ControllerName after use.

+

+  @retval EFI_SUCCESS           ControllerName has been formatted.

+

+  @retval EFI_OUT_OF_RESOURCES  Failed to allocate memory for ControllerName.

+**/

+STATIC

+EFI_STATUS

+FormatVgpuDevName (

+  IN  EFI_HANDLE               ControllerHandle,

+  IN  EFI_HANDLE               AgentHandle,

+  IN  EFI_DEVICE_PATH_PROTOCOL *DevicePath,

+  OUT CHAR16                   **ControllerName

+  )

+{

+  EFI_HANDLE          PciIoHandle;

+  EFI_PCI_IO_PROTOCOL *PciIo;

+  UINTN               Segment, Bus, Device, Function;

+  STATIC CONST CHAR16 ControllerNameStem[] = L"Virtio GPU Device";

+  UINTN               ControllerNameSize;

+

+  if (EFI_ERROR (gBS->LocateDevicePath (&gEfiPciIoProtocolGuid, &DevicePath,

+                        &PciIoHandle)) ||

+      EFI_ERROR (gBS->OpenProtocol (PciIoHandle, &gEfiPciIoProtocolGuid,

+                        (VOID **)&PciIo, AgentHandle, ControllerHandle,

+                        EFI_OPEN_PROTOCOL_GET_PROTOCOL)) ||

+      EFI_ERROR (PciIo->GetLocation (PciIo, &Segment, &Bus, &Device,

+                          &Function))) {

+    //

+    // Failed to retrieve location info, return verbatim copy of static string.

+    //

+    *ControllerName = AllocateCopyPool (sizeof ControllerNameStem,

+                        ControllerNameStem);

+    return (*ControllerName == NULL) ? EFI_OUT_OF_RESOURCES : EFI_SUCCESS;

+  }

+  //

+  // Location info available, format ControllerName dynamically.

+  //

+  ControllerNameSize = sizeof ControllerNameStem + // includes L'\0'

+                       sizeof (CHAR16) * (1 + 4 +  // Segment

+                                          1 + 2 +  // Bus

+                                          1 + 2 +  // Device

+                                          1 + 1    // Function

+                                          );

+  *ControllerName = AllocatePool (ControllerNameSize);

+  if (*ControllerName == NULL) {

+    return EFI_OUT_OF_RESOURCES;

+  }

+

+  UnicodeSPrintAsciiFormat (*ControllerName, ControllerNameSize,

+    "%s %04x:%02x:%02x.%x", ControllerNameStem, (UINT32)Segment, (UINT32)Bus,

+    (UINT32)Device, (UINT32)Function);

+  return EFI_SUCCESS;

+}

+

+/**

+  Dynamically allocate and initialize the VGPU_GOP child object within an

+  otherwise configured parent VGPU_DEV object.

+

+  This function adds a BY_CHILD_CONTROLLER reference to ParentBusController's

+  VIRTIO_DEVICE_PROTOCOL interface.

+

+  @param[in,out] ParentBus        The pre-initialized VGPU_DEV object that the

+                                  newly created VGPU_GOP object will be the

+                                  child of.

+

+  @param[in] ParentDevicePath     The device path protocol instance that is

+                                  installed on ParentBusController.

+

+  @param[in] ParentBusController  The UEFI controller handle on which the

+                                  ParentBus VGPU_DEV object and the

+                                  ParentDevicePath device path protocol are

+                                  installed.

+

+  @param[in] DriverBindingHandle  The DriverBindingHandle member of

+                                  EFI_DRIVER_BINDING_PROTOCOL whose Start()

+                                  function is calling this function. It is

+                                  passed as AgentHandle to gBS->OpenProtocol()

+                                  when creating the BY_CHILD_CONTROLLER

+                                  reference.

+

+  @retval EFI_SUCCESS           ParentBus->Child has been created and

+                                populated, and ParentBus->Child->GopHandle now

+                                references ParentBusController->VirtIo

+                                BY_CHILD_CONTROLLER.

+

+  @retval EFI_OUT_OF_RESOURCES  Memory allocation failed.

+

+  @return                       Error codes from underlying functions.

+**/

+STATIC

+EFI_STATUS

+InitVgpuGop (

+  IN OUT VGPU_DEV                 *ParentBus,

+  IN     EFI_DEVICE_PATH_PROTOCOL *ParentDevicePath,

+  IN     EFI_HANDLE               ParentBusController,

+  IN     EFI_HANDLE               DriverBindingHandle

+  )

+{

+  VGPU_GOP            *VgpuGop;

+  EFI_STATUS          Status;

+  CHAR16              *ParentBusName;

+  STATIC CONST CHAR16 NameSuffix[] = L" Head #0";

+  UINTN               NameSize;

+  CHAR16              *Name;

+  EFI_TPL             OldTpl;

+  VOID                *ParentVirtIo;

+

+  VgpuGop = AllocateZeroPool (sizeof *VgpuGop);

+  if (VgpuGop == NULL) {

+    return EFI_OUT_OF_RESOURCES;

+  }

+

+  VgpuGop->Signature = VGPU_GOP_SIG;

+  VgpuGop->ParentBus = ParentBus;

+

+  //

+  // Format a human-readable controller name for VGPU_GOP, and stash it for

+  // VirtioGpuGetControllerName() to look up. We simply append NameSuffix to

+  // ParentBus->BusName.

+  //

+  Status = LookupUnicodeString2 ("en", mComponentName2.SupportedLanguages,

+             ParentBus->BusName, &ParentBusName, FALSE /* Iso639Language */);

+  ASSERT_EFI_ERROR (Status);

+  NameSize = StrSize (ParentBusName) - sizeof (CHAR16) + sizeof NameSuffix;

+  Name = AllocatePool (NameSize);

+  if (Name == NULL) {

+    Status = EFI_OUT_OF_RESOURCES;

+    goto FreeVgpuGop;

+  }

+  UnicodeSPrintAsciiFormat (Name, NameSize, "%s%s", ParentBusName, NameSuffix);

+  Status = AddUnicodeString2 ("en", mComponentName2.SupportedLanguages,

+             &VgpuGop->GopName, Name, FALSE /* Iso639Language */);

+  FreePool (Name);

+  if (EFI_ERROR (Status)) {

+    goto FreeVgpuGop;

+  }

+

+  //

+  // Create the child device path.

+  //

+  VgpuGop->GopDevicePath = AppendDevicePathNode (ParentDevicePath,

+                             &mAcpiAdr.Header);

+  if (VgpuGop->GopDevicePath == NULL) {

+    Status = EFI_OUT_OF_RESOURCES;

+    goto FreeVgpuGopName;

+  }

+

+  //

+  // Mask protocol notify callbacks until we're done.

+  //

+  OldTpl = gBS->RaiseTPL (TPL_CALLBACK);

+

+  //

+  // Create the child handle with the child device path.

+  //

+  Status = gBS->InstallProtocolInterface (&VgpuGop->GopHandle,

+                  &gEfiDevicePathProtocolGuid, EFI_NATIVE_INTERFACE,

+                  VgpuGop->GopDevicePath);

+  if (EFI_ERROR (Status)) {

+    goto FreeDevicePath;

+  }

+

+  //

+  // The child handle must present a reference to the parent handle's Virtio

+  // Device Protocol interface.

+  //

+  Status = gBS->OpenProtocol (ParentBusController, &gVirtioDeviceProtocolGuid,

+                  &ParentVirtIo, DriverBindingHandle, VgpuGop->GopHandle,

+                  EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER);

+  if (EFI_ERROR (Status)) {

+    goto UninstallDevicePath;

+  }

+  ASSERT (ParentVirtIo == ParentBus->VirtIo);

+

+  //

+  // Initialize our Graphics Output Protocol.

+  //

+  // This means "nothing" for now.

+  //

+  Status = EFI_SUCCESS;

+  if (EFI_ERROR (Status)) {

+    goto CloseVirtIoByChild;

+  }

+

+  //

+  // Install the Graphics Output Protocol on the child handle.

+  //

+  Status = gBS->InstallProtocolInterface (&VgpuGop->GopHandle,

+                  &mDummyGraphicsOutputProtocolGuid, EFI_NATIVE_INTERFACE,

+                  &VgpuGop->Gop);

+  if (EFI_ERROR (Status)) {

+    goto UninitGop;

+  }

+

+  //

+  // We're done.

+  //

+  gBS->RestoreTPL (OldTpl);

+  ParentBus->Child = VgpuGop;

+  return EFI_SUCCESS;

+

+UninitGop:

+  //

+  // Nothing, for now.

+  //

+

+CloseVirtIoByChild:

+  gBS->CloseProtocol (ParentBusController, &gVirtioDeviceProtocolGuid,

+    DriverBindingHandle, VgpuGop->GopHandle);

+

+UninstallDevicePath:

+  gBS->UninstallProtocolInterface (VgpuGop->GopHandle,

+         &gEfiDevicePathProtocolGuid, VgpuGop->GopDevicePath);

+

+FreeDevicePath:

+  gBS->RestoreTPL (OldTpl);

+  FreePool (VgpuGop->GopDevicePath);

+

+FreeVgpuGopName:

+  FreeUnicodeStringTable (VgpuGop->GopName);

+

+FreeVgpuGop:

+  FreePool (VgpuGop);

+

+  return Status;

+}

+

+/**

+  Tear down and release the VGPU_GOP child object within the VGPU_DEV parent

+  object.

+

+  This function removes the BY_CHILD_CONTROLLER reference from

+  ParentBusController's VIRTIO_DEVICE_PROTOCOL interface.

+

+  @param[in,out] ParentBus        The VGPU_DEV object that the VGPU_GOP child

+                                  object will be removed from.

+

+  @param[in] ParentBusController  The UEFI controller handle on which the

+                                  ParentBus VGPU_DEV object is installed.

+

+  @param[in] DriverBindingHandle  The DriverBindingHandle member of

+                                  EFI_DRIVER_BINDING_PROTOCOL whose Stop()

+                                  function is calling this function. It is

+                                  passed as AgentHandle to gBS->CloseProtocol()

+                                  when removing the BY_CHILD_CONTROLLER

+                                  reference.

+**/

+STATIC

+VOID

+UninitVgpuGop (

+  IN OUT VGPU_DEV   *ParentBus,

+  IN     EFI_HANDLE ParentBusController,

+  IN     EFI_HANDLE DriverBindingHandle

+  )

+{

+  VGPU_GOP   *VgpuGop;

+  EFI_STATUS Status;

+

+  VgpuGop = ParentBus->Child;

+  Status = gBS->UninstallProtocolInterface (VgpuGop->GopHandle,

+                  &mDummyGraphicsOutputProtocolGuid, &VgpuGop->Gop);

+  ASSERT_EFI_ERROR (Status);

+

+  //

+  // Uninitialize VgpuGop->Gop.

+  //

+  // Nothing, for now.

+  //

+  Status = EFI_SUCCESS;

+  ASSERT_EFI_ERROR (Status);

+

+  Status = gBS->CloseProtocol (ParentBusController, &gVirtioDeviceProtocolGuid,

+                  DriverBindingHandle, VgpuGop->GopHandle);

+  ASSERT_EFI_ERROR (Status);

+

+  Status = gBS->UninstallProtocolInterface (VgpuGop->GopHandle,

+                  &gEfiDevicePathProtocolGuid, VgpuGop->GopDevicePath);

+  ASSERT_EFI_ERROR (Status);

+

+  FreePool (VgpuGop->GopDevicePath);

+  FreeUnicodeStringTable (VgpuGop->GopName);

+  FreePool (VgpuGop);

+

+  ParentBus->Child = NULL;

+}

+

+//

+// Driver Binding Protocol Implementation.

+//

+STATIC

+EFI_STATUS

+EFIAPI

+VirtioGpuDriverBindingSupported (

+  IN EFI_DRIVER_BINDING_PROTOCOL *This,

+  IN EFI_HANDLE                  ControllerHandle,

+  IN EFI_DEVICE_PATH_PROTOCOL    *RemainingDevicePath OPTIONAL

+  )

+{

+  EFI_STATUS             Status;

+  VIRTIO_DEVICE_PROTOCOL *VirtIo;

+

+  //

+  // - If RemainingDevicePath is NULL: the caller is interested in creating all

+  //   child handles.

+  // - If RemainingDevicePath points to an end node: the caller is not

+  //   interested in creating any child handle.

+  // - Otherwise, the caller would like to create the one child handle

+  //   specified in RemainingDevicePath. In this case we have to see if the

+  //   requested device path is supportable.

+  //

+  if (RemainingDevicePath != NULL &&

+      !IsDevicePathEnd (RemainingDevicePath) &&

+      (DevicePathNodeLength (RemainingDevicePath) != sizeof mAcpiAdr ||

+       CompareMem (RemainingDevicePath, &mAcpiAdr, sizeof mAcpiAdr) != 0)) {

+    return EFI_UNSUPPORTED;

+  }

+

+  //

+  // Open the Virtio Device Protocol interface on the controller, BY_DRIVER.

+  //

+  Status = gBS->OpenProtocol (ControllerHandle, &gVirtioDeviceProtocolGuid,

+                  (VOID **)&VirtIo, This->DriverBindingHandle,

+                  ControllerHandle, EFI_OPEN_PROTOCOL_BY_DRIVER);

+  if (EFI_ERROR (Status)) {

+    //

+    // If this fails, then by default we cannot support ControllerHandle. There

+    // is one exception: we've already bound the device, have not produced any

+    // GOP child controller, and now the caller wants us to produce the child

+    // controller (either specifically or as part of "all children"). That's

+    // allowed.

+    //

+    if (Status == EFI_ALREADY_STARTED) {

+      EFI_STATUS Status2;

+      VGPU_DEV   *VgpuDev;

+

+      Status2 = gBS->OpenProtocol (ControllerHandle, &gEfiCallerIdGuid,

+                       (VOID **)&VgpuDev, This->DriverBindingHandle,

+                       ControllerHandle, EFI_OPEN_PROTOCOL_GET_PROTOCOL);

+      ASSERT_EFI_ERROR (Status2);

+

+      if (VgpuDev->Child == NULL &&

+          (RemainingDevicePath == NULL ||

+           !IsDevicePathEnd (RemainingDevicePath))) {

+        Status = EFI_SUCCESS;

+      }

+    }

+

+    return Status;

+  }

+

+  //

+  // First BY_DRIVER open; check the VirtIo revision and subsystem.

+  //

+  if (VirtIo->Revision < VIRTIO_SPEC_REVISION (1, 0, 0) ||

+      VirtIo->SubSystemDeviceId != VIRTIO_SUBSYSTEM_GPU_DEVICE) {

+    Status = EFI_UNSUPPORTED;

+    goto CloseVirtIo;

+  }

+

+  //

+  // We'll need the device path of the VirtIo device both for formatting

+  // VGPU_DEV.BusName and for populating VGPU_GOP.GopDevicePath.

+  //

+  Status = gBS->OpenProtocol (ControllerHandle, &gEfiDevicePathProtocolGuid,

+                  NULL, This->DriverBindingHandle, ControllerHandle,

+                  EFI_OPEN_PROTOCOL_TEST_PROTOCOL);

+

+CloseVirtIo:

+  gBS->CloseProtocol (ControllerHandle, &gVirtioDeviceProtocolGuid,

+    This->DriverBindingHandle, ControllerHandle);

+

+  return Status;

+}

+

+STATIC

+EFI_STATUS

+EFIAPI

+VirtioGpuDriverBindingStart (

+  IN EFI_DRIVER_BINDING_PROTOCOL *This,

+  IN EFI_HANDLE                  ControllerHandle,

+  IN EFI_DEVICE_PATH_PROTOCOL    *RemainingDevicePath OPTIONAL

+  )

+{

+  EFI_STATUS               Status;

+  VIRTIO_DEVICE_PROTOCOL   *VirtIo;

+  BOOLEAN                  VirtIoBoundJustNow;

+  VGPU_DEV                 *VgpuDev;

+  EFI_DEVICE_PATH_PROTOCOL *DevicePath;

+

+  //

+  // Open the Virtio Device Protocol.

+  //

+  // The result of this operation, combined with the checks in

+  // VirtioGpuDriverBindingSupported(), uniquely tells us whether we are

+  // binding the VirtIo controller on this call (with or without creating child

+  // controllers), or else we're *only* creating child controllers.

+  //

+  Status = gBS->OpenProtocol (ControllerHandle, &gVirtioDeviceProtocolGuid,

+                  (VOID **)&VirtIo, This->DriverBindingHandle,

+                  ControllerHandle, EFI_OPEN_PROTOCOL_BY_DRIVER);

+  if (EFI_ERROR (Status)) {

+    //

+    // The assertions below are based on the success of

+    // VirtioGpuDriverBindingSupported(): we bound ControllerHandle earlier,

+    // without producing child handles, and now we're producing the GOP child

+    // handle only.

+    //

+    ASSERT (Status == EFI_ALREADY_STARTED);

+

+    Status = gBS->OpenProtocol (ControllerHandle, &gEfiCallerIdGuid,

+                    (VOID **)&VgpuDev, This->DriverBindingHandle,

+                    ControllerHandle, EFI_OPEN_PROTOCOL_GET_PROTOCOL);

+    ASSERT_EFI_ERROR (Status);

+

+    ASSERT (VgpuDev->Child == NULL);

+    ASSERT (

+      RemainingDevicePath == NULL || !IsDevicePathEnd (RemainingDevicePath));

+

+    VirtIoBoundJustNow = FALSE;

+  } else {

+    VirtIoBoundJustNow = TRUE;

+

+    //

+    // Allocate the private structure.

+    //

+    VgpuDev = AllocateZeroPool (sizeof *VgpuDev);

+    if (VgpuDev == NULL) {

+      Status = EFI_OUT_OF_RESOURCES;

+      goto CloseVirtIo;

+    }

+    VgpuDev->VirtIo = VirtIo;

+  }

+

+  //

+  // Grab the VirtIo controller's device path. This is necessary regardless of

+  // VirtIoBoundJustNow.

+  //

+  Status = gBS->OpenProtocol (ControllerHandle, &gEfiDevicePathProtocolGuid,

+                  (VOID **)&DevicePath, This->DriverBindingHandle,

+                  ControllerHandle, EFI_OPEN_PROTOCOL_GET_PROTOCOL);

+  if (EFI_ERROR (Status)) {

+    goto FreeVgpuDev;

+  }

+

+  //

+  // Create VGPU_DEV if we've bound the VirtIo controller right now (that is,

+  // if we aren't *only* creating child handles).

+  //

+  if (VirtIoBoundJustNow) {

+    CHAR16 *VgpuDevName;

+

+    //

+    // Format a human-readable controller name for VGPU_DEV, and stash it for

+    // VirtioGpuGetControllerName() to look up.

+    //

+    Status = FormatVgpuDevName (ControllerHandle, This->DriverBindingHandle,

+               DevicePath, &VgpuDevName);

+    if (EFI_ERROR (Status)) {

+      goto FreeVgpuDev;

+    }

+    Status = AddUnicodeString2 ("en", mComponentName2.SupportedLanguages,

+               &VgpuDev->BusName, VgpuDevName, FALSE /* Iso639Language */);

+    FreePool (VgpuDevName);

+    if (EFI_ERROR (Status)) {

+      goto FreeVgpuDev;

+    }

+

+    //

+    // Install the VGPU_DEV "protocol interface" on ControllerHandle.

+    //

+    Status = gBS->InstallProtocolInterface (&ControllerHandle,

+                    &gEfiCallerIdGuid, EFI_NATIVE_INTERFACE, VgpuDev);

+    if (EFI_ERROR (Status)) {

+      goto FreeVgpuDevBusName;

+    }

+

+    if (RemainingDevicePath != NULL && IsDevicePathEnd (RemainingDevicePath)) {

+      //

+      // No child handle should be produced; we're done.

+      //

+      DEBUG ((EFI_D_INFO, "%a: bound VirtIo=%p without producing GOP\n",

+        __FUNCTION__, (VOID *)VgpuDev->VirtIo));

+      return EFI_SUCCESS;

+    }

+  }

+

+  //

+  // Below we'll produce our single child handle: the caller requested it

+  // either specifically, or as part of all child handles.

+  //

+  ASSERT (VgpuDev->Child == NULL);

+  ASSERT (

+    RemainingDevicePath == NULL || !IsDevicePathEnd (RemainingDevicePath));

+

+  Status = InitVgpuGop (VgpuDev, DevicePath, ControllerHandle,

+             This->DriverBindingHandle);

+  if (EFI_ERROR (Status)) {

+    goto UninstallVgpuDev;

+  }

+

+  //

+  // We're done.

+  //

+  DEBUG ((EFI_D_INFO, "%a: produced GOP %a VirtIo=%p\n", __FUNCTION__,

+    VirtIoBoundJustNow ? "while binding" : "for pre-bound",

+    (VOID *)VgpuDev->VirtIo));

+  return EFI_SUCCESS;

+

+UninstallVgpuDev:

+  if (VirtIoBoundJustNow) {

+    gBS->UninstallProtocolInterface (ControllerHandle, &gEfiCallerIdGuid,

+           VgpuDev);

+  }

+

+FreeVgpuDevBusName:

+  if (VirtIoBoundJustNow) {

+    FreeUnicodeStringTable (VgpuDev->BusName);

+  }

+

+FreeVgpuDev:

+  if (VirtIoBoundJustNow) {

+    FreePool (VgpuDev);

+  }

+

+CloseVirtIo:

+  if (VirtIoBoundJustNow) {

+    gBS->CloseProtocol (ControllerHandle, &gVirtioDeviceProtocolGuid,

+      This->DriverBindingHandle, ControllerHandle);

+  }

+

+  return Status;

+}

+

+STATIC

+EFI_STATUS

+EFIAPI

+VirtioGpuDriverBindingStop (

+  IN  EFI_DRIVER_BINDING_PROTOCOL *This,

+  IN  EFI_HANDLE                  ControllerHandle,

+  IN  UINTN                       NumberOfChildren,

+  IN  EFI_HANDLE                  *ChildHandleBuffer OPTIONAL

+  )

+{

+  EFI_STATUS Status;

+  VGPU_DEV   *VgpuDev;

+

+  //

+  // Look up the VGPU_DEV "protocol interface" on ControllerHandle.

+  //

+  Status = gBS->OpenProtocol (ControllerHandle, &gEfiCallerIdGuid,

+                  (VOID **)&VgpuDev, This->DriverBindingHandle,

+                  ControllerHandle, EFI_OPEN_PROTOCOL_GET_PROTOCOL);

+  if (EFI_ERROR (Status)) {

+    return Status;

+  }

+  //

+  // Sanity check: if we found gEfiCallerIdGuid on ControllerHandle, then we

+  // keep its Virtio Device Protocol interface open BY_DRIVER.

+  //

+  ASSERT_EFI_ERROR (EfiTestManagedDevice (ControllerHandle,

+                      This->DriverBindingHandle, &gVirtioDeviceProtocolGuid));

+

+  switch (NumberOfChildren) {

+  case 0:

+    //

+    // The caller wants us to unbind the VirtIo controller.

+    //

+    if (VgpuDev->Child != NULL) {

+      //

+      // We still have the GOP child.

+      //

+      Status = EFI_DEVICE_ERROR;

+      break;

+    }

+

+    DEBUG ((EFI_D_INFO, "%a: unbinding GOP-less VirtIo=%p\n", __FUNCTION__,

+      (VOID *)VgpuDev->VirtIo));

+

+    Status = gBS->UninstallProtocolInterface (ControllerHandle,

+                    &gEfiCallerIdGuid, VgpuDev);

+    ASSERT_EFI_ERROR (Status);

+

+    FreeUnicodeStringTable (VgpuDev->BusName);

+    FreePool (VgpuDev);

+

+    Status = gBS->CloseProtocol (ControllerHandle, &gVirtioDeviceProtocolGuid,

+                    This->DriverBindingHandle, ControllerHandle);

+    ASSERT_EFI_ERROR (Status);

+    break;

+

+  case 1:

+    //

+    // The caller wants us to destroy our child GOP controller.

+    //

+    if (VgpuDev->Child == NULL ||

+        ChildHandleBuffer[0] != VgpuDev->Child->GopHandle) {

+      //

+      // We have no child controller at the moment, or it differs from the one

+      // the caller wants us to destroy. I.e., we don't own the child

+      // controller passed in.

+      //

+      Status = EFI_DEVICE_ERROR;

+      break;

+    }

+    //

+    // Sanity check: our GOP child controller keeps the VGPU_DEV controller's

+    // Virtio Device Protocol interface open BY_CHILD_CONTROLLER.

+    //

+    ASSERT_EFI_ERROR (EfiTestChildHandle (ControllerHandle,

+                        VgpuDev->Child->GopHandle,

+                        &gVirtioDeviceProtocolGuid));

+

+    DEBUG ((EFI_D_INFO, "%a: destroying GOP under VirtIo=%p\n", __FUNCTION__,

+      (VOID *)VgpuDev->VirtIo));

+    UninitVgpuGop (VgpuDev, ControllerHandle, This->DriverBindingHandle);

+    break;

+

+  default:

+    //

+    // Impossible, we never produced more than one child.

+    //

+    Status = EFI_DEVICE_ERROR;

+    break;

+  }

+  return Status;

+}

+

+STATIC EFI_DRIVER_BINDING_PROTOCOL mDriverBinding = {

+  VirtioGpuDriverBindingSupported,

+  VirtioGpuDriverBindingStart,

+  VirtioGpuDriverBindingStop,

+  0x10,                            // Version

+  NULL,                            // ImageHandle, overwritten in entry point

+  NULL                             // DriverBindingHandle, ditto

+};

+

+//

+// Entry point of the driver.

+//

+EFI_STATUS

+EFIAPI

+VirtioGpuEntryPoint (

+  IN EFI_HANDLE       ImageHandle,

+  IN EFI_SYSTEM_TABLE *SystemTable

+  )

+{

+  return EfiLibInstallDriverBindingComponentName2 (ImageHandle, SystemTable,

+           &mDriverBinding, ImageHandle, NULL /* ComponentName */,

+           &mComponentName2);

+}

diff --git a/OvmfPkg/VirtioGpuDxe/VirtioGpu.h b/OvmfPkg/VirtioGpuDxe/VirtioGpu.h
new file mode 100644
index 0000000..ca5805d
--- /dev/null
+++ b/OvmfPkg/VirtioGpuDxe/VirtioGpu.h
@@ -0,0 +1,106 @@
+/** @file

+

+  Internal type and macro definitions for the Virtio GPU hybrid 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_GPU_DXE_H_

+#define _VIRTIO_GPU_DXE_H_

+

+#include <Library/DebugLib.h>

+#include <Library/UefiLib.h>

+#include <Protocol/VirtioDevice.h>

+

+//

+// Forward declaration of VGPU_GOP.

+//

+typedef struct VGPU_GOP_STRUCT VGPU_GOP;

+

+//

+// The abstraction that directly corresponds to a Virtio GPU device.

+//

+// This structure will be installed on the handle that has the VirtIo Device

+// Protocol interface, with GUID gEfiCallerIdGuid. A similar trick is employed

+// in TerminalDxe, and it is necessary so that we can look up VGPU_DEV just

+// from the VirtIo Device Protocol handle in the Component Name 2 Protocol

+// implementation.

+//

+typedef struct {

+  //

+  // VirtIo represents access to the Virtio GPU device. Never NULL.

+  //

+  VIRTIO_DEVICE_PROTOCOL   *VirtIo;

+

+  //

+  // BusName carries a customized name for

+  // EFI_COMPONENT_NAME2_PROTOCOL.GetControllerName(). It is expressed in table

+  // form because it can theoretically support several languages. Never NULL.

+  //

+  EFI_UNICODE_STRING_TABLE *BusName;

+

+  //

+  // The Child field references the GOP wrapper structure. If this pointer is

+  // NULL, then the hybrid driver has bound (i.e., started) the

+  // VIRTIO_DEVICE_PROTOCOL controller without producing the child GOP

+  // controller (that is, after Start() was called with RemainingDevicePath

+  // pointing to and End of Device Path node). Child can be created and

+  // destroyed, even repeatedly, independently of VGPU_DEV.

+  //

+  // In practice, this field represents the single head (scanout) that we

+  // support.

+  //

+  VGPU_GOP                 *Child;

+} VGPU_DEV;

+

+//

+// The Graphics Output Protocol wrapper structure.

+//

+#define VGPU_GOP_SIG SIGNATURE_64 ('V', 'G', 'P', 'U', '_', 'G', 'O', 'P')

+

+struct VGPU_GOP_STRUCT {

+  UINT64                               Signature;

+

+  //

+  // ParentBus points to the parent VGPU_DEV object. Never NULL.

+  //

+  VGPU_DEV                             *ParentBus;

+

+  //

+  // GopName carries a customized name for

+  // EFI_COMPONENT_NAME2_PROTOCOL.GetControllerName(). It is expressed in table

+  // form because it can theoretically support several languages. Never NULL.

+  //

+  EFI_UNICODE_STRING_TABLE             *GopName;

+

+  //

+  // GopHandle is the UEFI child handle that carries the device path ending

+  // with the ACPI ADR node, and the Graphics Output Protocol. Never NULL.

+  //

+  EFI_HANDLE                           GopHandle;

+

+  //

+  // The GopDevicePath field is the device path installed on GopHandle,

+  // ending with an ACPI ADR node. Never NULL.

+  //

+  EFI_DEVICE_PATH_PROTOCOL             *GopDevicePath;

+

+  //

+  // The Gop field is installed on the child handle as Graphics Output Protocol

+  // interface.

+  //

+  // For now it is just a placeholder.

+  //

+  UINT8                                Gop;

+};

+

+#endif // _VIRTIO_GPU_DXE_H_

diff --git a/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf b/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf
new file mode 100644
index 0000000..948350d
--- /dev/null
+++ b/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf
@@ -0,0 +1,47 @@
+## @file

+#

+# This hybrid driver produces the Graphics Output Protocol for the Virtio GPU

+# device (head #0, only and unconditionally).

+#

+# 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                      = VirtioGpuDxe

+  FILE_GUID                      = D6099B94-CD97-4CC5-8714-7F6312701A8A

+  MODULE_TYPE                    = UEFI_DRIVER

+  VERSION_STRING                 = 1.0

+  ENTRY_POINT                    = VirtioGpuEntryPoint

+

+[Sources]

+  DriverBinding.c

+  VirtioGpu.h

+

+[Packages]

+  MdePkg/MdePkg.dec

+  OvmfPkg/OvmfPkg.dec

+

+[LibraryClasses]

+  BaseMemoryLib

+  DebugLib

+  DevicePathLib

+  MemoryAllocationLib

+  PrintLib

+  UefiBootServicesTableLib

+  UefiDriverEntryPoint

+  UefiLib

+

+[Protocols]

+  gEfiDevicePathProtocolGuid     ## TO_START ## BY_START

+  gEfiPciIoProtocolGuid          ## TO_START

+  gVirtioDeviceProtocolGuid      ## TO_START