]> git.proxmox.com Git - mirror_edk2.git/blob - OvmfPkg/VirtioGpuDxe/Gop.c
OvmfPkg/VirtioGpuDxe: helpers for backing store (de)allocation+(un)mapping
[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/MemoryAllocationLib.h>
18
19 #include "VirtioGpu.h"
20
21 /**
22 Release guest-side and host-side resources that are related to an initialized
23 VGPU_GOP.Gop.
24
25 param[in,out] VgpuGop The VGPU_GOP object to release resources for.
26
27 On input, the caller is responsible for having called
28 VgpuGop->Gop.SetMode() at least once successfully.
29 (This is equivalent to the requirement that
30 VgpuGop->BackingStore be non-NULL. It is also
31 equivalent to the requirement that VgpuGop->ResourceId
32 be nonzero.)
33
34 On output, resources will be released, and
35 VgpuGop->BackingStore and VgpuGop->ResourceId will be
36 nulled.
37
38 param[in] DisableHead Whether this head (scanout) currently references the
39 resource identified by VgpuGop->ResourceId. Only pass
40 FALSE when VgpuGop->Gop.SetMode() calls this function
41 while switching between modes, and set it to TRUE
42 every other time.
43 **/
44 VOID
45 ReleaseGopResources (
46 IN OUT VGPU_GOP *VgpuGop,
47 IN BOOLEAN DisableHead
48 )
49 {
50 EFI_STATUS Status;
51
52 ASSERT (VgpuGop->ResourceId != 0);
53 ASSERT (VgpuGop->BackingStore != NULL);
54
55 //
56 // If any of the following host-side destruction steps fail, we can't get out
57 // of an inconsistent state, so we'll hang. In general errors in object
58 // destruction can hardly be recovered from.
59 //
60 if (DisableHead) {
61 //
62 // Dissociate head (scanout) #0 from the currently used 2D host resource,
63 // by setting ResourceId=0 for it.
64 //
65 Status = VirtioGpuSetScanout (
66 VgpuGop->ParentBus, // VgpuDev
67 0, 0, 0, 0, // X, Y, Width, Height
68 0, // ScanoutId
69 0 // ResourceId
70 );
71 //
72 // HACK BEGINS HERE
73 //
74 // According to the GPU Device section of the VirtIo specification, the
75 // above operation is valid:
76 //
77 // "The driver can use resource_id = 0 to disable a scanout."
78 //
79 // However, in practice QEMU does not allow us to disable head (scanout) #0
80 // -- it rejects the command with response code 0x1202
81 // (VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID). Looking at the QEMU source
82 // code, function virtio_gpu_set_scanout() in "hw/display/virtio-gpu.c",
83 // this appears fully intentional, despite not being documented in the
84 // spec.
85 //
86 // Surprisingly, ignoring the error here, and proceeding to release
87 // host-side resources that presumably underlie head (scanout) #0, work
88 // without any problems -- the driver survives repeated "disconnect" /
89 // "connect -r" commands in the UEFI shell.
90 //
91 // So, for now, let's just suppress the error.
92 //
93 Status = EFI_SUCCESS;
94 //
95 // HACK ENDS HERE
96 //
97
98 ASSERT_EFI_ERROR (Status);
99 if (EFI_ERROR (Status)) {
100 CpuDeadLoop ();
101 }
102 }
103
104 //
105 // Detach backing pages from the currently used 2D host resource.
106 //
107 Status = VirtioGpuResourceDetachBacking (
108 VgpuGop->ParentBus, // VgpuDev
109 VgpuGop->ResourceId // ResourceId
110 );
111 ASSERT_EFI_ERROR (Status);
112 if (EFI_ERROR (Status)) {
113 CpuDeadLoop ();
114 }
115
116 //
117 // Release backing pages.
118 //
119 FreePages (VgpuGop->BackingStore, VgpuGop->NumberOfPages);
120 VgpuGop->BackingStore = NULL;
121 VgpuGop->NumberOfPages = 0;
122
123 //
124 // Destroy the currently used 2D host resource.
125 //
126 Status = VirtioGpuResourceUnref (
127 VgpuGop->ParentBus, // VgpuDev
128 VgpuGop->ResourceId // ResourceId
129 );
130 ASSERT_EFI_ERROR (Status);
131 if (EFI_ERROR (Status)) {
132 CpuDeadLoop ();
133 }
134 VgpuGop->ResourceId = 0;
135 }
136
137 //
138 // The resolutions supported by this driver.
139 //
140 typedef struct {
141 UINT32 Width;
142 UINT32 Height;
143 } GOP_RESOLUTION;
144
145 STATIC CONST GOP_RESOLUTION mGopResolutions[] = {
146 { 640, 480 },
147 { 800, 480 },
148 { 800, 600 },
149 { 832, 624 },
150 { 960, 640 },
151 { 1024, 600 },
152 { 1024, 768 },
153 { 1152, 864 },
154 { 1152, 870 },
155 { 1280, 720 },
156 { 1280, 760 },
157 { 1280, 768 },
158 { 1280, 800 },
159 { 1280, 960 },
160 { 1280, 1024 },
161 { 1360, 768 },
162 { 1366, 768 },
163 { 1400, 1050 },
164 { 1440, 900 },
165 { 1600, 900 },
166 { 1600, 1200 },
167 { 1680, 1050 },
168 { 1920, 1080 },
169 { 1920, 1200 },
170 { 1920, 1440 },
171 { 2000, 2000 },
172 { 2048, 1536 },
173 { 2048, 2048 },
174 { 2560, 1440 },
175 { 2560, 1600 },
176 { 2560, 2048 },
177 { 2800, 2100 },
178 { 3200, 2400 },
179 { 3840, 2160 },
180 { 4096, 2160 },
181 { 7680, 4320 },
182 { 8192, 4320 },
183 };
184
185 //
186 // Macro for casting VGPU_GOP.Gop to VGPU_GOP.
187 //
188 #define VGPU_GOP_FROM_GOP(GopPointer) \
189 CR (GopPointer, VGPU_GOP, Gop, VGPU_GOP_SIG)
190
191 //
192 // EFI_GRAPHICS_OUTPUT_PROTOCOL member functions.
193 //
194 STATIC
195 EFI_STATUS
196 EFIAPI
197 GopQueryMode (
198 IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
199 IN UINT32 ModeNumber,
200 OUT UINTN *SizeOfInfo,
201 OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION **Info
202 )
203 {
204 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *GopModeInfo;
205
206 if (ModeNumber >= ARRAY_SIZE (mGopResolutions)) {
207 return EFI_INVALID_PARAMETER;
208 }
209
210 GopModeInfo = AllocateZeroPool (sizeof *GopModeInfo);
211 if (GopModeInfo == NULL) {
212 return EFI_OUT_OF_RESOURCES;
213 }
214
215 GopModeInfo->HorizontalResolution = mGopResolutions[ModeNumber].Width;
216 GopModeInfo->VerticalResolution = mGopResolutions[ModeNumber].Height;
217 GopModeInfo->PixelFormat = PixelBltOnly;
218 GopModeInfo->PixelsPerScanLine = mGopResolutions[ModeNumber].Width;
219
220 *SizeOfInfo = sizeof *GopModeInfo;
221 *Info = GopModeInfo;
222 return EFI_SUCCESS;
223 }
224
225 STATIC
226 EFI_STATUS
227 EFIAPI
228 GopSetMode (
229 IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
230 IN UINT32 ModeNumber
231 )
232 {
233 VGPU_GOP *VgpuGop;
234 UINT32 NewResourceId;
235 UINTN NewNumberOfBytes;
236 UINTN NewNumberOfPages;
237 VOID *NewBackingStore;
238 EFI_STATUS Status;
239 EFI_STATUS Status2;
240
241 if (ModeNumber >= ARRAY_SIZE (mGopResolutions)) {
242 return EFI_UNSUPPORTED;
243 }
244
245 VgpuGop = VGPU_GOP_FROM_GOP (This);
246
247 //
248 // Distinguish the first (internal) call from the other (protocol consumer)
249 // calls.
250 //
251 if (VgpuGop->ResourceId == 0) {
252 //
253 // Set up the Gop -> GopMode -> GopModeInfo pointer chain, and the other
254 // (nonzero) constant fields.
255 //
256 // No direct framebuffer access is supported, only Blt() is.
257 //
258 VgpuGop->Gop.Mode = &VgpuGop->GopMode;
259
260 VgpuGop->GopMode.MaxMode = (UINT32)(ARRAY_SIZE (mGopResolutions));
261 VgpuGop->GopMode.Info = &VgpuGop->GopModeInfo;
262 VgpuGop->GopMode.SizeOfInfo = sizeof VgpuGop->GopModeInfo;
263
264 VgpuGop->GopModeInfo.PixelFormat = PixelBltOnly;
265
266 //
267 // This is the first time we create a host side resource.
268 //
269 NewResourceId = 1;
270 } else {
271 //
272 // We already have an active host side resource. Create the new one without
273 // interfering with the current one, so that we can cleanly bail out on
274 // error, without disturbing the current graphics mode.
275 //
276 // The formula below will alternate between IDs 1 and 2.
277 //
278 NewResourceId = 3 - VgpuGop->ResourceId;
279 }
280
281 //
282 // Create the 2D host resource.
283 //
284 Status = VirtioGpuResourceCreate2d (
285 VgpuGop->ParentBus, // VgpuDev
286 NewResourceId, // ResourceId
287 VirtioGpuFormatB8G8R8X8Unorm, // Format
288 mGopResolutions[ModeNumber].Width, // Width
289 mGopResolutions[ModeNumber].Height // Height
290 );
291 if (EFI_ERROR (Status)) {
292 return Status;
293 }
294
295 //
296 // Allocate guest backing store.
297 //
298 NewNumberOfBytes = mGopResolutions[ModeNumber].Width *
299 mGopResolutions[ModeNumber].Height * sizeof (UINT32);
300 NewNumberOfPages = EFI_SIZE_TO_PAGES (NewNumberOfBytes);
301 NewBackingStore = AllocatePages (NewNumberOfPages);
302 if (NewBackingStore == NULL) {
303 Status = EFI_OUT_OF_RESOURCES;
304 goto DestroyHostResource;
305 }
306 //
307 // Fill visible part of backing store with black.
308 //
309 ZeroMem (NewBackingStore, NewNumberOfBytes);
310
311 //
312 // Attach backing store to the host resource.
313 //
314 Status = VirtioGpuResourceAttachBacking (
315 VgpuGop->ParentBus, // VgpuDev
316 NewResourceId, // ResourceId
317 (UINTN)NewBackingStore, // BackingStoreDeviceAddress
318 NewNumberOfPages // NumberOfPages
319 );
320 if (EFI_ERROR (Status)) {
321 goto FreeBackingStore;
322 }
323
324 //
325 // Point head (scanout) #0 to the host resource.
326 //
327 Status = VirtioGpuSetScanout (
328 VgpuGop->ParentBus, // VgpuDev
329 0, // X
330 0, // Y
331 mGopResolutions[ModeNumber].Width, // Width
332 mGopResolutions[ModeNumber].Height, // Height
333 0, // ScanoutId
334 NewResourceId // ResourceId
335 );
336 if (EFI_ERROR (Status)) {
337 goto DetachBackingStore;
338 }
339
340 //
341 // If this is not the first (i.e., internal) call, then we have to (a) flush
342 // the new resource to head (scanout) #0, after having flipped the latter to
343 // the former above, plus (b) release the old resources.
344 //
345 if (VgpuGop->ResourceId != 0) {
346 Status = VirtioGpuResourceFlush (
347 VgpuGop->ParentBus, // VgpuDev
348 0, // X
349 0, // Y
350 mGopResolutions[ModeNumber].Width, // Width
351 mGopResolutions[ModeNumber].Height, // Height
352 NewResourceId // ResourceId
353 );
354 if (EFI_ERROR (Status)) {
355 //
356 // Flip head (scanout) #0 back to the current resource. If this fails, we
357 // cannot continue, as this error occurs on the error path and is
358 // therefore non-recoverable.
359 //
360 Status2 = VirtioGpuSetScanout (
361 VgpuGop->ParentBus, // VgpuDev
362 0, // X
363 0, // Y
364 mGopResolutions[This->Mode->Mode].Width, // Width
365 mGopResolutions[This->Mode->Mode].Height, // Height
366 0, // ScanoutId
367 VgpuGop->ResourceId // ResourceId
368 );
369 ASSERT_EFI_ERROR (Status2);
370 if (EFI_ERROR (Status2)) {
371 CpuDeadLoop ();
372 }
373 goto DetachBackingStore;
374 }
375
376 //
377 // Flush successful; release the old resources (without disabling head
378 // (scanout) #0).
379 //
380 ReleaseGopResources (VgpuGop, FALSE /* DisableHead */);
381 }
382
383 //
384 // This is either the first (internal) call when we have no old resources
385 // yet, or we've changed the mode successfully and released the old
386 // resources.
387 //
388 ASSERT (VgpuGop->ResourceId == 0);
389 ASSERT (VgpuGop->BackingStore == NULL);
390
391 VgpuGop->ResourceId = NewResourceId;
392 VgpuGop->BackingStore = NewBackingStore;
393 VgpuGop->NumberOfPages = NewNumberOfPages;
394
395 //
396 // Populate Mode and ModeInfo (mutable fields only).
397 //
398 VgpuGop->GopMode.Mode = ModeNumber;
399 VgpuGop->GopModeInfo.HorizontalResolution =
400 mGopResolutions[ModeNumber].Width;
401 VgpuGop->GopModeInfo.VerticalResolution = mGopResolutions[ModeNumber].Height;
402 VgpuGop->GopModeInfo.PixelsPerScanLine = mGopResolutions[ModeNumber].Width;
403 return EFI_SUCCESS;
404
405 DetachBackingStore:
406 Status2 = VirtioGpuResourceDetachBacking (VgpuGop->ParentBus, NewResourceId);
407 ASSERT_EFI_ERROR (Status2);
408 if (EFI_ERROR (Status2)) {
409 CpuDeadLoop ();
410 }
411
412 FreeBackingStore:
413 FreePages (NewBackingStore, NewNumberOfPages);
414
415 DestroyHostResource:
416 Status2 = VirtioGpuResourceUnref (VgpuGop->ParentBus, NewResourceId);
417 ASSERT_EFI_ERROR (Status2);
418 if (EFI_ERROR (Status2)) {
419 CpuDeadLoop ();
420 }
421
422 return Status;
423 }
424
425 STATIC
426 EFI_STATUS
427 EFIAPI
428 GopBlt (
429 IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
430 IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer, OPTIONAL
431 IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation,
432 IN UINTN SourceX,
433 IN UINTN SourceY,
434 IN UINTN DestinationX,
435 IN UINTN DestinationY,
436 IN UINTN Width,
437 IN UINTN Height,
438 IN UINTN Delta OPTIONAL
439 )
440 {
441 VGPU_GOP *VgpuGop;
442 UINT32 CurrentHorizontal;
443 UINT32 CurrentVertical;
444 UINTN SegmentSize;
445 UINTN Y;
446 UINTN ResourceOffset;
447 EFI_STATUS Status;
448
449 VgpuGop = VGPU_GOP_FROM_GOP (This);
450 CurrentHorizontal = VgpuGop->GopModeInfo.HorizontalResolution;
451 CurrentVertical = VgpuGop->GopModeInfo.VerticalResolution;
452
453 //
454 // We can avoid pixel format conversion in the guest because the internal
455 // representation of EFI_GRAPHICS_OUTPUT_BLT_PIXEL and that of
456 // VirtioGpuFormatB8G8R8X8Unorm are identical.
457 //
458 SegmentSize = Width * sizeof (UINT32);
459
460 //
461 // Delta is relevant for operations that read a rectangle from, or write a
462 // rectangle to, BltBuffer.
463 //
464 // In these cases, Delta is the stride of BltBuffer, in bytes. If Delta is
465 // zero, then Width is the entire width of BltBuffer, and the stride is
466 // supposed to be calculated from Width.
467 //
468 if (BltOperation == EfiBltVideoToBltBuffer ||
469 BltOperation == EfiBltBufferToVideo) {
470 if (Delta == 0) {
471 Delta = SegmentSize;
472 }
473 }
474
475 //
476 // For operations that write to the display, check if the destination fits
477 // onto the display.
478 //
479 if (BltOperation == EfiBltVideoFill ||
480 BltOperation == EfiBltBufferToVideo ||
481 BltOperation == EfiBltVideoToVideo) {
482 if (DestinationX > CurrentHorizontal ||
483 Width > CurrentHorizontal - DestinationX ||
484 DestinationY > CurrentVertical ||
485 Height > CurrentVertical - DestinationY) {
486 return EFI_INVALID_PARAMETER;
487 }
488 }
489
490 //
491 // For operations that read from the display, check if the source fits onto
492 // the display.
493 //
494 if (BltOperation == EfiBltVideoToBltBuffer ||
495 BltOperation == EfiBltVideoToVideo) {
496 if (SourceX > CurrentHorizontal ||
497 Width > CurrentHorizontal - SourceX ||
498 SourceY > CurrentVertical ||
499 Height > CurrentVertical - SourceY) {
500 return EFI_INVALID_PARAMETER;
501 }
502 }
503
504 //
505 // Render the request. For requests that do not modify the display, there
506 // won't be further steps.
507 //
508 switch (BltOperation) {
509 case EfiBltVideoFill:
510 //
511 // Write data from the BltBuffer pixel (0, 0) directly to every pixel of
512 // the video display rectangle (DestinationX, DestinationY) (DestinationX +
513 // Width, DestinationY + Height). Only one pixel will be used from the
514 // BltBuffer. Delta is NOT used.
515 //
516 for (Y = 0; Y < Height; ++Y) {
517 SetMem32 (
518 VgpuGop->BackingStore +
519 (DestinationY + Y) * CurrentHorizontal + DestinationX,
520 SegmentSize,
521 *(UINT32 *)BltBuffer
522 );
523 }
524 break;
525
526 case EfiBltVideoToBltBuffer:
527 //
528 // Read data from the video display rectangle (SourceX, SourceY) (SourceX +
529 // Width, SourceY + Height) and place it in the BltBuffer rectangle
530 // (DestinationX, DestinationY ) (DestinationX + Width, DestinationY +
531 // Height). If DestinationX or DestinationY is not zero then Delta must be
532 // set to the length in bytes of a row in the BltBuffer.
533 //
534 for (Y = 0; Y < Height; ++Y) {
535 CopyMem (
536 (UINT8 *)BltBuffer +
537 (DestinationY + Y) * Delta + DestinationX * sizeof *BltBuffer,
538 VgpuGop->BackingStore +
539 (SourceY + Y) * CurrentHorizontal + SourceX,
540 SegmentSize
541 );
542 }
543 return EFI_SUCCESS;
544
545 case EfiBltBufferToVideo:
546 //
547 // Write data from the BltBuffer rectangle (SourceX, SourceY) (SourceX +
548 // Width, SourceY + Height) directly to the video display rectangle
549 // (DestinationX, DestinationY) (DestinationX + Width, DestinationY +
550 // Height). If SourceX or SourceY is not zero then Delta must be set to the
551 // length in bytes of a row in the BltBuffer.
552 //
553 for (Y = 0; Y < Height; ++Y) {
554 CopyMem (
555 VgpuGop->BackingStore +
556 (DestinationY + Y) * CurrentHorizontal + DestinationX,
557 (UINT8 *)BltBuffer +
558 (SourceY + Y) * Delta + SourceX * sizeof *BltBuffer,
559 SegmentSize
560 );
561 }
562 break;
563
564 case EfiBltVideoToVideo:
565 //
566 // Copy from the video display rectangle (SourceX, SourceY) (SourceX +
567 // Width, SourceY + Height) to the video display rectangle (DestinationX,
568 // DestinationY) (DestinationX + Width, DestinationY + Height). The
569 // BltBuffer and Delta are not used in this mode.
570 //
571 // A single invocation of CopyMem() handles overlap between source and
572 // destination (that is, within a single line), but for multiple
573 // invocations, we must handle overlaps.
574 //
575 if (SourceY < DestinationY) {
576 Y = Height;
577 while (Y > 0) {
578 --Y;
579 CopyMem (
580 VgpuGop->BackingStore +
581 (DestinationY + Y) * CurrentHorizontal + DestinationX,
582 VgpuGop->BackingStore +
583 (SourceY + Y) * CurrentHorizontal + SourceX,
584 SegmentSize
585 );
586 }
587 } else {
588 for (Y = 0; Y < Height; ++Y) {
589 CopyMem (
590 VgpuGop->BackingStore +
591 (DestinationY + Y) * CurrentHorizontal + DestinationX,
592 VgpuGop->BackingStore +
593 (SourceY + Y) * CurrentHorizontal + SourceX,
594 SegmentSize
595 );
596 }
597 }
598 break;
599
600 default:
601 return EFI_INVALID_PARAMETER;
602 }
603
604 //
605 // For operations that wrote to the display, submit the updated area to the
606 // host -- update the host resource from guest memory.
607 //
608 ResourceOffset = sizeof (UINT32) * (DestinationY * CurrentHorizontal +
609 DestinationX);
610 Status = VirtioGpuTransferToHost2d (
611 VgpuGop->ParentBus, // VgpuDev
612 (UINT32)DestinationX, // X
613 (UINT32)DestinationY, // Y
614 (UINT32)Width, // Width
615 (UINT32)Height, // Height
616 ResourceOffset, // Offset
617 VgpuGop->ResourceId // ResourceId
618 );
619 if (EFI_ERROR (Status)) {
620 return Status;
621 }
622
623 //
624 // Flush the updated resource to the display.
625 //
626 Status = VirtioGpuResourceFlush (
627 VgpuGop->ParentBus, // VgpuDev
628 (UINT32)DestinationX, // X
629 (UINT32)DestinationY, // Y
630 (UINT32)Width, // Width
631 (UINT32)Height, // Height
632 VgpuGop->ResourceId // ResourceId
633 );
634 return Status;
635 }
636
637 //
638 // Template for initializing VGPU_GOP.Gop.
639 //
640 CONST EFI_GRAPHICS_OUTPUT_PROTOCOL mGopTemplate = {
641 GopQueryMode,
642 GopSetMode,
643 GopBlt,
644 NULL // Mode, to be overwritten in the actual protocol instance
645 };