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>
12 #include <Library/PcdLib.h>
14 #include "VirtioGpu.h"
17 Release guest-side and host-side resources that are related to an initialized
20 param[in,out] VgpuGop The VGPU_GOP object to release resources for.
22 On input, the caller is responsible for having called
23 VgpuGop->Gop.SetMode() at least once successfully.
24 (This is equivalent to the requirement that
25 VgpuGop->BackingStore be non-NULL. It is also
26 equivalent to the requirement that VgpuGop->ResourceId
29 On output, resources will be released, and
30 VgpuGop->BackingStore and VgpuGop->ResourceId will be
33 param[in] DisableHead Whether this head (scanout) currently references the
34 resource identified by VgpuGop->ResourceId. Only pass
35 FALSE when VgpuGop->Gop.SetMode() calls this function
36 while switching between modes, and set it to TRUE
41 IN OUT VGPU_GOP
*VgpuGop
,
42 IN BOOLEAN DisableHead
47 ASSERT (VgpuGop
->ResourceId
!= 0);
48 ASSERT (VgpuGop
->BackingStore
!= NULL
);
51 // If any of the following host-side destruction steps fail, we can't get out
52 // of an inconsistent state, so we'll hang. In general errors in object
53 // destruction can hardly be recovered from.
57 // Dissociate head (scanout) #0 from the currently used 2D host resource,
58 // by setting ResourceId=0 for it.
60 Status
= VirtioGpuSetScanout (
61 VgpuGop
->ParentBus
, // VgpuDev
65 0, // X, Y, Width, Height
72 // According to the GPU Device section of the VirtIo specification, the
73 // above operation is valid:
75 // "The driver can use resource_id = 0 to disable a scanout."
77 // However, in practice QEMU does not allow us to disable head (scanout) #0
78 // -- it rejects the command with response code 0x1202
79 // (VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID). Looking at the QEMU source
80 // code, function virtio_gpu_set_scanout() in "hw/display/virtio-gpu.c",
81 // this appears fully intentional, despite not being documented in the
84 // Surprisingly, ignoring the error here, and proceeding to release
85 // host-side resources that presumably underlie head (scanout) #0, work
86 // without any problems -- the driver survives repeated "disconnect" /
87 // "connect -r" commands in the UEFI shell.
89 // So, for now, let's just suppress the error.
96 ASSERT_EFI_ERROR (Status
);
97 if (EFI_ERROR (Status
)) {
103 // Detach backing pages from the currently used 2D host resource.
105 Status
= VirtioGpuResourceDetachBacking (
106 VgpuGop
->ParentBus
, // VgpuDev
107 VgpuGop
->ResourceId
// ResourceId
109 ASSERT_EFI_ERROR (Status
);
110 if (EFI_ERROR (Status
)) {
115 // Unmap and release backing pages.
117 VirtioGpuUnmapAndFreeBackingStore (
118 VgpuGop
->ParentBus
, // VgpuDev
119 VgpuGop
->NumberOfPages
, // NumberOfPages
120 VgpuGop
->BackingStore
, // HostAddress
121 VgpuGop
->BackingStoreMap
// Mapping
123 VgpuGop
->BackingStore
= NULL
;
124 VgpuGop
->NumberOfPages
= 0;
125 VgpuGop
->BackingStoreMap
= NULL
;
128 // Destroy the currently used 2D host resource.
130 Status
= VirtioGpuResourceUnref (
131 VgpuGop
->ParentBus
, // VgpuDev
132 VgpuGop
->ResourceId
// ResourceId
134 ASSERT_EFI_ERROR (Status
);
135 if (EFI_ERROR (Status
)) {
139 VgpuGop
->ResourceId
= 0;
143 // The resolutions supported by this driver.
150 STATIC CONST GOP_RESOLUTION mGopResolutions
[] = {
191 // Macro for casting VGPU_GOP.Gop to VGPU_GOP.
193 #define VGPU_GOP_FROM_GOP(GopPointer) \
194 CR (GopPointer, VGPU_GOP, Gop, VGPU_GOP_SIG)
199 GopNativeResolution (
200 IN VGPU_GOP
*VgpuGop
,
205 volatile VIRTIO_GPU_RESP_DISPLAY_INFO DisplayInfo
;
209 Status
= VirtioGpuGetDisplayInfo (VgpuGop
->ParentBus
, &DisplayInfo
);
210 if (Status
!= EFI_SUCCESS
) {
214 for (Index
= 0; Index
< VIRTIO_GPU_MAX_SCANOUTS
; Index
++) {
215 if (!DisplayInfo
.Pmodes
[Index
].Enabled
||
216 !DisplayInfo
.Pmodes
[Index
].Rectangle
.Width
||
217 !DisplayInfo
.Pmodes
[Index
].Rectangle
.Height
)
227 DisplayInfo
.Pmodes
[Index
].Rectangle
.Width
,
228 DisplayInfo
.Pmodes
[Index
].Rectangle
.Height
230 if ((*XRes
== 0) || (*YRes
== 0)) {
231 *XRes
= DisplayInfo
.Pmodes
[Index
].Rectangle
.Width
;
232 *YRes
= DisplayInfo
.Pmodes
[Index
].Rectangle
.Height
;
241 IN EFI_GRAPHICS_OUTPUT_PROTOCOL
*This
246 UINT32 XRes
= 0, YRes
= 0, Index
;
248 VgpuGop
= VGPU_GOP_FROM_GOP (This
);
251 // Set up the Gop -> GopMode -> GopModeInfo pointer chain, and the other
252 // (nonzero) constant fields.
254 // No direct framebuffer access is supported, only Blt() is.
256 VgpuGop
->Gop
.Mode
= &VgpuGop
->GopMode
;
258 VgpuGop
->GopMode
.MaxMode
= (UINT32
)(ARRAY_SIZE (mGopResolutions
));
259 VgpuGop
->GopMode
.Info
= &VgpuGop
->GopModeInfo
;
260 VgpuGop
->GopMode
.SizeOfInfo
= sizeof VgpuGop
->GopModeInfo
;
262 VgpuGop
->GopModeInfo
.PixelFormat
= PixelBltOnly
;
265 // query host for display resolution
267 GopNativeResolution (VgpuGop
, &XRes
, &YRes
);
268 if ((XRes
== 0) || (YRes
== 0)) {
272 if (PcdGet8 (PcdVideoResolutionSource
) == 0) {
273 Status
= PcdSet32S (PcdVideoHorizontalResolution
, XRes
);
274 ASSERT_RETURN_ERROR (Status
);
275 Status
= PcdSet32S (PcdVideoVerticalResolution
, YRes
);
276 ASSERT_RETURN_ERROR (Status
);
277 Status
= PcdSet8S (PcdVideoResolutionSource
, 2);
278 ASSERT_RETURN_ERROR (Status
);
281 VgpuGop
->NativeXRes
= XRes
;
282 VgpuGop
->NativeYRes
= YRes
;
283 for (Index
= 0; Index
< ARRAY_SIZE (mGopResolutions
); Index
++) {
284 if ((mGopResolutions
[Index
].Width
== XRes
) &&
285 (mGopResolutions
[Index
].Height
== YRes
))
287 // native resolution already is in mode list
293 VgpuGop
->GopMode
.MaxMode
++;
297 // EFI_GRAPHICS_OUTPUT_PROTOCOL member functions.
303 IN EFI_GRAPHICS_OUTPUT_PROTOCOL
*This
,
304 IN UINT32 ModeNumber
,
305 OUT UINTN
*SizeOfInfo
,
306 OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION
**Info
309 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION
*GopModeInfo
;
311 if ((Info
== NULL
) ||
312 (SizeOfInfo
== NULL
) ||
313 (ModeNumber
>= This
->Mode
->MaxMode
))
315 return EFI_INVALID_PARAMETER
;
318 GopModeInfo
= AllocateZeroPool (sizeof *GopModeInfo
);
319 if (GopModeInfo
== NULL
) {
320 return EFI_OUT_OF_RESOURCES
;
323 if (ModeNumber
< ARRAY_SIZE (mGopResolutions
)) {
324 GopModeInfo
->HorizontalResolution
= mGopResolutions
[ModeNumber
].Width
;
325 GopModeInfo
->VerticalResolution
= mGopResolutions
[ModeNumber
].Height
;
327 VGPU_GOP
*VgpuGop
= VGPU_GOP_FROM_GOP (This
);
328 GopModeInfo
->HorizontalResolution
= VgpuGop
->NativeXRes
;
329 GopModeInfo
->VerticalResolution
= VgpuGop
->NativeYRes
;
332 GopModeInfo
->PixelFormat
= PixelBltOnly
;
333 GopModeInfo
->PixelsPerScanLine
= GopModeInfo
->HorizontalResolution
;
335 *SizeOfInfo
= sizeof *GopModeInfo
;
344 IN EFI_GRAPHICS_OUTPUT_PROTOCOL
*This
,
349 UINT32 NewResourceId
;
350 UINTN NewNumberOfBytes
;
351 UINTN NewNumberOfPages
;
352 VOID
*NewBackingStore
;
353 EFI_PHYSICAL_ADDRESS NewBackingStoreDeviceAddress
;
354 VOID
*NewBackingStoreMap
;
356 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION
*GopModeInfo
;
362 // SetMode() call in InitVgpuGop() triggers this.
363 GopInitialize (This
);
366 Status
= GopQueryMode (This
, ModeNumber
, &SizeOfInfo
, &GopModeInfo
);
367 if (Status
!= EFI_SUCCESS
) {
371 VgpuGop
= VGPU_GOP_FROM_GOP (This
);
374 // Distinguish the first (internal) call from the other (protocol consumer)
377 if (VgpuGop
->ResourceId
== 0) {
379 // This is the first time we create a host side resource.
384 // We already have an active host side resource. Create the new one without
385 // interfering with the current one, so that we can cleanly bail out on
386 // error, without disturbing the current graphics mode.
388 // The formula below will alternate between IDs 1 and 2.
390 NewResourceId
= 3 - VgpuGop
->ResourceId
;
394 // Create the 2D host resource.
396 Status
= VirtioGpuResourceCreate2d (
397 VgpuGop
->ParentBus
, // VgpuDev
398 NewResourceId
, // ResourceId
399 VirtioGpuFormatB8G8R8X8Unorm
, // Format
400 GopModeInfo
->HorizontalResolution
, // Width
401 GopModeInfo
->VerticalResolution
// Height
403 if (EFI_ERROR (Status
)) {
408 // Allocate, zero and map guest backing store, for bus master common buffer
411 NewNumberOfBytes
= GopModeInfo
->HorizontalResolution
*
412 GopModeInfo
->VerticalResolution
* sizeof (UINT32
);
413 NewNumberOfPages
= EFI_SIZE_TO_PAGES (NewNumberOfBytes
);
414 Status
= VirtioGpuAllocateZeroAndMapBackingStore (
415 VgpuGop
->ParentBus
, // VgpuDev
416 NewNumberOfPages
, // NumberOfPages
417 &NewBackingStore
, // HostAddress
418 &NewBackingStoreDeviceAddress
, // DeviceAddress
419 &NewBackingStoreMap
// Mapping
421 if (EFI_ERROR (Status
)) {
422 goto DestroyHostResource
;
426 // Attach backing store to the host resource.
428 Status
= VirtioGpuResourceAttachBacking (
429 VgpuGop
->ParentBus
, // VgpuDev
430 NewResourceId
, // ResourceId
431 NewBackingStoreDeviceAddress
, // BackingStoreDeviceAddress
432 NewNumberOfPages
// NumberOfPages
434 if (EFI_ERROR (Status
)) {
435 goto UnmapAndFreeBackingStore
;
439 // Point head (scanout) #0 to the host resource.
441 Status
= VirtioGpuSetScanout (
442 VgpuGop
->ParentBus
, // VgpuDev
445 GopModeInfo
->HorizontalResolution
, // Width
446 GopModeInfo
->VerticalResolution
, // Height
448 NewResourceId
// ResourceId
450 if (EFI_ERROR (Status
)) {
451 goto DetachBackingStore
;
455 // If this is not the first (i.e., internal) call, then we have to (a) flush
456 // the new resource to head (scanout) #0, after having flipped the latter to
457 // the former above, plus (b) release the old resources.
459 if (VgpuGop
->ResourceId
!= 0) {
460 Status
= VirtioGpuResourceFlush (
461 VgpuGop
->ParentBus
, // VgpuDev
464 GopModeInfo
->HorizontalResolution
, // Width
465 GopModeInfo
->VerticalResolution
, // Height
466 NewResourceId
// ResourceId
468 if (EFI_ERROR (Status
)) {
470 // Flip head (scanout) #0 back to the current resource. If this fails, we
471 // cannot continue, as this error occurs on the error path and is
472 // therefore non-recoverable.
474 Status2
= VirtioGpuSetScanout (
475 VgpuGop
->ParentBus
, // VgpuDev
478 VgpuGop
->GopModeInfo
.HorizontalResolution
, // Width
479 VgpuGop
->GopModeInfo
.VerticalResolution
, // Height
481 VgpuGop
->ResourceId
// ResourceId
483 ASSERT_EFI_ERROR (Status2
);
484 if (EFI_ERROR (Status2
)) {
488 goto DetachBackingStore
;
492 // Flush successful; release the old resources (without disabling head
495 ReleaseGopResources (VgpuGop
, FALSE
/* DisableHead */);
499 // This is either the first (internal) call when we have no old resources
500 // yet, or we've changed the mode successfully and released the old
503 ASSERT (VgpuGop
->ResourceId
== 0);
504 ASSERT (VgpuGop
->BackingStore
== NULL
);
506 VgpuGop
->ResourceId
= NewResourceId
;
507 VgpuGop
->BackingStore
= NewBackingStore
;
508 VgpuGop
->NumberOfPages
= NewNumberOfPages
;
509 VgpuGop
->BackingStoreMap
= NewBackingStoreMap
;
512 // Populate Mode and ModeInfo (mutable fields only).
514 VgpuGop
->GopMode
.Mode
= ModeNumber
;
515 CopyMem (&VgpuGop
->GopModeInfo
, GopModeInfo
, sizeof VgpuGop
->GopModeInfo
);
516 FreePool (GopModeInfo
);
520 Status2
= VirtioGpuResourceDetachBacking (VgpuGop
->ParentBus
, NewResourceId
);
521 ASSERT_EFI_ERROR (Status2
);
522 if (EFI_ERROR (Status2
)) {
526 UnmapAndFreeBackingStore
:
527 VirtioGpuUnmapAndFreeBackingStore (
528 VgpuGop
->ParentBus
, // VgpuDev
529 NewNumberOfPages
, // NumberOfPages
530 NewBackingStore
, // HostAddress
531 NewBackingStoreMap
// Mapping
535 Status2
= VirtioGpuResourceUnref (VgpuGop
->ParentBus
, NewResourceId
);
536 ASSERT_EFI_ERROR (Status2
);
537 if (EFI_ERROR (Status2
)) {
541 FreePool (GopModeInfo
);
549 IN EFI_GRAPHICS_OUTPUT_PROTOCOL
*This
,
550 IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL
*BltBuffer OPTIONAL
,
551 IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation
,
554 IN UINTN DestinationX
,
555 IN UINTN DestinationY
,
558 IN UINTN Delta OPTIONAL
562 UINT32 CurrentHorizontal
;
563 UINT32 CurrentVertical
;
566 UINTN ResourceOffset
;
569 VgpuGop
= VGPU_GOP_FROM_GOP (This
);
570 CurrentHorizontal
= VgpuGop
->GopModeInfo
.HorizontalResolution
;
571 CurrentVertical
= VgpuGop
->GopModeInfo
.VerticalResolution
;
574 // We can avoid pixel format conversion in the guest because the internal
575 // representation of EFI_GRAPHICS_OUTPUT_BLT_PIXEL and that of
576 // VirtioGpuFormatB8G8R8X8Unorm are identical.
578 SegmentSize
= Width
* sizeof (UINT32
);
581 // Delta is relevant for operations that read a rectangle from, or write a
582 // rectangle to, BltBuffer.
584 // In these cases, Delta is the stride of BltBuffer, in bytes. If Delta is
585 // zero, then Width is the entire width of BltBuffer, and the stride is
586 // supposed to be calculated from Width.
588 if ((BltOperation
== EfiBltVideoToBltBuffer
) ||
589 (BltOperation
== EfiBltBufferToVideo
))
597 // For operations that write to the display, check if the destination fits
600 if ((BltOperation
== EfiBltVideoFill
) ||
601 (BltOperation
== EfiBltBufferToVideo
) ||
602 (BltOperation
== EfiBltVideoToVideo
))
604 if ((DestinationX
> CurrentHorizontal
) ||
605 (Width
> CurrentHorizontal
- DestinationX
) ||
606 (DestinationY
> CurrentVertical
) ||
607 (Height
> CurrentVertical
- DestinationY
))
609 return EFI_INVALID_PARAMETER
;
614 // For operations that read from the display, check if the source fits onto
617 if ((BltOperation
== EfiBltVideoToBltBuffer
) ||
618 (BltOperation
== EfiBltVideoToVideo
))
620 if ((SourceX
> CurrentHorizontal
) ||
621 (Width
> CurrentHorizontal
- SourceX
) ||
622 (SourceY
> CurrentVertical
) ||
623 (Height
> CurrentVertical
- SourceY
))
625 return EFI_INVALID_PARAMETER
;
630 // Render the request. For requests that do not modify the display, there
631 // won't be further steps.
633 switch (BltOperation
) {
634 case EfiBltVideoFill
:
636 // Write data from the BltBuffer pixel (0, 0) directly to every pixel of
637 // the video display rectangle (DestinationX, DestinationY) (DestinationX +
638 // Width, DestinationY + Height). Only one pixel will be used from the
639 // BltBuffer. Delta is NOT used.
641 for (Y
= 0; Y
< Height
; ++Y
) {
643 VgpuGop
->BackingStore
+
644 (DestinationY
+ Y
) * CurrentHorizontal
+ DestinationX
,
652 case EfiBltVideoToBltBuffer
:
654 // Read data from the video display rectangle (SourceX, SourceY) (SourceX +
655 // Width, SourceY + Height) and place it in the BltBuffer rectangle
656 // (DestinationX, DestinationY ) (DestinationX + Width, DestinationY +
657 // Height). If DestinationX or DestinationY is not zero then Delta must be
658 // set to the length in bytes of a row in the BltBuffer.
660 for (Y
= 0; Y
< Height
; ++Y
) {
663 (DestinationY
+ Y
) * Delta
+ DestinationX
* sizeof *BltBuffer
,
664 VgpuGop
->BackingStore
+
665 (SourceY
+ Y
) * CurrentHorizontal
+ SourceX
,
672 case EfiBltBufferToVideo
:
674 // Write data from the BltBuffer rectangle (SourceX, SourceY) (SourceX +
675 // Width, SourceY + Height) directly to the video display rectangle
676 // (DestinationX, DestinationY) (DestinationX + Width, DestinationY +
677 // Height). If SourceX or SourceY is not zero then Delta must be set to the
678 // length in bytes of a row in the BltBuffer.
680 for (Y
= 0; Y
< Height
; ++Y
) {
682 VgpuGop
->BackingStore
+
683 (DestinationY
+ Y
) * CurrentHorizontal
+ DestinationX
,
685 (SourceY
+ Y
) * Delta
+ SourceX
* sizeof *BltBuffer
,
692 case EfiBltVideoToVideo
:
694 // Copy from the video display rectangle (SourceX, SourceY) (SourceX +
695 // Width, SourceY + Height) to the video display rectangle (DestinationX,
696 // DestinationY) (DestinationX + Width, DestinationY + Height). The
697 // BltBuffer and Delta are not used in this mode.
699 // A single invocation of CopyMem() handles overlap between source and
700 // destination (that is, within a single line), but for multiple
701 // invocations, we must handle overlaps.
703 if (SourceY
< DestinationY
) {
708 VgpuGop
->BackingStore
+
709 (DestinationY
+ Y
) * CurrentHorizontal
+ DestinationX
,
710 VgpuGop
->BackingStore
+
711 (SourceY
+ Y
) * CurrentHorizontal
+ SourceX
,
716 for (Y
= 0; Y
< Height
; ++Y
) {
718 VgpuGop
->BackingStore
+
719 (DestinationY
+ Y
) * CurrentHorizontal
+ DestinationX
,
720 VgpuGop
->BackingStore
+
721 (SourceY
+ Y
) * CurrentHorizontal
+ SourceX
,
730 return EFI_INVALID_PARAMETER
;
734 // For operations that wrote to the display, submit the updated area to the
735 // host -- update the host resource from guest memory.
737 ResourceOffset
= sizeof (UINT32
) * (DestinationY
* CurrentHorizontal
+
739 Status
= VirtioGpuTransferToHost2d (
740 VgpuGop
->ParentBus
, // VgpuDev
741 (UINT32
)DestinationX
, // X
742 (UINT32
)DestinationY
, // Y
743 (UINT32
)Width
, // Width
744 (UINT32
)Height
, // Height
745 ResourceOffset
, // Offset
746 VgpuGop
->ResourceId
// ResourceId
748 if (EFI_ERROR (Status
)) {
753 // Flush the updated resource to the display.
755 Status
= VirtioGpuResourceFlush (
756 VgpuGop
->ParentBus
, // VgpuDev
757 (UINT32
)DestinationX
, // X
758 (UINT32
)DestinationY
, // Y
759 (UINT32
)Width
, // Width
760 (UINT32
)Height
, // Height
761 VgpuGop
->ResourceId
// ResourceId
767 // Template for initializing VGPU_GOP.Gop.
769 CONST EFI_GRAPHICS_OUTPUT_PROTOCOL mGopTemplate
= {
773 NULL
// Mode, to be overwritten in the actual protocol instance