From patchwork Fri Aug 19 12:49:31 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Laszlo Ersek X-Patchwork-Id: 74248 Delivered-To: patch@linaro.org Received: by 10.140.29.52 with SMTP id a49csp295765qga; Fri, 19 Aug 2016 05:50:18 -0700 (PDT) X-Received: by 10.98.14.208 with SMTP id 77mr13890164pfo.23.1471611004749; Fri, 19 Aug 2016 05:50:04 -0700 (PDT) Return-Path: Received: from ml01.01.org (ml01.01.org. [2001:19d0:306:5::1]) by mx.google.com with ESMTPS id su7si8189442pab.55.2016.08.19.05.50.04 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 19 Aug 2016 05:50:04 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of edk2-devel-bounces@lists.01.org designates 2001:19d0:306:5::1 as permitted sender) client-ip=2001:19d0:306:5::1; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of edk2-devel-bounces@lists.01.org designates 2001:19d0:306:5::1 as permitted sender) smtp.mailfrom=edk2-devel-bounces@lists.01.org Received: from [127.0.0.1] (localhost [IPv6:::1]) by ml01.01.org (Postfix) with ESMTP id 619731A1E3E; Fri, 19 Aug 2016 05:49:57 -0700 (PDT) X-Original-To: edk2-devel@ml01.01.org Delivered-To: edk2-devel@ml01.01.org Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ml01.01.org (Postfix) with ESMTPS id 86F5D1A1E25 for ; Fri, 19 Aug 2016 05:49:55 -0700 (PDT) Received: from int-mx11.intmail.prod.int.phx2.redhat.com (int-mx11.intmail.prod.int.phx2.redhat.com [10.5.11.24]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 1FBBB81F01; Fri, 19 Aug 2016 12:49:55 +0000 (UTC) Received: from lacos-laptop-7.usersys.redhat.com (ovpn-116-13.phx2.redhat.com [10.3.116.13]) by int-mx11.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id u7JCnaSp011583; Fri, 19 Aug 2016 08:49:54 -0400 From: Laszlo Ersek To: edk2-devel-01 Date: Fri, 19 Aug 2016 14:49:31 +0200 Message-Id: <20160819124932.29711-11-lersek@redhat.com> In-Reply-To: <20160819124932.29711-1-lersek@redhat.com> References: <20160819124932.29711-1-lersek@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.68 on 10.5.11.24 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.25]); Fri, 19 Aug 2016 12:49:55 +0000 (UTC) Subject: [edk2] [PATCH 10/11] OvmfPkg/VirtioGpuDxe: implement EFI_GRAPHICS_OUTPUT_PROTOCOL X-BeenThere: edk2-devel@lists.01.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: EDK II Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Jordan Justen , Ard Biesheuvel Errors-To: edk2-devel-bounces@lists.01.org Sender: "edk2-devel" 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 Cc: Jordan Justen Ref: https://tianocore.acgmultimedia.com/show_bug.cgi?id=66 Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Laszlo Ersek --- 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 diff --git a/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf b/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf index 7a6269eded51..04bc2964c223 100644 --- a/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf +++ b/OvmfPkg/VirtioGpuDxe/VirtioGpu.inf @@ -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 diff --git a/OvmfPkg/VirtioGpuDxe/VirtioGpu.h b/OvmfPkg/VirtioGpuDxe/VirtioGpu.h index f8839922487c..078b7d44d83e 100644 --- a/OvmfPkg/VirtioGpuDxe/VirtioGpu.h +++ b/OvmfPkg/VirtioGpuDxe/VirtioGpu.h @@ -17,12 +17,13 @@ #ifndef _VIRTIO_GPU_DXE_H_ #define _VIRTIO_GPU_DXE_H_ #include #include #include +#include #include // // 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_ diff --git a/OvmfPkg/VirtioGpuDxe/DriverBinding.c b/OvmfPkg/VirtioGpuDxe/DriverBinding.c index bdea55ef7dbf..33c1ad3b3110 100644 --- a/OvmfPkg/VirtioGpuDxe/DriverBinding.c +++ b/OvmfPkg/VirtioGpuDxe/DriverBinding.c @@ -26,22 +26,12 @@ #include #include #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, diff --git a/OvmfPkg/VirtioGpuDxe/Gop.c b/OvmfPkg/VirtioGpuDxe/Gop.c new file mode 100644 index 000000000000..c6ff9ed57461 --- /dev/null +++ b/OvmfPkg/VirtioGpuDxe/Gop.c @@ -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 +#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 (); + } + + // + // 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 +};