/** @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; }