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
ReleaseGopResources(IN OUT VGPU_GOP * VgpuGop,IN BOOLEAN DisableHead)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, 0, 0, 0, // X, Y, Width, Height
62 0, // ScanoutId
63 0 // ResourceId
64 );
65 //
66 // HACK BEGINS HERE
67 //
68 // According to the GPU Device section of the VirtIo specification, the
69 // above operation is valid:
70 //
71 // "The driver can use resource_id = 0 to disable a scanout."
72 //
73 // However, in practice QEMU does not allow us to disable head (scanout) #0
74 // -- it rejects the command with response code 0x1202
75 // (VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID). Looking at the QEMU source
76 // code, function virtio_gpu_set_scanout() in "hw/display/virtio-gpu.c",
77 // this appears fully intentional, despite not being documented in the
78 // spec.
79 //
80 // Surprisingly, ignoring the error here, and proceeding to release
81 // host-side resources that presumably underlie head (scanout) #0, work
82 // without any problems -- the driver survives repeated "disconnect" /
83 // "connect -r" commands in the UEFI shell.
84 //
85 // So, for now, let's just suppress the error.
86 //
87 Status = EFI_SUCCESS;
88 //
89 // HACK ENDS HERE
90 //
91
92 ASSERT_EFI_ERROR (Status);
93 if (EFI_ERROR (Status)) {
94 CpuDeadLoop ();
95 }
96 }
97
98 //
99 // Detach backing pages from the currently used 2D host resource.
100 //
101 Status = VirtioGpuResourceDetachBacking (
102 VgpuGop->ParentBus, // VgpuDev
103 VgpuGop->ResourceId // ResourceId
104 );
105 ASSERT_EFI_ERROR (Status);
106 if (EFI_ERROR (Status)) {
107 CpuDeadLoop ();
108 }
109
110 //
111 // Unmap and release backing pages.
112 //
113 VirtioGpuUnmapAndFreeBackingStore (
114 VgpuGop->ParentBus, // VgpuDev
115 VgpuGop->NumberOfPages, // NumberOfPages
116 VgpuGop->BackingStore, // HostAddress
117 VgpuGop->BackingStoreMap // Mapping
118 );
119 VgpuGop->BackingStore = NULL;
120 VgpuGop->NumberOfPages = 0;
121 VgpuGop->BackingStoreMap = NULL;
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
GopQueryMode(IN EFI_GRAPHICS_OUTPUT_PROTOCOL * This,IN UINT32 ModeNumber,OUT UINTN * SizeOfInfo,OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION ** Info)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
GopSetMode(IN EFI_GRAPHICS_OUTPUT_PROTOCOL * This,IN UINT32 ModeNumber)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_PHYSICAL_ADDRESS NewBackingStoreDeviceAddress;
239 VOID *NewBackingStoreMap;
240
241 EFI_STATUS Status;
242 EFI_STATUS Status2;
243
244 if (ModeNumber >= ARRAY_SIZE (mGopResolutions)) {
245 return EFI_UNSUPPORTED;
246 }
247
248 VgpuGop = VGPU_GOP_FROM_GOP (This);
249
250 //
251 // Distinguish the first (internal) call from the other (protocol consumer)
252 // calls.
253 //
254 if (VgpuGop->ResourceId == 0) {
255 //
256 // Set up the Gop -> GopMode -> GopModeInfo pointer chain, and the other
257 // (nonzero) constant fields.
258 //
259 // No direct framebuffer access is supported, only Blt() is.
260 //
261 VgpuGop->Gop.Mode = &VgpuGop->GopMode;
262
263 VgpuGop->GopMode.MaxMode = (UINT32)(ARRAY_SIZE (mGopResolutions));
264 VgpuGop->GopMode.Info = &VgpuGop->GopModeInfo;
265 VgpuGop->GopMode.SizeOfInfo = sizeof VgpuGop->GopModeInfo;
266
267 VgpuGop->GopModeInfo.PixelFormat = PixelBltOnly;
268
269 //
270 // This is the first time we create a host side resource.
271 //
272 NewResourceId = 1;
273 } else {
274 //
275 // We already have an active host side resource. Create the new one without
276 // interfering with the current one, so that we can cleanly bail out on
277 // error, without disturbing the current graphics mode.
278 //
279 // The formula below will alternate between IDs 1 and 2.
280 //
281 NewResourceId = 3 - VgpuGop->ResourceId;
282 }
283
284 //
285 // Create the 2D host resource.
286 //
287 Status = VirtioGpuResourceCreate2d (
288 VgpuGop->ParentBus, // VgpuDev
289 NewResourceId, // ResourceId
290 VirtioGpuFormatB8G8R8X8Unorm, // Format
291 mGopResolutions[ModeNumber].Width, // Width
292 mGopResolutions[ModeNumber].Height // Height
293 );
294 if (EFI_ERROR (Status)) {
295 return Status;
296 }
297
298 //
299 // Allocate, zero and map guest backing store, for bus master common buffer
300 // operation.
301 //
302 NewNumberOfBytes = mGopResolutions[ModeNumber].Width *
303 mGopResolutions[ModeNumber].Height * sizeof (UINT32);
304 NewNumberOfPages = EFI_SIZE_TO_PAGES (NewNumberOfBytes);
305 Status = VirtioGpuAllocateZeroAndMapBackingStore (
306 VgpuGop->ParentBus, // VgpuDev
307 NewNumberOfPages, // NumberOfPages
308 &NewBackingStore, // HostAddress
309 &NewBackingStoreDeviceAddress, // DeviceAddress
310 &NewBackingStoreMap // Mapping
311 );
312 if (EFI_ERROR (Status)) {
313 goto DestroyHostResource;
314 }
315
316 //
317 // Attach backing store to the host resource.
318 //
319 Status = VirtioGpuResourceAttachBacking (
320 VgpuGop->ParentBus, // VgpuDev
321 NewResourceId, // ResourceId
322 NewBackingStoreDeviceAddress, // BackingStoreDeviceAddress
323 NewNumberOfPages // NumberOfPages
324 );
325 if (EFI_ERROR (Status)) {
326 goto UnmapAndFreeBackingStore;
327 }
328
329 //
330 // Point head (scanout) #0 to the host resource.
331 //
332 Status = VirtioGpuSetScanout (
333 VgpuGop->ParentBus, // VgpuDev
334 0, // X
335 0, // Y
336 mGopResolutions[ModeNumber].Width, // Width
337 mGopResolutions[ModeNumber].Height, // Height
338 0, // ScanoutId
339 NewResourceId // ResourceId
340 );
341 if (EFI_ERROR (Status)) {
342 goto DetachBackingStore;
343 }
344
345 //
346 // If this is not the first (i.e., internal) call, then we have to (a) flush
347 // the new resource to head (scanout) #0, after having flipped the latter to
348 // the former above, plus (b) release the old resources.
349 //
350 if (VgpuGop->ResourceId != 0) {
351 Status = VirtioGpuResourceFlush (
352 VgpuGop->ParentBus, // VgpuDev
353 0, // X
354 0, // Y
355 mGopResolutions[ModeNumber].Width, // Width
356 mGopResolutions[ModeNumber].Height, // Height
357 NewResourceId // ResourceId
358 );
359 if (EFI_ERROR (Status)) {
360 //
361 // Flip head (scanout) #0 back to the current resource. If this fails, we
362 // cannot continue, as this error occurs on the error path and is
363 // therefore non-recoverable.
364 //
365 Status2 = VirtioGpuSetScanout (
366 VgpuGop->ParentBus, // VgpuDev
367 0, // X
368 0, // Y
369 mGopResolutions[This->Mode->Mode].Width, // Width
370 mGopResolutions[This->Mode->Mode].Height, // Height
371 0, // ScanoutId
372 VgpuGop->ResourceId // ResourceId
373 );
374 ASSERT_EFI_ERROR (Status2);
375 if (EFI_ERROR (Status2)) {
376 CpuDeadLoop ();
377 }
378 goto DetachBackingStore;
379 }
380
381 //
382 // Flush successful; release the old resources (without disabling head
383 // (scanout) #0).
384 //
385 ReleaseGopResources (VgpuGop, FALSE /* DisableHead */);
386 }
387
388 //
389 // This is either the first (internal) call when we have no old resources
390 // yet, or we've changed the mode successfully and released the old
391 // resources.
392 //
393 ASSERT (VgpuGop->ResourceId == 0);
394 ASSERT (VgpuGop->BackingStore == NULL);
395
396 VgpuGop->ResourceId = NewResourceId;
397 VgpuGop->BackingStore = NewBackingStore;
398 VgpuGop->NumberOfPages = NewNumberOfPages;
399 VgpuGop->BackingStoreMap = NewBackingStoreMap;
400
401 //
402 // Populate Mode and ModeInfo (mutable fields only).
403 //
404 VgpuGop->GopMode.Mode = ModeNumber;
405 VgpuGop->GopModeInfo.HorizontalResolution =
406 mGopResolutions[ModeNumber].Width;
407 VgpuGop->GopModeInfo.VerticalResolution = mGopResolutions[ModeNumber].Height;
408 VgpuGop->GopModeInfo.PixelsPerScanLine = mGopResolutions[ModeNumber].Width;
409 return EFI_SUCCESS;
410
411 DetachBackingStore:
412 Status2 = VirtioGpuResourceDetachBacking (VgpuGop->ParentBus, NewResourceId);
413 ASSERT_EFI_ERROR (Status2);
414 if (EFI_ERROR (Status2)) {
415 CpuDeadLoop ();
416 }
417
418 UnmapAndFreeBackingStore:
419 VirtioGpuUnmapAndFreeBackingStore (
420 VgpuGop->ParentBus, // VgpuDev
421 NewNumberOfPages, // NumberOfPages
422 NewBackingStore, // HostAddress
423 NewBackingStoreMap // Mapping
424 );
425
426 DestroyHostResource:
427 Status2 = VirtioGpuResourceUnref (VgpuGop->ParentBus, NewResourceId);
428 ASSERT_EFI_ERROR (Status2);
429 if (EFI_ERROR (Status2)) {
430 CpuDeadLoop ();
431 }
432
433 return Status;
434 }
435
436 STATIC
437 EFI_STATUS
438 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)439 GopBlt (
440 IN EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
441 IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer, OPTIONAL
442 IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation,
443 IN UINTN SourceX,
444 IN UINTN SourceY,
445 IN UINTN DestinationX,
446 IN UINTN DestinationY,
447 IN UINTN Width,
448 IN UINTN Height,
449 IN UINTN Delta OPTIONAL
450 )
451 {
452 VGPU_GOP *VgpuGop;
453 UINT32 CurrentHorizontal;
454 UINT32 CurrentVertical;
455 UINTN SegmentSize;
456 UINTN Y;
457 UINTN ResourceOffset;
458 EFI_STATUS Status;
459
460 VgpuGop = VGPU_GOP_FROM_GOP (This);
461 CurrentHorizontal = VgpuGop->GopModeInfo.HorizontalResolution;
462 CurrentVertical = VgpuGop->GopModeInfo.VerticalResolution;
463
464 //
465 // We can avoid pixel format conversion in the guest because the internal
466 // representation of EFI_GRAPHICS_OUTPUT_BLT_PIXEL and that of
467 // VirtioGpuFormatB8G8R8X8Unorm are identical.
468 //
469 SegmentSize = Width * sizeof (UINT32);
470
471 //
472 // Delta is relevant for operations that read a rectangle from, or write a
473 // rectangle to, BltBuffer.
474 //
475 // In these cases, Delta is the stride of BltBuffer, in bytes. If Delta is
476 // zero, then Width is the entire width of BltBuffer, and the stride is
477 // supposed to be calculated from Width.
478 //
479 if (BltOperation == EfiBltVideoToBltBuffer ||
480 BltOperation == EfiBltBufferToVideo) {
481 if (Delta == 0) {
482 Delta = SegmentSize;
483 }
484 }
485
486 //
487 // For operations that write to the display, check if the destination fits
488 // onto the display.
489 //
490 if (BltOperation == EfiBltVideoFill ||
491 BltOperation == EfiBltBufferToVideo ||
492 BltOperation == EfiBltVideoToVideo) {
493 if (DestinationX > CurrentHorizontal ||
494 Width > CurrentHorizontal - DestinationX ||
495 DestinationY > CurrentVertical ||
496 Height > CurrentVertical - DestinationY) {
497 return EFI_INVALID_PARAMETER;
498 }
499 }
500
501 //
502 // For operations that read from the display, check if the source fits onto
503 // the display.
504 //
505 if (BltOperation == EfiBltVideoToBltBuffer ||
506 BltOperation == EfiBltVideoToVideo) {
507 if (SourceX > CurrentHorizontal ||
508 Width > CurrentHorizontal - SourceX ||
509 SourceY > CurrentVertical ||
510 Height > CurrentVertical - SourceY) {
511 return EFI_INVALID_PARAMETER;
512 }
513 }
514
515 //
516 // Render the request. For requests that do not modify the display, there
517 // won't be further steps.
518 //
519 switch (BltOperation) {
520 case EfiBltVideoFill:
521 //
522 // Write data from the BltBuffer pixel (0, 0) directly to every pixel of
523 // the video display rectangle (DestinationX, DestinationY) (DestinationX +
524 // Width, DestinationY + Height). Only one pixel will be used from the
525 // BltBuffer. Delta is NOT used.
526 //
527 for (Y = 0; Y < Height; ++Y) {
528 SetMem32 (
529 VgpuGop->BackingStore +
530 (DestinationY + Y) * CurrentHorizontal + DestinationX,
531 SegmentSize,
532 *(UINT32 *)BltBuffer
533 );
534 }
535 break;
536
537 case EfiBltVideoToBltBuffer:
538 //
539 // Read data from the video display rectangle (SourceX, SourceY) (SourceX +
540 // Width, SourceY + Height) and place it in the BltBuffer rectangle
541 // (DestinationX, DestinationY ) (DestinationX + Width, DestinationY +
542 // Height). If DestinationX or DestinationY is not zero then Delta must be
543 // set to the length in bytes of a row in the BltBuffer.
544 //
545 for (Y = 0; Y < Height; ++Y) {
546 CopyMem (
547 (UINT8 *)BltBuffer +
548 (DestinationY + Y) * Delta + DestinationX * sizeof *BltBuffer,
549 VgpuGop->BackingStore +
550 (SourceY + Y) * CurrentHorizontal + SourceX,
551 SegmentSize
552 );
553 }
554 return EFI_SUCCESS;
555
556 case EfiBltBufferToVideo:
557 //
558 // Write data from the BltBuffer rectangle (SourceX, SourceY) (SourceX +
559 // Width, SourceY + Height) directly to the video display rectangle
560 // (DestinationX, DestinationY) (DestinationX + Width, DestinationY +
561 // Height). If SourceX or SourceY is not zero then Delta must be set to the
562 // length in bytes of a row in the BltBuffer.
563 //
564 for (Y = 0; Y < Height; ++Y) {
565 CopyMem (
566 VgpuGop->BackingStore +
567 (DestinationY + Y) * CurrentHorizontal + DestinationX,
568 (UINT8 *)BltBuffer +
569 (SourceY + Y) * Delta + SourceX * sizeof *BltBuffer,
570 SegmentSize
571 );
572 }
573 break;
574
575 case EfiBltVideoToVideo:
576 //
577 // Copy from the video display rectangle (SourceX, SourceY) (SourceX +
578 // Width, SourceY + Height) to the video display rectangle (DestinationX,
579 // DestinationY) (DestinationX + Width, DestinationY + Height). The
580 // BltBuffer and Delta are not used in this mode.
581 //
582 // A single invocation of CopyMem() handles overlap between source and
583 // destination (that is, within a single line), but for multiple
584 // invocations, we must handle overlaps.
585 //
586 if (SourceY < DestinationY) {
587 Y = Height;
588 while (Y > 0) {
589 --Y;
590 CopyMem (
591 VgpuGop->BackingStore +
592 (DestinationY + Y) * CurrentHorizontal + DestinationX,
593 VgpuGop->BackingStore +
594 (SourceY + Y) * CurrentHorizontal + SourceX,
595 SegmentSize
596 );
597 }
598 } else {
599 for (Y = 0; Y < Height; ++Y) {
600 CopyMem (
601 VgpuGop->BackingStore +
602 (DestinationY + Y) * CurrentHorizontal + DestinationX,
603 VgpuGop->BackingStore +
604 (SourceY + Y) * CurrentHorizontal + SourceX,
605 SegmentSize
606 );
607 }
608 }
609 break;
610
611 default:
612 return EFI_INVALID_PARAMETER;
613 }
614
615 //
616 // For operations that wrote to the display, submit the updated area to the
617 // host -- update the host resource from guest memory.
618 //
619 ResourceOffset = sizeof (UINT32) * (DestinationY * CurrentHorizontal +
620 DestinationX);
621 Status = VirtioGpuTransferToHost2d (
622 VgpuGop->ParentBus, // VgpuDev
623 (UINT32)DestinationX, // X
624 (UINT32)DestinationY, // Y
625 (UINT32)Width, // Width
626 (UINT32)Height, // Height
627 ResourceOffset, // Offset
628 VgpuGop->ResourceId // ResourceId
629 );
630 if (EFI_ERROR (Status)) {
631 return Status;
632 }
633
634 //
635 // Flush the updated resource to the display.
636 //
637 Status = VirtioGpuResourceFlush (
638 VgpuGop->ParentBus, // VgpuDev
639 (UINT32)DestinationX, // X
640 (UINT32)DestinationY, // Y
641 (UINT32)Width, // Width
642 (UINT32)Height, // Height
643 VgpuGop->ResourceId // ResourceId
644 );
645 return Status;
646 }
647
648 //
649 // Template for initializing VGPU_GOP.Gop.
650 //
651 CONST EFI_GRAPHICS_OUTPUT_PROTOCOL mGopTemplate = {
652 GopQueryMode,
653 GopSetMode,
654 GopBlt,
655 NULL // Mode, to be overwritten in the actual protocol instance
656 };
657