2 Install a fake VGABIOS service handler (real mode Int10h) for the buggy
3 Windows 2008 R2 SP1 UEFI guest.
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.
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>
12 Copyright (C) 2020, Rebecca Cran <rebecca@bsdio.com>
13 Copyright (C) 2015, Nahanni Systems, Inc.
14 Copyright (C) 2014, Red Hat, Inc.
15 Copyright (c) 2013 - 2014, Intel Corporation. All rights reserved.<BR>
17 SPDX-License-Identifier: BSD-2-Clause-Patent
21 #include <IndustryStandard/LegacyVgaBios.h>
22 #include <Library/DebugLib.h>
23 #include <Library/PciLib.h>
24 #include <Library/PrintLib.h>
37 // This string is displayed by Windows 2008 R2 SP1 in the Screen Resolution,
38 // Advanced Settings dialog. It should be short.
40 STATIC CONST CHAR8 mProductRevision
[] = "2.0";
42 #define NUM_VBE_MODES 3
43 STATIC CONST UINT16 vbeModeIds
[] = {
49 // Modes can be toggled with bit-0
50 #define VBE_MODE_ENABLED 0x00BB
51 #define VBE_MODE_DISABLED 0x00BA
53 STATIC VBE2_MODE_INFO vbeModes
[] = {
56 // ModeAttr - BytesPerScanLine
57 VBE_MODE_DISABLED
, 0x07, 0x00, 0x40, 0x40, 0xA000, 0x00, 0x0000, 640*4,
58 // Width, Height..., Vbe3
59 640, 480, 16, 8, 1, 32, 1, 0x06, 0, 0, 1,
61 0x08, 0x10, 0x08, 0x08, 0x08, 0x00, 0x08, 0x18, 0x00,
63 0xdeadbeef, 0x0000, 0x0000
67 // ModeAttr - BytesPerScanLine
68 VBE_MODE_DISABLED
, 0x07, 0x00, 0x40, 0x40, 0xA000, 0x00, 0x0000, 800*4,
69 // Width, Height..., Vbe3
70 800, 600, 16, 8, 1, 32, 1, 0x06, 0, 0, 1,
72 0x08, 0x10, 0x08, 0x08, 0x08, 0x00, 0x08, 0x18, 0x00,
74 0xdeadbeef, 0x0000, 0x0000
76 { // 0x141 1024x768x32
78 // ModeAttr - BytesPerScanLine
79 VBE_MODE_ENABLED
, 0x07, 0x00, 0x40, 0x40, 0xA000, 0x00, 0x0000, 1024*4,
80 // Width, Height..., Vbe3
81 1024, 768, 16, 8, 1, 32, 1, 0x06, 0, 0, 1,
83 0x08, 0x10, 0x08, 0x08, 0x08, 0x00, 0x08, 0x18, 0x00,
85 0xdeadbeef, 0x0000, 0x0000
90 Install the VBE Info and VBE Mode Info structures, and the VBE service
91 handler routine in the C segment. Point the real-mode Int10h interrupt vector
92 to the handler. The only advertised mode is 1024x768x32.
94 @param[in] CardName Name of the video card to be exposed in the
95 Product Name field of the VBE Info structure.
96 @param[in] FrameBufferBase Guest-physical base address of the video card's
101 IN CONST CHAR16
*CardName
,
102 IN EFI_PHYSICAL_ADDRESS FrameBufferBase
105 EFI_PHYSICAL_ADDRESS Segment0
, SegmentC
, SegmentF
;
112 VBE_INFO
*VbeInfoFull
;
113 VBE_INFO_BASE
*VbeInfo
;
116 VBE_MODE_INFO
*VbeModeInfo
;
124 // Attempt to cover the real mode IVT with an allocation. This is a UEFI
125 // driver, hence the arch protocols have been installed previously. Among
126 // those, the CPU arch protocol has configured the IDT, so we can overwrite
127 // the IVT used in real mode.
129 // The allocation request may fail, eg. if LegacyBiosDxe has already run.
132 Int0x10
= (IVT_ENTRY
*)(UINTN
)Segment0
+ 0x10;
133 Status
= gBS
->AllocatePages (AllocateAddress
, EfiBootServicesCode
,
134 Segment0Pages
, &Segment0
);
136 if (EFI_ERROR (Status
)) {
137 EFI_PHYSICAL_ADDRESS Handler
;
140 // Check if a video BIOS handler has been installed previously -- we
141 // shouldn't override a real video BIOS with our shim, nor our own shim if
142 // it's already present.
144 Handler
= (Int0x10
->Segment
<< 4) + Int0x10
->Offset
;
145 if (Handler
>= SegmentC
&& Handler
< SegmentF
) {
146 DEBUG ((DEBUG_VERBOSE
, "%a: Video BIOS handler found at %04x:%04x\n",
147 __FUNCTION__
, Int0x10
->Segment
, Int0x10
->Offset
));
152 // Otherwise we'll overwrite the Int10h vector, even though we may not own
155 DEBUG ((DEBUG_VERBOSE
, "%a: failed to allocate page at zero: %r\n",
156 __FUNCTION__
, Status
));
159 // We managed to allocate the page at zero. SVN r14218 guarantees that it
162 ASSERT (Int0x10
->Segment
== 0x0000);
163 ASSERT (Int0x10
->Offset
== 0x0000);
167 // Put the shim in place first.
169 Pam1Address
= PCI_LIB_ADDRESS (0, 0, 0, 0x5A);
171 // low nibble covers 0xC0000 to 0xC3FFF
172 // high nibble covers 0xC4000 to 0xC7FFF
173 // bit1 in each nibble is Write Enable
174 // bit0 in each nibble is Read Enable
176 Pam1
= PciRead8 (Pam1Address
);
177 PciWrite8 (Pam1Address
, Pam1
| (BIT1
| BIT0
));
180 // We never added memory space durig PEI or DXE for the C segment, so we
181 // don't need to (and can't) allocate from there. Also, guest operating
182 // systems will see a hole in the UEFI memory map there.
186 ASSERT (sizeof mVbeShim
<= EFI_PAGES_TO_SIZE (SegmentCPages
));
187 CopyMem ((VOID
*)(UINTN
)SegmentC
, mVbeShim
, sizeof mVbeShim
);
190 // Fill in the VBE INFO structure.
192 VbeInfoFull
= (VBE_INFO
*)(UINTN
)SegmentC
;
193 VbeInfo
= &VbeInfoFull
->Base
;
194 Ptr
= VbeInfoFull
->Buffer
;
196 CopyMem (VbeInfo
->Signature
, "VESA", 4);
197 VbeInfo
->VesaVersion
= 0x0200;
199 VbeInfo
->OemNameAddress
= (UINT32
)SegmentC
<< 12 | (UINT16
)((UINTN
)Ptr
-SegmentC
);
200 CopyMem (Ptr
, "FBSD", 5);
203 VbeInfo
->Capabilities
= BIT1
| BIT0
; // DAC can be switched into 8-bit mode
205 VbeInfo
->ModeListAddress
= (UINT32
)SegmentC
<< 12 | (UINT16
)((UINTN
)Ptr
-SegmentC
);
206 for (i
= 0; i
< NUM_VBE_MODES
; i
++) {
207 *(UINT16
*)Ptr
= vbeModeIds
[i
]; // mode number
210 *(UINT16
*)Ptr
= 0xFFFF; // mode list terminator
213 VbeInfo
->VideoMem64K
= (UINT16
)((1024 * 768 * 4 + 65535) / 65536);
214 VbeInfo
->OemSoftwareVersion
= 0x0200;
216 VbeInfo
->VendorNameAddress
= (UINT32
)SegmentC
<< 12 | (UINT16
)((UINTN
)Ptr
-SegmentC
);
217 CopyMem (Ptr
, "FBSD", 5);
220 VbeInfo
->ProductNameAddress
= (UINT32
)SegmentC
<< 12 | (UINT16
)((UINTN
)Ptr
-SegmentC
);
221 Printed
= AsciiSPrint ((CHAR8
*)Ptr
,
222 sizeof VbeInfoFull
->Buffer
- (Ptr
- VbeInfoFull
->Buffer
), "%s",
226 VbeInfo
->ProductRevAddress
= (UINT32
)SegmentC
<< 12 | (UINT16
)((UINTN
)Ptr
-SegmentC
);
227 CopyMem (Ptr
, mProductRevision
, sizeof mProductRevision
);
228 Ptr
+= sizeof mProductRevision
;
230 ASSERT (sizeof VbeInfoFull
->Buffer
>= Ptr
- VbeInfoFull
->Buffer
);
231 ZeroMem (Ptr
, sizeof VbeInfoFull
->Buffer
- (Ptr
- VbeInfoFull
->Buffer
));
234 // Fill in the VBE MODE INFO structure list
236 VbeModeInfo
= (VBE_MODE_INFO
*)(VbeInfoFull
+ 1);
237 Ptr
= (UINT8
*)VbeModeInfo
;
238 for (i
= 0; i
< NUM_VBE_MODES
; i
++) {
239 vbeModes
[i
].LfbAddress
= (UINT32
)FrameBufferBase
;
240 CopyMem (Ptr
, &vbeModes
[i
], 0x32);
244 ZeroMem (Ptr
, 56); // Clear remaining bytes
247 // Clear Write Enable (bit1), keep Read Enable (bit0) set
249 PciWrite8 (Pam1Address
, (Pam1
& ~BIT1
) | BIT0
);
252 // Second, point the Int10h vector at the shim.
254 Int0x10
->Segment
= (UINT16
) ((UINT32
)SegmentC
>> 4);
255 Int0x10
->Offset
= (UINT16
) ((UINTN
) (VbeModeInfo
+ 1) - SegmentC
);
257 DEBUG ((DEBUG_INFO
, "%a: VBE shim installed to %x:%x\n",
258 __FUNCTION__
, Int0x10
->Segment
, Int0x10
->Offset
));