From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- .../EFI/Firmware/OvmfPkg/VirtioGpuDxe/Commands.c | 767 +++++++++++++++++++ .../Firmware/OvmfPkg/VirtioGpuDxe/DriverBinding.c | 837 +++++++++++++++++++++ .../EFI/Firmware/OvmfPkg/VirtioGpuDxe/Gop.c | 656 ++++++++++++++++ .../EFI/Firmware/OvmfPkg/VirtioGpuDxe/VirtioGpu.h | 402 ++++++++++ .../Firmware/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf | 45 ++ 5 files changed, 2707 insertions(+) create mode 100644 src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/Commands.c create mode 100644 src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/DriverBinding.c create mode 100644 src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/Gop.c create mode 100644 src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/VirtioGpu.h create mode 100644 src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf (limited to 'src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe') diff --git a/src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/Commands.c b/src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/Commands.c new file mode 100644 index 00000000..fd44f585 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/Commands.c @@ -0,0 +1,767 @@ +/** @file + + VirtIo GPU initialization, and commands (primitives) for the GPU device. + + Copyright (C) 2016, Red Hat, Inc. + Copyright (c) 2017, AMD Inc, All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include + +#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; + UINT64 RingBaseShift; + + // + // 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 | VIRTIO_F_IOMMU_PLATFORM; + + // + // ... 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 (VgpuDev->VirtIo, QueueSize, &VgpuDev->Ring); + if (EFI_ERROR (Status)) { + goto Failed; + } + // + // If anything fails from here on, we have to release the ring. + // + Status = VirtioRingMap ( + VgpuDev->VirtIo, + &VgpuDev->Ring, + &RingBaseShift, + &VgpuDev->RingMap + ); + if (EFI_ERROR (Status)) { + goto ReleaseQueue; + } + // + // If anything fails from here on, we have to unmap the ring. + // + Status = VgpuDev->VirtIo->SetQueueAddress ( + VgpuDev->VirtIo, + &VgpuDev->Ring, + RingBaseShift + ); + if (EFI_ERROR (Status)) { + goto UnmapQueue; + } + + // + // 8. Set the DRIVER_OK status bit. + // + NextDevStat |= VSTAT_DRIVER_OK; + Status = VgpuDev->VirtIo->SetDeviceStatus (VgpuDev->VirtIo, NextDevStat); + if (EFI_ERROR (Status)) { + goto UnmapQueue; + } + + return EFI_SUCCESS; + +UnmapQueue: + VgpuDev->VirtIo->UnmapSharedBuffer (VgpuDev->VirtIo, VgpuDev->RingMap); + +ReleaseQueue: + VirtioRingUninit (VgpuDev->VirtIo, &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); + VgpuDev->VirtIo->UnmapSharedBuffer (VgpuDev->VirtIo, VgpuDev->RingMap); + VirtioRingUninit (VgpuDev->VirtIo, &VgpuDev->Ring); +} + +/** + Allocate, zero and map memory, for bus master common buffer operation, to be + attached as backing store to a host-side VirtIo GPU resource. + + @param[in] VgpuDev The VGPU_DEV object that represents the VirtIo GPU + device. + + @param[in] NumberOfPages The number of whole pages to allocate and map. + + @param[out] HostAddress The system memory address of the allocated area. + + @param[out] DeviceAddress The bus master device address of the allocated + area. The VirtIo GPU device may be programmed to + access the allocated area through DeviceAddress; + DeviceAddress is to be passed to the + VirtioGpuResourceAttachBacking() function, as the + BackingStoreDeviceAddress parameter. + + @param[out] Mapping A resulting token to pass to + VirtioGpuUnmapAndFreeBackingStore(). + + @retval EFI_SUCCESS The requested number of pages has been allocated, zeroed + and mapped. + + @return Status codes propagated from + VgpuDev->VirtIo->AllocateSharedPages() and + VirtioMapAllBytesInSharedBuffer(). +**/ +EFI_STATUS +VirtioGpuAllocateZeroAndMapBackingStore ( + IN VGPU_DEV *VgpuDev, + IN UINTN NumberOfPages, + OUT VOID **HostAddress, + OUT EFI_PHYSICAL_ADDRESS *DeviceAddress, + OUT VOID **Mapping + ) +{ + EFI_STATUS Status; + VOID *NewHostAddress; + + Status = VgpuDev->VirtIo->AllocateSharedPages ( + VgpuDev->VirtIo, + NumberOfPages, + &NewHostAddress + ); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Avoid exposing stale data to the device even temporarily: zero the area + // before mapping it. + // + ZeroMem (NewHostAddress, EFI_PAGES_TO_SIZE (NumberOfPages)); + + Status = VirtioMapAllBytesInSharedBuffer ( + VgpuDev->VirtIo, // VirtIo + VirtioOperationBusMasterCommonBuffer, // Operation + NewHostAddress, // HostAddress + EFI_PAGES_TO_SIZE (NumberOfPages), // NumberOfBytes + DeviceAddress, // DeviceAddress + Mapping // Mapping + ); + if (EFI_ERROR (Status)) { + goto FreeSharedPages; + } + + *HostAddress = NewHostAddress; + return EFI_SUCCESS; + +FreeSharedPages: + VgpuDev->VirtIo->FreeSharedPages ( + VgpuDev->VirtIo, + NumberOfPages, + NewHostAddress + ); + return Status; +} + +/** + Unmap and free memory originally allocated and mapped with + VirtioGpuAllocateZeroAndMapBackingStore(). + + If the memory allocated and mapped with + VirtioGpuAllocateZeroAndMapBackingStore() was attached to a host-side VirtIo + GPU resource with VirtioGpuResourceAttachBacking(), then the caller is + responsible for detaching the backing store from the same resource, with + VirtioGpuResourceDetachBacking(), before calling this function. + + @param[in] VgpuDev The VGPU_DEV object that represents the VirtIo GPU + device. + + @param[in] NumberOfPages The NumberOfPages parameter originally passed to + VirtioGpuAllocateZeroAndMapBackingStore(). + + @param[in] HostAddress The HostAddress value originally output by + VirtioGpuAllocateZeroAndMapBackingStore(). + + @param[in] Mapping The token that was originally output by + VirtioGpuAllocateZeroAndMapBackingStore(). +**/ +VOID +VirtioGpuUnmapAndFreeBackingStore ( + IN VGPU_DEV *VgpuDev, + IN UINTN NumberOfPages, + IN VOID *HostAddress, + IN VOID *Mapping + ) +{ + VgpuDev->VirtIo->UnmapSharedBuffer ( + VgpuDev->VirtIo, + Mapping + ); + VgpuDev->VirtIo->FreeSharedPages ( + VgpuDev->VirtIo, + NumberOfPages, + HostAddress + ); +} + +/** + 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; + + DEBUG ((DEBUG_VERBOSE, "%a: Context=0x%p\n", __FUNCTION__, Context)); + VgpuDev = Context; + VgpuDev->VirtIo->SetDeviceStatus (VgpuDev->VirtIo, 0); +} + +/** + Internal utility function that sends a request to the VirtIo GPU device + model, awaits the answer from the host, and returns a status. + + @param[in,out] VgpuDev The VGPU_DEV object that represents the VirtIo GPU + device. The caller is responsible to have + successfully invoked VirtioGpuInit() on VgpuDev + previously, while VirtioGpuUninit() must not have + been called on VgpuDev. + + @param[in] RequestType The type of the request. The caller is responsible + for providing a VirtioGpuCmd* RequestType which, on + success, elicits a VirtioGpuRespOkNodata response + from the host. + + @param[in] Fence Whether to enable fencing for this request. Fencing + forces the host to complete the command before + producing a response. If Fence is TRUE, then + VgpuDev->FenceId is consumed, and incremented. + + @param[in,out] Header Pointer to the caller-allocated request object. The + request must start with VIRTIO_GPU_CONTROL_HEADER. + This function overwrites all fields of Header before + submitting the request to the host: + + - it sets Type from RequestType, + + - it sets Flags and FenceId based on Fence, + + - it zeroes CtxId and Padding. + + @param[in] RequestSize Size of the entire caller-allocated request object, + including the leading VIRTIO_GPU_CONTROL_HEADER. + + @retval EFI_SUCCESS Operation successful. + + @retval EFI_DEVICE_ERROR The host rejected the request. The host error + code has been logged on the DEBUG_ERROR level. + + @return Codes for unexpected errors in VirtIo + messaging, or request/response + mapping/unmapping. +**/ +STATIC +EFI_STATUS +VirtioGpuSendCommand ( + IN OUT VGPU_DEV *VgpuDev, + IN VIRTIO_GPU_CONTROL_TYPE RequestType, + IN BOOLEAN Fence, + IN OUT volatile VIRTIO_GPU_CONTROL_HEADER *Header, + IN UINTN RequestSize + ) +{ + DESC_INDICES Indices; + volatile VIRTIO_GPU_CONTROL_HEADER Response; + EFI_STATUS Status; + UINT32 ResponseSize; + EFI_PHYSICAL_ADDRESS RequestDeviceAddress; + VOID *RequestMap; + EFI_PHYSICAL_ADDRESS ResponseDeviceAddress; + VOID *ResponseMap; + + // + // Initialize Header. + // + Header->Type = RequestType; + if (Fence) { + Header->Flags = VIRTIO_GPU_FLAG_FENCE; + Header->FenceId = VgpuDev->FenceId++; + } else { + Header->Flags = 0; + Header->FenceId = 0; + } + Header->CtxId = 0; + Header->Padding = 0; + + ASSERT (RequestSize >= sizeof *Header); + ASSERT (RequestSize <= MAX_UINT32); + + // + // Map request and response to bus master device addresses. + // + Status = VirtioMapAllBytesInSharedBuffer ( + VgpuDev->VirtIo, + VirtioOperationBusMasterRead, + (VOID *)Header, + RequestSize, + &RequestDeviceAddress, + &RequestMap + ); + if (EFI_ERROR (Status)) { + return Status; + } + Status = VirtioMapAllBytesInSharedBuffer ( + VgpuDev->VirtIo, + VirtioOperationBusMasterWrite, + (VOID *)&Response, + sizeof Response, + &ResponseDeviceAddress, + &ResponseMap + ); + if (EFI_ERROR (Status)) { + goto UnmapRequest; + } + + // + // Compose the descriptor chain. + // + VirtioPrepare (&VgpuDev->Ring, &Indices); + VirtioAppendDesc ( + &VgpuDev->Ring, + RequestDeviceAddress, + (UINT32)RequestSize, + VRING_DESC_F_NEXT, + &Indices + ); + VirtioAppendDesc ( + &VgpuDev->Ring, + ResponseDeviceAddress, + (UINT32)sizeof Response, + VRING_DESC_F_WRITE, + &Indices + ); + + // + // Send the command. + // + Status = VirtioFlush (VgpuDev->VirtIo, VIRTIO_GPU_CONTROL_QUEUE, + &VgpuDev->Ring, &Indices, &ResponseSize); + if (EFI_ERROR (Status)) { + goto UnmapResponse; + } + + // + // Verify response size. + // + if (ResponseSize != sizeof Response) { + DEBUG ((DEBUG_ERROR, "%a: malformed response to Request=0x%x\n", + __FUNCTION__, (UINT32)RequestType)); + Status = EFI_PROTOCOL_ERROR; + goto UnmapResponse; + } + + // + // Unmap response and request, in reverse order of mapping. On error, the + // respective mapping is invalidated anyway, only the data may not have been + // committed to system memory (in case of VirtioOperationBusMasterWrite). + // + Status = VgpuDev->VirtIo->UnmapSharedBuffer (VgpuDev->VirtIo, ResponseMap); + if (EFI_ERROR (Status)) { + goto UnmapRequest; + } + Status = VgpuDev->VirtIo->UnmapSharedBuffer (VgpuDev->VirtIo, RequestMap); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Parse the response. + // + if (Response.Type == VirtioGpuRespOkNodata) { + return EFI_SUCCESS; + } + + DEBUG ((DEBUG_ERROR, "%a: Request=0x%x Response=0x%x\n", __FUNCTION__, + (UINT32)RequestType, Response.Type)); + return EFI_DEVICE_ERROR; + +UnmapResponse: + VgpuDev->VirtIo->UnmapSharedBuffer (VgpuDev->VirtIo, ResponseMap); + +UnmapRequest: + VgpuDev->VirtIo->UnmapSharedBuffer (VgpuDev->VirtIo, RequestMap); + + return Status; +} + +/** + The following functions send requests to the VirtIo GPU device model, await + the answer from the host, and return a status. They share the following + interface details: + + @param[in,out] VgpuDev The VGPU_DEV object that represents the VirtIo GPU + device. The caller is responsible to have + successfully invoked VirtioGpuInit() on VgpuDev + previously, while VirtioGpuUninit() must not have + been called on VgpuDev. + + @retval EFI_INVALID_PARAMETER Invalid command-specific parameters were + detected by this driver. + + @retval EFI_SUCCESS Operation successful. + + @retval EFI_DEVICE_ERROR The host rejected the request. The host error + code has been logged on the DEBUG_ERROR level. + + @return Codes for unexpected errors in VirtIo + messaging. + + For the command-specific parameters, please consult the GPU Device section of + the VirtIo 1.0 specification (see references in + "OvmfPkg/Include/IndustryStandard/VirtioGpu.h"). +**/ +EFI_STATUS +VirtioGpuResourceCreate2d ( + IN OUT VGPU_DEV *VgpuDev, + IN UINT32 ResourceId, + IN VIRTIO_GPU_FORMATS Format, + IN UINT32 Width, + IN UINT32 Height + ) +{ + volatile VIRTIO_GPU_RESOURCE_CREATE_2D Request; + + if (ResourceId == 0) { + return EFI_INVALID_PARAMETER; + } + + Request.ResourceId = ResourceId; + Request.Format = (UINT32)Format; + Request.Width = Width; + Request.Height = Height; + + return VirtioGpuSendCommand ( + VgpuDev, + VirtioGpuCmdResourceCreate2d, + FALSE, // Fence + &Request.Header, + sizeof Request + ); +} + +EFI_STATUS +VirtioGpuResourceUnref ( + IN OUT VGPU_DEV *VgpuDev, + IN UINT32 ResourceId + ) +{ + volatile VIRTIO_GPU_RESOURCE_UNREF Request; + + if (ResourceId == 0) { + return EFI_INVALID_PARAMETER; + } + + Request.ResourceId = ResourceId; + Request.Padding = 0; + + return VirtioGpuSendCommand ( + VgpuDev, + VirtioGpuCmdResourceUnref, + FALSE, // Fence + &Request.Header, + sizeof Request + ); +} + +EFI_STATUS +VirtioGpuResourceAttachBacking ( + IN OUT VGPU_DEV *VgpuDev, + IN UINT32 ResourceId, + IN EFI_PHYSICAL_ADDRESS BackingStoreDeviceAddress, + IN UINTN NumberOfPages + ) +{ + volatile VIRTIO_GPU_RESOURCE_ATTACH_BACKING Request; + + if (ResourceId == 0) { + return EFI_INVALID_PARAMETER; + } + + Request.ResourceId = ResourceId; + Request.NrEntries = 1; + Request.Entry.Addr = BackingStoreDeviceAddress; + Request.Entry.Length = (UINT32)EFI_PAGES_TO_SIZE (NumberOfPages); + Request.Entry.Padding = 0; + + return VirtioGpuSendCommand ( + VgpuDev, + VirtioGpuCmdResourceAttachBacking, + FALSE, // Fence + &Request.Header, + sizeof Request + ); +} + +EFI_STATUS +VirtioGpuResourceDetachBacking ( + IN OUT VGPU_DEV *VgpuDev, + IN UINT32 ResourceId + ) +{ + volatile VIRTIO_GPU_RESOURCE_DETACH_BACKING Request; + + if (ResourceId == 0) { + return EFI_INVALID_PARAMETER; + } + + Request.ResourceId = ResourceId; + Request.Padding = 0; + + // + // In this case, we set Fence to TRUE, because after this function returns, + // the caller might reasonably want to repurpose the backing pages + // immediately. Thus we should ensure that the host releases all references + // to the backing pages before we return. + // + return VirtioGpuSendCommand ( + VgpuDev, + VirtioGpuCmdResourceDetachBacking, + TRUE, // Fence + &Request.Header, + sizeof Request + ); +} + +EFI_STATUS +VirtioGpuSetScanout ( + IN OUT VGPU_DEV *VgpuDev, + IN UINT32 X, + IN UINT32 Y, + IN UINT32 Width, + IN UINT32 Height, + IN UINT32 ScanoutId, + IN UINT32 ResourceId + ) +{ + volatile VIRTIO_GPU_SET_SCANOUT Request; + + // + // Unlike for most other commands, ResourceId=0 is valid; it + // is used to disable a scanout. + // + Request.Rectangle.X = X; + Request.Rectangle.Y = Y; + Request.Rectangle.Width = Width; + Request.Rectangle.Height = Height; + Request.ScanoutId = ScanoutId; + Request.ResourceId = ResourceId; + + return VirtioGpuSendCommand ( + VgpuDev, + VirtioGpuCmdSetScanout, + FALSE, // Fence + &Request.Header, + sizeof Request + ); +} + +EFI_STATUS +VirtioGpuTransferToHost2d ( + IN OUT VGPU_DEV *VgpuDev, + IN UINT32 X, + IN UINT32 Y, + IN UINT32 Width, + IN UINT32 Height, + IN UINT64 Offset, + IN UINT32 ResourceId + ) +{ + volatile VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D Request; + + if (ResourceId == 0) { + return EFI_INVALID_PARAMETER; + } + + Request.Rectangle.X = X; + Request.Rectangle.Y = Y; + Request.Rectangle.Width = Width; + Request.Rectangle.Height = Height; + Request.Offset = Offset; + Request.ResourceId = ResourceId; + Request.Padding = 0; + + return VirtioGpuSendCommand ( + VgpuDev, + VirtioGpuCmdTransferToHost2d, + FALSE, // Fence + &Request.Header, + sizeof Request + ); +} + +EFI_STATUS +VirtioGpuResourceFlush ( + IN OUT VGPU_DEV *VgpuDev, + IN UINT32 X, + IN UINT32 Y, + IN UINT32 Width, + IN UINT32 Height, + IN UINT32 ResourceId + ) +{ + volatile VIRTIO_GPU_RESOURCE_FLUSH Request; + + if (ResourceId == 0) { + return EFI_INVALID_PARAMETER; + } + + Request.Rectangle.X = X; + Request.Rectangle.Y = Y; + Request.Rectangle.Width = Width; + Request.Rectangle.Height = Height; + Request.ResourceId = ResourceId; + Request.Padding = 0; + + return VirtioGpuSendCommand ( + VgpuDev, + VirtioGpuCmdResourceFlush, + FALSE, // Fence + &Request.Header, + sizeof Request + ); +} diff --git a/src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/DriverBinding.c b/src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/DriverBinding.c new file mode 100644 index 00000000..9f24ae25 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/DriverBinding.c @@ -0,0 +1,837 @@ +/** @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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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", + __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 ((DEBUG_INFO, "%a: produced GOP %a VirtIo=%p\n", __FUNCTION__, + VirtIoBoundJustNow ? "while binding" : "for pre-bound", + (VOID *)VgpuDev->VirtIo)); + return EFI_SUCCESS; + +UninstallVgpuDev: + if (VirtIoBoundJustNow) { + gBS->UninstallProtocolInterface (ControllerHandle, &gEfiCallerIdGuid, + VgpuDev); + } + +CloseExitBoot: + if (VirtIoBoundJustNow) { + gBS->CloseEvent (VgpuDev->ExitBoot); + } + +UninitGpu: + if (VirtIoBoundJustNow) { + VirtioGpuUninit (VgpuDev); + } + +FreeVgpuDevBusName: + if (VirtIoBoundJustNow) { + FreeUnicodeStringTable (VgpuDev->BusName); + } + +FreeVgpuDev: + if (VirtIoBoundJustNow) { + FreePool (VgpuDev); + } + +CloseVirtIo: + if (VirtIoBoundJustNow) { + gBS->CloseProtocol (ControllerHandle, &gVirtioDeviceProtocolGuid, + This->DriverBindingHandle, ControllerHandle); + } + + return Status; +} + +STATIC +EFI_STATUS +EFIAPI +VirtioGpuDriverBindingStop ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE ControllerHandle, + IN UINTN NumberOfChildren, + IN EFI_HANDLE *ChildHandleBuffer OPTIONAL + ) +{ + EFI_STATUS Status; + VGPU_DEV *VgpuDev; + + // + // Look up the VGPU_DEV "protocol interface" on ControllerHandle. + // + Status = gBS->OpenProtocol (ControllerHandle, &gEfiCallerIdGuid, + (VOID **)&VgpuDev, This->DriverBindingHandle, + ControllerHandle, EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (EFI_ERROR (Status)) { + return Status; + } + // + // Sanity check: if we found gEfiCallerIdGuid on ControllerHandle, then we + // keep its Virtio Device Protocol interface open BY_DRIVER. + // + ASSERT_EFI_ERROR (EfiTestManagedDevice (ControllerHandle, + This->DriverBindingHandle, &gVirtioDeviceProtocolGuid)); + + switch (NumberOfChildren) { + case 0: + // + // The caller wants us to unbind the VirtIo controller. + // + if (VgpuDev->Child != NULL) { + // + // We still have the GOP child. + // + Status = EFI_DEVICE_ERROR; + break; + } + + DEBUG ((DEBUG_INFO, "%a: unbinding GOP-less VirtIo=%p\n", __FUNCTION__, + (VOID *)VgpuDev->VirtIo)); + + Status = gBS->UninstallProtocolInterface (ControllerHandle, + &gEfiCallerIdGuid, VgpuDev); + ASSERT_EFI_ERROR (Status); + + Status = gBS->CloseEvent (VgpuDev->ExitBoot); + ASSERT_EFI_ERROR (Status); + + VirtioGpuUninit (VgpuDev); + FreeUnicodeStringTable (VgpuDev->BusName); + FreePool (VgpuDev); + + Status = gBS->CloseProtocol (ControllerHandle, &gVirtioDeviceProtocolGuid, + This->DriverBindingHandle, ControllerHandle); + ASSERT_EFI_ERROR (Status); + break; + + case 1: + // + // The caller wants us to destroy our child GOP controller. + // + if (VgpuDev->Child == NULL || + ChildHandleBuffer[0] != VgpuDev->Child->GopHandle) { + // + // We have no child controller at the moment, or it differs from the one + // the caller wants us to destroy. I.e., we don't own the child + // controller passed in. + // + Status = EFI_DEVICE_ERROR; + break; + } + // + // Sanity check: our GOP child controller keeps the VGPU_DEV controller's + // Virtio Device Protocol interface open BY_CHILD_CONTROLLER. + // + ASSERT_EFI_ERROR (EfiTestChildHandle (ControllerHandle, + VgpuDev->Child->GopHandle, + &gVirtioDeviceProtocolGuid)); + + DEBUG ((DEBUG_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/src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/Gop.c b/src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/Gop.c new file mode 100644 index 00000000..bb29f7ee --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/Gop.c @@ -0,0 +1,656 @@ +/** @file + + EFI_GRAPHICS_OUTPUT_PROTOCOL member functions for the VirtIo GPU driver. + + Copyright (C) 2016, Red Hat, Inc. + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include + +#include "VirtioGpu.h" + +/** + Release guest-side and host-side resources that are related to an initialized + VGPU_GOP.Gop. + + param[in,out] VgpuGop The VGPU_GOP object to release resources for. + + On input, the caller is responsible for having called + VgpuGop->Gop.SetMode() at least once successfully. + (This is equivalent to the requirement that + VgpuGop->BackingStore be non-NULL. It is also + equivalent to the requirement that VgpuGop->ResourceId + be nonzero.) + + On output, resources will be released, and + VgpuGop->BackingStore and VgpuGop->ResourceId will be + nulled. + + param[in] DisableHead Whether this head (scanout) currently references the + resource identified by VgpuGop->ResourceId. Only pass + FALSE when VgpuGop->Gop.SetMode() calls this function + while switching between modes, and set it to TRUE + every other time. +**/ +VOID +ReleaseGopResources ( + IN OUT VGPU_GOP *VgpuGop, + IN BOOLEAN DisableHead + ) +{ + EFI_STATUS Status; + + ASSERT (VgpuGop->ResourceId != 0); + ASSERT (VgpuGop->BackingStore != NULL); + + // + // If any of the following host-side destruction steps fail, we can't get out + // of an inconsistent state, so we'll hang. In general errors in object + // destruction can hardly be recovered from. + // + if (DisableHead) { + // + // Dissociate head (scanout) #0 from the currently used 2D host resource, + // by setting ResourceId=0 for it. + // + Status = VirtioGpuSetScanout ( + VgpuGop->ParentBus, // VgpuDev + 0, 0, 0, 0, // X, Y, Width, Height + 0, // ScanoutId + 0 // ResourceId + ); + // + // HACK BEGINS HERE + // + // According to the GPU Device section of the VirtIo specification, the + // above operation is valid: + // + // "The driver can use resource_id = 0 to disable a scanout." + // + // However, in practice QEMU does not allow us to disable head (scanout) #0 + // -- it rejects the command with response code 0x1202 + // (VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID). Looking at the QEMU source + // code, function virtio_gpu_set_scanout() in "hw/display/virtio-gpu.c", + // this appears fully intentional, despite not being documented in the + // spec. + // + // Surprisingly, ignoring the error here, and proceeding to release + // host-side resources that presumably underlie head (scanout) #0, work + // without any problems -- the driver survives repeated "disconnect" / + // "connect -r" commands in the UEFI shell. + // + // So, for now, let's just suppress the error. + // + Status = EFI_SUCCESS; + // + // HACK ENDS HERE + // + + ASSERT_EFI_ERROR (Status); + if (EFI_ERROR (Status)) { + CpuDeadLoop (); + } + } + + // + // Detach backing pages from the currently used 2D host resource. + // + Status = VirtioGpuResourceDetachBacking ( + VgpuGop->ParentBus, // VgpuDev + VgpuGop->ResourceId // ResourceId + ); + ASSERT_EFI_ERROR (Status); + if (EFI_ERROR (Status)) { + CpuDeadLoop (); + } + + // + // Unmap and release backing pages. + // + VirtioGpuUnmapAndFreeBackingStore ( + VgpuGop->ParentBus, // VgpuDev + VgpuGop->NumberOfPages, // NumberOfPages + VgpuGop->BackingStore, // HostAddress + VgpuGop->BackingStoreMap // Mapping + ); + VgpuGop->BackingStore = NULL; + VgpuGop->NumberOfPages = 0; + VgpuGop->BackingStoreMap = NULL; + + // + // Destroy the currently used 2D host resource. + // + Status = VirtioGpuResourceUnref ( + VgpuGop->ParentBus, // VgpuDev + VgpuGop->ResourceId // ResourceId + ); + ASSERT_EFI_ERROR (Status); + if (EFI_ERROR (Status)) { + CpuDeadLoop (); + } + VgpuGop->ResourceId = 0; +} + +// +// The resolutions supported by this driver. +// +typedef struct { + UINT32 Width; + UINT32 Height; +} GOP_RESOLUTION; + +STATIC CONST GOP_RESOLUTION mGopResolutions[] = { + { 640, 480 }, + { 800, 480 }, + { 800, 600 }, + { 832, 624 }, + { 960, 640 }, + { 1024, 600 }, + { 1024, 768 }, + { 1152, 864 }, + { 1152, 870 }, + { 1280, 720 }, + { 1280, 760 }, + { 1280, 768 }, + { 1280, 800 }, + { 1280, 960 }, + { 1280, 1024 }, + { 1360, 768 }, + { 1366, 768 }, + { 1400, 1050 }, + { 1440, 900 }, + { 1600, 900 }, + { 1600, 1200 }, + { 1680, 1050 }, + { 1920, 1080 }, + { 1920, 1200 }, + { 1920, 1440 }, + { 2000, 2000 }, + { 2048, 1536 }, + { 2048, 2048 }, + { 2560, 1440 }, + { 2560, 1600 }, + { 2560, 2048 }, + { 2800, 2100 }, + { 3200, 2400 }, + { 3840, 2160 }, + { 4096, 2160 }, + { 7680, 4320 }, + { 8192, 4320 }, +}; + +// +// Macro for casting VGPU_GOP.Gop to VGPU_GOP. +// +#define VGPU_GOP_FROM_GOP(GopPointer) \ + CR (GopPointer, VGPU_GOP, Gop, VGPU_GOP_SIG) + +// +// EFI_GRAPHICS_OUTPUT_PROTOCOL member functions. +// +STATIC +EFI_STATUS +EFIAPI +GopQueryMode ( + IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This, + IN UINT32 ModeNumber, + OUT UINTN *SizeOfInfo, + OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION **Info + ) +{ + EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *GopModeInfo; + + if (ModeNumber >= ARRAY_SIZE (mGopResolutions)) { + return EFI_INVALID_PARAMETER; + } + + GopModeInfo = AllocateZeroPool (sizeof *GopModeInfo); + if (GopModeInfo == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + GopModeInfo->HorizontalResolution = mGopResolutions[ModeNumber].Width; + GopModeInfo->VerticalResolution = mGopResolutions[ModeNumber].Height; + GopModeInfo->PixelFormat = PixelBltOnly; + GopModeInfo->PixelsPerScanLine = mGopResolutions[ModeNumber].Width; + + *SizeOfInfo = sizeof *GopModeInfo; + *Info = GopModeInfo; + return EFI_SUCCESS; +} + +STATIC +EFI_STATUS +EFIAPI +GopSetMode ( + IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This, + IN UINT32 ModeNumber + ) +{ + VGPU_GOP *VgpuGop; + UINT32 NewResourceId; + UINTN NewNumberOfBytes; + UINTN NewNumberOfPages; + VOID *NewBackingStore; + EFI_PHYSICAL_ADDRESS NewBackingStoreDeviceAddress; + VOID *NewBackingStoreMap; + + EFI_STATUS Status; + EFI_STATUS Status2; + + if (ModeNumber >= ARRAY_SIZE (mGopResolutions)) { + return EFI_UNSUPPORTED; + } + + VgpuGop = VGPU_GOP_FROM_GOP (This); + + // + // Distinguish the first (internal) call from the other (protocol consumer) + // calls. + // + if (VgpuGop->ResourceId == 0) { + // + // Set up the Gop -> GopMode -> GopModeInfo pointer chain, and the other + // (nonzero) constant fields. + // + // No direct framebuffer access is supported, only Blt() is. + // + VgpuGop->Gop.Mode = &VgpuGop->GopMode; + + VgpuGop->GopMode.MaxMode = (UINT32)(ARRAY_SIZE (mGopResolutions)); + VgpuGop->GopMode.Info = &VgpuGop->GopModeInfo; + VgpuGop->GopMode.SizeOfInfo = sizeof VgpuGop->GopModeInfo; + + VgpuGop->GopModeInfo.PixelFormat = PixelBltOnly; + + // + // This is the first time we create a host side resource. + // + NewResourceId = 1; + } else { + // + // We already have an active host side resource. Create the new one without + // interfering with the current one, so that we can cleanly bail out on + // error, without disturbing the current graphics mode. + // + // The formula below will alternate between IDs 1 and 2. + // + NewResourceId = 3 - VgpuGop->ResourceId; + } + + // + // Create the 2D host resource. + // + Status = VirtioGpuResourceCreate2d ( + VgpuGop->ParentBus, // VgpuDev + NewResourceId, // ResourceId + VirtioGpuFormatB8G8R8X8Unorm, // Format + mGopResolutions[ModeNumber].Width, // Width + mGopResolutions[ModeNumber].Height // Height + ); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Allocate, zero and map guest backing store, for bus master common buffer + // operation. + // + NewNumberOfBytes = mGopResolutions[ModeNumber].Width * + mGopResolutions[ModeNumber].Height * sizeof (UINT32); + NewNumberOfPages = EFI_SIZE_TO_PAGES (NewNumberOfBytes); + Status = VirtioGpuAllocateZeroAndMapBackingStore ( + VgpuGop->ParentBus, // VgpuDev + NewNumberOfPages, // NumberOfPages + &NewBackingStore, // HostAddress + &NewBackingStoreDeviceAddress, // DeviceAddress + &NewBackingStoreMap // Mapping + ); + if (EFI_ERROR (Status)) { + goto DestroyHostResource; + } + + // + // Attach backing store to the host resource. + // + Status = VirtioGpuResourceAttachBacking ( + VgpuGop->ParentBus, // VgpuDev + NewResourceId, // ResourceId + NewBackingStoreDeviceAddress, // BackingStoreDeviceAddress + NewNumberOfPages // NumberOfPages + ); + if (EFI_ERROR (Status)) { + goto UnmapAndFreeBackingStore; + } + + // + // Point head (scanout) #0 to the host resource. + // + Status = VirtioGpuSetScanout ( + VgpuGop->ParentBus, // VgpuDev + 0, // X + 0, // Y + mGopResolutions[ModeNumber].Width, // Width + mGopResolutions[ModeNumber].Height, // Height + 0, // ScanoutId + NewResourceId // ResourceId + ); + if (EFI_ERROR (Status)) { + goto DetachBackingStore; + } + + // + // If this is not the first (i.e., internal) call, then we have to (a) flush + // the new resource to head (scanout) #0, after having flipped the latter to + // the former above, plus (b) release the old resources. + // + if (VgpuGop->ResourceId != 0) { + Status = VirtioGpuResourceFlush ( + VgpuGop->ParentBus, // VgpuDev + 0, // X + 0, // Y + mGopResolutions[ModeNumber].Width, // Width + mGopResolutions[ModeNumber].Height, // Height + NewResourceId // ResourceId + ); + if (EFI_ERROR (Status)) { + // + // Flip head (scanout) #0 back to the current resource. If this fails, we + // cannot continue, as this error occurs on the error path and is + // therefore non-recoverable. + // + Status2 = VirtioGpuSetScanout ( + VgpuGop->ParentBus, // VgpuDev + 0, // X + 0, // Y + mGopResolutions[This->Mode->Mode].Width, // Width + mGopResolutions[This->Mode->Mode].Height, // Height + 0, // ScanoutId + VgpuGop->ResourceId // ResourceId + ); + ASSERT_EFI_ERROR (Status2); + if (EFI_ERROR (Status2)) { + CpuDeadLoop (); + } + goto DetachBackingStore; + } + + // + // Flush successful; release the old resources (without disabling head + // (scanout) #0). + // + ReleaseGopResources (VgpuGop, FALSE /* DisableHead */); + } + + // + // This is either the first (internal) call when we have no old resources + // yet, or we've changed the mode successfully and released the old + // resources. + // + ASSERT (VgpuGop->ResourceId == 0); + ASSERT (VgpuGop->BackingStore == NULL); + + VgpuGop->ResourceId = NewResourceId; + VgpuGop->BackingStore = NewBackingStore; + VgpuGop->NumberOfPages = NewNumberOfPages; + VgpuGop->BackingStoreMap = NewBackingStoreMap; + + // + // Populate Mode and ModeInfo (mutable fields only). + // + VgpuGop->GopMode.Mode = ModeNumber; + VgpuGop->GopModeInfo.HorizontalResolution = + mGopResolutions[ModeNumber].Width; + VgpuGop->GopModeInfo.VerticalResolution = mGopResolutions[ModeNumber].Height; + VgpuGop->GopModeInfo.PixelsPerScanLine = mGopResolutions[ModeNumber].Width; + return EFI_SUCCESS; + +DetachBackingStore: + Status2 = VirtioGpuResourceDetachBacking (VgpuGop->ParentBus, NewResourceId); + ASSERT_EFI_ERROR (Status2); + if (EFI_ERROR (Status2)) { + CpuDeadLoop (); + } + +UnmapAndFreeBackingStore: + VirtioGpuUnmapAndFreeBackingStore ( + VgpuGop->ParentBus, // VgpuDev + NewNumberOfPages, // NumberOfPages + NewBackingStore, // HostAddress + NewBackingStoreMap // Mapping + ); + +DestroyHostResource: + Status2 = VirtioGpuResourceUnref (VgpuGop->ParentBus, NewResourceId); + ASSERT_EFI_ERROR (Status2); + if (EFI_ERROR (Status2)) { + CpuDeadLoop (); + } + + return Status; +} + +STATIC +EFI_STATUS +EFIAPI +GopBlt ( + IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This, + IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer, OPTIONAL + IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation, + IN UINTN SourceX, + IN UINTN SourceY, + IN UINTN DestinationX, + IN UINTN DestinationY, + IN UINTN Width, + IN UINTN Height, + IN UINTN Delta OPTIONAL + ) +{ + VGPU_GOP *VgpuGop; + UINT32 CurrentHorizontal; + UINT32 CurrentVertical; + UINTN SegmentSize; + UINTN Y; + UINTN ResourceOffset; + EFI_STATUS Status; + + VgpuGop = VGPU_GOP_FROM_GOP (This); + CurrentHorizontal = VgpuGop->GopModeInfo.HorizontalResolution; + CurrentVertical = VgpuGop->GopModeInfo.VerticalResolution; + + // + // We can avoid pixel format conversion in the guest because the internal + // representation of EFI_GRAPHICS_OUTPUT_BLT_PIXEL and that of + // VirtioGpuFormatB8G8R8X8Unorm are identical. + // + SegmentSize = Width * sizeof (UINT32); + + // + // Delta is relevant for operations that read a rectangle from, or write a + // rectangle to, BltBuffer. + // + // In these cases, Delta is the stride of BltBuffer, in bytes. If Delta is + // zero, then Width is the entire width of BltBuffer, and the stride is + // supposed to be calculated from Width. + // + if (BltOperation == EfiBltVideoToBltBuffer || + BltOperation == EfiBltBufferToVideo) { + if (Delta == 0) { + Delta = SegmentSize; + } + } + + // + // For operations that write to the display, check if the destination fits + // onto the display. + // + if (BltOperation == EfiBltVideoFill || + BltOperation == EfiBltBufferToVideo || + BltOperation == EfiBltVideoToVideo) { + if (DestinationX > CurrentHorizontal || + Width > CurrentHorizontal - DestinationX || + DestinationY > CurrentVertical || + Height > CurrentVertical - DestinationY) { + return EFI_INVALID_PARAMETER; + } + } + + // + // For operations that read from the display, check if the source fits onto + // the display. + // + if (BltOperation == EfiBltVideoToBltBuffer || + BltOperation == EfiBltVideoToVideo) { + if (SourceX > CurrentHorizontal || + Width > CurrentHorizontal - SourceX || + SourceY > CurrentVertical || + Height > CurrentVertical - SourceY) { + return EFI_INVALID_PARAMETER; + } + } + + // + // Render the request. For requests that do not modify the display, there + // won't be further steps. + // + switch (BltOperation) { + case EfiBltVideoFill: + // + // Write data from the BltBuffer pixel (0, 0) directly to every pixel of + // the video display rectangle (DestinationX, DestinationY) (DestinationX + + // Width, DestinationY + Height). Only one pixel will be used from the + // BltBuffer. Delta is NOT used. + // + for (Y = 0; Y < Height; ++Y) { + SetMem32 ( + VgpuGop->BackingStore + + (DestinationY + Y) * CurrentHorizontal + DestinationX, + SegmentSize, + *(UINT32 *)BltBuffer + ); + } + break; + + case EfiBltVideoToBltBuffer: + // + // Read data from the video display rectangle (SourceX, SourceY) (SourceX + + // Width, SourceY + Height) and place it in the BltBuffer rectangle + // (DestinationX, DestinationY ) (DestinationX + Width, DestinationY + + // Height). If DestinationX or DestinationY is not zero then Delta must be + // set to the length in bytes of a row in the BltBuffer. + // + for (Y = 0; Y < Height; ++Y) { + CopyMem ( + (UINT8 *)BltBuffer + + (DestinationY + Y) * Delta + DestinationX * sizeof *BltBuffer, + VgpuGop->BackingStore + + (SourceY + Y) * CurrentHorizontal + SourceX, + SegmentSize + ); + } + return EFI_SUCCESS; + + case EfiBltBufferToVideo: + // + // Write data from the BltBuffer rectangle (SourceX, SourceY) (SourceX + + // Width, SourceY + Height) directly to the video display rectangle + // (DestinationX, DestinationY) (DestinationX + Width, DestinationY + + // Height). If SourceX or SourceY is not zero then Delta must be set to the + // length in bytes of a row in the BltBuffer. + // + for (Y = 0; Y < Height; ++Y) { + CopyMem ( + VgpuGop->BackingStore + + (DestinationY + Y) * CurrentHorizontal + DestinationX, + (UINT8 *)BltBuffer + + (SourceY + Y) * Delta + SourceX * sizeof *BltBuffer, + SegmentSize + ); + } + break; + + case EfiBltVideoToVideo: + // + // Copy from the video display rectangle (SourceX, SourceY) (SourceX + + // Width, SourceY + Height) to the video display rectangle (DestinationX, + // DestinationY) (DestinationX + Width, DestinationY + Height). The + // BltBuffer and Delta are not used in this mode. + // + // A single invocation of CopyMem() handles overlap between source and + // destination (that is, within a single line), but for multiple + // invocations, we must handle overlaps. + // + if (SourceY < DestinationY) { + Y = Height; + while (Y > 0) { + --Y; + CopyMem ( + VgpuGop->BackingStore + + (DestinationY + Y) * CurrentHorizontal + DestinationX, + VgpuGop->BackingStore + + (SourceY + Y) * CurrentHorizontal + SourceX, + SegmentSize + ); + } + } else { + for (Y = 0; Y < Height; ++Y) { + CopyMem ( + VgpuGop->BackingStore + + (DestinationY + Y) * CurrentHorizontal + DestinationX, + VgpuGop->BackingStore + + (SourceY + Y) * CurrentHorizontal + SourceX, + SegmentSize + ); + } + } + break; + + default: + return EFI_INVALID_PARAMETER; + } + + // + // For operations that wrote to the display, submit the updated area to the + // host -- update the host resource from guest memory. + // + ResourceOffset = sizeof (UINT32) * (DestinationY * CurrentHorizontal + + DestinationX); + Status = VirtioGpuTransferToHost2d ( + VgpuGop->ParentBus, // VgpuDev + (UINT32)DestinationX, // X + (UINT32)DestinationY, // Y + (UINT32)Width, // Width + (UINT32)Height, // Height + ResourceOffset, // Offset + VgpuGop->ResourceId // ResourceId + ); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Flush the updated resource to the display. + // + Status = VirtioGpuResourceFlush ( + VgpuGop->ParentBus, // VgpuDev + (UINT32)DestinationX, // X + (UINT32)DestinationY, // Y + (UINT32)Width, // Width + (UINT32)Height, // Height + VgpuGop->ResourceId // ResourceId + ); + return Status; +} + +// +// Template for initializing VGPU_GOP.Gop. +// +CONST EFI_GRAPHICS_OUTPUT_PROTOCOL mGopTemplate = { + GopQueryMode, + GopSetMode, + GopBlt, + NULL // Mode, to be overwritten in the actual protocol instance +}; diff --git a/src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/VirtioGpu.h b/src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/VirtioGpu.h new file mode 100644 index 00000000..2f10c003 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/VirtioGpu.h @@ -0,0 +1,402 @@ +/** @file + + Internal type and macro definitions for the Virtio GPU hybrid driver. + + Copyright (C) 2016, Red Hat, Inc. + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef _VIRTIO_GPU_DXE_H_ +#define _VIRTIO_GPU_DXE_H_ + +#include +#include +#include +#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; + + // + // VirtIo ring used for VirtIo communication. + // + VRING Ring; + + // + // Token associated with Ring's mapping for bus master common buffer + // operation, from VirtioRingMap(). + // + VOID *RingMap; + + // + // Event to be signaled at ExitBootServices(). + // + EFI_EVENT ExitBoot; + + // + // Common running counter for all VirtIo GPU requests that ask for fencing. + // + UINT64 FenceId; + + // + // 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. + // + EFI_GRAPHICS_OUTPUT_PROTOCOL Gop; + + // + // Referenced by Gop.Mode, GopMode provides a summary about the supported + // graphics modes, and the current mode. + // + EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE GopMode; + + // + // Referenced by GopMode.Info, GopModeInfo provides detailed information + // about the current mode. + // + EFI_GRAPHICS_OUTPUT_MODE_INFORMATION GopModeInfo; + + // + // Identifier of the 2D host resource that is in use by this head (scanout) + // of the VirtIo GPU device. Zero until the first successful -- internal -- + // Gop.SetMode() call, never zero afterwards. + // + UINT32 ResourceId; + + // + // A number of whole pages providing the backing store for the 2D host + // resource identified by ResourceId above. NULL until the first successful + // -- internal -- Gop.SetMode() call, never NULL afterwards. + // + UINT32 *BackingStore; + UINTN NumberOfPages; + + // + // Token associated with BackingStore's mapping for bus master common + // buffer operation. BackingStoreMap is valid if, and only if, + // BackingStore is non-NULL. + // + VOID *BackingStoreMap; +}; + +// +// 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 + ); + +/** + Allocate, zero and map memory, for bus master common buffer operation, to be + attached as backing store to a host-side VirtIo GPU resource. + + @param[in] VgpuDev The VGPU_DEV object that represents the VirtIo GPU + device. + + @param[in] NumberOfPages The number of whole pages to allocate and map. + + @param[out] HostAddress The system memory address of the allocated area. + + @param[out] DeviceAddress The bus master device address of the allocated + area. The VirtIo GPU device may be programmed to + access the allocated area through DeviceAddress; + DeviceAddress is to be passed to the + VirtioGpuResourceAttachBacking() function, as the + BackingStoreDeviceAddress parameter. + + @param[out] Mapping A resulting token to pass to + VirtioGpuUnmapAndFreeBackingStore(). + + @retval EFI_SUCCESS The requested number of pages has been allocated, zeroed + and mapped. + + @return Status codes propagated from + VgpuDev->VirtIo->AllocateSharedPages() and + VirtioMapAllBytesInSharedBuffer(). +**/ +EFI_STATUS +VirtioGpuAllocateZeroAndMapBackingStore ( + IN VGPU_DEV *VgpuDev, + IN UINTN NumberOfPages, + OUT VOID **HostAddress, + OUT EFI_PHYSICAL_ADDRESS *DeviceAddress, + OUT VOID **Mapping + ); + +/** + Unmap and free memory originally allocated and mapped with + VirtioGpuAllocateZeroAndMapBackingStore(). + + If the memory allocated and mapped with + VirtioGpuAllocateZeroAndMapBackingStore() was attached to a host-side VirtIo + GPU resource with VirtioGpuResourceAttachBacking(), then the caller is + responsible for detaching the backing store from the same resource, with + VirtioGpuResourceDetachBacking(), before calling this function. + + @param[in] VgpuDev The VGPU_DEV object that represents the VirtIo GPU + device. + + @param[in] NumberOfPages The NumberOfPages parameter originally passed to + VirtioGpuAllocateZeroAndMapBackingStore(). + + @param[in] HostAddress The HostAddress value originally output by + VirtioGpuAllocateZeroAndMapBackingStore(). + + @param[in] Mapping The token that was originally output by + VirtioGpuAllocateZeroAndMapBackingStore(). +**/ +VOID +VirtioGpuUnmapAndFreeBackingStore ( + IN VGPU_DEV *VgpuDev, + IN UINTN NumberOfPages, + IN VOID *HostAddress, + IN VOID *Mapping + ); + +/** + 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 + ); + +/** + The following functions send requests to the VirtIo GPU device model, await + the answer from the host, and return a status. They share the following + interface details: + + @param[in,out] VgpuDev The VGPU_DEV object that represents the VirtIo GPU + device. The caller is responsible to have + successfully invoked VirtioGpuInit() on VgpuDev + previously, while VirtioGpuUninit() must not have + been called on VgpuDev. + + @retval EFI_INVALID_PARAMETER Invalid command-specific parameters were + detected by this driver. + + @retval EFI_SUCCESS Operation successful. + + @retval EFI_DEVICE_ERROR The host rejected the request. The host error + code has been logged on the DEBUG_ERROR level. + + @return Codes for unexpected errors in VirtIo + messaging. + + For the command-specific parameters, please consult the GPU Device section of + the VirtIo 1.0 specification (see references in + "OvmfPkg/Include/IndustryStandard/VirtioGpu.h"). +**/ +EFI_STATUS +VirtioGpuResourceCreate2d ( + IN OUT VGPU_DEV *VgpuDev, + IN UINT32 ResourceId, + IN VIRTIO_GPU_FORMATS Format, + IN UINT32 Width, + IN UINT32 Height + ); + +EFI_STATUS +VirtioGpuResourceUnref ( + IN OUT VGPU_DEV *VgpuDev, + IN UINT32 ResourceId + ); + +EFI_STATUS +VirtioGpuResourceAttachBacking ( + IN OUT VGPU_DEV *VgpuDev, + IN UINT32 ResourceId, + IN EFI_PHYSICAL_ADDRESS BackingStoreDeviceAddress, + IN UINTN NumberOfPages + ); + +EFI_STATUS +VirtioGpuResourceDetachBacking ( + IN OUT VGPU_DEV *VgpuDev, + IN UINT32 ResourceId + ); + +EFI_STATUS +VirtioGpuSetScanout ( + IN OUT VGPU_DEV *VgpuDev, + IN UINT32 X, + IN UINT32 Y, + IN UINT32 Width, + IN UINT32 Height, + IN UINT32 ScanoutId, + IN UINT32 ResourceId + ); + +EFI_STATUS +VirtioGpuTransferToHost2d ( + IN OUT VGPU_DEV *VgpuDev, + IN UINT32 X, + IN UINT32 Y, + IN UINT32 Width, + IN UINT32 Height, + IN UINT64 Offset, + IN UINT32 ResourceId + ); + +EFI_STATUS +VirtioGpuResourceFlush ( + IN OUT VGPU_DEV *VgpuDev, + IN UINT32 X, + IN UINT32 Y, + IN UINT32 Width, + IN UINT32 Height, + IN UINT32 ResourceId + ); + +/** + Release guest-side and host-side resources that are related to an initialized + VGPU_GOP.Gop. + + param[in,out] VgpuGop The VGPU_GOP object to release resources for. + + On input, the caller is responsible for having called + VgpuGop->Gop.SetMode() at least once successfully. + (This is equivalent to the requirement that + VgpuGop->BackingStore be non-NULL. It is also + equivalent to the requirement that VgpuGop->ResourceId + be nonzero.) + + On output, resources will be released, and + VgpuGop->BackingStore and VgpuGop->ResourceId will be + nulled. + + param[in] DisableHead Whether this head (scanout) currently references the + resource identified by VgpuGop->ResourceId. Only pass + FALSE when VgpuGop->Gop.SetMode() calls this function + while switching between modes, and set it to TRUE + every other time. +**/ +VOID +ReleaseGopResources ( + IN OUT VGPU_GOP *VgpuGop, + IN BOOLEAN DisableHead + ); + +// +// Template for initializing VGPU_GOP.Gop. +// +extern CONST EFI_GRAPHICS_OUTPUT_PROTOCOL mGopTemplate; + +#endif // _VIRTIO_GPU_DXE_H_ diff --git a/src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf b/src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf new file mode 100644 index 00000000..97d013ed --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf @@ -0,0 +1,45 @@ +## @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. +# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[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] + Commands.c + DriverBinding.c + Gop.c + VirtioGpu.h + +[Packages] + MdePkg/MdePkg.dec + OvmfPkg/OvmfPkg.dec + +[LibraryClasses] + BaseMemoryLib + DebugLib + DevicePathLib + MemoryAllocationLib + PrintLib + UefiBootServicesTableLib + UefiDriverEntryPoint + UefiLib + VirtioLib + +[Protocols] + gEfiDevicePathProtocolGuid ## TO_START ## BY_START + gEfiGraphicsOutputProtocolGuid ## BY_START + gEfiPciIoProtocolGuid ## TO_START + gVirtioDeviceProtocolGuid ## TO_START -- cgit v1.2.3