]> git.proxmox.com Git - mirror_edk2.git/blob - OvmfPkg/VirtioGpuDxe/Gop.c
c6ff9ed57461a0567f1a750e100dafef38bb1a95
[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 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
11
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.
14
15 **/
16
17 #include <Library/BaseMemoryLib.h>
18 #include <Library/MemoryAllocationLib.h>
19
20 #include "VirtioGpu.h"
21
22 /**
23 Release guest-side and host-side resources that are related to an initialized
24 VGPU_GOP.Gop.
25
26 param[in,out] VgpuGop The VGPU_GOP object to release resources for.
27
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
33 be nonzero.)
34
35 On output, resources will be released, and
36 VgpuGop->BackingStore and VgpuGop->ResourceId will be
37 nulled.
38
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
43 every other time.
44 **/
45 VOID
46 ReleaseGopResources (
47 IN OUT VGPU_GOP *VgpuGop,
48 IN BOOLEAN DisableHead
49 )
50 {
51 EFI_STATUS Status;
52
53 ASSERT (VgpuGop->ResourceId != 0);
54 ASSERT (VgpuGop->BackingStore != NULL);
55
56 //
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.
60 //
61 if (DisableHead) {
62 //
63 // Dissociate head (scanout) #0 from the currently used 2D host resource,
64 // by setting ResourceId=0 for it.
65 //
66 Status = VirtioGpuSetScanout (
67 VgpuGop->ParentBus, // VgpuDev
68 0, 0, 0, 0, // X, Y, Width, Height
69 0, // ScanoutId
70 0 // ResourceId
71 );
72 //
73 // HACK BEGINS HERE
74 //
75 // According to the GPU Device section of the VirtIo specification, the
76 // above operation is valid:
77 //
78 // "The driver can use resource_id = 0 to disable a scanout."
79 //
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
85 // spec.
86 //
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.
91 //
92 // So, for now, let's just suppress the error.
93 //
94 Status = EFI_SUCCESS;
95 //
96 // HACK ENDS HERE
97 //
98
99 ASSERT_EFI_ERROR (Status);
100 if (EFI_ERROR (Status)) {
101 CpuDeadLoop ();
102 }
103 }
104
105 //
106 // Detach backing pages from the currently used 2D host resource.
107 //
108 Status = VirtioGpuResourceDetachBacking (
109 VgpuGop->ParentBus, // VgpuDev
110 VgpuGop->ResourceId // ResourceId
111 );
112 ASSERT_EFI_ERROR (Status);
113 if (EFI_ERROR (Status)) {
114 CpuDeadLoop ();
115 }
116
117 //
118 // Release backing pages.
119 //
120 FreePages (VgpuGop->BackingStore, VgpuGop->NumberOfPages);
121 VgpuGop->BackingStore = NULL;
122 VgpuGop->NumberOfPages = 0;
123
124 //
125 // Destroy the currently used 2D host resource.
126 //
127 Status = VirtioGpuResourceUnref (
128 VgpuGop->ParentBus, // VgpuDev
129 VgpuGop->ResourceId // ResourceId
130 );
131 ASSERT_EFI_ERROR (Status);
132 if (EFI_ERROR (Status)) {
133 CpuDeadLoop ();
134 }
135 VgpuGop->ResourceId = 0;
136 }
137
138 //
139 // The resolutions supported by this driver.
140 //
141 typedef struct {
142 UINT32 Width;
143 UINT32 Height;
144 } GOP_RESOLUTION;
145
146 STATIC CONST GOP_RESOLUTION mGopResolutions[] = {
147 { 640, 480 },
148 { 800, 480 },
149 { 800, 600 },
150 { 832, 624 },
151 { 960, 640 },
152 { 1024, 600 },
153 { 1024, 768 },
154 { 1152, 864 },
155 { 1152, 870 },
156 { 1280, 720 },
157 { 1280, 760 },
158 { 1280, 768 },
159 { 1280, 800 },
160 { 1280, 960 },
161 { 1280, 1024 },
162 { 1360, 768 },
163 { 1366, 768 },
164 { 1400, 1050 },
165 { 1440, 900 },
166 { 1600, 900 },
167 { 1600, 1200 },
168 { 1680, 1050 },
169 { 1920, 1080 },
170 { 1920, 1200 },
171 { 1920, 1440 },
172 { 2000, 2000 },
173 { 2048, 1536 },
174 { 2048, 2048 },
175 { 2560, 1440 },
176 { 2560, 1600 },
177 { 2560, 2048 },
178 { 2800, 2100 },
179 { 3200, 2400 },
180 { 3840, 2160 },
181 { 4096, 2160 },
182 { 7680, 4320 },
183 { 8192, 4320 },
184 };
185
186 //
187 // Macro for casting VGPU_GOP.Gop to VGPU_GOP.
188 //
189 #define VGPU_GOP_FROM_GOP(GopPointer) \
190 CR (GopPointer, VGPU_GOP, Gop, VGPU_GOP_SIG)
191
192 //
193 // EFI_GRAPHICS_OUTPUT_PROTOCOL member functions.
194 //
195 STATIC
196 EFI_STATUS
197 EFIAPI
198 GopQueryMode (
199 IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
200 IN UINT32 ModeNumber,
201 OUT UINTN *SizeOfInfo,
202 OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION **Info
203 )
204 {
205 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *GopModeInfo;
206
207 if (ModeNumber >= sizeof mGopResolutions / sizeof mGopResolutions[0]) {
208 return EFI_INVALID_PARAMETER;
209 }
210
211 GopModeInfo = AllocateZeroPool (sizeof *GopModeInfo);
212 if (GopModeInfo == NULL) {
213 return EFI_OUT_OF_RESOURCES;
214 }
215
216 GopModeInfo->HorizontalResolution = mGopResolutions[ModeNumber].Width;
217 GopModeInfo->VerticalResolution = mGopResolutions[ModeNumber].Height;
218 GopModeInfo->PixelFormat = PixelBltOnly;
219 GopModeInfo->PixelsPerScanLine = mGopResolutions[ModeNumber].Width;
220
221 *SizeOfInfo = sizeof *GopModeInfo;
222 *Info = GopModeInfo;
223 return EFI_SUCCESS;
224 }
225
226 STATIC
227 EFI_STATUS
228 EFIAPI
229 GopSetMode (
230 IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
231 IN UINT32 ModeNumber
232 )
233 {
234 VGPU_GOP *VgpuGop;
235 UINT32 NewResourceId;
236 UINTN NewNumberOfBytes;
237 UINTN NewNumberOfPages;
238 VOID *NewBackingStore;
239 EFI_STATUS Status;
240 EFI_STATUS Status2;
241
242 if (ModeNumber >= sizeof mGopResolutions / sizeof mGopResolutions[0]) {
243 return EFI_UNSUPPORTED;
244 }
245
246 VgpuGop = VGPU_GOP_FROM_GOP (This);
247
248 //
249 // Distinguish the first (internal) call from the other (protocol consumer)
250 // calls.
251 //
252 if (VgpuGop->ResourceId == 0) {
253 //
254 // Set up the Gop -> GopMode -> GopModeInfo pointer chain, and the other
255 // (nonzero) constant fields.
256 //
257 // No direct framebuffer access is supported, only Blt() is.
258 //
259 VgpuGop->Gop.Mode = &VgpuGop->GopMode;
260
261 VgpuGop->GopMode.MaxMode = (UINT32)(sizeof mGopResolutions /
262 sizeof mGopResolutions[0]);
263 VgpuGop->GopMode.Info = &VgpuGop->GopModeInfo;
264 VgpuGop->GopMode.SizeOfInfo = sizeof VgpuGop->GopModeInfo;
265
266 VgpuGop->GopModeInfo.PixelFormat = PixelBltOnly;
267
268 //
269 // This is the first time we create a host side resource.
270 //
271 NewResourceId = 1;
272 } else {
273 //
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.
277 //
278 // The formula below will alternate between IDs 1 and 2.
279 //
280 NewResourceId = 3 - VgpuGop->ResourceId;
281 }
282
283 //
284 // Create the 2D host resource.
285 //
286 Status = VirtioGpuResourceCreate2d (
287 VgpuGop->ParentBus, // VgpuDev
288 NewResourceId, // ResourceId
289 VirtioGpuFormatB8G8R8X8Unorm, // Format
290 mGopResolutions[ModeNumber].Width, // Width
291 mGopResolutions[ModeNumber].Height // Height
292 );
293 if (EFI_ERROR (Status)) {
294 return Status;
295 }
296
297 //
298 // Allocate guest backing store.
299 //
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;
307 }
308 //
309 // Fill visible part of backing store with black.
310 //
311 ZeroMem (NewBackingStore, NewNumberOfBytes);
312
313 //
314 // Attach backing store to the host resource.
315 //
316 Status = VirtioGpuResourceAttachBacking (
317 VgpuGop->ParentBus, // VgpuDev
318 NewResourceId, // ResourceId
319 NewBackingStore, // FirstBackingPage
320 NewNumberOfPages // NumberOfPages
321 );
322 if (EFI_ERROR (Status)) {
323 goto FreeBackingStore;
324 }
325
326 //
327 // Point head (scanout) #0 to the host resource.
328 //
329 Status = VirtioGpuSetScanout (
330 VgpuGop->ParentBus, // VgpuDev
331 0, // X
332 0, // Y
333 mGopResolutions[ModeNumber].Width, // Width
334 mGopResolutions[ModeNumber].Height, // Height
335 0, // ScanoutId
336 NewResourceId // ResourceId
337 );
338 if (EFI_ERROR (Status)) {
339 goto DetachBackingStore;
340 }
341
342 //
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.
346 //
347 if (VgpuGop->ResourceId != 0) {
348 Status = VirtioGpuResourceFlush (
349 VgpuGop->ParentBus, // VgpuDev
350 0, // X
351 0, // Y
352 mGopResolutions[ModeNumber].Width, // Width
353 mGopResolutions[ModeNumber].Height, // Height
354 NewResourceId // ResourceId
355 );
356 if (EFI_ERROR (Status)) {
357 //
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.
361 //
362 Status2 = VirtioGpuSetScanout (
363 VgpuGop->ParentBus, // VgpuDev
364 0, // X
365 0, // Y
366 mGopResolutions[This->Mode->Mode].Width, // Width
367 mGopResolutions[This->Mode->Mode].Height, // Height
368 0, // ScanoutId
369 VgpuGop->ResourceId // ResourceId
370 );
371 ASSERT_EFI_ERROR (Status2);
372 if (EFI_ERROR (Status2)) {
373 CpuDeadLoop ();
374 }
375 goto DetachBackingStore;
376 }
377
378 //
379 // Flush successful; release the old resources (without disabling head
380 // (scanout) #0).
381 //
382 ReleaseGopResources (VgpuGop, FALSE /* DisableHead */);
383 }
384
385 //
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
388 // resources.
389 //
390 ASSERT (VgpuGop->ResourceId == 0);
391 ASSERT (VgpuGop->BackingStore == NULL);
392
393 VgpuGop->ResourceId = NewResourceId;
394 VgpuGop->BackingStore = NewBackingStore;
395 VgpuGop->NumberOfPages = NewNumberOfPages;
396
397 //
398 // Populate Mode and ModeInfo (mutable fields only).
399 //
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;
405 return EFI_SUCCESS;
406
407 DetachBackingStore:
408 Status2 = VirtioGpuResourceDetachBacking (VgpuGop->ParentBus, NewResourceId);
409 ASSERT_EFI_ERROR (Status2);
410 if (EFI_ERROR (Status2)) {
411 CpuDeadLoop ();
412 }
413
414 FreeBackingStore:
415 FreePages (NewBackingStore, NewNumberOfPages);
416
417 DestroyHostResource:
418 Status2 = VirtioGpuResourceUnref (VgpuGop->ParentBus, NewResourceId);
419 ASSERT_EFI_ERROR (Status2);
420 if (EFI_ERROR (Status2)) {
421 CpuDeadLoop ();
422 }
423
424 return Status;
425 }
426
427 STATIC
428 EFI_STATUS
429 EFIAPI
430 GopBlt (
431 IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
432 IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer, OPTIONAL
433 IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation,
434 IN UINTN SourceX,
435 IN UINTN SourceY,
436 IN UINTN DestinationX,
437 IN UINTN DestinationY,
438 IN UINTN Width,
439 IN UINTN Height,
440 IN UINTN Delta OPTIONAL
441 )
442 {
443 VGPU_GOP *VgpuGop;
444 UINT32 CurrentHorizontal;
445 UINT32 CurrentVertical;
446 UINTN SegmentSize;
447 UINTN Y;
448 UINTN ResourceOffset;
449 EFI_STATUS Status;
450
451 VgpuGop = VGPU_GOP_FROM_GOP (This);
452 CurrentHorizontal = VgpuGop->GopModeInfo.HorizontalResolution;
453 CurrentVertical = VgpuGop->GopModeInfo.VerticalResolution;
454
455 //
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.
459 //
460 SegmentSize = Width * sizeof (UINT32);
461
462 //
463 // Delta is relevant for operations that read a rectangle from, or write a
464 // rectangle to, BltBuffer.
465 //
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.
469 //
470 if (BltOperation == EfiBltVideoToBltBuffer ||
471 BltOperation == EfiBltBufferToVideo) {
472 if (Delta == 0) {
473 Delta = SegmentSize;
474 }
475 }
476
477 //
478 // For operations that write to the display, check if the destination fits
479 // onto the display.
480 //
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;
489 }
490 }
491
492 //
493 // For operations that read from the display, check if the source fits onto
494 // the display.
495 //
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;
503 }
504 }
505
506 //
507 // Render the request. For requests that do not modify the display, there
508 // won't be further steps.
509 //
510 switch (BltOperation) {
511 case EfiBltVideoFill:
512 //
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.
517 //
518 for (Y = 0; Y < Height; ++Y) {
519 SetMem32 (
520 VgpuGop->BackingStore +
521 (DestinationY + Y) * CurrentHorizontal + DestinationX,
522 SegmentSize,
523 *(UINT32 *)BltBuffer
524 );
525 }
526 break;
527
528 case EfiBltVideoToBltBuffer:
529 //
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.
535 //
536 for (Y = 0; Y < Height; ++Y) {
537 CopyMem (
538 (UINT8 *)BltBuffer +
539 (DestinationY + Y) * Delta + DestinationX * sizeof *BltBuffer,
540 VgpuGop->BackingStore +
541 (SourceY + Y) * CurrentHorizontal + SourceX,
542 SegmentSize
543 );
544 }
545 return EFI_SUCCESS;
546
547 case EfiBltBufferToVideo:
548 //
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.
554 //
555 for (Y = 0; Y < Height; ++Y) {
556 CopyMem (
557 VgpuGop->BackingStore +
558 (DestinationY + Y) * CurrentHorizontal + DestinationX,
559 (UINT8 *)BltBuffer +
560 (SourceY + Y) * Delta + SourceX * sizeof *BltBuffer,
561 SegmentSize
562 );
563 }
564 break;
565
566 case EfiBltVideoToVideo:
567 //
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.
572 //
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.
576 //
577 if (SourceY < DestinationY) {
578 Y = Height;
579 while (Y > 0) {
580 --Y;
581 CopyMem (
582 VgpuGop->BackingStore +
583 (DestinationY + Y) * CurrentHorizontal + DestinationX,
584 VgpuGop->BackingStore +
585 (SourceY + Y) * CurrentHorizontal + SourceX,
586 SegmentSize
587 );
588 }
589 } else {
590 for (Y = 0; Y < Height; ++Y) {
591 CopyMem (
592 VgpuGop->BackingStore +
593 (DestinationY + Y) * CurrentHorizontal + DestinationX,
594 VgpuGop->BackingStore +
595 (SourceY + Y) * CurrentHorizontal + SourceX,
596 SegmentSize
597 );
598 }
599 }
600 break;
601
602 default:
603 return EFI_INVALID_PARAMETER;
604 }
605
606 //
607 // For operations that wrote to the display, submit the updated area to the
608 // host -- update the host resource from guest memory.
609 //
610 ResourceOffset = sizeof (UINT32) * (DestinationY * CurrentHorizontal +
611 DestinationX);
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
620 );
621 if (EFI_ERROR (Status)) {
622 return Status;
623 }
624
625 //
626 // Flush the updated resource to the display.
627 //
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
635 );
636 return Status;
637 }
638
639 //
640 // Template for initializing VGPU_GOP.Gop.
641 //
642 CONST EFI_GRAPHICS_OUTPUT_PROTOCOL mGopTemplate = {
643 GopQueryMode,
644 GopSetMode,
645 GopBlt,
646 NULL // Mode, to be overwritten in the actual protocol instance
647 };