diff mbox

[edk2,05/11] OvmfPkg/VirtioGpuDxe: introduce with Component Name 2 and Driver Binding

Message ID 20160819124932.29711-6-lersek@redhat.com
State New
Headers show

Commit Message

Laszlo Ersek Aug. 19, 2016, 12:49 p.m. UTC
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>

---
 OvmfPkg/VirtioGpuDxe/VirtioGpu.inf   |  47 ++
 OvmfPkg/VirtioGpuDxe/VirtioGpu.h     | 106 +++
 OvmfPkg/VirtioGpuDxe/DriverBinding.c | 831 ++++++++++++++++++++
 3 files changed, 984 insertions(+)

-- 
2.9.2


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

Patch

diff --git a/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf b/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf
new file mode 100644
index 000000000000..948350dbce21
--- /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
diff --git a/OvmfPkg/VirtioGpuDxe/VirtioGpu.h b/OvmfPkg/VirtioGpuDxe/VirtioGpu.h
new file mode 100644
index 000000000000..ca5805df8442
--- /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/DriverBinding.c b/OvmfPkg/VirtioGpuDxe/DriverBinding.c
new file mode 100644
index 000000000000..b902a07871e0
--- /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);
+}