alder_lake_bios/Intel/AlderLake/AlderLakeChipsetPkg/CsmInt10HookSmm/CsmInt10HookSmm.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;
}