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 >= 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 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 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