bc90e067266de5e867764a93ed4f6ab9175b6827
[mirror_edk2.git] / OvmfPkg / QemuVideoDxe / VbeShim.c
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 Segment0AllocationStatus;
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 Segment0AllocationStatus = gBS->AllocatePages (
91 AllocateAddress,
92 EfiBootServicesCode,
93 Segment0Pages,
94 &Segment0
95 );
96
97 if (EFI_ERROR (Segment0AllocationStatus)) {
98 EFI_PHYSICAL_ADDRESS Handler;
99
100 //
101 // Check if a video BIOS handler has been installed previously -- we
102 // shouldn't override a real video BIOS with our shim, nor our own shim if
103 // it's already present.
104 //
105 Handler = (Int0x10->Segment << 4) + Int0x10->Offset;
106 if (Handler >= SegmentC && Handler < SegmentF) {
107 DEBUG ((EFI_D_INFO, "%a: Video BIOS handler found at %04x:%04x\n",
108 __FUNCTION__, Int0x10->Segment, Int0x10->Offset));
109 return;
110 }
111
112 //
113 // Otherwise we'll overwrite the Int10h vector, even though we may not own
114 // the page at zero.
115 //
116 DEBUG ((
117 DEBUG_INFO,
118 "%a: failed to allocate page at zero: %r\n",
119 __FUNCTION__,
120 Segment0AllocationStatus
121 ));
122 } else {
123 //
124 // We managed to allocate the page at zero. SVN r14218 guarantees that it
125 // is NUL-filled.
126 //
127 ASSERT (Int0x10->Segment == 0x0000);
128 ASSERT (Int0x10->Offset == 0x0000);
129 }
130
131 //
132 // Put the shim in place first.
133 //
134 Pam1Address = PCI_LIB_ADDRESS (0, 0, 0, 0x5A);
135 //
136 // low nibble covers 0xC0000 to 0xC3FFF
137 // high nibble covers 0xC4000 to 0xC7FFF
138 // bit1 in each nibble is Write Enable
139 // bit0 in each nibble is Read Enable
140 //
141 Pam1 = PciRead8 (Pam1Address);
142 PciWrite8 (Pam1Address, Pam1 | (BIT1 | BIT0));
143
144 //
145 // We never added memory space during PEI or DXE for the C segment, so we
146 // don't need to (and can't) allocate from there. Also, guest operating
147 // systems will see a hole in the UEFI memory map there.
148 //
149 SegmentCPages = 4;
150
151 ASSERT (sizeof mVbeShim <= EFI_PAGES_TO_SIZE (SegmentCPages));
152 CopyMem ((VOID *)(UINTN)SegmentC, mVbeShim, sizeof mVbeShim);
153
154 //
155 // Fill in the VBE INFO structure.
156 //
157 VbeInfoFull = (VBE_INFO *)(UINTN)SegmentC;
158 VbeInfo = &VbeInfoFull->Base;
159 Ptr = VbeInfoFull->Buffer;
160
161 CopyMem (VbeInfo->Signature, "VESA", 4);
162 VbeInfo->VesaVersion = 0x0300;
163
164 VbeInfo->OemNameAddress = (UINT32)SegmentC << 12 | (UINT16)(UINTN)Ptr;
165 CopyMem (Ptr, "QEMU", 5);
166 Ptr += 5;
167
168 VbeInfo->Capabilities = BIT0; // DAC can be switched into 8-bit mode
169
170 VbeInfo->ModeListAddress = (UINT32)SegmentC << 12 | (UINT16)(UINTN)Ptr;
171 *(UINT16*)Ptr = 0x00f1; // mode number
172 Ptr += 2;
173 *(UINT16*)Ptr = 0xFFFF; // mode list terminator
174 Ptr += 2;
175
176 VbeInfo->VideoMem64K = (UINT16)((1024 * 768 * 4 + 65535) / 65536);
177 VbeInfo->OemSoftwareVersion = 0x0000;
178
179 VbeInfo->VendorNameAddress = (UINT32)SegmentC << 12 | (UINT16)(UINTN)Ptr;
180 CopyMem (Ptr, "OVMF", 5);
181 Ptr += 5;
182
183 VbeInfo->ProductNameAddress = (UINT32)SegmentC << 12 | (UINT16)(UINTN)Ptr;
184 Printed = AsciiSPrint ((CHAR8 *)Ptr,
185 sizeof VbeInfoFull->Buffer - (Ptr - VbeInfoFull->Buffer), "%s",
186 CardName);
187 Ptr += Printed + 1;
188
189 VbeInfo->ProductRevAddress = (UINT32)SegmentC << 12 | (UINT16)(UINTN)Ptr;
190 CopyMem (Ptr, mProductRevision, sizeof mProductRevision);
191 Ptr += sizeof mProductRevision;
192
193 ASSERT (sizeof VbeInfoFull->Buffer >= Ptr - VbeInfoFull->Buffer);
194 ZeroMem (Ptr, sizeof VbeInfoFull->Buffer - (Ptr - VbeInfoFull->Buffer));
195
196 //
197 // Fil in the VBE MODE INFO structure.
198 //
199 VbeModeInfo = (VBE_MODE_INFO *)(VbeInfoFull + 1);
200
201 //
202 // bit0: mode supported by present hardware configuration
203 // bit1: optional information available (must be =1 for VBE v1.2+)
204 // bit3: set if color, clear if monochrome
205 // bit4: set if graphics mode, clear if text mode
206 // bit5: mode is not VGA-compatible
207 // bit7: linear framebuffer mode supported
208 //
209 VbeModeInfo->ModeAttr = BIT7 | BIT5 | BIT4 | BIT3 | BIT1 | BIT0;
210
211 //
212 // bit0: exists
213 // bit1: bit1: readable
214 // bit2: writeable
215 //
216 VbeModeInfo->WindowAAttr = BIT2 | BIT1 | BIT0;
217
218 VbeModeInfo->WindowBAttr = 0x00;
219 VbeModeInfo->WindowGranularityKB = 0x0040;
220 VbeModeInfo->WindowSizeKB = 0x0040;
221 VbeModeInfo->WindowAStartSegment = 0xA000;
222 VbeModeInfo->WindowBStartSegment = 0x0000;
223 VbeModeInfo->WindowPositioningAddress = 0x0000;
224 VbeModeInfo->BytesPerScanLine = 1024 * 4;
225
226 VbeModeInfo->Width = 1024;
227 VbeModeInfo->Height = 768;
228 VbeModeInfo->CharCellWidth = 8;
229 VbeModeInfo->CharCellHeight = 16;
230 VbeModeInfo->NumPlanes = 1;
231 VbeModeInfo->BitsPerPixel = 32;
232 VbeModeInfo->NumBanks = 1;
233 VbeModeInfo->MemoryModel = 6; // direct color
234 VbeModeInfo->BankSizeKB = 0;
235 VbeModeInfo->NumImagePagesLessOne = 0;
236 VbeModeInfo->Vbe3 = 0x01;
237
238 VbeModeInfo->RedMaskSize = 8;
239 VbeModeInfo->RedMaskPos = 16;
240 VbeModeInfo->GreenMaskSize = 8;
241 VbeModeInfo->GreenMaskPos = 8;
242 VbeModeInfo->BlueMaskSize = 8;
243 VbeModeInfo->BlueMaskPos = 0;
244 VbeModeInfo->ReservedMaskSize = 8;
245 VbeModeInfo->ReservedMaskPos = 24;
246
247 //
248 // bit1: Bytes in reserved field may be used by application
249 //
250 VbeModeInfo->DirectColorModeInfo = BIT1;
251
252 VbeModeInfo->LfbAddress = (UINT32)FrameBufferBase;
253 VbeModeInfo->OffScreenAddress = 0;
254 VbeModeInfo->OffScreenSizeKB = 0;
255
256 VbeModeInfo->BytesPerScanLineLinear = 1024 * 4;
257 VbeModeInfo->NumImagesLessOneBanked = 0;
258 VbeModeInfo->NumImagesLessOneLinear = 0;
259 VbeModeInfo->RedMaskSizeLinear = 8;
260 VbeModeInfo->RedMaskPosLinear = 16;
261 VbeModeInfo->GreenMaskSizeLinear = 8;
262 VbeModeInfo->GreenMaskPosLinear = 8;
263 VbeModeInfo->BlueMaskSizeLinear = 8;
264 VbeModeInfo->BlueMaskPosLinear = 0;
265 VbeModeInfo->ReservedMaskSizeLinear = 8;
266 VbeModeInfo->ReservedMaskPosLinear = 24;
267 VbeModeInfo->MaxPixelClockHz = 0;
268
269 ZeroMem (VbeModeInfo->Reserved, sizeof VbeModeInfo->Reserved);
270
271 //
272 // Clear Write Enable (bit1), keep Read Enable (bit0) set
273 //
274 PciWrite8 (Pam1Address, (Pam1 & ~BIT1) | BIT0);
275
276 //
277 // Second, point the Int10h vector at the shim.
278 //
279 Int0x10->Segment = (UINT16) ((UINT32)SegmentC >> 4);
280 Int0x10->Offset = (UINT16) ((UINTN) (VbeModeInfo + 1) - SegmentC);
281
282 DEBUG ((EFI_D_INFO, "%a: VBE shim installed\n", __FUNCTION__));
283 }