/** @file

  Implement the Driver Binding Protocol and the Component Name 2 Protocol for
  the Virtio GPU hybrid driver.

  Copyright (C) 2016, Red Hat, Inc.

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#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"

//
// 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.
  //
  // Fill in the function members of VgpuGop->Gop from the template, then set
  // up the rest of the GOP infrastructure by calling SetMode() right now.
  //
  CopyMem (&VgpuGop->Gop, &mGopTemplate, sizeof mGopTemplate);
  Status = VgpuGop->Gop.SetMode (&VgpuGop->Gop, 0);
  if (EFI_ERROR (Status)) {
    goto CloseVirtIoByChild;
  }

  //
  // Install the Graphics Output Protocol on the child handle.
  //
  Status = gBS->InstallProtocolInterface (&VgpuGop->GopHandle,
                  &gEfiGraphicsOutputProtocolGuid, EFI_NATIVE_INTERFACE,
                  &VgpuGop->Gop);
  if (EFI_ERROR (Status)) {
    goto UninitGop;
  }

  //
  // We're done.
  //
  gBS->RestoreTPL (OldTpl);
  ParentBus->Child = VgpuGop;
  return EFI_SUCCESS;

UninitGop:
  ReleaseGopResources (VgpuGop, TRUE /* DisableHead */);

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,
                  &gEfiGraphicsOutputProtocolGuid, &VgpuGop->Gop);
  ASSERT_EFI_ERROR (Status);

  //
  // Uninitialize VgpuGop->Gop.
  //
  ReleaseGopResources (VgpuGop, TRUE /* DisableHead */);

  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;
    }

    Status = VirtioGpuInit (VgpuDev);
    if (EFI_ERROR (Status)) {
      goto FreeVgpuDevBusName;
    }

    Status = gBS->CreateEvent (EVT_SIGNAL_EXIT_BOOT_SERVICES, TPL_CALLBACK,
                    VirtioGpuExitBoot, VgpuDev /* NotifyContext */,
                    &VgpuDev->ExitBoot);
    if (EFI_ERROR (Status)) {
      goto UninitGpu;
    }

    //
    // Install the VGPU_DEV "protocol interface" on ControllerHandle.
    //
    Status = gBS->InstallProtocolInterface (&ControllerHandle,
                    &gEfiCallerIdGuid, EFI_NATIVE_INTERFACE, VgpuDev);
    if (EFI_ERROR (Status)) {
      goto CloseExitBoot;
    }

    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);
  }

CloseExitBoot:
  if (VirtIoBoundJustNow) {
    gBS->CloseEvent (VgpuDev->ExitBoot);
  }

UninitGpu:
  if (VirtIoBoundJustNow) {
    VirtioGpuUninit (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);

    Status = gBS->CloseEvent (VgpuDev->ExitBoot);
    ASSERT_EFI_ERROR (Status);

    VirtioGpuUninit (VgpuDev);
    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);
}
