/** @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 <Library/MemoryAllocationLib.h> | |
#include <Library/PcdLib.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 (); | |
} | |
// | |
// 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) | |
STATIC | |
VOID | |
EFIAPI | |
GopNativeResolution ( | |
IN VGPU_GOP *VgpuGop, | |
OUT UINT32 *XRes, | |
OUT UINT32 *YRes | |
) | |
{ | |
volatile VIRTIO_GPU_RESP_DISPLAY_INFO DisplayInfo; | |
EFI_STATUS Status; | |
UINTN Index; | |
Status = VirtioGpuGetDisplayInfo (VgpuGop->ParentBus, &DisplayInfo); | |
if (Status != EFI_SUCCESS) { | |
return; | |
} | |
for (Index = 0; Index < VIRTIO_GPU_MAX_SCANOUTS; Index++) { | |
if (!DisplayInfo.Pmodes[Index].Enabled || | |
!DisplayInfo.Pmodes[Index].Rectangle.Width || | |
!DisplayInfo.Pmodes[Index].Rectangle.Height) | |
{ | |
continue; | |
} | |
DEBUG (( | |
DEBUG_INFO, | |
"%a: #%d: %dx%d\n", | |
__func__, | |
Index, | |
DisplayInfo.Pmodes[Index].Rectangle.Width, | |
DisplayInfo.Pmodes[Index].Rectangle.Height | |
)); | |
if ((*XRes == 0) || (*YRes == 0)) { | |
*XRes = DisplayInfo.Pmodes[Index].Rectangle.Width; | |
*YRes = DisplayInfo.Pmodes[Index].Rectangle.Height; | |
} | |
} | |
} | |
STATIC | |
VOID | |
EFIAPI | |
GopInitialize ( | |
IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This | |
) | |
{ | |
VGPU_GOP *VgpuGop; | |
EFI_STATUS Status; | |
UINT32 XRes = 0, YRes = 0, Index; | |
VgpuGop = VGPU_GOP_FROM_GOP (This); | |
// | |
// 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; | |
// | |
// query host for display resolution | |
// | |
GopNativeResolution (VgpuGop, &XRes, &YRes); | |
if ((XRes == 0) || (YRes == 0)) { | |
return; | |
} | |
if (PcdGet8 (PcdVideoResolutionSource) == 0) { | |
Status = PcdSet32S (PcdVideoHorizontalResolution, XRes); | |
ASSERT_RETURN_ERROR (Status); | |
Status = PcdSet32S (PcdVideoVerticalResolution, YRes); | |
ASSERT_RETURN_ERROR (Status); | |
Status = PcdSet8S (PcdVideoResolutionSource, 2); | |
ASSERT_RETURN_ERROR (Status); | |
} | |
VgpuGop->NativeXRes = XRes; | |
VgpuGop->NativeYRes = YRes; | |
for (Index = 0; Index < ARRAY_SIZE (mGopResolutions); Index++) { | |
if ((mGopResolutions[Index].Width == XRes) && | |
(mGopResolutions[Index].Height == YRes)) | |
{ | |
// native resolution already is in mode list | |
return; | |
} | |
} | |
// add to mode list | |
VgpuGop->GopMode.MaxMode++; | |
} | |
// | |
// 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 ((Info == NULL) || | |
(SizeOfInfo == NULL) || | |
(ModeNumber >= This->Mode->MaxMode)) | |
{ | |
return EFI_INVALID_PARAMETER; | |
} | |
GopModeInfo = AllocateZeroPool (sizeof *GopModeInfo); | |
if (GopModeInfo == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
if (ModeNumber < ARRAY_SIZE (mGopResolutions)) { | |
GopModeInfo->HorizontalResolution = mGopResolutions[ModeNumber].Width; | |
GopModeInfo->VerticalResolution = mGopResolutions[ModeNumber].Height; | |
} else { | |
VGPU_GOP *VgpuGop = VGPU_GOP_FROM_GOP (This); | |
GopModeInfo->HorizontalResolution = VgpuGop->NativeXRes; | |
GopModeInfo->VerticalResolution = VgpuGop->NativeYRes; | |
} | |
GopModeInfo->PixelFormat = PixelBltOnly; | |
GopModeInfo->PixelsPerScanLine = GopModeInfo->HorizontalResolution; | |
*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; | |
UINTN SizeOfInfo; | |
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *GopModeInfo; | |
EFI_STATUS Status; | |
EFI_STATUS Status2; | |
if (!This->Mode) { | |
// SetMode() call in InitVgpuGop() triggers this. | |
GopInitialize (This); | |
} | |
Status = GopQueryMode (This, ModeNumber, &SizeOfInfo, &GopModeInfo); | |
if (Status != EFI_SUCCESS) { | |
return Status; | |
} | |
VgpuGop = VGPU_GOP_FROM_GOP (This); | |
// | |
// Distinguish the first (internal) call from the other (protocol consumer) | |
// calls. | |
// | |
if (VgpuGop->ResourceId == 0) { | |
// | |
// 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 | |
GopModeInfo->HorizontalResolution, // Width | |
GopModeInfo->VerticalResolution // Height | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// Allocate, zero and map guest backing store, for bus master common buffer | |
// operation. | |
// | |
NewNumberOfBytes = GopModeInfo->HorizontalResolution * | |
GopModeInfo->VerticalResolution * 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 | |
GopModeInfo->HorizontalResolution, // Width | |
GopModeInfo->VerticalResolution, // 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 | |
GopModeInfo->HorizontalResolution, // Width | |
GopModeInfo->VerticalResolution, // 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 | |
VgpuGop->GopModeInfo.HorizontalResolution, // Width | |
VgpuGop->GopModeInfo.VerticalResolution, // 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; | |
CopyMem (&VgpuGop->GopModeInfo, GopModeInfo, sizeof VgpuGop->GopModeInfo); | |
FreePool (GopModeInfo); | |
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 (); | |
} | |
FreePool (GopModeInfo); | |
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 | |
}; |