/** @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 #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 (); } // // 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 };