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   SPDX-License-Identifier: BSD-2-Clause-Patent
16 **/
17 
18 #include <IndustryStandard/LegacyVgaBios.h>
19 #include <Library/DebugLib.h>
20 #include <Library/PciLib.h>
21 #include <Library/PrintLib.h>
22 #include <OvmfPlatforms.h>
23 
24 #include "Qemu.h"
25 #include "VbeShim.h"
26 
27 #pragma pack (1)
28 typedef struct {
29   UINT16 Offset;
30   UINT16 Segment;
31 } IVT_ENTRY;
32 #pragma pack ()
33 
34 //
35 // This string is displayed by Windows 2008 R2 SP1 in the Screen Resolution,
36 // Advanced Settings dialog. It should be short.
37 //
38 STATIC CONST CHAR8 mProductRevision[] = "OVMF Int10h (fake)";
39 
40 /**
41   Install the VBE Info and VBE Mode Info structures, and the VBE service
42   handler routine in the C segment. Point the real-mode Int10h interrupt vector
43   to the handler. The only advertised mode is 1024x768x32.
44 
45   @param[in] CardName         Name of the video card to be exposed in the
46                               Product Name field of the VBE Info structure. The
47                               parameter must originate from a
48                               QEMU_VIDEO_CARD.Name field.
49   @param[in] FrameBufferBase  Guest-physical base address of the video card's
50                               frame buffer.
51 **/
52 VOID
InstallVbeShim(IN CONST CHAR16 * CardName,IN EFI_PHYSICAL_ADDRESS FrameBufferBase)53 InstallVbeShim (
54   IN CONST CHAR16         *CardName,
55   IN EFI_PHYSICAL_ADDRESS FrameBufferBase
56   )
57 {
58   EFI_PHYSICAL_ADDRESS Segment0, SegmentC, SegmentF;
59   UINTN                Segment0Pages;
60   IVT_ENTRY            *Int0x10;
61   EFI_STATUS           Segment0AllocationStatus;
62   UINT16               HostBridgeDevId;
63   UINTN                Pam1Address;
64   UINT8                Pam1;
65   UINTN                SegmentCPages;
66   VBE_INFO             *VbeInfoFull;
67   VBE_INFO_BASE        *VbeInfo;
68   UINT8                *Ptr;
69   UINTN                Printed;
70   VBE_MODE_INFO        *VbeModeInfo;
71 
72   if ((PcdGet8 (PcdNullPointerDetectionPropertyMask) & (BIT0|BIT7)) == BIT0) {
73     DEBUG ((
74       DEBUG_WARN,
75       "%a: page 0 protected, not installing VBE shim\n",
76       __FUNCTION__
77       ));
78     DEBUG ((
79       DEBUG_WARN,
80       "%a: page 0 protection prevents Windows 7 from booting anyway\n",
81       __FUNCTION__
82       ));
83     return;
84   }
85 
86   Segment0 = 0x00000;
87   SegmentC = 0xC0000;
88   SegmentF = 0xF0000;
89 
90   //
91   // Attempt to cover the real mode IVT with an allocation. This is a UEFI
92   // driver, hence the arch protocols have been installed previously. Among
93   // those, the CPU arch protocol has configured the IDT, so we can overwrite
94   // the IVT used in real mode.
95   //
96   // The allocation request may fail, eg. if LegacyBiosDxe has already run.
97   //
98   Segment0Pages = 1;
99   Int0x10       = (IVT_ENTRY *)(UINTN)(Segment0 + 0x10 * sizeof (IVT_ENTRY));
100   Segment0AllocationStatus = gBS->AllocatePages (
101                                     AllocateAddress,
102                                     EfiBootServicesCode,
103                                     Segment0Pages,
104                                     &Segment0
105                                     );
106 
107   if (EFI_ERROR (Segment0AllocationStatus)) {
108     EFI_PHYSICAL_ADDRESS Handler;
109 
110     //
111     // Check if a video BIOS handler has been installed previously -- we
112     // shouldn't override a real video BIOS with our shim, nor our own shim if
113     // it's already present.
114     //
115     Handler = (Int0x10->Segment << 4) + Int0x10->Offset;
116     if (Handler >= SegmentC && Handler < SegmentF) {
117       DEBUG ((DEBUG_INFO, "%a: Video BIOS handler found at %04x:%04x\n",
118         __FUNCTION__, Int0x10->Segment, Int0x10->Offset));
119       return;
120     }
121 
122     //
123     // Otherwise we'll overwrite the Int10h vector, even though we may not own
124     // the page at zero.
125     //
126     DEBUG ((
127       DEBUG_INFO,
128       "%a: failed to allocate page at zero: %r\n",
129       __FUNCTION__,
130       Segment0AllocationStatus
131       ));
132   } else {
133     //
134     // We managed to allocate the page at zero. SVN r14218 guarantees that it
135     // is NUL-filled.
136     //
137     ASSERT (Int0x10->Segment == 0x0000);
138     ASSERT (Int0x10->Offset  == 0x0000);
139   }
140 
141   //
142   // Put the shim in place first.
143   //
144   // Start by determining the address of the PAM1 register.
145   //
146   HostBridgeDevId = PcdGet16 (PcdOvmfHostBridgePciDevId);
147   switch (HostBridgeDevId) {
148   case INTEL_82441_DEVICE_ID:
149     Pam1Address = PMC_REGISTER_PIIX4 (PIIX4_PAM1);
150     break;
151   case INTEL_Q35_MCH_DEVICE_ID:
152     Pam1Address = DRAMC_REGISTER_Q35 (MCH_PAM1);
153     break;
154   default:
155     DEBUG ((
156       DEBUG_ERROR,
157       "%a: unknown host bridge device ID: 0x%04x\n",
158       __FUNCTION__,
159       HostBridgeDevId
160       ));
161     ASSERT (FALSE);
162 
163     if (!EFI_ERROR (Segment0AllocationStatus)) {
164       gBS->FreePages (Segment0, Segment0Pages);
165     }
166     return;
167   }
168   //
169   // low nibble covers 0xC0000 to 0xC3FFF
170   // high nibble covers 0xC4000 to 0xC7FFF
171   // bit1 in each nibble is Write Enable
172   // bit0 in each nibble is Read Enable
173   //
174   Pam1 = PciRead8 (Pam1Address);
175   PciWrite8 (Pam1Address, Pam1 | (BIT1 | BIT0));
176 
177   //
178   // We never added memory space during PEI or DXE for the C segment, so we
179   // don't need to (and can't) allocate from there. Also, guest operating
180   // systems will see a hole in the UEFI memory map there.
181   //
182   SegmentCPages = 4;
183 
184   ASSERT (sizeof mVbeShim <= EFI_PAGES_TO_SIZE (SegmentCPages));
185   CopyMem ((VOID *)(UINTN)SegmentC, mVbeShim, sizeof mVbeShim);
186 
187   //
188   // Fill in the VBE INFO structure.
189   //
190   VbeInfoFull = (VBE_INFO *)(UINTN)SegmentC;
191   VbeInfo     = &VbeInfoFull->Base;
192   Ptr         = VbeInfoFull->Buffer;
193 
194   CopyMem (VbeInfo->Signature, "VESA", 4);
195   VbeInfo->VesaVersion = 0x0300;
196 
197   VbeInfo->OemNameAddress = (UINT32)SegmentC << 12 | (UINT16)(UINTN)Ptr;
198   CopyMem (Ptr, "QEMU", 5);
199   Ptr += 5;
200 
201   VbeInfo->Capabilities = BIT0; // DAC can be switched into 8-bit mode
202 
203   VbeInfo->ModeListAddress = (UINT32)SegmentC << 12 | (UINT16)(UINTN)Ptr;
204   *(UINT16*)Ptr = 0x00f1; // mode number
205   Ptr += 2;
206   *(UINT16*)Ptr = 0xFFFF; // mode list terminator
207   Ptr += 2;
208 
209   VbeInfo->VideoMem64K = (UINT16)((1024 * 768 * 4 + 65535) / 65536);
210   VbeInfo->OemSoftwareVersion = 0x0000;
211 
212   VbeInfo->VendorNameAddress = (UINT32)SegmentC << 12 | (UINT16)(UINTN)Ptr;
213   CopyMem (Ptr, "OVMF", 5);
214   Ptr += 5;
215 
216   VbeInfo->ProductNameAddress = (UINT32)SegmentC << 12 | (UINT16)(UINTN)Ptr;
217   Printed = AsciiSPrint ((CHAR8 *)Ptr,
218               sizeof VbeInfoFull->Buffer - (Ptr - VbeInfoFull->Buffer), "%s",
219               CardName);
220   Ptr += Printed + 1;
221 
222   VbeInfo->ProductRevAddress = (UINT32)SegmentC << 12 | (UINT16)(UINTN)Ptr;
223   CopyMem (Ptr, mProductRevision, sizeof mProductRevision);
224   Ptr += sizeof mProductRevision;
225 
226   ASSERT (sizeof VbeInfoFull->Buffer >= Ptr - VbeInfoFull->Buffer);
227   ZeroMem (Ptr, sizeof VbeInfoFull->Buffer - (Ptr - VbeInfoFull->Buffer));
228 
229   //
230   // Fil in the VBE MODE INFO structure.
231   //
232   VbeModeInfo = (VBE_MODE_INFO *)(VbeInfoFull + 1);
233 
234   //
235   // bit0: mode supported by present hardware configuration
236   // bit1: optional information available (must be =1 for VBE v1.2+)
237   // bit3: set if color, clear if monochrome
238   // bit4: set if graphics mode, clear if text mode
239   // bit5: mode is not VGA-compatible
240   // bit7: linear framebuffer mode supported
241   //
242   VbeModeInfo->ModeAttr = BIT7 | BIT5 | BIT4 | BIT3 | BIT1 | BIT0;
243 
244   //
245   // bit0: exists
246   // bit1: bit1: readable
247   // bit2: writeable
248   //
249   VbeModeInfo->WindowAAttr              = BIT2 | BIT1 | BIT0;
250 
251   VbeModeInfo->WindowBAttr              = 0x00;
252   VbeModeInfo->WindowGranularityKB      = 0x0040;
253   VbeModeInfo->WindowSizeKB             = 0x0040;
254   VbeModeInfo->WindowAStartSegment      = 0xA000;
255   VbeModeInfo->WindowBStartSegment      = 0x0000;
256   VbeModeInfo->WindowPositioningAddress = 0x0000;
257   VbeModeInfo->BytesPerScanLine         = 1024 * 4;
258 
259   VbeModeInfo->Width                = 1024;
260   VbeModeInfo->Height               = 768;
261   VbeModeInfo->CharCellWidth        = 8;
262   VbeModeInfo->CharCellHeight       = 16;
263   VbeModeInfo->NumPlanes            = 1;
264   VbeModeInfo->BitsPerPixel         = 32;
265   VbeModeInfo->NumBanks             = 1;
266   VbeModeInfo->MemoryModel          = 6; // direct color
267   VbeModeInfo->BankSizeKB           = 0;
268   VbeModeInfo->NumImagePagesLessOne = 0;
269   VbeModeInfo->Vbe3                 = 0x01;
270 
271   VbeModeInfo->RedMaskSize      = 8;
272   VbeModeInfo->RedMaskPos       = 16;
273   VbeModeInfo->GreenMaskSize    = 8;
274   VbeModeInfo->GreenMaskPos     = 8;
275   VbeModeInfo->BlueMaskSize     = 8;
276   VbeModeInfo->BlueMaskPos      = 0;
277   VbeModeInfo->ReservedMaskSize = 8;
278   VbeModeInfo->ReservedMaskPos  = 24;
279 
280   //
281   // bit1: Bytes in reserved field may be used by application
282   //
283   VbeModeInfo->DirectColorModeInfo = BIT1;
284 
285   VbeModeInfo->LfbAddress       = (UINT32)FrameBufferBase;
286   VbeModeInfo->OffScreenAddress = 0;
287   VbeModeInfo->OffScreenSizeKB  = 0;
288 
289   VbeModeInfo->BytesPerScanLineLinear = 1024 * 4;
290   VbeModeInfo->NumImagesLessOneBanked = 0;
291   VbeModeInfo->NumImagesLessOneLinear = 0;
292   VbeModeInfo->RedMaskSizeLinear      = 8;
293   VbeModeInfo->RedMaskPosLinear       = 16;
294   VbeModeInfo->GreenMaskSizeLinear    = 8;
295   VbeModeInfo->GreenMaskPosLinear     = 8;
296   VbeModeInfo->BlueMaskSizeLinear     = 8;
297   VbeModeInfo->BlueMaskPosLinear      = 0;
298   VbeModeInfo->ReservedMaskSizeLinear = 8;
299   VbeModeInfo->ReservedMaskPosLinear  = 24;
300   VbeModeInfo->MaxPixelClockHz        = 0;
301 
302   ZeroMem (VbeModeInfo->Reserved, sizeof VbeModeInfo->Reserved);
303 
304   //
305   // Clear Write Enable (bit1), keep Read Enable (bit0) set
306   //
307   PciWrite8 (Pam1Address, (Pam1 & ~BIT1) | BIT0);
308 
309   //
310   // Second, point the Int10h vector at the shim.
311   //
312   Int0x10->Segment = (UINT16) ((UINT32)SegmentC >> 4);
313   Int0x10->Offset  = (UINT16) ((UINTN) (VbeModeInfo + 1) - SegmentC);
314 
315   DEBUG ((DEBUG_INFO, "%a: VBE shim installed\n", __FUNCTION__));
316 }
317