]> git.proxmox.com Git - mirror_edk2.git/blob - OvmfPkg/VirtioGpuDxe/Gop.c
OvmfPkg/VirtioGpuDxe: use GopQueryMode in GopSetMode
[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 //
196 // EFI_GRAPHICS_OUTPUT_PROTOCOL member functions.
197 //
198 STATIC
199 EFI_STATUS
200 EFIAPI
201 GopQueryMode (
202 IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
203 IN UINT32 ModeNumber,
204 OUT UINTN *SizeOfInfo,
205 OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION **Info
206 )
207 {
208 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *GopModeInfo;
209
210 if (ModeNumber >= ARRAY_SIZE (mGopResolutions)) {
211 return EFI_INVALID_PARAMETER;
212 }
213
214 GopModeInfo = AllocateZeroPool (sizeof *GopModeInfo);
215 if (GopModeInfo == NULL) {
216 return EFI_OUT_OF_RESOURCES;
217 }
218
219 GopModeInfo->HorizontalResolution = mGopResolutions[ModeNumber].Width;
220 GopModeInfo->VerticalResolution = mGopResolutions[ModeNumber].Height;
221 GopModeInfo->PixelFormat = PixelBltOnly;
222 GopModeInfo->PixelsPerScanLine = mGopResolutions[ModeNumber].Width;
223
224 *SizeOfInfo = sizeof *GopModeInfo;
225 *Info = GopModeInfo;
226 return EFI_SUCCESS;
227 }
228
229 STATIC
230 EFI_STATUS
231 EFIAPI
232 GopSetMode (
233 IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
234 IN UINT32 ModeNumber
235 )
236 {
237 VGPU_GOP *VgpuGop;
238 UINT32 NewResourceId;
239 UINTN NewNumberOfBytes;
240 UINTN NewNumberOfPages;
241 VOID *NewBackingStore;
242 EFI_PHYSICAL_ADDRESS NewBackingStoreDeviceAddress;
243 VOID *NewBackingStoreMap;
244 UINTN SizeOfInfo;
245 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *GopModeInfo;
246
247 EFI_STATUS Status;
248 EFI_STATUS Status2;
249
250 Status = GopQueryMode (This, ModeNumber, &SizeOfInfo, &GopModeInfo);
251 if (Status != EFI_SUCCESS) {
252 return Status;
253 }
254
255 VgpuGop = VGPU_GOP_FROM_GOP (This);
256
257 //
258 // Distinguish the first (internal) call from the other (protocol consumer)
259 // calls.
260 //
261 if (VgpuGop->ResourceId == 0) {
262 //
263 // Set up the Gop -> GopMode -> GopModeInfo pointer chain, and the other
264 // (nonzero) constant fields.
265 //
266 // No direct framebuffer access is supported, only Blt() is.
267 //
268 VgpuGop->Gop.Mode = &VgpuGop->GopMode;
269
270 VgpuGop->GopMode.MaxMode = (UINT32)(ARRAY_SIZE (mGopResolutions));
271 VgpuGop->GopMode.Info = &VgpuGop->GopModeInfo;
272 VgpuGop->GopMode.SizeOfInfo = sizeof VgpuGop->GopModeInfo;
273
274 VgpuGop->GopModeInfo.PixelFormat = PixelBltOnly;
275
276 //
277 // This is the first time we create a host side resource.
278 //
279 NewResourceId = 1;
280 } else {
281 //
282 // We already have an active host side resource. Create the new one without
283 // interfering with the current one, so that we can cleanly bail out on
284 // error, without disturbing the current graphics mode.
285 //
286 // The formula below will alternate between IDs 1 and 2.
287 //
288 NewResourceId = 3 - VgpuGop->ResourceId;
289 }
290
291 //
292 // Create the 2D host resource.
293 //
294 Status = VirtioGpuResourceCreate2d (
295 VgpuGop->ParentBus, // VgpuDev
296 NewResourceId, // ResourceId
297 VirtioGpuFormatB8G8R8X8Unorm, // Format
298 GopModeInfo->HorizontalResolution, // Width
299 GopModeInfo->VerticalResolution // Height
300 );
301 if (EFI_ERROR (Status)) {
302 return Status;
303 }
304
305 //
306 // Allocate, zero and map guest backing store, for bus master common buffer
307 // operation.
308 //
309 NewNumberOfBytes = GopModeInfo->HorizontalResolution *
310 GopModeInfo->VerticalResolution * sizeof (UINT32);
311 NewNumberOfPages = EFI_SIZE_TO_PAGES (NewNumberOfBytes);
312 Status = VirtioGpuAllocateZeroAndMapBackingStore (
313 VgpuGop->ParentBus, // VgpuDev
314 NewNumberOfPages, // NumberOfPages
315 &NewBackingStore, // HostAddress
316 &NewBackingStoreDeviceAddress, // DeviceAddress
317 &NewBackingStoreMap // Mapping
318 );
319 if (EFI_ERROR (Status)) {
320 goto DestroyHostResource;
321 }
322
323 //
324 // Attach backing store to the host resource.
325 //
326 Status = VirtioGpuResourceAttachBacking (
327 VgpuGop->ParentBus, // VgpuDev
328 NewResourceId, // ResourceId
329 NewBackingStoreDeviceAddress, // BackingStoreDeviceAddress
330 NewNumberOfPages // NumberOfPages
331 );
332 if (EFI_ERROR (Status)) {
333 goto UnmapAndFreeBackingStore;
334 }
335
336 //
337 // Point head (scanout) #0 to the host resource.
338 //
339 Status = VirtioGpuSetScanout (
340 VgpuGop->ParentBus, // VgpuDev
341 0, // X
342 0, // Y
343 GopModeInfo->HorizontalResolution, // Width
344 GopModeInfo->VerticalResolution, // Height
345 0, // ScanoutId
346 NewResourceId // ResourceId
347 );
348 if (EFI_ERROR (Status)) {
349 goto DetachBackingStore;
350 }
351
352 //
353 // If this is not the first (i.e., internal) call, then we have to (a) flush
354 // the new resource to head (scanout) #0, after having flipped the latter to
355 // the former above, plus (b) release the old resources.
356 //
357 if (VgpuGop->ResourceId != 0) {
358 Status = VirtioGpuResourceFlush (
359 VgpuGop->ParentBus, // VgpuDev
360 0, // X
361 0, // Y
362 GopModeInfo->HorizontalResolution, // Width
363 GopModeInfo->VerticalResolution, // Height
364 NewResourceId // ResourceId
365 );
366 if (EFI_ERROR (Status)) {
367 //
368 // Flip head (scanout) #0 back to the current resource. If this fails, we
369 // cannot continue, as this error occurs on the error path and is
370 // therefore non-recoverable.
371 //
372 Status2 = VirtioGpuSetScanout (
373 VgpuGop->ParentBus, // VgpuDev
374 0, // X
375 0, // Y
376 VgpuGop->GopModeInfo.HorizontalResolution, // Width
377 VgpuGop->GopModeInfo.VerticalResolution, // Height
378 0, // ScanoutId
379 VgpuGop->ResourceId // ResourceId
380 );
381 ASSERT_EFI_ERROR (Status2);
382 if (EFI_ERROR (Status2)) {
383 CpuDeadLoop ();
384 }
385
386 goto DetachBackingStore;
387 }
388
389 //
390 // Flush successful; release the old resources (without disabling head
391 // (scanout) #0).
392 //
393 ReleaseGopResources (VgpuGop, FALSE /* DisableHead */);
394 }
395
396 //
397 // This is either the first (internal) call when we have no old resources
398 // yet, or we've changed the mode successfully and released the old
399 // resources.
400 //
401 ASSERT (VgpuGop->ResourceId == 0);
402 ASSERT (VgpuGop->BackingStore == NULL);
403
404 VgpuGop->ResourceId = NewResourceId;
405 VgpuGop->BackingStore = NewBackingStore;
406 VgpuGop->NumberOfPages = NewNumberOfPages;
407 VgpuGop->BackingStoreMap = NewBackingStoreMap;
408
409 //
410 // Populate Mode and ModeInfo (mutable fields only).
411 //
412 VgpuGop->GopMode.Mode = ModeNumber;
413 VgpuGop->GopModeInfo = *GopModeInfo;
414 FreePool (GopModeInfo);
415 return EFI_SUCCESS;
416
417 DetachBackingStore:
418 Status2 = VirtioGpuResourceDetachBacking (VgpuGop->ParentBus, NewResourceId);
419 ASSERT_EFI_ERROR (Status2);
420 if (EFI_ERROR (Status2)) {
421 CpuDeadLoop ();
422 }
423
424 UnmapAndFreeBackingStore:
425 VirtioGpuUnmapAndFreeBackingStore (
426 VgpuGop->ParentBus, // VgpuDev
427 NewNumberOfPages, // NumberOfPages
428 NewBackingStore, // HostAddress
429 NewBackingStoreMap // Mapping
430 );
431
432 DestroyHostResource:
433 Status2 = VirtioGpuResourceUnref (VgpuGop->ParentBus, NewResourceId);
434 ASSERT_EFI_ERROR (Status2);
435 if (EFI_ERROR (Status2)) {
436 CpuDeadLoop ();
437 }
438
439 FreePool (GopModeInfo);
440 return Status;
441 }
442
443 STATIC
444 EFI_STATUS
445 EFIAPI
446 GopBlt (
447 IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
448 IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer OPTIONAL,
449 IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation,
450 IN UINTN SourceX,
451 IN UINTN SourceY,
452 IN UINTN DestinationX,
453 IN UINTN DestinationY,
454 IN UINTN Width,
455 IN UINTN Height,
456 IN UINTN Delta OPTIONAL
457 )
458 {
459 VGPU_GOP *VgpuGop;
460 UINT32 CurrentHorizontal;
461 UINT32 CurrentVertical;
462 UINTN SegmentSize;
463 UINTN Y;
464 UINTN ResourceOffset;
465 EFI_STATUS Status;
466
467 VgpuGop = VGPU_GOP_FROM_GOP (This);
468 CurrentHorizontal = VgpuGop->GopModeInfo.HorizontalResolution;
469 CurrentVertical = VgpuGop->GopModeInfo.VerticalResolution;
470
471 //
472 // We can avoid pixel format conversion in the guest because the internal
473 // representation of EFI_GRAPHICS_OUTPUT_BLT_PIXEL and that of
474 // VirtioGpuFormatB8G8R8X8Unorm are identical.
475 //
476 SegmentSize = Width * sizeof (UINT32);
477
478 //
479 // Delta is relevant for operations that read a rectangle from, or write a
480 // rectangle to, BltBuffer.
481 //
482 // In these cases, Delta is the stride of BltBuffer, in bytes. If Delta is
483 // zero, then Width is the entire width of BltBuffer, and the stride is
484 // supposed to be calculated from Width.
485 //
486 if ((BltOperation == EfiBltVideoToBltBuffer) ||
487 (BltOperation == EfiBltBufferToVideo))
488 {
489 if (Delta == 0) {
490 Delta = SegmentSize;
491 }
492 }
493
494 //
495 // For operations that write to the display, check if the destination fits
496 // onto the display.
497 //
498 if ((BltOperation == EfiBltVideoFill) ||
499 (BltOperation == EfiBltBufferToVideo) ||
500 (BltOperation == EfiBltVideoToVideo))
501 {
502 if ((DestinationX > CurrentHorizontal) ||
503 (Width > CurrentHorizontal - DestinationX) ||
504 (DestinationY > CurrentVertical) ||
505 (Height > CurrentVertical - DestinationY))
506 {
507 return EFI_INVALID_PARAMETER;
508 }
509 }
510
511 //
512 // For operations that read from the display, check if the source fits onto
513 // the display.
514 //
515 if ((BltOperation == EfiBltVideoToBltBuffer) ||
516 (BltOperation == EfiBltVideoToVideo))
517 {
518 if ((SourceX > CurrentHorizontal) ||
519 (Width > CurrentHorizontal - SourceX) ||
520 (SourceY > CurrentVertical) ||
521 (Height > CurrentVertical - SourceY))
522 {
523 return EFI_INVALID_PARAMETER;
524 }
525 }
526
527 //
528 // Render the request. For requests that do not modify the display, there
529 // won't be further steps.
530 //
531 switch (BltOperation) {
532 case EfiBltVideoFill:
533 //
534 // Write data from the BltBuffer pixel (0, 0) directly to every pixel of
535 // the video display rectangle (DestinationX, DestinationY) (DestinationX +
536 // Width, DestinationY + Height). Only one pixel will be used from the
537 // BltBuffer. Delta is NOT used.
538 //
539 for (Y = 0; Y < Height; ++Y) {
540 SetMem32 (
541 VgpuGop->BackingStore +
542 (DestinationY + Y) * CurrentHorizontal + DestinationX,
543 SegmentSize,
544 *(UINT32 *)BltBuffer
545 );
546 }
547
548 break;
549
550 case EfiBltVideoToBltBuffer:
551 //
552 // Read data from the video display rectangle (SourceX, SourceY) (SourceX +
553 // Width, SourceY + Height) and place it in the BltBuffer rectangle
554 // (DestinationX, DestinationY ) (DestinationX + Width, DestinationY +
555 // Height). If DestinationX or DestinationY is not zero then Delta must be
556 // set to the length in bytes of a row in the BltBuffer.
557 //
558 for (Y = 0; Y < Height; ++Y) {
559 CopyMem (
560 (UINT8 *)BltBuffer +
561 (DestinationY + Y) * Delta + DestinationX * sizeof *BltBuffer,
562 VgpuGop->BackingStore +
563 (SourceY + Y) * CurrentHorizontal + SourceX,
564 SegmentSize
565 );
566 }
567
568 return EFI_SUCCESS;
569
570 case EfiBltBufferToVideo:
571 //
572 // Write data from the BltBuffer rectangle (SourceX, SourceY) (SourceX +
573 // Width, SourceY + Height) directly to the video display rectangle
574 // (DestinationX, DestinationY) (DestinationX + Width, DestinationY +
575 // Height). If SourceX or SourceY is not zero then Delta must be set to the
576 // length in bytes of a row in the BltBuffer.
577 //
578 for (Y = 0; Y < Height; ++Y) {
579 CopyMem (
580 VgpuGop->BackingStore +
581 (DestinationY + Y) * CurrentHorizontal + DestinationX,
582 (UINT8 *)BltBuffer +
583 (SourceY + Y) * Delta + SourceX * sizeof *BltBuffer,
584 SegmentSize
585 );
586 }
587
588 break;
589
590 case EfiBltVideoToVideo:
591 //
592 // Copy from the video display rectangle (SourceX, SourceY) (SourceX +
593 // Width, SourceY + Height) to the video display rectangle (DestinationX,
594 // DestinationY) (DestinationX + Width, DestinationY + Height). The
595 // BltBuffer and Delta are not used in this mode.
596 //
597 // A single invocation of CopyMem() handles overlap between source and
598 // destination (that is, within a single line), but for multiple
599 // invocations, we must handle overlaps.
600 //
601 if (SourceY < DestinationY) {
602 Y = Height;
603 while (Y > 0) {
604 --Y;
605 CopyMem (
606 VgpuGop->BackingStore +
607 (DestinationY + Y) * CurrentHorizontal + DestinationX,
608 VgpuGop->BackingStore +
609 (SourceY + Y) * CurrentHorizontal + SourceX,
610 SegmentSize
611 );
612 }
613 } else {
614 for (Y = 0; Y < Height; ++Y) {
615 CopyMem (
616 VgpuGop->BackingStore +
617 (DestinationY + Y) * CurrentHorizontal + DestinationX,
618 VgpuGop->BackingStore +
619 (SourceY + Y) * CurrentHorizontal + SourceX,
620 SegmentSize
621 );
622 }
623 }
624
625 break;
626
627 default:
628 return EFI_INVALID_PARAMETER;
629 }
630
631 //
632 // For operations that wrote to the display, submit the updated area to the
633 // host -- update the host resource from guest memory.
634 //
635 ResourceOffset = sizeof (UINT32) * (DestinationY * CurrentHorizontal +
636 DestinationX);
637 Status = VirtioGpuTransferToHost2d (
638 VgpuGop->ParentBus, // VgpuDev
639 (UINT32)DestinationX, // X
640 (UINT32)DestinationY, // Y
641 (UINT32)Width, // Width
642 (UINT32)Height, // Height
643 ResourceOffset, // Offset
644 VgpuGop->ResourceId // ResourceId
645 );
646 if (EFI_ERROR (Status)) {
647 return Status;
648 }
649
650 //
651 // Flush the updated resource to the display.
652 //
653 Status = VirtioGpuResourceFlush (
654 VgpuGop->ParentBus, // VgpuDev
655 (UINT32)DestinationX, // X
656 (UINT32)DestinationY, // Y
657 (UINT32)Width, // Width
658 (UINT32)Height, // Height
659 VgpuGop->ResourceId // ResourceId
660 );
661 return Status;
662 }
663
664 //
665 // Template for initializing VGPU_GOP.Gop.
666 //
667 CONST EFI_GRAPHICS_OUTPUT_PROTOCOL mGopTemplate = {
668 GopQueryMode,
669 GopSetMode,
670 GopBlt,
671 NULL // Mode, to be overwritten in the actual protocol instance
672 };