]> git.proxmox.com Git - mirror_edk2.git/blobdiff - OvmfPkg/QemuVideoDxe/VbeShim.c
OvmfPkg: QemuVideoDxe: Int10h stub for Windows 7 & 2008 (stdvga, QXL)
[mirror_edk2.git] / OvmfPkg / QemuVideoDxe / VbeShim.c
diff --git a/OvmfPkg/QemuVideoDxe/VbeShim.c b/OvmfPkg/QemuVideoDxe/VbeShim.c
new file mode 100644 (file)
index 0000000..ae25b64
--- /dev/null
@@ -0,0 +1,275 @@
+/** @file\r
+  Install a fake VGABIOS service handler (real mode Int10h) for the buggy\r
+  Windows 2008 R2 SP1 UEFI guest.\r
+\r
+  The handler is never meant to be directly executed by a VCPU; it's there for\r
+  the internal real mode emulator of Windows 2008 R2 SP1.\r
+\r
+  The code is based on Ralf Brown's Interrupt List:\r
+  <http://www.cs.cmu.edu/~ralf/files.html>\r
+  <http://www.ctyme.com/rbrown.htm>\r
+\r
+  Copyright (C) 2014, Red Hat, Inc.\r
+  Copyright (c) 2013 - 2014, Intel Corporation. All rights reserved.<BR>\r
+\r
+  This program and the accompanying materials are licensed and made available\r
+  under the terms and conditions of the BSD License which accompanies this\r
+  distribution. The full text of the license may be found at\r
+  http://opensource.org/licenses/bsd-license.php\r
+\r
+  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT\r
+  WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
+**/\r
+\r
+#include <IndustryStandard/LegacyVgaBios.h>\r
+#include <Library/DebugLib.h>\r
+#include <Library/PciLib.h>\r
+#include <Library/PrintLib.h>\r
+\r
+#include "Qemu.h"\r
+#include "VbeShim.h"\r
+\r
+#pragma pack (1)\r
+typedef struct {\r
+  UINT16 Offset;\r
+  UINT16 Segment;\r
+} IVT_ENTRY;\r
+#pragma pack ()\r
+\r
+//\r
+// This string is displayed by Windows 2008 R2 SP1 in the Screen Resolution,\r
+// Advanced Settings dialog. It should be short.\r
+//\r
+STATIC CONST CHAR8 mProductRevision[] = "OVMF Int10h (fake)";\r
+\r
+/**\r
+  Install the VBE Info and VBE Mode Info structures, and the VBE service\r
+  handler routine in the C segment. Point the real-mode Int10h interrupt vector\r
+  to the handler. The only advertised mode is 1024x768x32.\r
+\r
+  @param[in] CardName         Name of the video card to be exposed in the\r
+                              Product Name field of the VBE Info structure. The\r
+                              parameter must originate from a\r
+                              QEMU_VIDEO_CARD.Name field.\r
+  @param[in] FrameBufferBase  Guest-physical base address of the video card's\r
+                              frame buffer.\r
+**/\r
+VOID\r
+InstallVbeShim (\r
+  IN CONST CHAR16         *CardName,\r
+  IN EFI_PHYSICAL_ADDRESS FrameBufferBase\r
+  )\r
+{\r
+  EFI_PHYSICAL_ADDRESS Segment0, SegmentC, SegmentF;\r
+  UINTN                Segment0Pages;\r
+  IVT_ENTRY            *Int0x10;\r
+  EFI_STATUS           Status;\r
+  UINTN                Pam1Address;\r
+  UINT8                Pam1;\r
+  UINTN                SegmentCPages;\r
+  VBE_INFO             *VbeInfoFull;\r
+  VBE_INFO_BASE        *VbeInfo;\r
+  UINT8                *Ptr;\r
+  UINTN                Printed;\r
+  VBE_MODE_INFO        *VbeModeInfo;\r
+\r
+  Segment0 = 0x00000;\r
+  SegmentC = 0xC0000;\r
+  SegmentF = 0xF0000;\r
+\r
+  //\r
+  // Attempt to cover the real mode IVT with an allocation. This is a UEFI\r
+  // driver, hence the arch protocols have been installed previously. Among\r
+  // those, the CPU arch protocol has configured the IDT, so we can overwrite\r
+  // the IVT used in real mode.\r
+  //\r
+  // The allocation request may fail, eg. if LegacyBiosDxe has already run.\r
+  //\r
+  Segment0Pages = 1;\r
+  Int0x10       = (IVT_ENTRY *)(UINTN)Segment0 + 0x10;\r
+  Status = gBS->AllocatePages (AllocateAddress, EfiBootServicesCode,\r
+                  Segment0Pages, &Segment0);\r
+\r
+  if (EFI_ERROR (Status)) {\r
+    EFI_PHYSICAL_ADDRESS Handler;\r
+\r
+    //\r
+    // Check if a video BIOS handler has been installed previously -- we\r
+    // shouldn't override a real video BIOS with our shim, nor our own shim if\r
+    // it's already present.\r
+    //\r
+    Handler = (Int0x10->Segment << 4) + Int0x10->Offset;\r
+    if (Handler >= SegmentC && Handler < SegmentF) {\r
+      DEBUG ((EFI_D_VERBOSE, "%a: Video BIOS handler found at %04x:%04x\n",\r
+        __FUNCTION__, Int0x10->Segment, Int0x10->Offset));\r
+      return;\r
+    }\r
+\r
+    //\r
+    // Otherwise we'll overwrite the Int10h vector, even though we may not own\r
+    // the page at zero.\r
+    //\r
+    DEBUG ((EFI_D_VERBOSE, "%a: failed to allocate page at zero: %r\n",\r
+      __FUNCTION__, Status));\r
+  } else {\r
+    //\r
+    // We managed to allocate the page at zero. SVN r14218 guarantees that it\r
+    // is NUL-filled.\r
+    //\r
+    ASSERT (Int0x10->Segment == 0x0000);\r
+    ASSERT (Int0x10->Offset  == 0x0000);\r
+  }\r
+\r
+  //\r
+  // Put the shim in place first.\r
+  //\r
+  Pam1Address = PCI_LIB_ADDRESS (0, 0, 0, 0x5A);\r
+  //\r
+  // low nibble covers 0xC0000 to 0xC3FFF\r
+  // high nibble covers 0xC4000 to 0xC7FFF\r
+  // bit1 in each nibble is Write Enable\r
+  // bit0 in each nibble is Read Enable\r
+  //\r
+  Pam1 = PciRead8 (Pam1Address);\r
+  PciWrite8 (Pam1Address, Pam1 | (BIT1 | BIT0));\r
+\r
+  //\r
+  // We never added memory space durig PEI or DXE for the C segment, so we\r
+  // don't need to (and can't) allocate from there. Also, guest operating\r
+  // systems will see a hole in the UEFI memory map there.\r
+  //\r
+  SegmentCPages = 4;\r
+\r
+  ASSERT (sizeof mVbeShim <= EFI_PAGES_TO_SIZE (SegmentCPages));\r
+  CopyMem ((VOID *)(UINTN)SegmentC, mVbeShim, sizeof mVbeShim);\r
+\r
+  //\r
+  // Fill in the VBE INFO structure.\r
+  //\r
+  VbeInfoFull = (VBE_INFO *)(UINTN)SegmentC;\r
+  VbeInfo     = &VbeInfoFull->Base;\r
+  Ptr         = VbeInfoFull->Buffer;\r
+\r
+  CopyMem (VbeInfo->Signature, "VESA", 4);\r
+  VbeInfo->VesaVersion = 0x0300;\r
+\r
+  VbeInfo->OemNameAddress = (UINT32)(SegmentC << 12 | (UINT16)(UINTN)Ptr);\r
+  CopyMem (Ptr, "QEMU", 5);\r
+  Ptr += 5;\r
+\r
+  VbeInfo->Capabilities = BIT0; // DAC can be switched into 8-bit mode\r
+\r
+  VbeInfo->ModeListAddress = (UINT32)(SegmentC << 12 | (UINT16)(UINTN)Ptr);\r
+  *(UINT16*)Ptr = 0x00f1; // mode number\r
+  Ptr += 2;\r
+  *(UINT16*)Ptr = 0xFFFF; // mode list terminator\r
+  Ptr += 2;\r
+\r
+  VbeInfo->VideoMem64K = (UINT16)((1024 * 768 * 4 + 65535) / 65536);\r
+  VbeInfo->OemSoftwareVersion = 0x0000;\r
+\r
+  VbeInfo->VendorNameAddress = (UINT32)(SegmentC << 12 | (UINT16)(UINTN)Ptr);\r
+  CopyMem (Ptr, "OVMF", 5);\r
+  Ptr += 5;\r
+\r
+  VbeInfo->ProductNameAddress = (UINT32)(SegmentC << 12 | (UINT16)(UINTN)Ptr);\r
+  Printed = AsciiSPrint ((CHAR8 *)Ptr,\r
+              sizeof VbeInfoFull->Buffer - (Ptr - VbeInfoFull->Buffer), "%s",\r
+              CardName);\r
+  Ptr += Printed + 1;\r
+\r
+  VbeInfo->ProductRevAddress = (UINT32)(SegmentC << 12 | (UINT16)(UINTN)Ptr);\r
+  CopyMem (Ptr, mProductRevision, sizeof mProductRevision);\r
+  Ptr += sizeof mProductRevision;\r
+\r
+  ASSERT (sizeof VbeInfoFull->Buffer >= Ptr - VbeInfoFull->Buffer);\r
+  ZeroMem (Ptr, sizeof VbeInfoFull->Buffer - (Ptr - VbeInfoFull->Buffer));\r
+\r
+  //\r
+  // Fil in the VBE MODE INFO structure.\r
+  //\r
+  VbeModeInfo = (VBE_MODE_INFO *)(VbeInfoFull + 1);\r
+\r
+  //\r
+  // bit0: mode supported by present hardware configuration\r
+  // bit1: optional information available (must be =1 for VBE v1.2+)\r
+  // bit3: set if color, clear if monochrome\r
+  // bit4: set if graphics mode, clear if text mode\r
+  // bit5: mode is not VGA-compatible\r
+  // bit7: linear framebuffer mode supported\r
+  //\r
+  VbeModeInfo->ModeAttr = BIT7 | BIT5 | BIT4 | BIT3 | BIT1 | BIT0;\r
+\r
+  //\r
+  // bit0: exists\r
+  // bit1: bit1: readable\r
+  // bit2: writeable\r
+  //\r
+  VbeModeInfo->WindowAAttr              = BIT2 | BIT1 | BIT0;\r
+\r
+  VbeModeInfo->WindowBAttr              = 0x00;\r
+  VbeModeInfo->WindowGranularityKB      = 0x0040;\r
+  VbeModeInfo->WindowSizeKB             = 0x0040;\r
+  VbeModeInfo->WindowAStartSegment      = 0xA000;\r
+  VbeModeInfo->WindowBStartSegment      = 0x0000;\r
+  VbeModeInfo->WindowPositioningAddress = 0x0000;\r
+  VbeModeInfo->BytesPerScanLine         = 1024 * 4;\r
+\r
+  VbeModeInfo->Width                = 1024;\r
+  VbeModeInfo->Height               = 768;\r
+  VbeModeInfo->CharCellWidth        = 8;\r
+  VbeModeInfo->CharCellHeight       = 16;\r
+  VbeModeInfo->NumPlanes            = 1;\r
+  VbeModeInfo->BitsPerPixel         = 32;\r
+  VbeModeInfo->NumBanks             = 1;\r
+  VbeModeInfo->MemoryModel          = 6; // direct color\r
+  VbeModeInfo->BankSizeKB           = 0;\r
+  VbeModeInfo->NumImagePagesLessOne = 0;\r
+  VbeModeInfo->Vbe3                 = 0x01;\r
+\r
+  VbeModeInfo->RedMaskSize      = 8;\r
+  VbeModeInfo->RedMaskPos       = 16;\r
+  VbeModeInfo->GreenMaskSize    = 8;\r
+  VbeModeInfo->GreenMaskPos     = 8;\r
+  VbeModeInfo->BlueMaskSize     = 8;\r
+  VbeModeInfo->BlueMaskPos      = 0;\r
+  VbeModeInfo->ReservedMaskSize = 8;\r
+  VbeModeInfo->ReservedMaskPos  = 24;\r
+\r
+  //\r
+  // bit1: Bytes in reserved field may be used by application\r
+  //\r
+  VbeModeInfo->DirectColorModeInfo = BIT1;\r
+\r
+  VbeModeInfo->LfbAddress       = (UINT32)FrameBufferBase;\r
+  VbeModeInfo->OffScreenAddress = 0;\r
+  VbeModeInfo->OffScreenSizeKB  = 0;\r
+\r
+  VbeModeInfo->BytesPerScanLineLinear = 1024 * 4;\r
+  VbeModeInfo->NumImagesLessOneBanked = 0;\r
+  VbeModeInfo->NumImagesLessOneLinear = 0;\r
+  VbeModeInfo->RedMaskSizeLinear      = 8;\r
+  VbeModeInfo->RedMaskPosLinear       = 16;\r
+  VbeModeInfo->GreenMaskSizeLinear    = 8;\r
+  VbeModeInfo->GreenMaskPosLinear     = 8;\r
+  VbeModeInfo->BlueMaskSizeLinear     = 8;\r
+  VbeModeInfo->BlueMaskPosLinear      = 0;\r
+  VbeModeInfo->ReservedMaskSizeLinear = 8;\r
+  VbeModeInfo->ReservedMaskPosLinear  = 24;\r
+  VbeModeInfo->MaxPixelClockHz        = 0;\r
+\r
+  ZeroMem (VbeModeInfo->Reserved, sizeof VbeModeInfo->Reserved);\r
+\r
+  //\r
+  // Clear Write Enable (bit1), keep Read Enable (bit0) set\r
+  //\r
+  PciWrite8 (Pam1Address, (Pam1 & ~BIT1) | BIT0);\r
+\r
+  //\r
+  // Second, point the Int10h vector at the shim.\r
+  //\r
+  Int0x10->Segment = SegmentC >> 4;\r
+  Int0x10->Offset  = (EFI_PHYSICAL_ADDRESS)(UINTN)(VbeModeInfo + 1) - SegmentC;\r
+\r
+  DEBUG ((EFI_D_INFO, "%a: VBE shim installed\n", __FUNCTION__));\r
+}\r