| /** @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 (( | |
| DEBUG_INFO, | |
| "%a: bound VirtIo=%p without producing GOP\n", | |
| __func__, | |
| (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 (( | |
| DEBUG_INFO, | |
| "%a: produced GOP %a VirtIo=%p\n", | |
| __func__, | |
| 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 (( | |
| DEBUG_INFO, | |
| "%a: unbinding GOP-less VirtIo=%p\n", | |
| __func__, | |
| (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 (( | |
| DEBUG_INFO, | |
| "%a: destroying GOP under VirtIo=%p\n", | |
| __func__, | |
| (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 | |
| ); | |
| } |