Home | History | Annotate | Download | only in QemuVideoDxe
      1 /** @file
      2   Install a fake VGABIOS service handler (real mode Int10h) for the buggy
      3   Windows 2008 R2 SP1 UEFI guest.
      4 
      5   The handler is never meant to be directly executed by a VCPU; it's there for
      6   the internal real mode emulator of Windows 2008 R2 SP1.
      7 
      8   The code is based on Ralf Brown's Interrupt List:
      9   <http://www.cs.cmu.edu/~ralf/files.html>
     10   <http://www.ctyme.com/rbrown.htm>
     11 
     12   Copyright (C) 2014, Red Hat, Inc.
     13   Copyright (c) 2013 - 2014, Intel Corporation. All rights reserved.<BR>
     14 
     15   This program and the accompanying materials are licensed and made available
     16   under the terms and conditions of the BSD License which accompanies this
     17   distribution. The full text of the license may be found at
     18   http://opensource.org/licenses/bsd-license.php
     19 
     20   THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT
     21   WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
     22 **/
     23 
     24 #include <IndustryStandard/LegacyVgaBios.h>
     25 #include <Library/DebugLib.h>
     26 #include <Library/PciLib.h>
     27 #include <Library/PrintLib.h>
     28 
     29 #include "Qemu.h"
     30 #include "VbeShim.h"
     31 
     32 #pragma pack (1)
     33 typedef struct {
     34   UINT16 Offset;
     35   UINT16 Segment;
     36 } IVT_ENTRY;
     37 #pragma pack ()
     38 
     39 //
     40 // This string is displayed by Windows 2008 R2 SP1 in the Screen Resolution,
     41 // Advanced Settings dialog. It should be short.
     42 //
     43 STATIC CONST CHAR8 mProductRevision[] = "OVMF Int10h (fake)";
     44 
     45 /**
     46   Install the VBE Info and VBE Mode Info structures, and the VBE service
     47   handler routine in the C segment. Point the real-mode Int10h interrupt vector
     48   to the handler. The only advertised mode is 1024x768x32.
     49 
     50   @param[in] CardName         Name of the video card to be exposed in the
     51                               Product Name field of the VBE Info structure. The
     52                               parameter must originate from a
     53                               QEMU_VIDEO_CARD.Name field.
     54   @param[in] FrameBufferBase  Guest-physical base address of the video card's
     55                               frame buffer.
     56 **/
     57 VOID
     58 InstallVbeShim (
     59   IN CONST CHAR16         *CardName,
     60   IN EFI_PHYSICAL_ADDRESS FrameBufferBase
     61   )
     62 {
     63   EFI_PHYSICAL_ADDRESS Segment0, SegmentC, SegmentF;
     64   UINTN                Segment0Pages;
     65   IVT_ENTRY            *Int0x10;
     66   EFI_STATUS           Status;
     67   UINTN                Pam1Address;
     68   UINT8                Pam1;
     69   UINTN                SegmentCPages;
     70   VBE_INFO             *VbeInfoFull;
     71   VBE_INFO_BASE        *VbeInfo;
     72   UINT8                *Ptr;
     73   UINTN                Printed;
     74   VBE_MODE_INFO        *VbeModeInfo;
     75 
     76   Segment0 = 0x00000;
     77   SegmentC = 0xC0000;
     78   SegmentF = 0xF0000;
     79 
     80   //
     81   // Attempt to cover the real mode IVT with an allocation. This is a UEFI
     82   // driver, hence the arch protocols have been installed previously. Among
     83   // those, the CPU arch protocol has configured the IDT, so we can overwrite
     84   // the IVT used in real mode.
     85   //
     86   // The allocation request may fail, eg. if LegacyBiosDxe has already run.
     87   //
     88   Segment0Pages = 1;
     89   Int0x10       = (IVT_ENTRY *)(UINTN)Segment0 + 0x10;
     90   Status = gBS->AllocatePages (AllocateAddress, EfiBootServicesCode,
     91                   Segment0Pages, &Segment0);
     92 
     93   if (EFI_ERROR (Status)) {
     94     EFI_PHYSICAL_ADDRESS Handler;
     95 
     96     //
     97     // Check if a video BIOS handler has been installed previously -- we
     98     // shouldn't override a real video BIOS with our shim, nor our own shim if
     99     // it's already present.
    100     //
    101     Handler = (Int0x10->Segment << 4) + Int0x10->Offset;
    102     if (Handler >= SegmentC && Handler < SegmentF) {
    103       DEBUG ((EFI_D_INFO, "%a: Video BIOS handler found at %04x:%04x\n",
    104         __FUNCTION__, Int0x10->Segment, Int0x10->Offset));
    105       return;
    106     }
    107 
    108     //
    109     // Otherwise we'll overwrite the Int10h vector, even though we may not own
    110     // the page at zero.
    111     //
    112     DEBUG ((EFI_D_INFO, "%a: failed to allocate page at zero: %r\n",
    113       __FUNCTION__, Status));
    114   } else {
    115     //
    116     // We managed to allocate the page at zero. SVN r14218 guarantees that it
    117     // is NUL-filled.
    118     //
    119     ASSERT (Int0x10->Segment == 0x0000);
    120     ASSERT (Int0x10->Offset  == 0x0000);
    121   }
    122 
    123   //
    124   // Put the shim in place first.
    125   //
    126   Pam1Address = PCI_LIB_ADDRESS (0, 0, 0, 0x5A);
    127   //
    128   // low nibble covers 0xC0000 to 0xC3FFF
    129   // high nibble covers 0xC4000 to 0xC7FFF
    130   // bit1 in each nibble is Write Enable
    131   // bit0 in each nibble is Read Enable
    132   //
    133   Pam1 = PciRead8 (Pam1Address);
    134   PciWrite8 (Pam1Address, Pam1 | (BIT1 | BIT0));
    135 
    136   //
    137   // We never added memory space during PEI or DXE for the C segment, so we
    138   // don't need to (and can't) allocate from there. Also, guest operating
    139   // systems will see a hole in the UEFI memory map there.
    140   //
    141   SegmentCPages = 4;
    142 
    143   ASSERT (sizeof mVbeShim <= EFI_PAGES_TO_SIZE (SegmentCPages));
    144   CopyMem ((VOID *)(UINTN)SegmentC, mVbeShim, sizeof mVbeShim);
    145 
    146   //
    147   // Fill in the VBE INFO structure.
    148   //
    149   VbeInfoFull = (VBE_INFO *)(UINTN)SegmentC;
    150   VbeInfo     = &VbeInfoFull->Base;
    151   Ptr         = VbeInfoFull->Buffer;
    152 
    153   CopyMem (VbeInfo->Signature, "VESA", 4);
    154   VbeInfo->VesaVersion = 0x0300;
    155 
    156   VbeInfo->OemNameAddress = (UINT32)SegmentC << 12 | (UINT16)(UINTN)Ptr;
    157   CopyMem (Ptr, "QEMU", 5);
    158   Ptr += 5;
    159 
    160   VbeInfo->Capabilities = BIT0; // DAC can be switched into 8-bit mode
    161 
    162   VbeInfo->ModeListAddress = (UINT32)SegmentC << 12 | (UINT16)(UINTN)Ptr;
    163   *(UINT16*)Ptr = 0x00f1; // mode number
    164   Ptr += 2;
    165   *(UINT16*)Ptr = 0xFFFF; // mode list terminator
    166   Ptr += 2;
    167 
    168   VbeInfo->VideoMem64K = (UINT16)((1024 * 768 * 4 + 65535) / 65536);
    169   VbeInfo->OemSoftwareVersion = 0x0000;
    170 
    171   VbeInfo->VendorNameAddress = (UINT32)SegmentC << 12 | (UINT16)(UINTN)Ptr;
    172   CopyMem (Ptr, "OVMF", 5);
    173   Ptr += 5;
    174 
    175   VbeInfo->ProductNameAddress = (UINT32)SegmentC << 12 | (UINT16)(UINTN)Ptr;
    176   Printed = AsciiSPrint ((CHAR8 *)Ptr,
    177               sizeof VbeInfoFull->Buffer - (Ptr - VbeInfoFull->Buffer), "%s",
    178               CardName);
    179   Ptr += Printed + 1;
    180 
    181   VbeInfo->ProductRevAddress = (UINT32)SegmentC << 12 | (UINT16)(UINTN)Ptr;
    182   CopyMem (Ptr, mProductRevision, sizeof mProductRevision);
    183   Ptr += sizeof mProductRevision;
    184 
    185   ASSERT (sizeof VbeInfoFull->Buffer >= Ptr - VbeInfoFull->Buffer);
    186   ZeroMem (Ptr, sizeof VbeInfoFull->Buffer - (Ptr - VbeInfoFull->Buffer));
    187 
    188   //
    189   // Fil in the VBE MODE INFO structure.
    190   //
    191   VbeModeInfo = (VBE_MODE_INFO *)(VbeInfoFull + 1);
    192 
    193   //
    194   // bit0: mode supported by present hardware configuration
    195   // bit1: optional information available (must be =1 for VBE v1.2+)
    196   // bit3: set if color, clear if monochrome
    197   // bit4: set if graphics mode, clear if text mode
    198   // bit5: mode is not VGA-compatible
    199   // bit7: linear framebuffer mode supported
    200   //
    201   VbeModeInfo->ModeAttr = BIT7 | BIT5 | BIT4 | BIT3 | BIT1 | BIT0;
    202 
    203   //
    204   // bit0: exists
    205   // bit1: bit1: readable
    206   // bit2: writeable
    207   //
    208   VbeModeInfo->WindowAAttr              = BIT2 | BIT1 | BIT0;
    209 
    210   VbeModeInfo->WindowBAttr              = 0x00;
    211   VbeModeInfo->WindowGranularityKB      = 0x0040;
    212   VbeModeInfo->WindowSizeKB             = 0x0040;
    213   VbeModeInfo->WindowAStartSegment      = 0xA000;
    214   VbeModeInfo->WindowBStartSegment      = 0x0000;
    215   VbeModeInfo->WindowPositioningAddress = 0x0000;
    216   VbeModeInfo->BytesPerScanLine         = 1024 * 4;
    217 
    218   VbeModeInfo->Width                = 1024;
    219   VbeModeInfo->Height               = 768;
    220   VbeModeInfo->CharCellWidth        = 8;
    221   VbeModeInfo->CharCellHeight       = 16;
    222   VbeModeInfo->NumPlanes            = 1;
    223   VbeModeInfo->BitsPerPixel         = 32;
    224   VbeModeInfo->NumBanks             = 1;
    225   VbeModeInfo->MemoryModel          = 6; // direct color
    226   VbeModeInfo->BankSizeKB           = 0;
    227   VbeModeInfo->NumImagePagesLessOne = 0;
    228   VbeModeInfo->Vbe3                 = 0x01;
    229 
    230   VbeModeInfo->RedMaskSize      = 8;
    231   VbeModeInfo->RedMaskPos       = 16;
    232   VbeModeInfo->GreenMaskSize    = 8;
    233   VbeModeInfo->GreenMaskPos     = 8;
    234   VbeModeInfo->BlueMaskSize     = 8;
    235   VbeModeInfo->BlueMaskPos      = 0;
    236   VbeModeInfo->ReservedMaskSize = 8;
    237   VbeModeInfo->ReservedMaskPos  = 24;
    238 
    239   //
    240   // bit1: Bytes in reserved field may be used by application
    241   //
    242   VbeModeInfo->DirectColorModeInfo = BIT1;
    243 
    244   VbeModeInfo->LfbAddress       = (UINT32)FrameBufferBase;
    245   VbeModeInfo->OffScreenAddress = 0;
    246   VbeModeInfo->OffScreenSizeKB  = 0;
    247 
    248   VbeModeInfo->BytesPerScanLineLinear = 1024 * 4;
    249   VbeModeInfo->NumImagesLessOneBanked = 0;
    250   VbeModeInfo->NumImagesLessOneLinear = 0;
    251   VbeModeInfo->RedMaskSizeLinear      = 8;
    252   VbeModeInfo->RedMaskPosLinear       = 16;
    253   VbeModeInfo->GreenMaskSizeLinear    = 8;
    254   VbeModeInfo->GreenMaskPosLinear     = 8;
    255   VbeModeInfo->BlueMaskSizeLinear     = 8;
    256   VbeModeInfo->BlueMaskPosLinear      = 0;
    257   VbeModeInfo->ReservedMaskSizeLinear = 8;
    258   VbeModeInfo->ReservedMaskPosLinear  = 24;
    259   VbeModeInfo->MaxPixelClockHz        = 0;
    260 
    261   ZeroMem (VbeModeInfo->Reserved, sizeof VbeModeInfo->Reserved);
    262 
    263   //
    264   // Clear Write Enable (bit1), keep Read Enable (bit0) set
    265   //
    266   PciWrite8 (Pam1Address, (Pam1 & ~BIT1) | BIT0);
    267 
    268   //
    269   // Second, point the Int10h vector at the shim.
    270   //
    271   Int0x10->Segment = (UINT16) ((UINT32)SegmentC >> 4);
    272   Int0x10->Offset  = (UINT16) ((UINTN) (VbeModeInfo + 1) - SegmentC);
    273 
    274   DEBUG ((EFI_D_INFO, "%a: VBE shim installed\n", __FUNCTION__));
    275 }
    276