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