@@ -23,12 +23,13 @@ [Defines]
VERSION_STRING = 1.0
ENTRY_POINT = VirtioGpuEntryPoint
[Sources]
Commands.c
DriverBinding.c
+ Gop.c
VirtioGpu.h
[Packages]
MdePkg/MdePkg.dec
OvmfPkg/OvmfPkg.dec
@@ -42,8 +43,9 @@ [LibraryClasses]
UefiDriverEntryPoint
UefiLib
VirtioLib
[Protocols]
gEfiDevicePathProtocolGuid ## TO_START ## BY_START
+ gEfiGraphicsOutputProtocolGuid ## BY_START
gEfiPciIoProtocolGuid ## TO_START
gVirtioDeviceProtocolGuid ## TO_START
@@ -17,12 +17,13 @@
#ifndef _VIRTIO_GPU_DXE_H_
#define _VIRTIO_GPU_DXE_H_
#include <IndustryStandard/VirtioGpu.h>
#include <Library/DebugLib.h>
#include <Library/UefiLib.h>
+#include <Protocol/GraphicsOutput.h>
#include <Protocol/VirtioDevice.h>
//
// Forward declaration of VGPU_GOP.
//
typedef struct VGPU_GOP_STRUCT VGPU_GOP;
@@ -111,15 +112,40 @@ struct VGPU_GOP_STRUCT {
EFI_DEVICE_PATH_PROTOCOL *GopDevicePath;
//
// The Gop field is installed on the child handle as Graphics Output Protocol
// interface.
//
- // For now it is just a placeholder.
+ EFI_GRAPHICS_OUTPUT_PROTOCOL Gop;
+
//
- UINT8 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;
};
//
// VirtIo GPU initialization, and commands (primitives) for the GPU device.
//
/**
@@ -261,7 +287,41 @@ VirtioGpuResourceFlush (
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_
@@ -26,22 +26,12 @@
#include <Protocol/DriverBinding.h>
#include <Protocol/PciIo.h>
#include "VirtioGpu.h"
//
-// Dummy Graphics Output Protocol GUID: a temporary placeholder for the EFI
-// counterpart. It will be replaced with the real thing as soon as we implement
-// the EFI GOP. Refer to VGPU_GOP.Gop.
-//
-STATIC EFI_GUID mDummyGraphicsOutputProtocolGuid = {
- 0x4983f8dc, 0x2782, 0x415b,
- { 0x91, 0xf5, 0x2c, 0xeb, 0x48, 0x4a, 0x0f, 0xe9 }
-};
-
-//
// The device path node that describes the Video Output Device Attributes for
// the single head (UEFI child handle) that we support.
//
// The ACPI_DISPLAY_ADR() macro corresponds to Table B-2, section "B.4.2 _DOD"
// in the ACPI 3.0b spec, or more recently, to Table B-379, section "B.3.2
// _DOD" in the ACPI 6.0 spec.
@@ -353,24 +343,26 @@ InitVgpuGop (
}
ASSERT (ParentVirtIo == ParentBus->VirtIo);
//
// Initialize our Graphics Output Protocol.
//
- // This means "nothing" for now.
+ // 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.
//
- Status = EFI_SUCCESS;
+ 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,
- &mDummyGraphicsOutputProtocolGuid, EFI_NATIVE_INTERFACE,
+ &gEfiGraphicsOutputProtocolGuid, EFI_NATIVE_INTERFACE,
&VgpuGop->Gop);
if (EFI_ERROR (Status)) {
goto UninitGop;
}
//
@@ -378,15 +370,13 @@ InitVgpuGop (
//
gBS->RestoreTPL (OldTpl);
ParentBus->Child = VgpuGop;
return EFI_SUCCESS;
UninitGop:
- //
- // Nothing, for now.
- //
+ ReleaseGopResources (VgpuGop, TRUE /* DisableHead */);
CloseVirtIoByChild:
gBS->CloseProtocol (ParentBusController, &gVirtioDeviceProtocolGuid,
DriverBindingHandle, VgpuGop->GopHandle);
UninstallDevicePath:
@@ -436,22 +426,19 @@ UninitVgpuGop (
{
VGPU_GOP *VgpuGop;
EFI_STATUS Status;
VgpuGop = ParentBus->Child;
Status = gBS->UninstallProtocolInterface (VgpuGop->GopHandle,
- &mDummyGraphicsOutputProtocolGuid, &VgpuGop->Gop);
+ &gEfiGraphicsOutputProtocolGuid, &VgpuGop->Gop);
ASSERT_EFI_ERROR (Status);
//
// Uninitialize VgpuGop->Gop.
//
- // Nothing, for now.
- //
- Status = EFI_SUCCESS;
- ASSERT_EFI_ERROR (Status);
+ ReleaseGopResources (VgpuGop, TRUE /* DisableHead */);
Status = gBS->CloseProtocol (ParentBusController, &gVirtioDeviceProtocolGuid,
DriverBindingHandle, VgpuGop->GopHandle);
ASSERT_EFI_ERROR (Status);
Status = gBS->UninstallProtocolInterface (VgpuGop->GopHandle,
new file mode 100644
@@ -0,0 +1,647 @@
+/** @file
+
+ EFI_GRAPHICS_OUTPUT_PROTOCOL member functions for the VirtIo GPU driver.
+
+ Copyright (C) 2016, Red Hat, Inc.
+
+ This program and the accompanying materials are licensed and made available
+ under the terms and conditions of the BSD License which accompanies this
+ distribution. The full text of the license may be found at
+ http://opensource.org/licenses/bsd-license.php
+
+ THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT
+ WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
+
+**/
+
+#include <Library/BaseMemoryLib.h>
+#include <Library/MemoryAllocationLib.h>
+
+#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 ();
+ }
+
+ //
+ // Release backing pages.
+ //
+ FreePages (VgpuGop->BackingStore, VgpuGop->NumberOfPages);
+ VgpuGop->BackingStore = NULL;
+ VgpuGop->NumberOfPages = 0;
+
+ //
+ // 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 >= sizeof mGopResolutions / sizeof mGopResolutions[0]) {
+ 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_STATUS Status;
+ EFI_STATUS Status2;
+
+ if (ModeNumber >= sizeof mGopResolutions / sizeof mGopResolutions[0]) {
+ 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)(sizeof mGopResolutions /
+ sizeof mGopResolutions[0]);
+ 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 guest backing store.
+ //
+ NewNumberOfBytes = mGopResolutions[ModeNumber].Width *
+ mGopResolutions[ModeNumber].Height * sizeof (UINT32);
+ NewNumberOfPages = EFI_SIZE_TO_PAGES (NewNumberOfBytes);
+ NewBackingStore = AllocatePages (NewNumberOfPages);
+ if (NewBackingStore == NULL) {
+ Status = EFI_OUT_OF_RESOURCES;
+ goto DestroyHostResource;
+ }
+ //
+ // Fill visible part of backing store with black.
+ //
+ ZeroMem (NewBackingStore, NewNumberOfBytes);
+
+ //
+ // Attach backing store to the host resource.
+ //
+ Status = VirtioGpuResourceAttachBacking (
+ VgpuGop->ParentBus, // VgpuDev
+ NewResourceId, // ResourceId
+ NewBackingStore, // FirstBackingPage
+ NewNumberOfPages // NumberOfPages
+ );
+ if (EFI_ERROR (Status)) {
+ goto FreeBackingStore;
+ }
+
+ //
+ // 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;
+
+ //
+ // 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 ();
+ }
+
+FreeBackingStore:
+ FreePages (NewBackingStore, NewNumberOfPages);
+
+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
+};
In this patch we replace our "dummy" Graphics Output Protocol interface with the real one. We exploit that EFI_GRAPHICS_OUTPUT_BLT_PIXEL and VirtioGpuFormatB8G8R8X8Unorm have identical representations; this lets us forego any pixel format conversions in the guest. For messaging the VirtIo GPU device, we use the primitives introduced in the previous patch. Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org> Cc: Jordan Justen <jordan.l.justen@intel.com> Ref: https://tianocore.acgmultimedia.com/show_bug.cgi?id=66 Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Laszlo Ersek <lersek@redhat.com> --- OvmfPkg/VirtioGpuDxe/VirtioGpu.inf | 2 + OvmfPkg/VirtioGpuDxe/VirtioGpu.h | 64 +- OvmfPkg/VirtioGpuDxe/DriverBinding.c | 29 +- OvmfPkg/VirtioGpuDxe/Gop.c | 647 ++++++++++++++++++++ 4 files changed, 719 insertions(+), 23 deletions(-) -- 2.9.2 _______________________________________________ edk2-devel mailing list edk2-devel@lists.01.org https://lists.01.org/mailman/listinfo/edk2-devel