3 EFI_GRAPHICS_OUTPUT_PROTOCOL member functions for the VirtIo GPU driver.
5 Copyright (C) 2016, Red Hat, Inc.
7 This program and the accompanying materials are licensed and made available
8 under the terms and conditions of the BSD License which accompanies this
9 distribution. The full text of the license may be found at
10 http://opensource.org/licenses/bsd-license.php
12 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT
13 WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
17 #include <Library/BaseMemoryLib.h>
18 #include <Library/MemoryAllocationLib.h>
20 #include "VirtioGpu.h"
23 Release guest-side and host-side resources that are related to an initialized
26 param[in,out] VgpuGop The VGPU_GOP object to release resources for.
28 On input, the caller is responsible for having called
29 VgpuGop->Gop.SetMode() at least once successfully.
30 (This is equivalent to the requirement that
31 VgpuGop->BackingStore be non-NULL. It is also
32 equivalent to the requirement that VgpuGop->ResourceId
35 On output, resources will be released, and
36 VgpuGop->BackingStore and VgpuGop->ResourceId will be
39 param[in] DisableHead Whether this head (scanout) currently references the
40 resource identified by VgpuGop->ResourceId. Only pass
41 FALSE when VgpuGop->Gop.SetMode() calls this function
42 while switching between modes, and set it to TRUE
47 IN OUT VGPU_GOP
*VgpuGop
,
48 IN BOOLEAN DisableHead
53 ASSERT (VgpuGop
->ResourceId
!= 0);
54 ASSERT (VgpuGop
->BackingStore
!= NULL
);
57 // If any of the following host-side destruction steps fail, we can't get out
58 // of an inconsistent state, so we'll hang. In general errors in object
59 // destruction can hardly be recovered from.
63 // Dissociate head (scanout) #0 from the currently used 2D host resource,
64 // by setting ResourceId=0 for it.
66 Status
= VirtioGpuSetScanout (
67 VgpuGop
->ParentBus
, // VgpuDev
68 0, 0, 0, 0, // X, Y, Width, Height
75 // According to the GPU Device section of the VirtIo specification, the
76 // above operation is valid:
78 // "The driver can use resource_id = 0 to disable a scanout."
80 // However, in practice QEMU does not allow us to disable head (scanout) #0
81 // -- it rejects the command with response code 0x1202
82 // (VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID). Looking at the QEMU source
83 // code, function virtio_gpu_set_scanout() in "hw/display/virtio-gpu.c",
84 // this appears fully intentional, despite not being documented in the
87 // Surprisingly, ignoring the error here, and proceeding to release
88 // host-side resources that presumably underlie head (scanout) #0, work
89 // without any problems -- the driver survives repeated "disconnect" /
90 // "connect -r" commands in the UEFI shell.
92 // So, for now, let's just suppress the error.
99 ASSERT_EFI_ERROR (Status
);
100 if (EFI_ERROR (Status
)) {
106 // Detach backing pages from the currently used 2D host resource.
108 Status
= VirtioGpuResourceDetachBacking (
109 VgpuGop
->ParentBus
, // VgpuDev
110 VgpuGop
->ResourceId
// ResourceId
112 ASSERT_EFI_ERROR (Status
);
113 if (EFI_ERROR (Status
)) {
118 // Release backing pages.
120 FreePages (VgpuGop
->BackingStore
, VgpuGop
->NumberOfPages
);
121 VgpuGop
->BackingStore
= NULL
;
122 VgpuGop
->NumberOfPages
= 0;
125 // Destroy the currently used 2D host resource.
127 Status
= VirtioGpuResourceUnref (
128 VgpuGop
->ParentBus
, // VgpuDev
129 VgpuGop
->ResourceId
// ResourceId
131 ASSERT_EFI_ERROR (Status
);
132 if (EFI_ERROR (Status
)) {
135 VgpuGop
->ResourceId
= 0;
139 // The resolutions supported by this driver.
146 STATIC CONST GOP_RESOLUTION mGopResolutions
[] = {
187 // Macro for casting VGPU_GOP.Gop to VGPU_GOP.
189 #define VGPU_GOP_FROM_GOP(GopPointer) \
190 CR (GopPointer, VGPU_GOP, Gop, VGPU_GOP_SIG)
193 // EFI_GRAPHICS_OUTPUT_PROTOCOL member functions.
199 IN EFI_GRAPHICS_OUTPUT_PROTOCOL
*This
,
200 IN UINT32 ModeNumber
,
201 OUT UINTN
*SizeOfInfo
,
202 OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION
**Info
205 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION
*GopModeInfo
;
207 if (ModeNumber
>= sizeof mGopResolutions
/ sizeof mGopResolutions
[0]) {
208 return EFI_INVALID_PARAMETER
;
211 GopModeInfo
= AllocateZeroPool (sizeof *GopModeInfo
);
212 if (GopModeInfo
== NULL
) {
213 return EFI_OUT_OF_RESOURCES
;
216 GopModeInfo
->HorizontalResolution
= mGopResolutions
[ModeNumber
].Width
;
217 GopModeInfo
->VerticalResolution
= mGopResolutions
[ModeNumber
].Height
;
218 GopModeInfo
->PixelFormat
= PixelBltOnly
;
219 GopModeInfo
->PixelsPerScanLine
= mGopResolutions
[ModeNumber
].Width
;
221 *SizeOfInfo
= sizeof *GopModeInfo
;
230 IN EFI_GRAPHICS_OUTPUT_PROTOCOL
*This
,
235 UINT32 NewResourceId
;
236 UINTN NewNumberOfBytes
;
237 UINTN NewNumberOfPages
;
238 VOID
*NewBackingStore
;
242 if (ModeNumber
>= sizeof mGopResolutions
/ sizeof mGopResolutions
[0]) {
243 return EFI_UNSUPPORTED
;
246 VgpuGop
= VGPU_GOP_FROM_GOP (This
);
249 // Distinguish the first (internal) call from the other (protocol consumer)
252 if (VgpuGop
->ResourceId
== 0) {
254 // Set up the Gop -> GopMode -> GopModeInfo pointer chain, and the other
255 // (nonzero) constant fields.
257 // No direct framebuffer access is supported, only Blt() is.
259 VgpuGop
->Gop
.Mode
= &VgpuGop
->GopMode
;
261 VgpuGop
->GopMode
.MaxMode
= (UINT32
)(sizeof mGopResolutions
/
262 sizeof mGopResolutions
[0]);
263 VgpuGop
->GopMode
.Info
= &VgpuGop
->GopModeInfo
;
264 VgpuGop
->GopMode
.SizeOfInfo
= sizeof VgpuGop
->GopModeInfo
;
266 VgpuGop
->GopModeInfo
.PixelFormat
= PixelBltOnly
;
269 // This is the first time we create a host side resource.
274 // We already have an active host side resource. Create the new one without
275 // interfering with the current one, so that we can cleanly bail out on
276 // error, without disturbing the current graphics mode.
278 // The formula below will alternate between IDs 1 and 2.
280 NewResourceId
= 3 - VgpuGop
->ResourceId
;
284 // Create the 2D host resource.
286 Status
= VirtioGpuResourceCreate2d (
287 VgpuGop
->ParentBus
, // VgpuDev
288 NewResourceId
, // ResourceId
289 VirtioGpuFormatB8G8R8X8Unorm
, // Format
290 mGopResolutions
[ModeNumber
].Width
, // Width
291 mGopResolutions
[ModeNumber
].Height
// Height
293 if (EFI_ERROR (Status
)) {
298 // Allocate guest backing store.
300 NewNumberOfBytes
= mGopResolutions
[ModeNumber
].Width
*
301 mGopResolutions
[ModeNumber
].Height
* sizeof (UINT32
);
302 NewNumberOfPages
= EFI_SIZE_TO_PAGES (NewNumberOfBytes
);
303 NewBackingStore
= AllocatePages (NewNumberOfPages
);
304 if (NewBackingStore
== NULL
) {
305 Status
= EFI_OUT_OF_RESOURCES
;
306 goto DestroyHostResource
;
309 // Fill visible part of backing store with black.
311 ZeroMem (NewBackingStore
, NewNumberOfBytes
);
314 // Attach backing store to the host resource.
316 Status
= VirtioGpuResourceAttachBacking (
317 VgpuGop
->ParentBus
, // VgpuDev
318 NewResourceId
, // ResourceId
319 NewBackingStore
, // FirstBackingPage
320 NewNumberOfPages
// NumberOfPages
322 if (EFI_ERROR (Status
)) {
323 goto FreeBackingStore
;
327 // Point head (scanout) #0 to the host resource.
329 Status
= VirtioGpuSetScanout (
330 VgpuGop
->ParentBus
, // VgpuDev
333 mGopResolutions
[ModeNumber
].Width
, // Width
334 mGopResolutions
[ModeNumber
].Height
, // Height
336 NewResourceId
// ResourceId
338 if (EFI_ERROR (Status
)) {
339 goto DetachBackingStore
;
343 // If this is not the first (i.e., internal) call, then we have to (a) flush
344 // the new resource to head (scanout) #0, after having flipped the latter to
345 // the former above, plus (b) release the old resources.
347 if (VgpuGop
->ResourceId
!= 0) {
348 Status
= VirtioGpuResourceFlush (
349 VgpuGop
->ParentBus
, // VgpuDev
352 mGopResolutions
[ModeNumber
].Width
, // Width
353 mGopResolutions
[ModeNumber
].Height
, // Height
354 NewResourceId
// ResourceId
356 if (EFI_ERROR (Status
)) {
358 // Flip head (scanout) #0 back to the current resource. If this fails, we
359 // cannot continue, as this error occurs on the error path and is
360 // therefore non-recoverable.
362 Status2
= VirtioGpuSetScanout (
363 VgpuGop
->ParentBus
, // VgpuDev
366 mGopResolutions
[This
->Mode
->Mode
].Width
, // Width
367 mGopResolutions
[This
->Mode
->Mode
].Height
, // Height
369 VgpuGop
->ResourceId
// ResourceId
371 ASSERT_EFI_ERROR (Status2
);
372 if (EFI_ERROR (Status2
)) {
375 goto DetachBackingStore
;
379 // Flush successful; release the old resources (without disabling head
382 ReleaseGopResources (VgpuGop
, FALSE
/* DisableHead */);
386 // This is either the first (internal) call when we have no old resources
387 // yet, or we've changed the mode successfully and released the old
390 ASSERT (VgpuGop
->ResourceId
== 0);
391 ASSERT (VgpuGop
->BackingStore
== NULL
);
393 VgpuGop
->ResourceId
= NewResourceId
;
394 VgpuGop
->BackingStore
= NewBackingStore
;
395 VgpuGop
->NumberOfPages
= NewNumberOfPages
;
398 // Populate Mode and ModeInfo (mutable fields only).
400 VgpuGop
->GopMode
.Mode
= ModeNumber
;
401 VgpuGop
->GopModeInfo
.HorizontalResolution
=
402 mGopResolutions
[ModeNumber
].Width
;
403 VgpuGop
->GopModeInfo
.VerticalResolution
= mGopResolutions
[ModeNumber
].Height
;
404 VgpuGop
->GopModeInfo
.PixelsPerScanLine
= mGopResolutions
[ModeNumber
].Width
;
408 Status2
= VirtioGpuResourceDetachBacking (VgpuGop
->ParentBus
, NewResourceId
);
409 ASSERT_EFI_ERROR (Status2
);
410 if (EFI_ERROR (Status2
)) {
415 FreePages (NewBackingStore
, NewNumberOfPages
);
418 Status2
= VirtioGpuResourceUnref (VgpuGop
->ParentBus
, NewResourceId
);
419 ASSERT_EFI_ERROR (Status2
);
420 if (EFI_ERROR (Status2
)) {
431 IN EFI_GRAPHICS_OUTPUT_PROTOCOL
*This
,
432 IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL
*BltBuffer
, OPTIONAL
433 IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation
,
436 IN UINTN DestinationX
,
437 IN UINTN DestinationY
,
440 IN UINTN Delta OPTIONAL
444 UINT32 CurrentHorizontal
;
445 UINT32 CurrentVertical
;
448 UINTN ResourceOffset
;
451 VgpuGop
= VGPU_GOP_FROM_GOP (This
);
452 CurrentHorizontal
= VgpuGop
->GopModeInfo
.HorizontalResolution
;
453 CurrentVertical
= VgpuGop
->GopModeInfo
.VerticalResolution
;
456 // We can avoid pixel format conversion in the guest because the internal
457 // representation of EFI_GRAPHICS_OUTPUT_BLT_PIXEL and that of
458 // VirtioGpuFormatB8G8R8X8Unorm are identical.
460 SegmentSize
= Width
* sizeof (UINT32
);
463 // Delta is relevant for operations that read a rectangle from, or write a
464 // rectangle to, BltBuffer.
466 // In these cases, Delta is the stride of BltBuffer, in bytes. If Delta is
467 // zero, then Width is the entire width of BltBuffer, and the stride is
468 // supposed to be calculated from Width.
470 if (BltOperation
== EfiBltVideoToBltBuffer
||
471 BltOperation
== EfiBltBufferToVideo
) {
478 // For operations that write to the display, check if the destination fits
481 if (BltOperation
== EfiBltVideoFill
||
482 BltOperation
== EfiBltBufferToVideo
||
483 BltOperation
== EfiBltVideoToVideo
) {
484 if (DestinationX
> CurrentHorizontal
||
485 Width
> CurrentHorizontal
- DestinationX
||
486 DestinationY
> CurrentVertical
||
487 Height
> CurrentVertical
- DestinationY
) {
488 return EFI_INVALID_PARAMETER
;
493 // For operations that read from the display, check if the source fits onto
496 if (BltOperation
== EfiBltVideoToBltBuffer
||
497 BltOperation
== EfiBltVideoToVideo
) {
498 if (SourceX
> CurrentHorizontal
||
499 Width
> CurrentHorizontal
- SourceX
||
500 SourceY
> CurrentVertical
||
501 Height
> CurrentVertical
- SourceY
) {
502 return EFI_INVALID_PARAMETER
;
507 // Render the request. For requests that do not modify the display, there
508 // won't be further steps.
510 switch (BltOperation
) {
511 case EfiBltVideoFill
:
513 // Write data from the BltBuffer pixel (0, 0) directly to every pixel of
514 // the video display rectangle (DestinationX, DestinationY) (DestinationX +
515 // Width, DestinationY + Height). Only one pixel will be used from the
516 // BltBuffer. Delta is NOT used.
518 for (Y
= 0; Y
< Height
; ++Y
) {
520 VgpuGop
->BackingStore
+
521 (DestinationY
+ Y
) * CurrentHorizontal
+ DestinationX
,
528 case EfiBltVideoToBltBuffer
:
530 // Read data from the video display rectangle (SourceX, SourceY) (SourceX +
531 // Width, SourceY + Height) and place it in the BltBuffer rectangle
532 // (DestinationX, DestinationY ) (DestinationX + Width, DestinationY +
533 // Height). If DestinationX or DestinationY is not zero then Delta must be
534 // set to the length in bytes of a row in the BltBuffer.
536 for (Y
= 0; Y
< Height
; ++Y
) {
539 (DestinationY
+ Y
) * Delta
+ DestinationX
* sizeof *BltBuffer
,
540 VgpuGop
->BackingStore
+
541 (SourceY
+ Y
) * CurrentHorizontal
+ SourceX
,
547 case EfiBltBufferToVideo
:
549 // Write data from the BltBuffer rectangle (SourceX, SourceY) (SourceX +
550 // Width, SourceY + Height) directly to the video display rectangle
551 // (DestinationX, DestinationY) (DestinationX + Width, DestinationY +
552 // Height). If SourceX or SourceY is not zero then Delta must be set to the
553 // length in bytes of a row in the BltBuffer.
555 for (Y
= 0; Y
< Height
; ++Y
) {
557 VgpuGop
->BackingStore
+
558 (DestinationY
+ Y
) * CurrentHorizontal
+ DestinationX
,
560 (SourceY
+ Y
) * Delta
+ SourceX
* sizeof *BltBuffer
,
566 case EfiBltVideoToVideo
:
568 // Copy from the video display rectangle (SourceX, SourceY) (SourceX +
569 // Width, SourceY + Height) to the video display rectangle (DestinationX,
570 // DestinationY) (DestinationX + Width, DestinationY + Height). The
571 // BltBuffer and Delta are not used in this mode.
573 // A single invocation of CopyMem() handles overlap between source and
574 // destination (that is, within a single line), but for multiple
575 // invocations, we must handle overlaps.
577 if (SourceY
< DestinationY
) {
582 VgpuGop
->BackingStore
+
583 (DestinationY
+ Y
) * CurrentHorizontal
+ DestinationX
,
584 VgpuGop
->BackingStore
+
585 (SourceY
+ Y
) * CurrentHorizontal
+ SourceX
,
590 for (Y
= 0; Y
< Height
; ++Y
) {
592 VgpuGop
->BackingStore
+
593 (DestinationY
+ Y
) * CurrentHorizontal
+ DestinationX
,
594 VgpuGop
->BackingStore
+
595 (SourceY
+ Y
) * CurrentHorizontal
+ SourceX
,
603 return EFI_INVALID_PARAMETER
;
607 // For operations that wrote to the display, submit the updated area to the
608 // host -- update the host resource from guest memory.
610 ResourceOffset
= sizeof (UINT32
) * (DestinationY
* CurrentHorizontal
+
612 Status
= VirtioGpuTransferToHost2d (
613 VgpuGop
->ParentBus
, // VgpuDev
614 (UINT32
)DestinationX
, // X
615 (UINT32
)DestinationY
, // Y
616 (UINT32
)Width
, // Width
617 (UINT32
)Height
, // Height
618 ResourceOffset
, // Offset
619 VgpuGop
->ResourceId
// ResourceId
621 if (EFI_ERROR (Status
)) {
626 // Flush the updated resource to the display.
628 Status
= VirtioGpuResourceFlush (
629 VgpuGop
->ParentBus
, // VgpuDev
630 (UINT32
)DestinationX
, // X
631 (UINT32
)DestinationY
, // Y
632 (UINT32
)Width
, // Width
633 (UINT32
)Height
, // Height
634 VgpuGop
->ResourceId
// ResourceId
640 // Template for initializing VGPU_GOP.Gop.
642 CONST EFI_GRAPHICS_OUTPUT_PROTOCOL mGopTemplate
= {
646 NULL
// Mode, to be overwritten in the actual protocol instance