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
InstallVbeShim(IN CONST CHAR16 * CardName,IN EFI_PHYSICAL_ADDRESS FrameBufferBase)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