diff --git a/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf b/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf
index 948350dbce21..7a6269eded51 100644
--- a/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf
+++ b/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf
@@ -21,12 +21,13 @@ [Defines]
   FILE_GUID                      = D6099B94-CD97-4CC5-8714-7F6312701A8A
   MODULE_TYPE                    = UEFI_DRIVER
   VERSION_STRING                 = 1.0
   ENTRY_POINT                    = VirtioGpuEntryPoint
 
 [Sources]
+  Commands.c
   DriverBinding.c
   VirtioGpu.h
 
 [Packages]
   MdePkg/MdePkg.dec
   OvmfPkg/OvmfPkg.dec
@@ -37,11 +38,12 @@ [LibraryClasses]
   DevicePathLib
   MemoryAllocationLib
   PrintLib
   UefiBootServicesTableLib
   UefiDriverEntryPoint
   UefiLib
+  VirtioLib
 
 [Protocols]
   gEfiDevicePathProtocolGuid     ## TO_START ## BY_START
   gEfiPciIoProtocolGuid          ## TO_START
   gVirtioDeviceProtocolGuid      ## TO_START
diff --git a/OvmfPkg/VirtioGpuDxe/VirtioGpu.h b/OvmfPkg/VirtioGpuDxe/VirtioGpu.h
index ca5805df8442..97767dba709f 100644
--- a/OvmfPkg/VirtioGpuDxe/VirtioGpu.h
+++ b/OvmfPkg/VirtioGpuDxe/VirtioGpu.h
@@ -46,12 +46,22 @@ typedef struct {
   // 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;
 
   //
+  // VirtIo ring used for VirtIo communication.
+  //
+  VRING                    Ring;
+
+  //
+  // Event to be signaled at ExitBootServices().
+  //
+  EFI_EVENT                ExitBoot;
+
+  //
   // 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.
@@ -100,7 +110,65 @@ struct VGPU_GOP_STRUCT {
   //
   // For now it is just a placeholder.
   //
   UINT8                                Gop;
 };
 
+//
+// VirtIo GPU initialization, and commands (primitives) for the GPU device.
+//
+/**
+  Configure the VirtIo GPU device that underlies VgpuDev.
+
+  @param[in,out] VgpuDev  The VGPU_DEV object to set up VirtIo messaging for.
+                          On input, the caller is responsible for having
+                          initialized VgpuDev->VirtIo. On output, VgpuDev->Ring
+                          has been initialized, and synchronous VirtIo GPU
+                          commands (primitives) can be submitted to the device.
+
+  @retval EFI_SUCCESS      VirtIo GPU configuration successful.
+
+  @retval EFI_UNSUPPORTED  The host-side configuration of the VirtIo GPU is not
+                           supported by this driver.
+
+  @retval                  Error codes from underlying functions.
+**/
+EFI_STATUS
+VirtioGpuInit (
+  IN OUT VGPU_DEV *VgpuDev
+  );
+
+/**
+  De-configure the VirtIo GPU device that underlies VgpuDev.
+
+  @param[in,out] VgpuDev  The VGPU_DEV object to tear down VirtIo messaging
+                          for. On input, the caller is responsible for having
+                          called VirtioGpuInit(). On output, VgpuDev->Ring has
+                          been uninitialized; VirtIo GPU commands (primitives)
+                          can no longer be submitted to the device.
+**/
+VOID
+VirtioGpuUninit (
+  IN OUT VGPU_DEV *VgpuDev
+  );
+
+/**
+  EFI_EVENT_NOTIFY function for the VGPU_DEV.ExitBoot event. It resets the
+  VirtIo device, causing it to release its resources and to forget its
+  configuration.
+
+  This function may only be called (that is, VGPU_DEV.ExitBoot may only be
+  signaled) after VirtioGpuInit() returns and before VirtioGpuUninit() is
+  called.
+
+  @param[in] Event    Event whose notification function is being invoked.
+
+  @param[in] Context  Pointer to the associated VGPU_DEV object.
+**/
+VOID
+EFIAPI
+VirtioGpuExitBoot (
+  IN EFI_EVENT Event,
+  IN VOID      *Context
+  );
+
 #endif // _VIRTIO_GPU_DXE_H_
diff --git a/OvmfPkg/VirtioGpuDxe/Commands.c b/OvmfPkg/VirtioGpuDxe/Commands.c
new file mode 100644
index 000000000000..804de950ff24
--- /dev/null
+++ b/OvmfPkg/VirtioGpuDxe/Commands.c
@@ -0,0 +1,214 @@
+/** @file
+
+  VirtIo GPU initialization, and commands (primitives) for the GPU device.
+
+  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 <IndustryStandard/VirtioGpu.h>
+#include <Library/VirtioLib.h>
+
+#include "VirtioGpu.h"
+
+/**
+  Configure the VirtIo GPU device that underlies VgpuDev.
+
+  @param[in,out] VgpuDev  The VGPU_DEV object to set up VirtIo messaging for.
+                          On input, the caller is responsible for having
+                          initialized VgpuDev->VirtIo. On output, VgpuDev->Ring
+                          has been initialized, and synchronous VirtIo GPU
+                          commands (primitives) can be submitted to the device.
+
+  @retval EFI_SUCCESS      VirtIo GPU configuration successful.
+
+  @retval EFI_UNSUPPORTED  The host-side configuration of the VirtIo GPU is not
+                           supported by this driver.
+
+  @retval                  Error codes from underlying functions.
+**/
+EFI_STATUS
+VirtioGpuInit (
+  IN OUT VGPU_DEV *VgpuDev
+  )
+{
+  UINT8      NextDevStat;
+  EFI_STATUS Status;
+  UINT64     Features;
+  UINT16     QueueSize;
+
+  //
+  // Execute virtio-v1.0-cs04, 3.1.1 Driver Requirements: Device
+  // Initialization.
+  //
+  // 1. Reset the device.
+  //
+  NextDevStat = 0;
+  Status = VgpuDev->VirtIo->SetDeviceStatus (VgpuDev->VirtIo, NextDevStat);
+  if (EFI_ERROR (Status)) {
+    goto Failed;
+  }
+
+  //
+  // 2. Set the ACKNOWLEDGE status bit [...]
+  //
+  NextDevStat |= VSTAT_ACK;
+  Status = VgpuDev->VirtIo->SetDeviceStatus (VgpuDev->VirtIo, NextDevStat);
+  if (EFI_ERROR (Status)) {
+    goto Failed;
+  }
+
+  //
+  // 3. Set the DRIVER status bit [...]
+  //
+  NextDevStat |= VSTAT_DRIVER;
+  Status = VgpuDev->VirtIo->SetDeviceStatus (VgpuDev->VirtIo, NextDevStat);
+  if (EFI_ERROR (Status)) {
+    goto Failed;
+  }
+
+  //
+  // 4. Read device feature bits...
+  //
+  Status = VgpuDev->VirtIo->GetDeviceFeatures (VgpuDev->VirtIo, &Features);
+  if (EFI_ERROR (Status)) {
+    goto Failed;
+  }
+  if ((Features & VIRTIO_F_VERSION_1) == 0) {
+    Status = EFI_UNSUPPORTED;
+    goto Failed;
+  }
+  //
+  // We only want the most basic 2D features.
+  //
+  Features &= VIRTIO_F_VERSION_1;
+
+  //
+  // ... and write the subset of feature bits understood by the [...] driver to
+  // the device. [...]
+  // 5. Set the FEATURES_OK status bit.
+  // 6. Re-read device status to ensure the FEATURES_OK bit is still set [...]
+  //
+  Status = Virtio10WriteFeatures (VgpuDev->VirtIo, Features, &NextDevStat);
+  if (EFI_ERROR (Status)) {
+    goto Failed;
+  }
+
+  //
+  // 7. Perform device-specific setup, including discovery of virtqueues for
+  // the device [...]
+  //
+  Status = VgpuDev->VirtIo->SetQueueSel (VgpuDev->VirtIo,
+                              VIRTIO_GPU_CONTROL_QUEUE);
+  if (EFI_ERROR (Status)) {
+    goto Failed;
+  }
+  Status = VgpuDev->VirtIo->GetQueueNumMax (VgpuDev->VirtIo, &QueueSize);
+  if (EFI_ERROR (Status)) {
+    goto Failed;
+  }
+
+  //
+  // We implement each VirtIo GPU command that we use with two descriptors:
+  // request, response.
+  //
+  if (QueueSize < 2) {
+    Status = EFI_UNSUPPORTED;
+    goto Failed;
+  }
+
+  //
+  // [...] population of virtqueues [...]
+  //
+  Status = VirtioRingInit (QueueSize, &VgpuDev->Ring);
+  if (EFI_ERROR (Status)) {
+    goto Failed;
+  }
+  Status = VgpuDev->VirtIo->SetQueueAddress (VgpuDev->VirtIo, &VgpuDev->Ring);
+  if (EFI_ERROR (Status)) {
+    goto ReleaseQueue;
+  }
+
+  //
+  // 8. Set the DRIVER_OK status bit.
+  //
+  NextDevStat |= VSTAT_DRIVER_OK;
+  Status = VgpuDev->VirtIo->SetDeviceStatus (VgpuDev->VirtIo, NextDevStat);
+  if (EFI_ERROR (Status)) {
+    goto ReleaseQueue;
+  }
+
+  return EFI_SUCCESS;
+
+ReleaseQueue:
+  VirtioRingUninit (&VgpuDev->Ring);
+
+Failed:
+  //
+  // If any of these steps go irrecoverably wrong, the driver SHOULD set the
+  // FAILED status bit to indicate that it has given up on the device (it can
+  // reset the device later to restart if desired). [...]
+  //
+  // VirtIo access failure here should not mask the original error.
+  //
+  NextDevStat |= VSTAT_FAILED;
+  VgpuDev->VirtIo->SetDeviceStatus (VgpuDev->VirtIo, NextDevStat);
+
+  return Status;
+}
+
+/**
+  De-configure the VirtIo GPU device that underlies VgpuDev.
+
+  @param[in,out] VgpuDev  The VGPU_DEV object to tear down VirtIo messaging
+                          for. On input, the caller is responsible for having
+                          called VirtioGpuInit(). On output, VgpuDev->Ring has
+                          been uninitialized; VirtIo GPU commands (primitives)
+                          can no longer be submitted to the device.
+**/
+VOID
+VirtioGpuUninit (
+  IN OUT VGPU_DEV *VgpuDev
+  )
+{
+  //
+  // Resetting the VirtIo device makes it release its resources and forget its
+  // configuration.
+  //
+  VgpuDev->VirtIo->SetDeviceStatus (VgpuDev->VirtIo, 0);
+  VirtioRingUninit (&VgpuDev->Ring);
+}
+
+/**
+  EFI_EVENT_NOTIFY function for the VGPU_DEV.ExitBoot event. It resets the
+  VirtIo device, causing it to release its resources and to forget its
+  configuration.
+
+  This function may only be called (that is, VGPU_DEV.ExitBoot may only be
+  signaled) after VirtioGpuInit() returns and before VirtioGpuUninit() is
+  called.
+
+  @param[in] Event    Event whose notification function is being invoked.
+
+  @param[in] Context  Pointer to the associated VGPU_DEV object.
+**/
+VOID
+EFIAPI
+VirtioGpuExitBoot (
+  IN EFI_EVENT Event,
+  IN VOID      *Context
+  )
+{
+  VGPU_DEV *VgpuDev;
+
+  VgpuDev = Context;
+  VgpuDev->VirtIo->SetDeviceStatus (VgpuDev->VirtIo, 0);
+}
diff --git a/OvmfPkg/VirtioGpuDxe/DriverBinding.c b/OvmfPkg/VirtioGpuDxe/DriverBinding.c
index b902a07871e0..bdea55ef7dbf 100644
--- a/OvmfPkg/VirtioGpuDxe/DriverBinding.c
+++ b/OvmfPkg/VirtioGpuDxe/DriverBinding.c
@@ -643,19 +643,31 @@ VirtioGpuDriverBindingStart (
                &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 FreeVgpuDevBusName;
+      goto CloseExitBoot;
     }
 
     if (RemainingDevicePath != NULL && IsDevicePathEnd (RemainingDevicePath)) {
       //
       // No child handle should be produced; we're done.
       //
@@ -690,12 +702,22 @@ VirtioGpuDriverBindingStart (
 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:
@@ -758,12 +780,16 @@ VirtioGpuDriverBindingStop (
       (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);
