]> git.proxmox.com Git - mirror_edk2.git/blob - OvmfPkg/VirtioGpuDxe/Gop.c
OvmfPkg/VirtioGpuDxe: move code to GopInitialize
[mirror_edk2.git] / OvmfPkg / VirtioGpuDxe / Gop.c
1 /** @file
2
3 EFI_GRAPHICS_OUTPUT_PROTOCOL member functions for the VirtIo GPU driver.
4
5 Copyright (C) 2016, Red Hat, Inc.
6
7 SPDX-License-Identifier: BSD-2-Clause-Patent
8
9 **/
10
11 #include <Library/MemoryAllocationLib.h>
12
13 #include "VirtioGpu.h"
14
15 /**
16 Release guest-side and host-side resources that are related to an initialized
17 VGPU_GOP.Gop.
18
19 param[in,out] VgpuGop The VGPU_GOP object to release resources for.
20
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
26 be nonzero.)
27
28 On output, resources will be released, and
29 VgpuGop->BackingStore and VgpuGop->ResourceId will be
30 nulled.
31
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
36 every other time.
37 **/
38 VOID
39 ReleaseGopResources (
40 IN OUT VGPU_GOP *VgpuGop,
41 IN BOOLEAN DisableHead
42 )
43 {
44 EFI_STATUS Status;
45
46 ASSERT (VgpuGop->ResourceId != 0);
47 ASSERT (VgpuGop->BackingStore != NULL);
48
49 //
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.
53 //
54 if (DisableHead) {
55 //
56 // Dissociate head (scanout) #0 from the currently used 2D host resource,
57 // by setting ResourceId=0 for it.
58 //
59 Status = VirtioGpuSetScanout (
60 VgpuGop->ParentBus, // VgpuDev
61 0,
62 0,
63 0,
64 0, // X, Y, Width, Height
65 0, // ScanoutId
66 0 // ResourceId
67 );
68 //
69 // HACK BEGINS HERE
70 //
71 // According to the GPU Device section of the VirtIo specification, the
72 // above operation is valid:
73 //
74 // "The driver can use resource_id = 0 to disable a scanout."
75 //
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
81 // spec.
82 //
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.
87 //
88 // So, for now, let's just suppress the error.
89 //
90 Status = EFI_SUCCESS;
91 //
92 // HACK ENDS HERE
93 //
94
95 ASSERT_EFI_ERROR (Status);
96 if (EFI_ERROR (Status)) {
97 CpuDeadLoop ();
98 }
99 }
100
101 //
102 // Detach backing pages from the currently used 2D host resource.
103 //
104 Status = VirtioGpuResourceDetachBacking (
105 VgpuGop->ParentBus, // VgpuDev
106 VgpuGop->ResourceId // ResourceId
107 );
108 ASSERT_EFI_ERROR (Status);
109 if (EFI_ERROR (Status)) {
110 CpuDeadLoop ();
111 }
112
113 //
114 // Unmap and release backing pages.
115 //
116 VirtioGpuUnmapAndFreeBackingStore (
117 VgpuGop->ParentBus, // VgpuDev
118 VgpuGop->NumberOfPages, // NumberOfPages
119 VgpuGop->BackingStore, // HostAddress
120 VgpuGop->BackingStoreMap // Mapping
121 );
122 VgpuGop->BackingStore = NULL;
123 VgpuGop->NumberOfPages = 0;
124 VgpuGop->BackingStoreMap = NULL;
125
126 //
127 // Destroy the currently used 2D host resource.
128 //
129 Status = VirtioGpuResourceUnref (
130 VgpuGop->ParentBus, // VgpuDev
131 VgpuGop->ResourceId // ResourceId
132 );
133 ASSERT_EFI_ERROR (Status);
134 if (EFI_ERROR (Status)) {
135 CpuDeadLoop ();
136 }
137
138 VgpuGop->ResourceId = 0;
139 }
140
141 //
142 // The resolutions supported by this driver.
143 //
144 typedef struct {
145 UINT32 Width;
146 UINT32 Height;
147 } GOP_RESOLUTION;
148
149 STATIC CONST GOP_RESOLUTION mGopResolutions[] = {
150 { 640, 480 },
151 { 800, 480 },
152 { 800, 600 },
153 { 832, 624 },
154 { 960, 640 },
155 { 1024, 600 },
156 { 1024, 768 },
157 { 1152, 864 },
158 { 1152, 870 },
159 { 1280, 720 },
160 { 1280, 760 },
161 { 1280, 768 },
162 { 1280, 800 },
163 { 1280, 960 },
164 { 1280, 1024 },
165 { 1360, 768 },
166 { 1366, 768 },
167 { 1400, 1050 },
168 { 1440, 900 },
169 { 1600, 900 },
170 { 1600, 1200 },
171 { 1680, 1050 },
172 { 1920, 1080 },
173 { 1920, 1200 },
174 { 1920, 1440 },
175 { 2000, 2000 },
176 { 2048, 1536 },
177 { 2048, 2048 },
178 { 2560, 1440 },
179 { 2560, 1600 },
180 { 2560, 2048 },
181 { 2800, 2100 },
182 { 3200, 2400 },
183 { 3840, 2160 },
184 { 4096, 2160 },
185 { 7680, 4320 },
186 { 8192, 4320 },
187 };
188
189 //
190 // Macro for casting VGPU_GOP.Gop to VGPU_GOP.
191 //
192 #define VGPU_GOP_FROM_GOP(GopPointer) \
193 CR (GopPointer, VGPU_GOP, Gop, VGPU_GOP_SIG)
194
195 STATIC
196 VOID
197 EFIAPI
198 GopInitialize (
199 IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This
200 )
201 {
202 VGPU_GOP *VgpuGop;
203
204 VgpuGop = VGPU_GOP_FROM_GOP (This);
205
206 //
207 // Set up the Gop -> GopMode -> GopModeInfo pointer chain, and the other
208 // (nonzero) constant fields.
209 //
210 // No direct framebuffer access is supported, only Blt() is.
211 //
212 VgpuGop->Gop.Mode = &VgpuGop->GopMode;
213
214 VgpuGop->GopMode.MaxMode = (UINT32)(ARRAY_SIZE (mGopResolutions));
215 VgpuGop->GopMode.Info = &VgpuGop->GopModeInfo;
216 VgpuGop->GopMode.SizeOfInfo = sizeof VgpuGop->GopModeInfo;
217
218 VgpuGop->GopModeInfo.PixelFormat = PixelBltOnly;
219 }
220
221 //
222 // EFI_GRAPHICS_OUTPUT_PROTOCOL member functions.
223 //
224 STATIC
225 EFI_STATUS
226 EFIAPI
227 GopQueryMode (
228 IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
229 IN UINT32 ModeNumber,
230 OUT UINTN *SizeOfInfo,
231 OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION **Info
232 )
233 {
234 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *GopModeInfo;
235
236 if (ModeNumber >= This->Mode->MaxMode) {
237 return EFI_INVALID_PARAMETER;
238 }
239
240 GopModeInfo = AllocateZeroPool (sizeof *GopModeInfo);
241 if (GopModeInfo == NULL) {
242 return EFI_OUT_OF_RESOURCES;
243 }
244
245 GopModeInfo->HorizontalResolution = mGopResolutions[ModeNumber].Width;
246 GopModeInfo->VerticalResolution = mGopResolutions[ModeNumber].Height;
247 GopModeInfo->PixelFormat = PixelBltOnly;
248 GopModeInfo->PixelsPerScanLine = mGopResolutions[ModeNumber].Width;
249
250 *SizeOfInfo = sizeof *GopModeInfo;
251 *Info = GopModeInfo;
252 return EFI_SUCCESS;
253 }
254
255 STATIC
256 EFI_STATUS
257 EFIAPI
258 GopSetMode (
259 IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
260 IN UINT32 ModeNumber
261 )
262 {
263 VGPU_GOP *VgpuGop;
264 UINT32 NewResourceId;
265 UINTN NewNumberOfBytes;
266 UINTN NewNumberOfPages;
267 VOID *NewBackingStore;
268 EFI_PHYSICAL_ADDRESS NewBackingStoreDeviceAddress;
269 VOID *NewBackingStoreMap;
270 UINTN SizeOfInfo;
271 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *GopModeInfo;
272
273 EFI_STATUS Status;
274 EFI_STATUS Status2;
275
276 if (!This->Mode) {
277 // SetMode() call in InitVgpuGop() triggers this.
278 GopInitialize (This);
279 }
280
281 Status = GopQueryMode (This, ModeNumber, &SizeOfInfo, &GopModeInfo);
282 if (Status != EFI_SUCCESS) {
283 return Status;
284 }
285
286 VgpuGop = VGPU_GOP_FROM_GOP (This);
287
288 //
289 // Distinguish the first (internal) call from the other (protocol consumer)
290 // calls.
291 //
292 if (VgpuGop->ResourceId == 0) {
293 //
294 // This is the first time we create a host side resource.
295 //
296 NewResourceId = 1;
297 } else {
298 //
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.
302 //
303 // The formula below will alternate between IDs 1 and 2.
304 //
305 NewResourceId = 3 - VgpuGop->ResourceId;
306 }
307
308 //
309 // Create the 2D host resource.
310 //
311 Status = VirtioGpuResourceCreate2d (
312 VgpuGop->ParentBus, // VgpuDev
313 NewResourceId, // ResourceId
314 VirtioGpuFormatB8G8R8X8Unorm, // Format
315 GopModeInfo->HorizontalResolution, // Width
316 GopModeInfo->VerticalResolution // Height
317 );
318 if (EFI_ERROR (Status)) {
319 return Status;
320 }
321
322 //
323 // Allocate, zero and map guest backing store, for bus master common buffer
324 // operation.
325 //
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
335 );
336 if (EFI_ERROR (Status)) {
337 goto DestroyHostResource;
338 }
339
340 //
341 // Attach backing store to the host resource.
342 //
343 Status = VirtioGpuResourceAttachBacking (
344 VgpuGop->ParentBus, // VgpuDev
345 NewResourceId, // ResourceId
346 NewBackingStoreDeviceAddress, // BackingStoreDeviceAddress
347 NewNumberOfPages // NumberOfPages
348 );
349 if (EFI_ERROR (Status)) {
350 goto UnmapAndFreeBackingStore;
351 }
352
353 //
354 // Point head (scanout) #0 to the host resource.
355 //
356 Status = VirtioGpuSetScanout (
357 VgpuGop->ParentBus, // VgpuDev
358 0, // X
359 0, // Y
360 GopModeInfo->HorizontalResolution, // Width
361 GopModeInfo->VerticalResolution, // Height
362 0, // ScanoutId
363 NewResourceId // ResourceId
364 );
365 if (EFI_ERROR (Status)) {
366 goto DetachBackingStore;
367 }
368
369 //
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.
373 //
374 if (VgpuGop->ResourceId != 0) {
375 Status = VirtioGpuResourceFlush (
376 VgpuGop->ParentBus, // VgpuDev
377 0, // X
378 0, // Y
379 GopModeInfo->HorizontalResolution, // Width
380 GopModeInfo->VerticalResolution, // Height
381 NewResourceId // ResourceId
382 );
383 if (EFI_ERROR (Status)) {
384 //
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.
388 //
389 Status2 = VirtioGpuSetScanout (
390 VgpuGop->ParentBus, // VgpuDev
391 0, // X
392 0, // Y
393 VgpuGop->GopModeInfo.HorizontalResolution, // Width
394 VgpuGop->GopModeInfo.VerticalResolution, // Height
395 0, // ScanoutId
396 VgpuGop->ResourceId // ResourceId
397 );
398 ASSERT_EFI_ERROR (Status2);
399 if (EFI_ERROR (Status2)) {
400 CpuDeadLoop ();
401 }
402
403 goto DetachBackingStore;
404 }
405
406 //
407 // Flush successful; release the old resources (without disabling head
408 // (scanout) #0).
409 //
410 ReleaseGopResources (VgpuGop, FALSE /* DisableHead */);
411 }
412
413 //
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
416 // resources.
417 //
418 ASSERT (VgpuGop->ResourceId == 0);
419 ASSERT (VgpuGop->BackingStore == NULL);
420
421 VgpuGop->ResourceId = NewResourceId;
422 VgpuGop->BackingStore = NewBackingStore;
423 VgpuGop->NumberOfPages = NewNumberOfPages;
424 VgpuGop->BackingStoreMap = NewBackingStoreMap;
425
426 //
427 // Populate Mode and ModeInfo (mutable fields only).
428 //
429 VgpuGop->GopMode.Mode = ModeNumber;
430 VgpuGop->GopModeInfo = *GopModeInfo;
431 FreePool (GopModeInfo);
432 return EFI_SUCCESS;
433
434 DetachBackingStore:
435 Status2 = VirtioGpuResourceDetachBacking (VgpuGop->ParentBus, NewResourceId);
436 ASSERT_EFI_ERROR (Status2);
437 if (EFI_ERROR (Status2)) {
438 CpuDeadLoop ();
439 }
440
441 UnmapAndFreeBackingStore:
442 VirtioGpuUnmapAndFreeBackingStore (
443 VgpuGop->ParentBus, // VgpuDev
444 NewNumberOfPages, // NumberOfPages
445 NewBackingStore, // HostAddress
446 NewBackingStoreMap // Mapping
447 );
448
449 DestroyHostResource:
450 Status2 = VirtioGpuResourceUnref (VgpuGop->ParentBus, NewResourceId);
451 ASSERT_EFI_ERROR (Status2);
452 if (EFI_ERROR (Status2)) {
453 CpuDeadLoop ();
454 }
455
456 FreePool (GopModeInfo);
457 return Status;
458 }
459
460 STATIC
461 EFI_STATUS
462 EFIAPI
463 GopBlt (
464 IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
465 IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer OPTIONAL,
466 IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation,
467 IN UINTN SourceX,
468 IN UINTN SourceY,
469 IN UINTN DestinationX,
470 IN UINTN DestinationY,
471 IN UINTN Width,
472 IN UINTN Height,
473 IN UINTN Delta OPTIONAL
474 )
475 {
476 VGPU_GOP *VgpuGop;
477 UINT32 CurrentHorizontal;
478 UINT32 CurrentVertical;
479 UINTN SegmentSize;
480 UINTN Y;
481 UINTN ResourceOffset;
482 EFI_STATUS Status;
483
484 VgpuGop = VGPU_GOP_FROM_GOP (This);
485 CurrentHorizontal = VgpuGop->GopModeInfo.HorizontalResolution;
486 CurrentVertical = VgpuGop->GopModeInfo.VerticalResolution;
487
488 //
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.
492 //
493 SegmentSize = Width * sizeof (UINT32);
494
495 //
496 // Delta is relevant for operations that read a rectangle from, or write a
497 // rectangle to, BltBuffer.
498 //
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.
502 //
503 if ((BltOperation == EfiBltVideoToBltBuffer) ||
504 (BltOperation == EfiBltBufferToVideo))
505 {
506 if (Delta == 0) {
507 Delta = SegmentSize;
508 }
509 }
510
511 //
512 // For operations that write to the display, check if the destination fits
513 // onto the display.
514 //
515 if ((BltOperation == EfiBltVideoFill) ||
516 (BltOperation == EfiBltBufferToVideo) ||
517 (BltOperation == EfiBltVideoToVideo))
518 {
519 if ((DestinationX > CurrentHorizontal) ||
520 (Width > CurrentHorizontal - DestinationX) ||
521 (DestinationY > CurrentVertical) ||
522 (Height > CurrentVertical - DestinationY))
523 {
524 return EFI_INVALID_PARAMETER;
525 }
526 }
527
528 //
529 // For operations that read from the display, check if the source fits onto
530 // the display.
531 //
532 if ((BltOperation == EfiBltVideoToBltBuffer) ||
533 (BltOperation == EfiBltVideoToVideo))
534 {
535 if ((SourceX > CurrentHorizontal) ||
536 (Width > CurrentHorizontal - SourceX) ||
537 (SourceY > CurrentVertical) ||
538 (Height > CurrentVertical - SourceY))
539 {
540 return EFI_INVALID_PARAMETER;
541 }
542 }
543
544 //
545 // Render the request. For requests that do not modify the display, there
546 // won't be further steps.
547 //
548 switch (BltOperation) {
549 case EfiBltVideoFill:
550 //
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.
555 //
556 for (Y = 0; Y < Height; ++Y) {
557 SetMem32 (
558 VgpuGop->BackingStore +
559 (DestinationY + Y) * CurrentHorizontal + DestinationX,
560 SegmentSize,
561 *(UINT32 *)BltBuffer
562 );
563 }
564
565 break;
566
567 case EfiBltVideoToBltBuffer:
568 //
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.
574 //
575 for (Y = 0; Y < Height; ++Y) {
576 CopyMem (
577 (UINT8 *)BltBuffer +
578 (DestinationY + Y) * Delta + DestinationX * sizeof *BltBuffer,
579 VgpuGop->BackingStore +
580 (SourceY + Y) * CurrentHorizontal + SourceX,
581 SegmentSize
582 );
583 }
584
585 return EFI_SUCCESS;
586
587 case EfiBltBufferToVideo:
588 //
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.
594 //
595 for (Y = 0; Y < Height; ++Y) {
596 CopyMem (
597 VgpuGop->BackingStore +
598 (DestinationY + Y) * CurrentHorizontal + DestinationX,
599 (UINT8 *)BltBuffer +
600 (SourceY + Y) * Delta + SourceX * sizeof *BltBuffer,
601 SegmentSize
602 );
603 }
604
605 break;
606
607 case EfiBltVideoToVideo:
608 //
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.
613 //
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.
617 //
618 if (SourceY < DestinationY) {
619 Y = Height;
620 while (Y > 0) {
621 --Y;
622 CopyMem (
623 VgpuGop->BackingStore +
624 (DestinationY + Y) * CurrentHorizontal + DestinationX,
625 VgpuGop->BackingStore +
626 (SourceY + Y) * CurrentHorizontal + SourceX,
627 SegmentSize
628 );
629 }
630 } else {
631 for (Y = 0; Y < Height; ++Y) {
632 CopyMem (
633 VgpuGop->BackingStore +
634 (DestinationY + Y) * CurrentHorizontal + DestinationX,
635 VgpuGop->BackingStore +
636 (SourceY + Y) * CurrentHorizontal + SourceX,
637 SegmentSize
638 );
639 }
640 }
641
642 break;
643
644 default:
645 return EFI_INVALID_PARAMETER;
646 }
647
648 //
649 // For operations that wrote to the display, submit the updated area to the
650 // host -- update the host resource from guest memory.
651 //
652 ResourceOffset = sizeof (UINT32) * (DestinationY * CurrentHorizontal +
653 DestinationX);
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
662 );
663 if (EFI_ERROR (Status)) {
664 return Status;
665 }
666
667 //
668 // Flush the updated resource to the display.
669 //
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
677 );
678 return Status;
679 }
680
681 //
682 // Template for initializing VGPU_GOP.Gop.
683 //
684 CONST EFI_GRAPHICS_OUTPUT_PROTOCOL mGopTemplate = {
685 GopQueryMode,
686 GopSetMode,
687 GopBlt,
688 NULL // Mode, to be overwritten in the actual protocol instance
689 };