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