text: /** @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 "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
  };
  