/** @file | |
This is the code for Boot Script Executer module. | |
This driver is dispatched by Dxe core and the driver will reload itself to ACPI reserved memory | |
in the entry point. The functionality is to interpret and restore the S3 boot script | |
Copyright (c) 2006 - 2017, Intel Corporation. All rights reserved.<BR> | |
Copyright (c) 2017, AMD Incorporated. All rights reserved.<BR> | |
This program and the accompanying materials | |
are licensed and made available under the terms and conditions of the BSD License | |
which accompanies this distribution. The full text of the license may be found at | |
http://opensource.org/licenses/bsd-license.php | |
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
**/ | |
#include "ScriptExecute.h" | |
EFI_GUID mBootScriptExecutorImageGuid = { | |
0x9a8d3433, 0x9fe8, 0x42b6, { 0x87, 0xb, 0x1e, 0x31, 0xc8, 0x4e, 0xbe, 0x3b } | |
}; | |
BOOLEAN mPage1GSupport = FALSE; | |
UINT64 mAddressEncMask = 0; | |
/** | |
Entry function of Boot script exector. This function will be executed in | |
S3 boot path. | |
This function should not return, because it is invoked by switch stack. | |
@param AcpiS3Context a pointer to a structure of ACPI_S3_CONTEXT | |
@param PeiS3ResumeState a pointer to a structure of PEI_S3_RESUME_STATE | |
@retval EFI_INVALID_PARAMETER - OS waking vector not found | |
@retval EFI_UNSUPPORTED - something wrong when we resume to OS | |
**/ | |
EFI_STATUS | |
EFIAPI | |
S3BootScriptExecutorEntryFunction ( | |
IN ACPI_S3_CONTEXT *AcpiS3Context, | |
IN PEI_S3_RESUME_STATE *PeiS3ResumeState | |
) | |
{ | |
EFI_ACPI_4_0_FIRMWARE_ACPI_CONTROL_STRUCTURE *Facs; | |
EFI_STATUS Status; | |
UINTN TempStackTop; | |
UINTN TempStack[0x10]; | |
UINTN AsmTransferControl16Address; | |
IA32_DESCRIPTOR IdtDescriptor; | |
// | |
// Disable interrupt of Debug timer, since new IDT table cannot handle it. | |
// | |
SaveAndSetDebugTimerInterrupt (FALSE); | |
AsmReadIdtr (&IdtDescriptor); | |
// | |
// Restore IDT for debug | |
// | |
SetIdtEntry (AcpiS3Context); | |
// | |
// Initialize Debug Agent to support source level debug in S3 path, it will disable interrupt and Debug Timer. | |
// | |
InitializeDebugAgent (DEBUG_AGENT_INIT_S3, (VOID *)&IdtDescriptor, NULL); | |
// | |
// Because not install BootScriptExecute PPI(used just in this module), So just pass NULL | |
// for that parameter. | |
// | |
Status = S3BootScriptExecute (); | |
// | |
// If invalid script table or opcode in S3 boot script table. | |
// | |
ASSERT_EFI_ERROR (Status); | |
if (EFI_ERROR (Status)) { | |
CpuDeadLoop (); | |
return Status; | |
} | |
AsmWbinvd (); | |
// | |
// Get ACPI Table Address | |
// | |
Facs = (EFI_ACPI_4_0_FIRMWARE_ACPI_CONTROL_STRUCTURE *) ((UINTN) (AcpiS3Context->AcpiFacsTable)); | |
// | |
// We need turn back to S3Resume - install boot script done ppi and report status code on S3resume. | |
// | |
if (PeiS3ResumeState != 0) { | |
// | |
// Need report status back to S3ResumePeim. | |
// If boot script execution is failed, S3ResumePeim wil report the error status code. | |
// | |
PeiS3ResumeState->ReturnStatus = (UINT64)(UINTN)Status; | |
if (FeaturePcdGet (PcdDxeIplSwitchToLongMode)) { | |
// | |
// X64 S3 Resume | |
// | |
DEBUG ((DEBUG_INFO, "Call AsmDisablePaging64() to return to S3 Resume in PEI Phase\n")); | |
PeiS3ResumeState->AsmTransferControl = (EFI_PHYSICAL_ADDRESS)(UINTN)AsmTransferControl32; | |
if ((Facs != NULL) && | |
(Facs->Signature == EFI_ACPI_4_0_FIRMWARE_ACPI_CONTROL_STRUCTURE_SIGNATURE) && | |
(Facs->FirmwareWakingVector != 0) ) { | |
// | |
// more step needed - because relative address is handled differently between X64 and IA32. | |
// | |
AsmTransferControl16Address = (UINTN)AsmTransferControl16; | |
AsmFixAddress16 = (UINT32)AsmTransferControl16Address; | |
AsmJmpAddr32 = (UINT32)((Facs->FirmwareWakingVector & 0xF) | ((Facs->FirmwareWakingVector & 0xFFFF0) << 12)); | |
} | |
AsmDisablePaging64 ( | |
PeiS3ResumeState->ReturnCs, | |
(UINT32)PeiS3ResumeState->ReturnEntryPoint, | |
(UINT32)(UINTN)AcpiS3Context, | |
(UINT32)(UINTN)PeiS3ResumeState, | |
(UINT32)PeiS3ResumeState->ReturnStackPointer | |
); | |
} else { | |
// | |
// IA32 S3 Resume | |
// | |
DEBUG ((DEBUG_INFO, "Call SwitchStack() to return to S3 Resume in PEI Phase\n")); | |
PeiS3ResumeState->AsmTransferControl = (EFI_PHYSICAL_ADDRESS)(UINTN)AsmTransferControl; | |
SwitchStack ( | |
(SWITCH_STACK_ENTRY_POINT)(UINTN)PeiS3ResumeState->ReturnEntryPoint, | |
(VOID *)(UINTN)AcpiS3Context, | |
(VOID *)(UINTN)PeiS3ResumeState, | |
(VOID *)(UINTN)PeiS3ResumeState->ReturnStackPointer | |
); | |
} | |
// | |
// Never run to here | |
// | |
CpuDeadLoop(); | |
return EFI_UNSUPPORTED; | |
} | |
// | |
// S3ResumePeim does not provide a way to jump back to itself, so resume to OS here directly | |
// | |
if (Facs->XFirmwareWakingVector != 0) { | |
// | |
// Switch to native waking vector | |
// | |
TempStackTop = (UINTN)&TempStack + sizeof(TempStack); | |
if ((Facs->Version == EFI_ACPI_4_0_FIRMWARE_ACPI_CONTROL_STRUCTURE_VERSION) && | |
((Facs->Flags & EFI_ACPI_4_0_64BIT_WAKE_SUPPORTED_F) != 0) && | |
((Facs->Flags & EFI_ACPI_4_0_OSPM_64BIT_WAKE__F) != 0)) { | |
// | |
// X64 long mode waking vector | |
// | |
DEBUG ((DEBUG_INFO, "Transfer to 64bit OS waking vector - %x\r\n", (UINTN)Facs->XFirmwareWakingVector)); | |
if (FeaturePcdGet (PcdDxeIplSwitchToLongMode)) { | |
SwitchStack ( | |
(SWITCH_STACK_ENTRY_POINT)(UINTN)Facs->XFirmwareWakingVector, | |
NULL, | |
NULL, | |
(VOID *)(UINTN)TempStackTop | |
); | |
} else { | |
// Unsupported for 32bit DXE, 64bit OS vector | |
DEBUG (( EFI_D_ERROR, "Unsupported for 32bit DXE transfer to 64bit OS waking vector!\r\n")); | |
ASSERT (FALSE); | |
} | |
} else { | |
// | |
// IA32 protected mode waking vector (Page disabled) | |
// | |
DEBUG ((DEBUG_INFO, "Transfer to 32bit OS waking vector - %x\r\n", (UINTN)Facs->XFirmwareWakingVector)); | |
if (FeaturePcdGet (PcdDxeIplSwitchToLongMode)) { | |
AsmDisablePaging64 ( | |
0x10, | |
(UINT32)Facs->XFirmwareWakingVector, | |
0, | |
0, | |
(UINT32)TempStackTop | |
); | |
} else { | |
SwitchStack ( | |
(SWITCH_STACK_ENTRY_POINT)(UINTN)Facs->XFirmwareWakingVector, | |
NULL, | |
NULL, | |
(VOID *)(UINTN)TempStackTop | |
); | |
} | |
} | |
} else { | |
// | |
// 16bit Realmode waking vector | |
// | |
DEBUG ((DEBUG_INFO, "Transfer to 16bit OS waking vector - %x\r\n", (UINTN)Facs->FirmwareWakingVector)); | |
AsmTransferControl (Facs->FirmwareWakingVector, 0x0); | |
} | |
// | |
// Never run to here | |
// | |
CpuDeadLoop(); | |
return EFI_UNSUPPORTED; | |
} | |
/** | |
Register image to memory profile. | |
@param FileName File name of the image. | |
@param ImageBase Image base address. | |
@param ImageSize Image size. | |
@param FileType File type of the image. | |
**/ | |
VOID | |
RegisterMemoryProfileImage ( | |
IN EFI_GUID *FileName, | |
IN PHYSICAL_ADDRESS ImageBase, | |
IN UINT64 ImageSize, | |
IN EFI_FV_FILETYPE FileType | |
) | |
{ | |
EFI_STATUS Status; | |
EDKII_MEMORY_PROFILE_PROTOCOL *ProfileProtocol; | |
MEDIA_FW_VOL_FILEPATH_DEVICE_PATH *FilePath; | |
UINT8 TempBuffer[sizeof (MEDIA_FW_VOL_FILEPATH_DEVICE_PATH) + sizeof (EFI_DEVICE_PATH_PROTOCOL)]; | |
if ((PcdGet8 (PcdMemoryProfilePropertyMask) & BIT0) != 0) { | |
FilePath = (MEDIA_FW_VOL_FILEPATH_DEVICE_PATH *)TempBuffer; | |
Status = gBS->LocateProtocol (&gEdkiiMemoryProfileGuid, NULL, (VOID **) &ProfileProtocol); | |
if (!EFI_ERROR (Status)) { | |
EfiInitializeFwVolDevicepathNode (FilePath, FileName); | |
SetDevicePathEndNode (FilePath + 1); | |
Status = ProfileProtocol->RegisterImage ( | |
ProfileProtocol, | |
(EFI_DEVICE_PATH_PROTOCOL *) FilePath, | |
ImageBase, | |
ImageSize, | |
FileType | |
); | |
} | |
} | |
} | |
/** | |
This is the Event notification function to reload BootScriptExecutor image | |
to RESERVED mem and save it to LockBox. | |
@param Event Pointer to this event | |
@param Context Event handler private data | |
**/ | |
VOID | |
EFIAPI | |
ReadyToLockEventNotify ( | |
IN EFI_EVENT Event, | |
IN VOID *Context | |
) | |
{ | |
EFI_STATUS Status; | |
VOID *Interface; | |
UINT8 *Buffer; | |
UINTN BufferSize; | |
EFI_HANDLE NewImageHandle; | |
UINTN Pages; | |
EFI_PHYSICAL_ADDRESS FfsBuffer; | |
PE_COFF_LOADER_IMAGE_CONTEXT ImageContext; | |
Status = gBS->LocateProtocol (&gEfiDxeSmmReadyToLockProtocolGuid, NULL, &Interface); | |
if (EFI_ERROR (Status)) { | |
return; | |
} | |
// | |
// A workaround: Here we install a dummy handle | |
// | |
NewImageHandle = NULL; | |
Status = gBS->InstallProtocolInterface ( | |
&NewImageHandle, | |
&gEfiCallerIdGuid, | |
EFI_NATIVE_INTERFACE, | |
NULL | |
); | |
ASSERT_EFI_ERROR (Status); | |
// | |
// Reload BootScriptExecutor image itself to RESERVED mem | |
// | |
Status = GetSectionFromAnyFv ( | |
&gEfiCallerIdGuid, | |
EFI_SECTION_PE32, | |
0, | |
(VOID **) &Buffer, | |
&BufferSize | |
); | |
ASSERT_EFI_ERROR (Status); | |
ImageContext.Handle = Buffer; | |
ImageContext.ImageRead = PeCoffLoaderImageReadFromMemory; | |
// | |
// Get information about the image being loaded | |
// | |
Status = PeCoffLoaderGetImageInfo (&ImageContext); | |
ASSERT_EFI_ERROR (Status); | |
if (ImageContext.SectionAlignment > EFI_PAGE_SIZE) { | |
Pages = EFI_SIZE_TO_PAGES ((UINTN) (ImageContext.ImageSize + ImageContext.SectionAlignment)); | |
} else { | |
Pages = EFI_SIZE_TO_PAGES ((UINTN) ImageContext.ImageSize); | |
} | |
FfsBuffer = 0xFFFFFFFF; | |
Status = gBS->AllocatePages ( | |
AllocateMaxAddress, | |
EfiReservedMemoryType, | |
Pages, | |
&FfsBuffer | |
); | |
ASSERT_EFI_ERROR (Status); | |
ImageContext.ImageAddress = (PHYSICAL_ADDRESS)(UINTN)FfsBuffer; | |
// | |
// Align buffer on section boundary | |
// | |
ImageContext.ImageAddress += ImageContext.SectionAlignment - 1; | |
ImageContext.ImageAddress &= ~((EFI_PHYSICAL_ADDRESS)ImageContext.SectionAlignment - 1); | |
// | |
// Load the image to our new buffer | |
// | |
Status = PeCoffLoaderLoadImage (&ImageContext); | |
ASSERT_EFI_ERROR (Status); | |
// | |
// Relocate the image in our new buffer | |
// | |
Status = PeCoffLoaderRelocateImage (&ImageContext); | |
ASSERT_EFI_ERROR (Status); | |
// | |
// Free the buffer allocated by ReadSection since the image has been relocated in the new buffer | |
// | |
gBS->FreePool (Buffer); | |
// | |
// Flush the instruction cache so the image data is written before we execute it | |
// | |
InvalidateInstructionCacheRange ((VOID *)(UINTN)ImageContext.ImageAddress, (UINTN)ImageContext.ImageSize); | |
RegisterMemoryProfileImage ( | |
&gEfiCallerIdGuid, | |
ImageContext.ImageAddress, | |
ImageContext.ImageSize, | |
EFI_FV_FILETYPE_DRIVER | |
); | |
Status = ((EFI_IMAGE_ENTRY_POINT)(UINTN)(ImageContext.EntryPoint)) (NewImageHandle, gST); | |
ASSERT_EFI_ERROR (Status); | |
// | |
// Additional step for BootScript integrity | |
// Save BootScriptExecutor image | |
// | |
Status = SaveLockBox ( | |
&mBootScriptExecutorImageGuid, | |
(VOID *)(UINTN)ImageContext.ImageAddress, | |
(UINTN)ImageContext.ImageSize | |
); | |
ASSERT_EFI_ERROR (Status); | |
Status = SetLockBoxAttributes (&mBootScriptExecutorImageGuid, LOCK_BOX_ATTRIBUTE_RESTORE_IN_PLACE); | |
ASSERT_EFI_ERROR (Status); | |
gBS->CloseEvent (Event); | |
} | |
/** | |
Entrypoint of Boot script exector driver, this function will be executed in | |
normal boot phase and invoked by DXE dispatch. | |
@param[in] ImageHandle The firmware allocated handle for the EFI image. | |
@param[in] SystemTable A pointer to the EFI System Table. | |
@retval EFI_SUCCESS The entry point is executed successfully. | |
@retval other Some error occurs when executing this entry point. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
BootScriptExecutorEntryPoint ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
UINTN BufferSize; | |
UINTN Pages; | |
BOOT_SCRIPT_EXECUTOR_VARIABLE *EfiBootScriptExecutorVariable; | |
EFI_PHYSICAL_ADDRESS BootScriptExecutorBuffer; | |
EFI_STATUS Status; | |
VOID *DevicePath; | |
EFI_EVENT ReadyToLockEvent; | |
VOID *Registration; | |
UINT32 RegEax; | |
UINT32 RegEdx; | |
if (!PcdGetBool (PcdAcpiS3Enable)) { | |
return EFI_UNSUPPORTED; | |
} | |
// | |
// Make sure AddressEncMask is contained to smallest supported address field. | |
// | |
mAddressEncMask = PcdGet64 (PcdPteMemoryEncryptionAddressOrMask) & PAGING_1G_ADDRESS_MASK_64; | |
// | |
// Test if the gEfiCallerIdGuid of this image is already installed. if not, the entry | |
// point is loaded by DXE code which is the first time loaded. or else, it is already | |
// be reloaded be itself.This is a work-around | |
// | |
Status = gBS->LocateProtocol (&gEfiCallerIdGuid, NULL, &DevicePath); | |
if (EFI_ERROR (Status)) { | |
// | |
// Create ReadyToLock event to reload BootScriptExecutor image | |
// to RESERVED mem and save it to LockBox. | |
// | |
ReadyToLockEvent = EfiCreateProtocolNotifyEvent ( | |
&gEfiDxeSmmReadyToLockProtocolGuid, | |
TPL_NOTIFY, | |
ReadyToLockEventNotify, | |
NULL, | |
&Registration | |
); | |
ASSERT (ReadyToLockEvent != NULL); | |
} else { | |
// | |
// the entry point is invoked after reloading. following code only run in RESERVED mem | |
// | |
if (PcdGetBool(PcdUse1GPageTable)) { | |
AsmCpuid (0x80000000, &RegEax, NULL, NULL, NULL); | |
if (RegEax >= 0x80000001) { | |
AsmCpuid (0x80000001, NULL, NULL, NULL, &RegEdx); | |
if ((RegEdx & BIT26) != 0) { | |
mPage1GSupport = TRUE; | |
} | |
} | |
} | |
BufferSize = sizeof (BOOT_SCRIPT_EXECUTOR_VARIABLE); | |
BootScriptExecutorBuffer = 0xFFFFFFFF; | |
Pages = EFI_SIZE_TO_PAGES(BufferSize); | |
Status = gBS->AllocatePages ( | |
AllocateMaxAddress, | |
EfiReservedMemoryType, | |
Pages, | |
&BootScriptExecutorBuffer | |
); | |
ASSERT_EFI_ERROR (Status); | |
EfiBootScriptExecutorVariable = (BOOT_SCRIPT_EXECUTOR_VARIABLE *)(UINTN)BootScriptExecutorBuffer; | |
EfiBootScriptExecutorVariable->BootScriptExecutorEntrypoint = (UINTN) S3BootScriptExecutorEntryFunction ; | |
Status = SaveLockBox ( | |
&gEfiBootScriptExecutorVariableGuid, | |
&BootScriptExecutorBuffer, | |
sizeof(BootScriptExecutorBuffer) | |
); | |
ASSERT_EFI_ERROR (Status); | |
// | |
// Additional step for BootScript integrity | |
// Save BootScriptExecutor context | |
// | |
Status = SaveLockBox ( | |
&gEfiBootScriptExecutorContextGuid, | |
EfiBootScriptExecutorVariable, | |
sizeof(*EfiBootScriptExecutorVariable) | |
); | |
ASSERT_EFI_ERROR (Status); | |
Status = SetLockBoxAttributes (&gEfiBootScriptExecutorContextGuid, LOCK_BOX_ATTRIBUTE_RESTORE_IN_PLACE); | |
ASSERT_EFI_ERROR (Status); | |
} | |
return EFI_SUCCESS; | |
} | |