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
61 0, 0, 0, 0, // X, Y, Width, Height
68 // According to the GPU Device section of the VirtIo specification, the
69 // above operation is valid:
71 // "The driver can use resource_id = 0 to disable a scanout."
73 // However, in practice QEMU does not allow us to disable head (scanout) #0
74 // -- it rejects the command with response code 0x1202
75 // (VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID). Looking at the QEMU source
76 // code, function virtio_gpu_set_scanout() in "hw/display/virtio-gpu.c",
77 // this appears fully intentional, despite not being documented in the
80 // Surprisingly, ignoring the error here, and proceeding to release
81 // host-side resources that presumably underlie head (scanout) #0, work
82 // without any problems -- the driver survives repeated "disconnect" /
83 // "connect -r" commands in the UEFI shell.
85 // So, for now, let's just suppress the error.
92 ASSERT_EFI_ERROR (Status
);
93 if (EFI_ERROR (Status
)) {
99 // Detach backing pages from the currently used 2D host resource.
101 Status
= VirtioGpuResourceDetachBacking (
102 VgpuGop
->ParentBus
, // VgpuDev
103 VgpuGop
->ResourceId
// ResourceId
105 ASSERT_EFI_ERROR (Status
);
106 if (EFI_ERROR (Status
)) {
111 // Unmap and release backing pages.
113 VirtioGpuUnmapAndFreeBackingStore (
114 VgpuGop
->ParentBus
, // VgpuDev
115 VgpuGop
->NumberOfPages
, // NumberOfPages
116 VgpuGop
->BackingStore
, // HostAddress
117 VgpuGop
->BackingStoreMap
// Mapping
119 VgpuGop
->BackingStore
= NULL
;
120 VgpuGop
->NumberOfPages
= 0;
121 VgpuGop
->BackingStoreMap
= NULL
;
124 // Destroy the currently used 2D host resource.
126 Status
= VirtioGpuResourceUnref (
127 VgpuGop
->ParentBus
, // VgpuDev
128 VgpuGop
->ResourceId
// ResourceId
130 ASSERT_EFI_ERROR (Status
);
131 if (EFI_ERROR (Status
)) {
134 VgpuGop
->ResourceId
= 0;
138 // The resolutions supported by this driver.
145 STATIC CONST GOP_RESOLUTION mGopResolutions
[] = {
186 // Macro for casting VGPU_GOP.Gop to VGPU_GOP.
188 #define VGPU_GOP_FROM_GOP(GopPointer) \
189 CR (GopPointer, VGPU_GOP, Gop, VGPU_GOP_SIG)
192 // EFI_GRAPHICS_OUTPUT_PROTOCOL member functions.
198 IN EFI_GRAPHICS_OUTPUT_PROTOCOL
*This
,
199 IN UINT32 ModeNumber
,
200 OUT UINTN
*SizeOfInfo
,
201 OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION
**Info
204 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION
*GopModeInfo
;
206 if (ModeNumber
>= ARRAY_SIZE (mGopResolutions
)) {
207 return EFI_INVALID_PARAMETER
;
210 GopModeInfo
= AllocateZeroPool (sizeof *GopModeInfo
);
211 if (GopModeInfo
== NULL
) {
212 return EFI_OUT_OF_RESOURCES
;
215 GopModeInfo
->HorizontalResolution
= mGopResolutions
[ModeNumber
].Width
;
216 GopModeInfo
->VerticalResolution
= mGopResolutions
[ModeNumber
].Height
;
217 GopModeInfo
->PixelFormat
= PixelBltOnly
;
218 GopModeInfo
->PixelsPerScanLine
= mGopResolutions
[ModeNumber
].Width
;
220 *SizeOfInfo
= sizeof *GopModeInfo
;
229 IN EFI_GRAPHICS_OUTPUT_PROTOCOL
*This
,
234 UINT32 NewResourceId
;
235 UINTN NewNumberOfBytes
;
236 UINTN NewNumberOfPages
;
237 VOID
*NewBackingStore
;
238 EFI_PHYSICAL_ADDRESS NewBackingStoreDeviceAddress
;
239 VOID
*NewBackingStoreMap
;
244 if (ModeNumber
>= ARRAY_SIZE (mGopResolutions
)) {
245 return EFI_UNSUPPORTED
;
248 VgpuGop
= VGPU_GOP_FROM_GOP (This
);
251 // Distinguish the first (internal) call from the other (protocol consumer)
254 if (VgpuGop
->ResourceId
== 0) {
256 // Set up the Gop -> GopMode -> GopModeInfo pointer chain, and the other
257 // (nonzero) constant fields.
259 // No direct framebuffer access is supported, only Blt() is.
261 VgpuGop
->Gop
.Mode
= &VgpuGop
->GopMode
;
263 VgpuGop
->GopMode
.MaxMode
= (UINT32
)(ARRAY_SIZE (mGopResolutions
));
264 VgpuGop
->GopMode
.Info
= &VgpuGop
->GopModeInfo
;
265 VgpuGop
->GopMode
.SizeOfInfo
= sizeof VgpuGop
->GopModeInfo
;
267 VgpuGop
->GopModeInfo
.PixelFormat
= PixelBltOnly
;
270 // This is the first time we create a host side resource.
275 // We already have an active host side resource. Create the new one without
276 // interfering with the current one, so that we can cleanly bail out on
277 // error, without disturbing the current graphics mode.
279 // The formula below will alternate between IDs 1 and 2.
281 NewResourceId
= 3 - VgpuGop
->ResourceId
;
285 // Create the 2D host resource.
287 Status
= VirtioGpuResourceCreate2d (
288 VgpuGop
->ParentBus
, // VgpuDev
289 NewResourceId
, // ResourceId
290 VirtioGpuFormatB8G8R8X8Unorm
, // Format
291 mGopResolutions
[ModeNumber
].Width
, // Width
292 mGopResolutions
[ModeNumber
].Height
// Height
294 if (EFI_ERROR (Status
)) {
299 // Allocate, zero and map guest backing store, for bus master common buffer
302 NewNumberOfBytes
= mGopResolutions
[ModeNumber
].Width
*
303 mGopResolutions
[ModeNumber
].Height
* sizeof (UINT32
);
304 NewNumberOfPages
= EFI_SIZE_TO_PAGES (NewNumberOfBytes
);
305 Status
= VirtioGpuAllocateZeroAndMapBackingStore (
306 VgpuGop
->ParentBus
, // VgpuDev
307 NewNumberOfPages
, // NumberOfPages
308 &NewBackingStore
, // HostAddress
309 &NewBackingStoreDeviceAddress
, // DeviceAddress
310 &NewBackingStoreMap
// Mapping
312 if (EFI_ERROR (Status
)) {
313 goto DestroyHostResource
;
317 // Attach backing store to the host resource.
319 Status
= VirtioGpuResourceAttachBacking (
320 VgpuGop
->ParentBus
, // VgpuDev
321 NewResourceId
, // ResourceId
322 NewBackingStoreDeviceAddress
, // BackingStoreDeviceAddress
323 NewNumberOfPages
// NumberOfPages
325 if (EFI_ERROR (Status
)) {
326 goto UnmapAndFreeBackingStore
;
330 // Point head (scanout) #0 to the host resource.
332 Status
= VirtioGpuSetScanout (
333 VgpuGop
->ParentBus
, // VgpuDev
336 mGopResolutions
[ModeNumber
].Width
, // Width
337 mGopResolutions
[ModeNumber
].Height
, // Height
339 NewResourceId
// ResourceId
341 if (EFI_ERROR (Status
)) {
342 goto DetachBackingStore
;
346 // If this is not the first (i.e., internal) call, then we have to (a) flush
347 // the new resource to head (scanout) #0, after having flipped the latter to
348 // the former above, plus (b) release the old resources.
350 if (VgpuGop
->ResourceId
!= 0) {
351 Status
= VirtioGpuResourceFlush (
352 VgpuGop
->ParentBus
, // VgpuDev
355 mGopResolutions
[ModeNumber
].Width
, // Width
356 mGopResolutions
[ModeNumber
].Height
, // Height
357 NewResourceId
// ResourceId
359 if (EFI_ERROR (Status
)) {
361 // Flip head (scanout) #0 back to the current resource. If this fails, we
362 // cannot continue, as this error occurs on the error path and is
363 // therefore non-recoverable.
365 Status2
= VirtioGpuSetScanout (
366 VgpuGop
->ParentBus
, // VgpuDev
369 mGopResolutions
[This
->Mode
->Mode
].Width
, // Width
370 mGopResolutions
[This
->Mode
->Mode
].Height
, // Height
372 VgpuGop
->ResourceId
// ResourceId
374 ASSERT_EFI_ERROR (Status2
);
375 if (EFI_ERROR (Status2
)) {
378 goto DetachBackingStore
;
382 // Flush successful; release the old resources (without disabling head
385 ReleaseGopResources (VgpuGop
, FALSE
/* DisableHead */);
389 // This is either the first (internal) call when we have no old resources
390 // yet, or we've changed the mode successfully and released the old
393 ASSERT (VgpuGop
->ResourceId
== 0);
394 ASSERT (VgpuGop
->BackingStore
== NULL
);
396 VgpuGop
->ResourceId
= NewResourceId
;
397 VgpuGop
->BackingStore
= NewBackingStore
;
398 VgpuGop
->NumberOfPages
= NewNumberOfPages
;
399 VgpuGop
->BackingStoreMap
= NewBackingStoreMap
;
402 // Populate Mode and ModeInfo (mutable fields only).
404 VgpuGop
->GopMode
.Mode
= ModeNumber
;
405 VgpuGop
->GopModeInfo
.HorizontalResolution
=
406 mGopResolutions
[ModeNumber
].Width
;
407 VgpuGop
->GopModeInfo
.VerticalResolution
= mGopResolutions
[ModeNumber
].Height
;
408 VgpuGop
->GopModeInfo
.PixelsPerScanLine
= mGopResolutions
[ModeNumber
].Width
;
412 Status2
= VirtioGpuResourceDetachBacking (VgpuGop
->ParentBus
, NewResourceId
);
413 ASSERT_EFI_ERROR (Status2
);
414 if (EFI_ERROR (Status2
)) {
418 UnmapAndFreeBackingStore
:
419 VirtioGpuUnmapAndFreeBackingStore (
420 VgpuGop
->ParentBus
, // VgpuDev
421 NewNumberOfPages
, // NumberOfPages
422 NewBackingStore
, // HostAddress
423 NewBackingStoreMap
// Mapping
427 Status2
= VirtioGpuResourceUnref (VgpuGop
->ParentBus
, NewResourceId
);
428 ASSERT_EFI_ERROR (Status2
);
429 if (EFI_ERROR (Status2
)) {
440 IN EFI_GRAPHICS_OUTPUT_PROTOCOL
*This
,
441 IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL
*BltBuffer
, OPTIONAL
442 IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation
,
445 IN UINTN DestinationX
,
446 IN UINTN DestinationY
,
449 IN UINTN Delta OPTIONAL
453 UINT32 CurrentHorizontal
;
454 UINT32 CurrentVertical
;
457 UINTN ResourceOffset
;
460 VgpuGop
= VGPU_GOP_FROM_GOP (This
);
461 CurrentHorizontal
= VgpuGop
->GopModeInfo
.HorizontalResolution
;
462 CurrentVertical
= VgpuGop
->GopModeInfo
.VerticalResolution
;
465 // We can avoid pixel format conversion in the guest because the internal
466 // representation of EFI_GRAPHICS_OUTPUT_BLT_PIXEL and that of
467 // VirtioGpuFormatB8G8R8X8Unorm are identical.
469 SegmentSize
= Width
* sizeof (UINT32
);
472 // Delta is relevant for operations that read a rectangle from, or write a
473 // rectangle to, BltBuffer.
475 // In these cases, Delta is the stride of BltBuffer, in bytes. If Delta is
476 // zero, then Width is the entire width of BltBuffer, and the stride is
477 // supposed to be calculated from Width.
479 if (BltOperation
== EfiBltVideoToBltBuffer
||
480 BltOperation
== EfiBltBufferToVideo
) {
487 // For operations that write to the display, check if the destination fits
490 if (BltOperation
== EfiBltVideoFill
||
491 BltOperation
== EfiBltBufferToVideo
||
492 BltOperation
== EfiBltVideoToVideo
) {
493 if (DestinationX
> CurrentHorizontal
||
494 Width
> CurrentHorizontal
- DestinationX
||
495 DestinationY
> CurrentVertical
||
496 Height
> CurrentVertical
- DestinationY
) {
497 return EFI_INVALID_PARAMETER
;
502 // For operations that read from the display, check if the source fits onto
505 if (BltOperation
== EfiBltVideoToBltBuffer
||
506 BltOperation
== EfiBltVideoToVideo
) {
507 if (SourceX
> CurrentHorizontal
||
508 Width
> CurrentHorizontal
- SourceX
||
509 SourceY
> CurrentVertical
||
510 Height
> CurrentVertical
- SourceY
) {
511 return EFI_INVALID_PARAMETER
;
516 // Render the request. For requests that do not modify the display, there
517 // won't be further steps.
519 switch (BltOperation
) {
520 case EfiBltVideoFill
:
522 // Write data from the BltBuffer pixel (0, 0) directly to every pixel of
523 // the video display rectangle (DestinationX, DestinationY) (DestinationX +
524 // Width, DestinationY + Height). Only one pixel will be used from the
525 // BltBuffer. Delta is NOT used.
527 for (Y
= 0; Y
< Height
; ++Y
) {
529 VgpuGop
->BackingStore
+
530 (DestinationY
+ Y
) * CurrentHorizontal
+ DestinationX
,
537 case EfiBltVideoToBltBuffer
:
539 // Read data from the video display rectangle (SourceX, SourceY) (SourceX +
540 // Width, SourceY + Height) and place it in the BltBuffer rectangle
541 // (DestinationX, DestinationY ) (DestinationX + Width, DestinationY +
542 // Height). If DestinationX or DestinationY is not zero then Delta must be
543 // set to the length in bytes of a row in the BltBuffer.
545 for (Y
= 0; Y
< Height
; ++Y
) {
548 (DestinationY
+ Y
) * Delta
+ DestinationX
* sizeof *BltBuffer
,
549 VgpuGop
->BackingStore
+
550 (SourceY
+ Y
) * CurrentHorizontal
+ SourceX
,
556 case EfiBltBufferToVideo
:
558 // Write data from the BltBuffer rectangle (SourceX, SourceY) (SourceX +
559 // Width, SourceY + Height) directly to the video display rectangle
560 // (DestinationX, DestinationY) (DestinationX + Width, DestinationY +
561 // Height). If SourceX or SourceY is not zero then Delta must be set to the
562 // length in bytes of a row in the BltBuffer.
564 for (Y
= 0; Y
< Height
; ++Y
) {
566 VgpuGop
->BackingStore
+
567 (DestinationY
+ Y
) * CurrentHorizontal
+ DestinationX
,
569 (SourceY
+ Y
) * Delta
+ SourceX
* sizeof *BltBuffer
,
575 case EfiBltVideoToVideo
:
577 // Copy from the video display rectangle (SourceX, SourceY) (SourceX +
578 // Width, SourceY + Height) to the video display rectangle (DestinationX,
579 // DestinationY) (DestinationX + Width, DestinationY + Height). The
580 // BltBuffer and Delta are not used in this mode.
582 // A single invocation of CopyMem() handles overlap between source and
583 // destination (that is, within a single line), but for multiple
584 // invocations, we must handle overlaps.
586 if (SourceY
< DestinationY
) {
591 VgpuGop
->BackingStore
+
592 (DestinationY
+ Y
) * CurrentHorizontal
+ DestinationX
,
593 VgpuGop
->BackingStore
+
594 (SourceY
+ Y
) * CurrentHorizontal
+ SourceX
,
599 for (Y
= 0; Y
< Height
; ++Y
) {
601 VgpuGop
->BackingStore
+
602 (DestinationY
+ Y
) * CurrentHorizontal
+ DestinationX
,
603 VgpuGop
->BackingStore
+
604 (SourceY
+ Y
) * CurrentHorizontal
+ SourceX
,
612 return EFI_INVALID_PARAMETER
;
616 // For operations that wrote to the display, submit the updated area to the
617 // host -- update the host resource from guest memory.
619 ResourceOffset
= sizeof (UINT32
) * (DestinationY
* CurrentHorizontal
+
621 Status
= VirtioGpuTransferToHost2d (
622 VgpuGop
->ParentBus
, // VgpuDev
623 (UINT32
)DestinationX
, // X
624 (UINT32
)DestinationY
, // Y
625 (UINT32
)Width
, // Width
626 (UINT32
)Height
, // Height
627 ResourceOffset
, // Offset
628 VgpuGop
->ResourceId
// ResourceId
630 if (EFI_ERROR (Status
)) {
635 // Flush the updated resource to the display.
637 Status
= VirtioGpuResourceFlush (
638 VgpuGop
->ParentBus
, // VgpuDev
639 (UINT32
)DestinationX
, // X
640 (UINT32
)DestinationY
, // Y
641 (UINT32
)Width
, // Width
642 (UINT32
)Height
, // Height
643 VgpuGop
->ResourceId
// ResourceId
649 // Template for initializing VGPU_GOP.Gop.
651 CONST EFI_GRAPHICS_OUTPUT_PROTOCOL mGopTemplate
= {
655 NULL
// Mode, to be overwritten in the actual protocol instance