3 EFI_GRAPHICS_OUTPUT_PROTOCOL member functions for the VirtIo GPU driver.
5 Copyright (C) 2016, Red Hat, Inc.
7 SPDX-License-Identifier: BSD-2-Clause-Patent
11 #include <Library/MemoryAllocationLib.h>
13 #include "VirtioGpu.h"
16 Release guest-side and host-side resources that are related to an initialized
19 param[in,out] VgpuGop The VGPU_GOP object to release resources for.
21 On input, the caller is responsible for having called
22 VgpuGop->Gop.SetMode() at least once successfully.
23 (This is equivalent to the requirement that
24 VgpuGop->BackingStore be non-NULL. It is also
25 equivalent to the requirement that VgpuGop->ResourceId
28 On output, resources will be released, and
29 VgpuGop->BackingStore and VgpuGop->ResourceId will be
32 param[in] DisableHead Whether this head (scanout) currently references the
33 resource identified by VgpuGop->ResourceId. Only pass
34 FALSE when VgpuGop->Gop.SetMode() calls this function
35 while switching between modes, and set it to TRUE
40 IN OUT VGPU_GOP
*VgpuGop
,
41 IN BOOLEAN DisableHead
46 ASSERT (VgpuGop
->ResourceId
!= 0);
47 ASSERT (VgpuGop
->BackingStore
!= NULL
);
50 // If any of the following host-side destruction steps fail, we can't get out
51 // of an inconsistent state, so we'll hang. In general errors in object
52 // destruction can hardly be recovered from.
56 // Dissociate head (scanout) #0 from the currently used 2D host resource,
57 // by setting ResourceId=0 for it.
59 Status
= VirtioGpuSetScanout (
60 VgpuGop
->ParentBus
, // VgpuDev
64 0, // X, Y, Width, Height
71 // According to the GPU Device section of the VirtIo specification, the
72 // above operation is valid:
74 // "The driver can use resource_id = 0 to disable a scanout."
76 // However, in practice QEMU does not allow us to disable head (scanout) #0
77 // -- it rejects the command with response code 0x1202
78 // (VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID). Looking at the QEMU source
79 // code, function virtio_gpu_set_scanout() in "hw/display/virtio-gpu.c",
80 // this appears fully intentional, despite not being documented in the
83 // Surprisingly, ignoring the error here, and proceeding to release
84 // host-side resources that presumably underlie head (scanout) #0, work
85 // without any problems -- the driver survives repeated "disconnect" /
86 // "connect -r" commands in the UEFI shell.
88 // So, for now, let's just suppress the error.
95 ASSERT_EFI_ERROR (Status
);
96 if (EFI_ERROR (Status
)) {
102 // Detach backing pages from the currently used 2D host resource.
104 Status
= VirtioGpuResourceDetachBacking (
105 VgpuGop
->ParentBus
, // VgpuDev
106 VgpuGop
->ResourceId
// ResourceId
108 ASSERT_EFI_ERROR (Status
);
109 if (EFI_ERROR (Status
)) {
114 // Unmap and release backing pages.
116 VirtioGpuUnmapAndFreeBackingStore (
117 VgpuGop
->ParentBus
, // VgpuDev
118 VgpuGop
->NumberOfPages
, // NumberOfPages
119 VgpuGop
->BackingStore
, // HostAddress
120 VgpuGop
->BackingStoreMap
// Mapping
122 VgpuGop
->BackingStore
= NULL
;
123 VgpuGop
->NumberOfPages
= 0;
124 VgpuGop
->BackingStoreMap
= NULL
;
127 // Destroy the currently used 2D host resource.
129 Status
= VirtioGpuResourceUnref (
130 VgpuGop
->ParentBus
, // VgpuDev
131 VgpuGop
->ResourceId
// ResourceId
133 ASSERT_EFI_ERROR (Status
);
134 if (EFI_ERROR (Status
)) {
138 VgpuGop
->ResourceId
= 0;
142 // The resolutions supported by this driver.
149 STATIC CONST GOP_RESOLUTION mGopResolutions
[] = {
190 // Macro for casting VGPU_GOP.Gop to VGPU_GOP.
192 #define VGPU_GOP_FROM_GOP(GopPointer) \
193 CR (GopPointer, VGPU_GOP, Gop, VGPU_GOP_SIG)
199 IN EFI_GRAPHICS_OUTPUT_PROTOCOL
*This
204 VgpuGop
= VGPU_GOP_FROM_GOP (This
);
207 // Set up the Gop -> GopMode -> GopModeInfo pointer chain, and the other
208 // (nonzero) constant fields.
210 // No direct framebuffer access is supported, only Blt() is.
212 VgpuGop
->Gop
.Mode
= &VgpuGop
->GopMode
;
214 VgpuGop
->GopMode
.MaxMode
= (UINT32
)(ARRAY_SIZE (mGopResolutions
));
215 VgpuGop
->GopMode
.Info
= &VgpuGop
->GopModeInfo
;
216 VgpuGop
->GopMode
.SizeOfInfo
= sizeof VgpuGop
->GopModeInfo
;
218 VgpuGop
->GopModeInfo
.PixelFormat
= PixelBltOnly
;
222 // EFI_GRAPHICS_OUTPUT_PROTOCOL member functions.
228 IN EFI_GRAPHICS_OUTPUT_PROTOCOL
*This
,
229 IN UINT32 ModeNumber
,
230 OUT UINTN
*SizeOfInfo
,
231 OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION
**Info
234 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION
*GopModeInfo
;
236 if (ModeNumber
>= This
->Mode
->MaxMode
) {
237 return EFI_INVALID_PARAMETER
;
240 GopModeInfo
= AllocateZeroPool (sizeof *GopModeInfo
);
241 if (GopModeInfo
== NULL
) {
242 return EFI_OUT_OF_RESOURCES
;
245 GopModeInfo
->HorizontalResolution
= mGopResolutions
[ModeNumber
].Width
;
246 GopModeInfo
->VerticalResolution
= mGopResolutions
[ModeNumber
].Height
;
247 GopModeInfo
->PixelFormat
= PixelBltOnly
;
248 GopModeInfo
->PixelsPerScanLine
= mGopResolutions
[ModeNumber
].Width
;
250 *SizeOfInfo
= sizeof *GopModeInfo
;
259 IN EFI_GRAPHICS_OUTPUT_PROTOCOL
*This
,
264 UINT32 NewResourceId
;
265 UINTN NewNumberOfBytes
;
266 UINTN NewNumberOfPages
;
267 VOID
*NewBackingStore
;
268 EFI_PHYSICAL_ADDRESS NewBackingStoreDeviceAddress
;
269 VOID
*NewBackingStoreMap
;
271 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION
*GopModeInfo
;
277 // SetMode() call in InitVgpuGop() triggers this.
278 GopInitialize (This
);
281 Status
= GopQueryMode (This
, ModeNumber
, &SizeOfInfo
, &GopModeInfo
);
282 if (Status
!= EFI_SUCCESS
) {
286 VgpuGop
= VGPU_GOP_FROM_GOP (This
);
289 // Distinguish the first (internal) call from the other (protocol consumer)
292 if (VgpuGop
->ResourceId
== 0) {
294 // This is the first time we create a host side resource.
299 // We already have an active host side resource. Create the new one without
300 // interfering with the current one, so that we can cleanly bail out on
301 // error, without disturbing the current graphics mode.
303 // The formula below will alternate between IDs 1 and 2.
305 NewResourceId
= 3 - VgpuGop
->ResourceId
;
309 // Create the 2D host resource.
311 Status
= VirtioGpuResourceCreate2d (
312 VgpuGop
->ParentBus
, // VgpuDev
313 NewResourceId
, // ResourceId
314 VirtioGpuFormatB8G8R8X8Unorm
, // Format
315 GopModeInfo
->HorizontalResolution
, // Width
316 GopModeInfo
->VerticalResolution
// Height
318 if (EFI_ERROR (Status
)) {
323 // Allocate, zero and map guest backing store, for bus master common buffer
326 NewNumberOfBytes
= GopModeInfo
->HorizontalResolution
*
327 GopModeInfo
->VerticalResolution
* sizeof (UINT32
);
328 NewNumberOfPages
= EFI_SIZE_TO_PAGES (NewNumberOfBytes
);
329 Status
= VirtioGpuAllocateZeroAndMapBackingStore (
330 VgpuGop
->ParentBus
, // VgpuDev
331 NewNumberOfPages
, // NumberOfPages
332 &NewBackingStore
, // HostAddress
333 &NewBackingStoreDeviceAddress
, // DeviceAddress
334 &NewBackingStoreMap
// Mapping
336 if (EFI_ERROR (Status
)) {
337 goto DestroyHostResource
;
341 // Attach backing store to the host resource.
343 Status
= VirtioGpuResourceAttachBacking (
344 VgpuGop
->ParentBus
, // VgpuDev
345 NewResourceId
, // ResourceId
346 NewBackingStoreDeviceAddress
, // BackingStoreDeviceAddress
347 NewNumberOfPages
// NumberOfPages
349 if (EFI_ERROR (Status
)) {
350 goto UnmapAndFreeBackingStore
;
354 // Point head (scanout) #0 to the host resource.
356 Status
= VirtioGpuSetScanout (
357 VgpuGop
->ParentBus
, // VgpuDev
360 GopModeInfo
->HorizontalResolution
, // Width
361 GopModeInfo
->VerticalResolution
, // Height
363 NewResourceId
// ResourceId
365 if (EFI_ERROR (Status
)) {
366 goto DetachBackingStore
;
370 // If this is not the first (i.e., internal) call, then we have to (a) flush
371 // the new resource to head (scanout) #0, after having flipped the latter to
372 // the former above, plus (b) release the old resources.
374 if (VgpuGop
->ResourceId
!= 0) {
375 Status
= VirtioGpuResourceFlush (
376 VgpuGop
->ParentBus
, // VgpuDev
379 GopModeInfo
->HorizontalResolution
, // Width
380 GopModeInfo
->VerticalResolution
, // Height
381 NewResourceId
// ResourceId
383 if (EFI_ERROR (Status
)) {
385 // Flip head (scanout) #0 back to the current resource. If this fails, we
386 // cannot continue, as this error occurs on the error path and is
387 // therefore non-recoverable.
389 Status2
= VirtioGpuSetScanout (
390 VgpuGop
->ParentBus
, // VgpuDev
393 VgpuGop
->GopModeInfo
.HorizontalResolution
, // Width
394 VgpuGop
->GopModeInfo
.VerticalResolution
, // Height
396 VgpuGop
->ResourceId
// ResourceId
398 ASSERT_EFI_ERROR (Status2
);
399 if (EFI_ERROR (Status2
)) {
403 goto DetachBackingStore
;
407 // Flush successful; release the old resources (without disabling head
410 ReleaseGopResources (VgpuGop
, FALSE
/* DisableHead */);
414 // This is either the first (internal) call when we have no old resources
415 // yet, or we've changed the mode successfully and released the old
418 ASSERT (VgpuGop
->ResourceId
== 0);
419 ASSERT (VgpuGop
->BackingStore
== NULL
);
421 VgpuGop
->ResourceId
= NewResourceId
;
422 VgpuGop
->BackingStore
= NewBackingStore
;
423 VgpuGop
->NumberOfPages
= NewNumberOfPages
;
424 VgpuGop
->BackingStoreMap
= NewBackingStoreMap
;
427 // Populate Mode and ModeInfo (mutable fields only).
429 VgpuGop
->GopMode
.Mode
= ModeNumber
;
430 VgpuGop
->GopModeInfo
= *GopModeInfo
;
431 FreePool (GopModeInfo
);
435 Status2
= VirtioGpuResourceDetachBacking (VgpuGop
->ParentBus
, NewResourceId
);
436 ASSERT_EFI_ERROR (Status2
);
437 if (EFI_ERROR (Status2
)) {
441 UnmapAndFreeBackingStore
:
442 VirtioGpuUnmapAndFreeBackingStore (
443 VgpuGop
->ParentBus
, // VgpuDev
444 NewNumberOfPages
, // NumberOfPages
445 NewBackingStore
, // HostAddress
446 NewBackingStoreMap
// Mapping
450 Status2
= VirtioGpuResourceUnref (VgpuGop
->ParentBus
, NewResourceId
);
451 ASSERT_EFI_ERROR (Status2
);
452 if (EFI_ERROR (Status2
)) {
456 FreePool (GopModeInfo
);
464 IN EFI_GRAPHICS_OUTPUT_PROTOCOL
*This
,
465 IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL
*BltBuffer OPTIONAL
,
466 IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation
,
469 IN UINTN DestinationX
,
470 IN UINTN DestinationY
,
473 IN UINTN Delta OPTIONAL
477 UINT32 CurrentHorizontal
;
478 UINT32 CurrentVertical
;
481 UINTN ResourceOffset
;
484 VgpuGop
= VGPU_GOP_FROM_GOP (This
);
485 CurrentHorizontal
= VgpuGop
->GopModeInfo
.HorizontalResolution
;
486 CurrentVertical
= VgpuGop
->GopModeInfo
.VerticalResolution
;
489 // We can avoid pixel format conversion in the guest because the internal
490 // representation of EFI_GRAPHICS_OUTPUT_BLT_PIXEL and that of
491 // VirtioGpuFormatB8G8R8X8Unorm are identical.
493 SegmentSize
= Width
* sizeof (UINT32
);
496 // Delta is relevant for operations that read a rectangle from, or write a
497 // rectangle to, BltBuffer.
499 // In these cases, Delta is the stride of BltBuffer, in bytes. If Delta is
500 // zero, then Width is the entire width of BltBuffer, and the stride is
501 // supposed to be calculated from Width.
503 if ((BltOperation
== EfiBltVideoToBltBuffer
) ||
504 (BltOperation
== EfiBltBufferToVideo
))
512 // For operations that write to the display, check if the destination fits
515 if ((BltOperation
== EfiBltVideoFill
) ||
516 (BltOperation
== EfiBltBufferToVideo
) ||
517 (BltOperation
== EfiBltVideoToVideo
))
519 if ((DestinationX
> CurrentHorizontal
) ||
520 (Width
> CurrentHorizontal
- DestinationX
) ||
521 (DestinationY
> CurrentVertical
) ||
522 (Height
> CurrentVertical
- DestinationY
))
524 return EFI_INVALID_PARAMETER
;
529 // For operations that read from the display, check if the source fits onto
532 if ((BltOperation
== EfiBltVideoToBltBuffer
) ||
533 (BltOperation
== EfiBltVideoToVideo
))
535 if ((SourceX
> CurrentHorizontal
) ||
536 (Width
> CurrentHorizontal
- SourceX
) ||
537 (SourceY
> CurrentVertical
) ||
538 (Height
> CurrentVertical
- SourceY
))
540 return EFI_INVALID_PARAMETER
;
545 // Render the request. For requests that do not modify the display, there
546 // won't be further steps.
548 switch (BltOperation
) {
549 case EfiBltVideoFill
:
551 // Write data from the BltBuffer pixel (0, 0) directly to every pixel of
552 // the video display rectangle (DestinationX, DestinationY) (DestinationX +
553 // Width, DestinationY + Height). Only one pixel will be used from the
554 // BltBuffer. Delta is NOT used.
556 for (Y
= 0; Y
< Height
; ++Y
) {
558 VgpuGop
->BackingStore
+
559 (DestinationY
+ Y
) * CurrentHorizontal
+ DestinationX
,
567 case EfiBltVideoToBltBuffer
:
569 // Read data from the video display rectangle (SourceX, SourceY) (SourceX +
570 // Width, SourceY + Height) and place it in the BltBuffer rectangle
571 // (DestinationX, DestinationY ) (DestinationX + Width, DestinationY +
572 // Height). If DestinationX or DestinationY is not zero then Delta must be
573 // set to the length in bytes of a row in the BltBuffer.
575 for (Y
= 0; Y
< Height
; ++Y
) {
578 (DestinationY
+ Y
) * Delta
+ DestinationX
* sizeof *BltBuffer
,
579 VgpuGop
->BackingStore
+
580 (SourceY
+ Y
) * CurrentHorizontal
+ SourceX
,
587 case EfiBltBufferToVideo
:
589 // Write data from the BltBuffer rectangle (SourceX, SourceY) (SourceX +
590 // Width, SourceY + Height) directly to the video display rectangle
591 // (DestinationX, DestinationY) (DestinationX + Width, DestinationY +
592 // Height). If SourceX or SourceY is not zero then Delta must be set to the
593 // length in bytes of a row in the BltBuffer.
595 for (Y
= 0; Y
< Height
; ++Y
) {
597 VgpuGop
->BackingStore
+
598 (DestinationY
+ Y
) * CurrentHorizontal
+ DestinationX
,
600 (SourceY
+ Y
) * Delta
+ SourceX
* sizeof *BltBuffer
,
607 case EfiBltVideoToVideo
:
609 // Copy from the video display rectangle (SourceX, SourceY) (SourceX +
610 // Width, SourceY + Height) to the video display rectangle (DestinationX,
611 // DestinationY) (DestinationX + Width, DestinationY + Height). The
612 // BltBuffer and Delta are not used in this mode.
614 // A single invocation of CopyMem() handles overlap between source and
615 // destination (that is, within a single line), but for multiple
616 // invocations, we must handle overlaps.
618 if (SourceY
< DestinationY
) {
623 VgpuGop
->BackingStore
+
624 (DestinationY
+ Y
) * CurrentHorizontal
+ DestinationX
,
625 VgpuGop
->BackingStore
+
626 (SourceY
+ Y
) * CurrentHorizontal
+ SourceX
,
631 for (Y
= 0; Y
< Height
; ++Y
) {
633 VgpuGop
->BackingStore
+
634 (DestinationY
+ Y
) * CurrentHorizontal
+ DestinationX
,
635 VgpuGop
->BackingStore
+
636 (SourceY
+ Y
) * CurrentHorizontal
+ SourceX
,
645 return EFI_INVALID_PARAMETER
;
649 // For operations that wrote to the display, submit the updated area to the
650 // host -- update the host resource from guest memory.
652 ResourceOffset
= sizeof (UINT32
) * (DestinationY
* CurrentHorizontal
+
654 Status
= VirtioGpuTransferToHost2d (
655 VgpuGop
->ParentBus
, // VgpuDev
656 (UINT32
)DestinationX
, // X
657 (UINT32
)DestinationY
, // Y
658 (UINT32
)Width
, // Width
659 (UINT32
)Height
, // Height
660 ResourceOffset
, // Offset
661 VgpuGop
->ResourceId
// ResourceId
663 if (EFI_ERROR (Status
)) {
668 // Flush the updated resource to the display.
670 Status
= VirtioGpuResourceFlush (
671 VgpuGop
->ParentBus
, // VgpuDev
672 (UINT32
)DestinationX
, // X
673 (UINT32
)DestinationY
, // Y
674 (UINT32
)Width
, // Width
675 (UINT32
)Height
, // Height
676 VgpuGop
->ResourceId
// ResourceId
682 // Template for initializing VGPU_GOP.Gop.
684 CONST EFI_GRAPHICS_OUTPUT_PROTOCOL mGopTemplate
= {
688 NULL
// Mode, to be overwritten in the actual protocol instance