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)
33typedef 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//
43STATIC 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**/
57VOID
58InstallVbeShim (
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_VERBOSE, "%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_VERBOSE, "%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 durig 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