/** @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 < 640) || (YRes < 480)) {
    /* ignore hint, GraphicsConsoleDxe needs 640x480 or larger */
    return;
  }

  if (PcdGet8 (PcdVideoResolutionSource) == 0) {
    Status = PcdSet32S (PcdVideoHorizontalResolution, XRes);
    ASSERT_RETURN_ERROR (Status);
    Status = PcdSet32S (PcdSetupVideoHorizontalResolution, XRes);
    ASSERT_RETURN_ERROR (Status);
    Status = PcdSet32S (PcdVideoVerticalResolution, YRes);
    ASSERT_RETURN_ERROR (Status);
    Status = PcdSet32S (PcdSetupVideoVerticalResolution, 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
};
