summaryrefslogtreecommitdiffstats
path: root/OvmfPkg/VirtioGpuDxe
diff options
context:
space:
mode:
authorLaszlo Ersek <lersek@redhat.com>2016-08-15 15:34:32 +0200
committerLaszlo Ersek <lersek@redhat.com>2016-09-01 22:54:55 +0200
commita2a4fa66701dd385495e9dec36650be635dd09dc (patch)
tree45828437ca5fd07db07cc8a78db43caadab194d1 /OvmfPkg/VirtioGpuDxe
parent92dc5e9d74c295a1e3a3ef10f1206c3dcb72a58d (diff)
downloadedk2-a2a4fa66701dd385495e9dec36650be635dd09dc.tar.gz
edk2-a2a4fa66701dd385495e9dec36650be635dd09dc.tar.bz2
edk2-a2a4fa66701dd385495e9dec36650be635dd09dc.zip
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>
Diffstat (limited to 'OvmfPkg/VirtioGpuDxe')
-rw-r--r--OvmfPkg/VirtioGpuDxe/DriverBinding.c831
-rw-r--r--OvmfPkg/VirtioGpuDxe/VirtioGpu.h106
-rw-r--r--OvmfPkg/VirtioGpuDxe/VirtioGpu.inf47
3 files changed, 984 insertions, 0 deletions
diff --git a/OvmfPkg/VirtioGpuDxe/DriverBinding.c b/OvmfPkg/VirtioGpuDxe/DriverBinding.c
new file mode 100644
index 0000000000..b902a07871
--- /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 0000000000..ca5805df84
--- /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 0000000000..948350dbce
--- /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