531 lines
15 KiB
C
531 lines
15 KiB
C
/** @file
|
|
Hook legacy INT10 to trigger software SMI for INTEL VBIOS workaround. (IBP:549021)
|
|
;******************************************************************************
|
|
;* Copyright (c) 2012 - 2018, Insyde Software Corp. All Rights Reserved.
|
|
;*
|
|
;* You may not reproduce, distribute, publish, display, perform, modify, adapt,
|
|
;* transmit, broadcast, present, recite, release, license or otherwise exploit
|
|
;* any part of this publication in any form, by any means, without the prior
|
|
;* written permission of Insyde Software Corporation.
|
|
;*
|
|
;******************************************************************************
|
|
*/
|
|
|
|
#include "CsmInt10HookSmm.h"
|
|
|
|
extern EFI_GUID gEfiVbiosWaFileGuid;
|
|
|
|
EFI_SMM_SYSTEM_TABLE2 *mSmst = NULL;
|
|
BIOS_VIDEO_DEV_SIMPLE *mVideoDevSimple = NULL;
|
|
|
|
#define INT10_HOOK_PRIVATE_DATA_VARIABLE_NAME L"Int10HookPrivateDataVariable"
|
|
#define INT10_HOOK_PRIVATE_DATA_GUID \
|
|
{0x80469736, 0x6678, 0x4857, {0xAB, 0x14, 0xCF, 0xDB, 0xDA, 0x3F, 0x48, 0x72}}
|
|
|
|
EFI_GUID gInt10HookPrivateDataGuid = INT10_HOOK_PRIVATE_DATA_GUID;
|
|
|
|
STATIC
|
|
VOID
|
|
InstallInt10VbiosWaOpRom (
|
|
IN EFI_EVENT Event,
|
|
IN VOID *Handle
|
|
);
|
|
|
|
STATIC
|
|
EFI_STATUS
|
|
EFIAPI
|
|
INT10SwSmiCallback (
|
|
IN EFI_HANDLE Handle,
|
|
IN CONST VOID *Context,
|
|
IN OUT VOID *CommBuffer,
|
|
IN OUT UINTN *CommBufferSize
|
|
);
|
|
|
|
STATIC
|
|
VOID
|
|
SmmInt10CallBack (
|
|
IN UINT32 ModeNumber
|
|
);
|
|
|
|
|
|
/**
|
|
|
|
Install a notification function, InstallInt10VbiosWaOpRom(), for gEfiChangeVbiosBootDisplayProtocolGuid in DXE.
|
|
Register a SMI callback, INT10SwSmiCallback(), for INT10 in SMM.
|
|
|
|
@param ImageHandle EFI_HANDLE
|
|
@param SystemTable EFI_SYSTEM_TABLE pointer
|
|
|
|
@retval EFI_SUCCESS Driver Dispatch success
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
CsmInt10HookInitialize (
|
|
IN EFI_HANDLE ImageHandle,
|
|
IN EFI_SYSTEM_TABLE *SystemTable
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_EVENT VbiosHookEvent;
|
|
EFI_SMM_BASE2_PROTOCOL *SmmBase;
|
|
EFI_SMM_SW_DISPATCH2_PROTOCOL *SwDispatch;
|
|
EFI_SMM_SW_REGISTER_CONTEXT SwContext;
|
|
EFI_HANDLE SwHandle;
|
|
BOOLEAN InSmm;
|
|
|
|
SmmBase = NULL;
|
|
SwDispatch = NULL;
|
|
|
|
Status = gBS->LocateProtocol (&gEfiSmmBase2ProtocolGuid, NULL, (VOID **)&SmmBase);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
InSmm = FALSE;
|
|
SmmBase->InSmm (SmmBase, &InSmm);
|
|
|
|
if (!InSmm) {
|
|
EfiCreateProtocolNotifyEvent (
|
|
&gEfiChangeVbiosBootDisplayProtocolGuid,
|
|
TPL_NOTIFY,
|
|
InstallInt10VbiosWaOpRom,
|
|
NULL,
|
|
&VbiosHookEvent
|
|
);
|
|
} else {
|
|
//
|
|
// In SMM!
|
|
//
|
|
Status = SmmBase->GetSmstLocation (SmmBase, &mSmst);
|
|
if (EFI_ERROR(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
Status = mSmst->SmmLocateProtocol (
|
|
&gEfiSmmSwDispatch2ProtocolGuid,
|
|
NULL,
|
|
(VOID **)&SwDispatch
|
|
);
|
|
if (EFI_ERROR(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Register SwSmi for INT10 to trigger
|
|
//
|
|
SwContext.SwSmiInputValue = INT10_HOOK_FOR_INTEL_VBIOS;
|
|
|
|
Status = SwDispatch->Register (
|
|
SwDispatch,
|
|
INT10SwSmiCallback,
|
|
&SwContext,
|
|
&SwHandle
|
|
);
|
|
if (EFI_ERROR(Status)) {
|
|
return Status;
|
|
}
|
|
}
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
|
|
Install a option ROM to invoke SMI for CSM INT10 set VBE mode function call(AX = 4F02h)
|
|
|
|
@param Event the event object
|
|
@param Handle
|
|
|
|
@retval
|
|
**/
|
|
STATIC
|
|
VOID
|
|
InstallInt10VbiosWaOpRom (
|
|
IN EFI_EVENT Event,
|
|
IN VOID *Handle
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_LEGACY_BIOS_PROTOCOL *LegacyBios;
|
|
EFI_IA32_REGISTER_SET Regs;
|
|
VOID *Table;
|
|
UINTN TableSize;
|
|
UINTN TablePtr;
|
|
VOID *LegacyRegion;
|
|
EFI_HANDLE pHandle;
|
|
UINT32 idx;
|
|
BIOS_VIDEO_SIMPLE_MODE_DATA *SimpleModeData;
|
|
BIOS_VIDEO_DEV_SIMPLE *VideoDevSimple;
|
|
UINT16 *ModeNumberPtr;
|
|
UINT16 *TmpModeNumberPtr;
|
|
UINT32 MaxMode;
|
|
VESA_BIOS_EXTENSIONS_MODE_INFORMATION_BLOCK *VbeModeInformationBlock;
|
|
VESA_BIOS_EXTENSIONS_INFORMATION_BLOCK *VbeInformationBlock;
|
|
VOID *Ptr;
|
|
|
|
LegacyBios = NULL;
|
|
Table = NULL;
|
|
LegacyRegion = NULL;
|
|
SimpleModeData = NULL;
|
|
VideoDevSimple = NULL;
|
|
MaxMode = 0;
|
|
idx = 0;
|
|
pHandle = NULL;
|
|
ModeNumberPtr = NULL;
|
|
TmpModeNumberPtr = NULL;
|
|
VbeModeInformationBlock = NULL;
|
|
VbeInformationBlock = NULL;
|
|
Ptr = NULL;
|
|
|
|
//
|
|
// Make sure the protocol is really installed
|
|
//
|
|
Status = gBS->LocateProtocol (&gEfiChangeVbiosBootDisplayProtocolGuid, NULL, (VOID **)&Ptr);
|
|
if (EFI_ERROR (Status)) {
|
|
return;
|
|
}
|
|
|
|
Status = gBS->CloseEvent (Event);
|
|
if (EFI_ERROR (Status)) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Prepare/install set mode related data package for the SMI to use.
|
|
//
|
|
Status = gBS->LocateProtocol (&gEfiLegacyBiosProtocolGuid, NULL, (VOID **)&LegacyBios);
|
|
if (EFI_ERROR (Status)) {
|
|
return;
|
|
}
|
|
|
|
Status = DxeCsSvcLegacyRegionAccessCtrl (0xE0000, 0x20000, LEGACY_REGION_ACCESS_UNLOCK);
|
|
|
|
//
|
|
// To find the required size of availabe free memory under 1MB to store mode data
|
|
//
|
|
Status = LegacyBios->GetLegacyRegion (
|
|
LegacyBios,
|
|
sizeof (VESA_BIOS_EXTENSIONS_INFORMATION_BLOCK) +
|
|
sizeof (VESA_BIOS_EXTENSIONS_MODE_INFORMATION_BLOCK),
|
|
0x0002, // 0xE0000 block
|
|
0x01,
|
|
&LegacyRegion
|
|
);
|
|
//
|
|
// Prepare the memory for legacy calls to use.
|
|
//
|
|
VbeInformationBlock = (VESA_BIOS_EXTENSIONS_INFORMATION_BLOCK *) (UINTN) (LegacyRegion);
|
|
VbeModeInformationBlock = (VESA_BIOS_EXTENSIONS_MODE_INFORMATION_BLOCK *) (VbeInformationBlock + 1);
|
|
|
|
//
|
|
// Call 0x4F00, Return VBE controller Information, to get the list of supported modes. Store it in ModeNumberPtr.
|
|
//
|
|
gBS->SetMem (&Regs, sizeof (Regs), 0);
|
|
Regs.X.AX = VESA_BIOS_EXTENSIONS_RETURN_CONTROLLER_INFORMATION;
|
|
gBS->SetMem (VbeInformationBlock, sizeof (VESA_BIOS_EXTENSIONS_INFORMATION_BLOCK), 0);
|
|
VbeInformationBlock->VESASignature = VESA_BIOS_EXTENSIONS_VBE2_SIGNATURE;
|
|
Regs.X.ES = EFI_SEGMENT ((UINTN) VbeInformationBlock);
|
|
Regs.X.DI = EFI_OFFSET ((UINTN) VbeInformationBlock);
|
|
LegacyBios->Int86 (LegacyBios, 0x10, &Regs);
|
|
|
|
//
|
|
// See if the VESA call succeeded
|
|
//
|
|
if (Regs.X.AX != VESA_BIOS_EXTENSIONS_STATUS_SUCCESS) {
|
|
DxeCsSvcLegacyRegionAccessCtrl (0xE0000, 0x20000, LEGACY_REGION_ACCESS_LOCK);
|
|
return;
|
|
}
|
|
//
|
|
// Check for 'VESA' signature
|
|
//
|
|
if (VbeInformationBlock->VESASignature != VESA_BIOS_EXTENSIONS_VESA_SIGNATURE) {
|
|
DxeCsSvcLegacyRegionAccessCtrl (0xE0000, 0x20000, LEGACY_REGION_ACCESS_LOCK);
|
|
return;
|
|
}
|
|
//
|
|
// Check to see if this is VBE 2.0 or higher
|
|
//
|
|
if (VbeInformationBlock->VESAVersion < VESA_BIOS_EXTENSIONS_VERSION_2_0) {
|
|
DxeCsSvcLegacyRegionAccessCtrl (0xE0000, 0x20000, LEGACY_REGION_ACCESS_LOCK);
|
|
return;
|
|
}
|
|
|
|
ModeNumberPtr = (UINT16 *)
|
|
(
|
|
(((UINTN) VbeInformationBlock->VideoModePtr & 0xffff0000) >> 12) |
|
|
((UINTN) VbeInformationBlock->VideoModePtr & 0x0000ffff)
|
|
);
|
|
TmpModeNumberPtr = ModeNumberPtr;
|
|
|
|
//
|
|
// Count the maximun mode number.
|
|
//
|
|
for (; *TmpModeNumberPtr != VESA_BIOS_EXTENSIONS_END_OF_MODE_LIST; TmpModeNumberPtr++) {
|
|
MaxMode++;
|
|
}
|
|
|
|
//
|
|
// Prepare EfiACPIMemoryNVS memory to store mode data.
|
|
//
|
|
gBS->AllocatePool (EfiACPIMemoryNVS, sizeof(BIOS_VIDEO_DEV_SIMPLE), &VideoDevSimple);
|
|
gBS->AllocatePool (EfiACPIMemoryNVS, sizeof(BIOS_VIDEO_SIMPLE_MODE_DATA) * MaxMode, &SimpleModeData);
|
|
ZeroMem (VideoDevSimple, sizeof(BIOS_VIDEO_DEV_SIMPLE));
|
|
ZeroMem (SimpleModeData, sizeof(BIOS_VIDEO_SIMPLE_MODE_DATA) * MaxMode);
|
|
|
|
//
|
|
// Read detail mode data according to *ModeNumberPtr.
|
|
//
|
|
VideoDevSimple->SimpleModeData = SimpleModeData;
|
|
for (; *ModeNumberPtr != VESA_BIOS_EXTENSIONS_END_OF_MODE_LIST; ModeNumberPtr++) {
|
|
//
|
|
// Make sure this is a mode number defined by the VESA VBE specification. If it isn'tm then skip this mode number.
|
|
//
|
|
if ((*ModeNumberPtr & VESA_BIOS_EXTENSIONS_MODE_NUMBER_VESA) == 0) {
|
|
continue;
|
|
}
|
|
//
|
|
// Get the information about the mode
|
|
//
|
|
gBS->SetMem (&Regs, sizeof (Regs), 0);
|
|
Regs.X.AX = VESA_BIOS_EXTENSIONS_RETURN_MODE_INFORMATION;
|
|
Regs.X.CX = *ModeNumberPtr;
|
|
gBS->SetMem (VbeModeInformationBlock, sizeof (VESA_BIOS_EXTENSIONS_MODE_INFORMATION_BLOCK), 0);
|
|
Regs.X.ES = EFI_SEGMENT ((UINTN) VbeModeInformationBlock);
|
|
Regs.X.DI = EFI_OFFSET ((UINTN) VbeModeInformationBlock);
|
|
|
|
LegacyBios->Int86 (LegacyBios, 0x10, &Regs);
|
|
//
|
|
// See if the call succeeded. If it didn't, then try the next mode.
|
|
//
|
|
if (Regs.X.AX != VESA_BIOS_EXTENSIONS_STATUS_SUCCESS) {
|
|
continue;
|
|
}
|
|
//
|
|
// See if the mode supports a linear frame buffer. If it doesn't then try the next mode.
|
|
//
|
|
if ((VbeModeInformationBlock->ModeAttributes & VESA_BIOS_EXTENSIONS_MODE_ATTRIBUTE_LINEAR_FRAME_BUFFER) == 0) {
|
|
continue;
|
|
}
|
|
//
|
|
// See if the physical base pointer for the linear mode is valid. If it isn't then try the next mode.
|
|
//
|
|
if (VbeModeInformationBlock->PhysBasePtr == 0) {
|
|
continue;
|
|
}
|
|
|
|
SimpleModeData[idx].VbeModeNumber = *ModeNumberPtr;
|
|
SimpleModeData[idx].LinearFrameBuffer = (VOID *) (UINTN)VbeModeInformationBlock->PhysBasePtr;
|
|
SimpleModeData[idx].FrameBufferSize = ((UINT32)(VbeModeInformationBlock->BytesPerScanLine * 8) / VbeModeInformationBlock->BitsPerPixel) *
|
|
(UINT32)(VbeModeInformationBlock->YResolution * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
|
|
idx++;
|
|
}
|
|
|
|
LegacyRegion = NULL;
|
|
VideoDevSimple->MaxMode = idx;
|
|
|
|
//
|
|
// Store the mode data in INT10_HOOK_PRIVATE_DATA_VARIABLE_NAME volatile variable.
|
|
//
|
|
Status = CommonSetVariable (
|
|
INT10_HOOK_PRIVATE_DATA_VARIABLE_NAME,
|
|
&gInt10HookPrivateDataGuid,
|
|
EFI_VARIABLE_BOOTSERVICE_ACCESS,
|
|
sizeof (UINTN),
|
|
(VOID **)&VideoDevSimple
|
|
);
|
|
|
|
//
|
|
// Read the option rom.
|
|
//
|
|
Status = GetSectionFromFv (
|
|
&gEfiVbiosWaFileGuid,
|
|
EFI_SECTION_RAW,
|
|
0,
|
|
&Table,
|
|
&TableSize
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
DxeCsSvcLegacyRegionAccessCtrl (0xE0000, 0x20000, LEGACY_REGION_ACCESS_LOCK);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Find the required size of availabe free memory under 1MB to store legacy option rom.
|
|
//
|
|
Status = LegacyBios->GetLegacyRegion (
|
|
LegacyBios,
|
|
TableSize,
|
|
0x0002, // 0xE0000 block
|
|
0x01,
|
|
&LegacyRegion
|
|
);
|
|
|
|
//
|
|
// Copy the option rom to legacy memory
|
|
//
|
|
TablePtr =(UINTN)LegacyRegion;
|
|
|
|
CopyMem((VOID *)TablePtr,
|
|
Table,
|
|
TableSize
|
|
);
|
|
|
|
Regs.X.AX = INT10_HOOK_FOR_INTEL_VBIOS;
|
|
Regs.X.DX = SW_SMI_PORT;
|
|
//
|
|
// call into our option rom to hook the INT10
|
|
//
|
|
LegacyBios->FarCall86 (
|
|
LegacyBios,
|
|
(UINT16)(TablePtr >> 4),
|
|
0x03,
|
|
&Regs,
|
|
NULL,
|
|
0
|
|
);
|
|
Status = DxeCsSvcLegacyRegionAccessCtrl (0xE0000, 0x20000, LEGACY_REGION_ACCESS_LOCK);
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
|
|
Confirm if it is the correct SMI we want and call the buffer clearing function.
|
|
|
|
@param Handle
|
|
@param Context
|
|
@param CommBuffer
|
|
@param CommBufferSize
|
|
|
|
@retval EFI_SUCCESS SMI finishes successfully
|
|
**/
|
|
STATIC
|
|
EFI_STATUS
|
|
EFIAPI
|
|
INT10SwSmiCallback (
|
|
IN EFI_HANDLE Handle,
|
|
IN CONST VOID *Context,
|
|
IN OUT VOID *CommBuffer,
|
|
IN OUT UINTN *CommBufferSize
|
|
)
|
|
{
|
|
EFI_SMM_CPU_PROTOCOL *SmmCpu;
|
|
UINTN Index;
|
|
EFI_STATUS Status;
|
|
UINT32 Eax;
|
|
UINT32 Ebx;
|
|
UINT32 Edx;
|
|
|
|
SmmCpu = NULL;
|
|
Eax = 0;
|
|
Ebx = 0;
|
|
Edx = 0;
|
|
|
|
//
|
|
// Locate Smm Cpu protocol for Cpu save state manipulation
|
|
//
|
|
Status = mSmst->SmmLocateProtocol (
|
|
&gEfiSmmCpuProtocolGuid,
|
|
NULL,
|
|
(VOID **)&SmmCpu
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Find out which CPU triggered the S/W SMI
|
|
//
|
|
for (Index = 0; Index < mSmst->NumberOfCpus; Index++) {
|
|
SmmCpu->ReadSaveState (
|
|
SmmCpu,
|
|
sizeof (UINT32),
|
|
EFI_SMM_SAVE_STATE_REGISTER_RAX,
|
|
Index,
|
|
&Eax
|
|
);
|
|
|
|
SmmCpu->ReadSaveState (
|
|
SmmCpu,
|
|
sizeof (UINT32),
|
|
EFI_SMM_SAVE_STATE_REGISTER_RDX,
|
|
Index,
|
|
&Edx
|
|
);
|
|
|
|
if (((Eax & 0xff) == INT10_HOOK_FOR_INTEL_VBIOS) && ((Edx & 0xffff) == SW_SMI_PORT)) {
|
|
//
|
|
// CPU found!
|
|
//
|
|
SmmCpu->ReadSaveState (
|
|
SmmCpu,
|
|
sizeof (UINT32),
|
|
EFI_SMM_SAVE_STATE_REGISTER_RBX,
|
|
Index,
|
|
&Ebx
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
if (Index == mSmst->NumberOfCpus) {
|
|
//
|
|
// Error out due to CPU not found
|
|
//
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
SmmInt10CallBack (Ebx & 0x1FF);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
INT10 Smm Callback of EFI_INT10_SERVICE_PROTOCOL
|
|
Check if the VBIOS set mode function requires BIOS' help to clear framebuffer.
|
|
|
|
@param[in] Ebx Point to Cpu registers.
|
|
|
|
**/
|
|
STATIC
|
|
VOID
|
|
SmmInt10CallBack (
|
|
IN UINT32 ModeNumber
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
BIOS_VIDEO_SIMPLE_MODE_DATA *LocalModeData;
|
|
UINTN Index;
|
|
BOOLEAN IsWorkaroundRequired;
|
|
UINTN VariableSize;
|
|
UINTN DataAddress;
|
|
|
|
if (mVideoDevSimple == NULL) {
|
|
VariableSize = sizeof (UINTN);
|
|
Status = CommonGetVariable (
|
|
INT10_HOOK_PRIVATE_DATA_VARIABLE_NAME,
|
|
&gInt10HookPrivateDataGuid,
|
|
&VariableSize,
|
|
(VOID *)&DataAddress
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
return;
|
|
}
|
|
mVideoDevSimple = (BIOS_VIDEO_DEV_SIMPLE *)(UINTN)DataAddress;
|
|
}
|
|
|
|
LocalModeData = mVideoDevSimple->SimpleModeData;
|
|
IsWorkaroundRequired = FALSE;
|
|
for (Index = 0; Index < mVideoDevSimple->MaxMode; Index++) {
|
|
if ((LocalModeData[Index].VbeModeNumber == ModeNumber) &&
|
|
(LocalModeData[Index].FrameBufferSize > 0x800000)) {
|
|
IsWorkaroundRequired = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (IsWorkaroundRequired) {
|
|
ZeroMem (LocalModeData[Index].LinearFrameBuffer, LocalModeData[Index].FrameBufferSize);
|
|
}
|
|
|
|
return;
|
|
}
|