From a2a4fa66701dd385495e9dec36650be635dd09dc Mon Sep 17 00:00:00 2001 From: Laszlo Ersek Date: Mon, 15 Aug 2016 15:34:32 +0200 Subject: 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 Cc: Jordan Justen Ref: https://tianocore.acgmultimedia.com/show_bug.cgi?id=66 Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Laszlo Ersek Reviewed-by: Jordan Justen --- OvmfPkg/VirtioGpuDxe/DriverBinding.c | 831 +++++++++++++++++++++++++++++++++++ OvmfPkg/VirtioGpuDxe/VirtioGpu.h | 106 +++++ OvmfPkg/VirtioGpuDxe/VirtioGpu.inf | 47 ++ 3 files changed, 984 insertions(+) create mode 100644 OvmfPkg/VirtioGpuDxe/DriverBinding.c create mode 100644 OvmfPkg/VirtioGpuDxe/VirtioGpu.h create mode 100644 OvmfPkg/VirtioGpuDxe/VirtioGpu.inf (limited to 'OvmfPkg') 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include + +// +// 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 -- cgit v1.2.3