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