--- /dev/null
+/** @file\r
+ This driver is used for SMM S3 support for the bootloader that\r
+ doesn't support SMM.\r
+ The payload would save SMM rebase info to SMM communication area.\r
+ The bootloader is expected to rebase the SMM and trigger SMI by\r
+ writting 0xB2 port with given value from SMM communication area.\r
+ The paylaod SMM handler got chance to restore regs in S3 path.\r
+\r
+ Copyright (c) 2021, Intel Corporation. All rights reserved.<BR>\r
+ SPDX-License-Identifier: BSD-2-Clause-Patent\r
+\r
+**/\r
+\r
+#include <BlSupportSmm.h>\r
+\r
+PLD_S3_COMMUNICATION mPldS3Hob;\r
+EFI_SMRAM_HOB_DESCRIPTOR_BLOCK *mSmramHob = NULL;\r
+PLD_SMM_REGISTERS *mSmmRegisterHob = NULL;;\r
+UINT64 mSmmFeatureControl = 0;\r
+\r
+/**\r
+ Save SMM rebase and SMI handler information to SMM communication area\r
+\r
+ The function detects SMM communication region for boot loader, if it is detected, it\r
+ will save SMM rebase information and S3 SMI handler information to SMM communication\r
+ region. Bootloader should consume these information in S3 path to restore smm base,\r
+ and write the 0xB2 port to trigger SMI so that payload could resume S3 registers.\r
+\r
+ @param[in] BlSwSmiHandlerInput Value written to 0xB2 to trigger SMI handler.\r
+\r
+ @retval EFI_SUCCESS Save SMM info success.\r
+ @retval Others Failed to save SMM info.\r
+**/\r
+EFI_STATUS\r
+SaveSmmInfoForS3 (\r
+ IN UINT8 BlSwSmiHandlerInput\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ EFI_PROCESSOR_INFORMATION ProcessorInfo;\r
+ EFI_MP_SERVICES_PROTOCOL *MpService;\r
+ CPU_SMMBASE *SmmBaseInfo;\r
+ PLD_TO_BL_SMM_INFO *PldSmmInfo;\r
+ UINTN Index;\r
+\r
+ PldSmmInfo = (PLD_TO_BL_SMM_INFO *)(UINTN)mPldS3Hob.CommBuffer.PhysicalStart;\r
+ PldSmmInfo->Header.Header.HobLength = (UINT16)(sizeof (PLD_TO_BL_SMM_INFO) + gSmst->NumberOfCpus * sizeof (CPU_SMMBASE));\r
+ for (Index = 0; Index < mSmramHob->NumberOfSmmReservedRegions; Index++) {\r
+ if ((mPldS3Hob.CommBuffer.PhysicalStart >= mSmramHob->Descriptor[Index].PhysicalStart) &&\r
+ (mPldS3Hob.CommBuffer.PhysicalStart < mSmramHob->Descriptor[Index].PhysicalStart + mSmramHob->Descriptor[Index].PhysicalSize)) {\r
+ break;\r
+ }\r
+ }\r
+ if (Index == mSmramHob->NumberOfSmmReservedRegions) {\r
+ return EFI_NOT_FOUND;\r
+ }\r
+\r
+ //\r
+ // Make sure the dedicated region for SMM info communication whose attribute is "allocated" (i.e., excluded from SMM memory service)\r
+ //\r
+ if ((mSmramHob->Descriptor[Index].RegionState & EFI_ALLOCATED) == 0) {\r
+ DEBUG ((DEBUG_ERROR, "SMM communication region not set to EFI_ALLOCATED\n"));\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+\r
+ if (((UINTN)PldSmmInfo + PldSmmInfo->Header.Header.HobLength) > (mSmramHob->Descriptor[Index].PhysicalStart + mSmramHob->Descriptor[Index].PhysicalSize)) {\r
+ DEBUG ((DEBUG_ERROR, "SMM communication buffer (0x%x) is too small.\n", (UINTN)PldSmmInfo + PldSmmInfo->Header.Header.HobLength));\r
+ return EFI_BUFFER_TOO_SMALL;\r
+ }\r
+\r
+ CopyGuid (&PldSmmInfo->Header.Name, &gS3CommunicationGuid);\r
+ PldSmmInfo->Header.Header.HobType = EFI_HOB_TYPE_GUID_EXTENSION;\r
+ PldSmmInfo->S3Info.SwSmiTriggerValue = BlSwSmiHandlerInput;\r
+\r
+ //\r
+ // Save APIC ID and SMM base\r
+ //\r
+ Status = gBS->LocateProtocol (&gEfiMpServiceProtocolGuid, NULL, (VOID **)&MpService);\r
+ if (EFI_ERROR(Status)) {\r
+ return Status;\r
+ }\r
+ PldSmmInfo->S3Info.CpuCount = (UINT32)gSmst->NumberOfCpus;\r
+ SmmBaseInfo = &PldSmmInfo->S3Info.SmmBase[0];\r
+ for (Index = 0; Index < gSmst->NumberOfCpus; Index++) {\r
+ Status = MpService->GetProcessorInfo (MpService, Index, &ProcessorInfo);\r
+ if (EFI_ERROR(Status)) {\r
+ return Status;\r
+ }\r
+\r
+ SmmBaseInfo->ApicId = (UINT32)(UINTN)ProcessorInfo.ProcessorId;\r
+ SmmBaseInfo->SmmBase = (UINT32)(UINTN)gSmst->CpuSaveState[Index] - SMRAM_SAVE_STATE_MAP_OFFSET;\r
+ DEBUG ((DEBUG_INFO, "CPU%d ID:%02X Base: %08X\n", Index, SmmBaseInfo->ApicId, SmmBaseInfo->SmmBase));\r
+ SmmBaseInfo++;\r
+ }\r
+\r
+ return EFI_SUCCESS;\r
+}\r
+\r
+\r
+/**\r
+ Get specified SMI register based on given register ID\r
+\r
+ @param[in] Id The register ID to get.\r
+\r
+ @retval NULL The register is not found\r
+ @return smi register\r
+\r
+**/\r
+PLD_GENERIC_REGISTER *\r
+GetRegisterById (\r
+ UINT64 Id\r
+ )\r
+{\r
+ UINT32 Index;\r
+\r
+ for (Index = 0; Index < mSmmRegisterHob->Count; Index++) {\r
+ if (mSmmRegisterHob->Registers[Index].Id == Id) {\r
+ return &mSmmRegisterHob->Registers[Index];\r
+ }\r
+ }\r
+ return NULL;\r
+}\r
+\r
+/**\r
+ Set SMM SMI Global enable lock\r
+\r
+**/\r
+VOID\r
+LockSmiGlobalEn (\r
+ VOID\r
+ )\r
+{\r
+ PLD_GENERIC_REGISTER *SmiLockReg;\r
+\r
+ DEBUG ((DEBUG_ERROR, "LockSmiGlobalEn .....\n"));\r
+\r
+ SmiLockReg = GetRegisterById (REGISTER_ID_SMI_GBL_EN_LOCK);\r
+ if (SmiLockReg == NULL) {\r
+ DEBUG ((DEBUG_ERROR, "SMI global enable lock reg not found.\n"));\r
+ return;\r
+ }\r
+\r
+ //\r
+ // Set SMM SMI lock in S3 path\r
+ //\r
+ if ((SmiLockReg->Address.AccessSize == EFI_ACPI_3_0_DWORD) &&\r
+ (SmiLockReg->Address.Address != 0) &&\r
+ (SmiLockReg->Address.RegisterBitWidth == 1) &&\r
+ (SmiLockReg->Address.AddressSpaceId == EFI_ACPI_3_0_SYSTEM_MEMORY) &&\r
+ (SmiLockReg->Value == 1)) {\r
+ DEBUG ((DEBUG_ERROR, "LockSmiGlobalEn ....is locked\n"));\r
+\r
+ MmioOr32 ((UINT32)SmiLockReg->Address.Address, 1 << SmiLockReg->Address.RegisterBitOffset);\r
+ } else {\r
+ DEBUG ((DEBUG_ERROR, "Unexpected SMM SMI lock register, need enhancement here.\n"));\r
+ }\r
+}\r
+\r
+/**\r
+ Check and set SMM feature lock bit and code check enable bit\r
+ in S3 path.\r
+\r
+**/\r
+VOID\r
+SmmFeatureLockOnS3 (\r
+ VOID\r
+ )\r
+{\r
+\r
+ if (mSmmFeatureControl != 0) {\r
+ return;\r
+ }\r
+\r
+ mSmmFeatureControl = AsmReadMsr64(MSR_SMM_FEATURE_CONTROL);\r
+ if ((mSmmFeatureControl & 0x5) != 0x5) {\r
+ //\r
+ // Set Lock bit [BIT0] for this register and SMM code check enable bit [BIT2]\r
+ //\r
+ AsmWriteMsr64 (MSR_SMM_FEATURE_CONTROL, mSmmFeatureControl | 0x5);\r
+ }\r
+ mSmmFeatureControl = AsmReadMsr64(MSR_SMM_FEATURE_CONTROL);\r
+}\r
+\r
+\r
+\r
+/**\r
+ Function to program SMRR base and mask.\r
+\r
+ @param[in] ProcedureArgument Pointer to SMRR_BASE_MASK structure.\r
+**/\r
+VOID\r
+SetSmrr (\r
+ IN VOID *ProcedureArgument\r
+ )\r
+{\r
+ if (ProcedureArgument != NULL) {\r
+ AsmWriteMsr64 (MSR_IA32_SMRR_PHYSBASE, ((SMRR_BASE_MASK *)ProcedureArgument)->Base);\r
+ AsmWriteMsr64 (MSR_IA32_SMRR_PHYSMASK, ((SMRR_BASE_MASK *)ProcedureArgument)->Mask);\r
+ }\r
+}\r
+\r
+/**\r
+ Set SMRR in S3 path.\r
+\r
+**/\r
+VOID\r
+SetSmrrOnS3 (\r
+ VOID\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ SMRR_BASE_MASK Arguments;\r
+ UINTN Index;\r
+ UINT32 SmmBase;\r
+ UINT32 SmmSize;\r
+\r
+ if ((AsmReadMsr64 (MSR_IA32_SMRR_PHYSBASE) != 0) && ((AsmReadMsr64 (MSR_IA32_SMRR_PHYSMASK) & BIT11) != 0)) {\r
+ return;\r
+ }\r
+\r
+ SmmBase = (UINT32)(UINTN)mSmramHob->Descriptor[0].PhysicalStart;\r
+ SmmSize = (UINT32)(UINTN)mSmramHob->Descriptor[0].PhysicalSize;\r
+ if ((mSmramHob->NumberOfSmmReservedRegions > 2) || (mSmramHob->NumberOfSmmReservedRegions == 0)) {\r
+ DEBUG ((DEBUG_ERROR, "%d SMM ranges are not supported.\n", mSmramHob->NumberOfSmmReservedRegions));\r
+ return;\r
+ } else if (mSmramHob->NumberOfSmmReservedRegions == 2) {\r
+ if ((mSmramHob->Descriptor[1].PhysicalStart + mSmramHob->Descriptor[1].PhysicalSize) == SmmBase){\r
+ SmmBase = (UINT32)(UINTN)mSmramHob->Descriptor[1].PhysicalStart;\r
+ } else if (mSmramHob->Descriptor[1].PhysicalStart != (SmmBase + SmmSize)) {\r
+ DEBUG ((DEBUG_ERROR, "Two SMM regions are not continous.\n"));\r
+ return;\r
+ }\r
+ SmmSize += (UINT32)(UINTN)mSmramHob->Descriptor[1].PhysicalSize;\r
+ }\r
+\r
+ if ((SmmBase == 0) || (SmmSize < SIZE_4KB)) {\r
+ DEBUG ((DEBUG_ERROR, "Invalid SMM range.\n"));\r
+ return ;\r
+ }\r
+\r
+ //\r
+ // SMRR size must be of length 2^n\r
+ // SMRR base alignment cannot be less than SMRR length\r
+ //\r
+ if ((SmmSize != GetPowerOfTwo32 (SmmSize)) || ((SmmBase & ~(SmmSize - 1)) != SmmBase)) {\r
+ DEBUG ((DEBUG_ERROR, " Invalid SMM range.\n"));\r
+ return ;\r
+ }\r
+\r
+ //\r
+ // Calculate smrr base, mask and pass them as arguments.\r
+ //\r
+ Arguments.Base = (SmmSize | MTRR_CACHE_WRITE_BACK);\r
+ Arguments.Mask = (~(SmmSize - 1) & EFI_MSR_SMRR_MASK);\r
+\r
+ //\r
+ // Set SMRR valid bit\r
+ //\r
+ Arguments.Mask |= BIT11;\r
+\r
+ //\r
+ // Program smrr base and mask on BSP first and then on APs\r
+ //\r
+ SetSmrr(&Arguments);\r
+ for (Index = 0; Index < gSmst->NumberOfCpus; Index++) {\r
+ if (Index != gSmst->CurrentlyExecutingCpu) {\r
+ Status = gSmst->SmmStartupThisAp (SetSmrr, Index, (VOID *)&Arguments);\r
+ if (EFI_ERROR(Status)) {\r
+ DEBUG ((DEBUG_ERROR, "Programming SMRR on AP# %d status: %r\n", Index, Status));\r
+ }\r
+ }\r
+ }\r
+}\r
+\r
+\r
+/**\r
+ Software SMI callback for restoring SMRR base and mask in S3 path.\r
+\r
+ @param[in] DispatchHandle The unique handle assigned to this handler by SmiHandlerRegister().\r
+ @param[in] Context Points to an optional handler context which was specified when the\r
+ handler was registered.\r
+ @param[in, out] CommBuffer A pointer to a collection of data in memory that will\r
+ be conveyed from a non-SMM environment into an SMM environment.\r
+ @param[in, out] CommBufferSize The size of the CommBuffer.\r
+\r
+ @retval EFI_SUCCESS The interrupt was handled successfully.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+BlSwSmiHandler (\r
+ IN EFI_HANDLE DispatchHandle,\r
+ IN CONST VOID *Context,\r
+ IN OUT VOID *CommBuffer,\r
+ IN OUT UINTN *CommBufferSize\r
+ )\r
+{\r
+ SetSmrrOnS3 ();\r
+ SmmFeatureLockOnS3 ();\r
+ LockSmiGlobalEn ();\r
+\r
+ return EFI_SUCCESS;\r
+}\r
+\r
+\r
+/**\r
+ Lock SMI in this SMM ready to lock event.\r
+\r
+ @param Protocol Points to the protocol's unique identifier\r
+ @param Interface Points to the interface instance\r
+ @param Handle The handle on which the interface was installed\r
+\r
+ @retval EFI_SUCCESS SmmEventCallback runs successfully\r
+ @retval EFI_NOT_FOUND The Fvb protocol for variable is not found.\r
+ **/\r
+EFI_STATUS\r
+EFIAPI\r
+BlSupportSmmReadyToLockCallback (\r
+ IN CONST EFI_GUID *Protocol,\r
+ IN VOID *Interface,\r
+ IN EFI_HANDLE Handle\r
+ )\r
+{\r
+ //\r
+ // Set SMM SMI lock\r
+ //\r
+ LockSmiGlobalEn ();\r
+ return EFI_SUCCESS;\r
+}\r
+\r
+\r
+/**\r
+ The driver's entry point.\r
+\r
+ @param[in] ImageHandle The firmware allocated handle for the EFI image.\r
+ @param[in] SystemTable A pointer to the EFI System Table.\r
+\r
+ @retval EFI_SUCCESS The entry point is executed successfully.\r
+ @retval Others Some error occurs when executing this entry point.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+BlSupportSmm (\r
+ IN EFI_HANDLE ImageHandle,\r
+ IN EFI_SYSTEM_TABLE *SystemTable\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ EFI_SMM_SW_DISPATCH2_PROTOCOL *SwDispatch;\r
+ EFI_SMM_SW_REGISTER_CONTEXT SwContext;\r
+ EFI_HANDLE SwHandle;\r
+ EFI_HOB_GUID_TYPE *GuidHob;\r
+ VOID *SmmHob;\r
+ VOID *Registration;\r
+\r
+ //\r
+ // Get SMM S3 communication hob and save it\r
+ //\r
+ GuidHob = GetFirstGuidHob (&gS3CommunicationGuid);\r
+ if (GuidHob != NULL) {\r
+ SmmHob = (VOID *) (GET_GUID_HOB_DATA(GuidHob));\r
+ CopyMem (&mPldS3Hob, SmmHob, GET_GUID_HOB_DATA_SIZE(GuidHob));\r
+ } else {\r
+ return EFI_NOT_FOUND;\r
+ }\r
+\r
+ if (mPldS3Hob.PldAcpiS3Enable) {\r
+ // Other drivers will take care of S3.\r
+ return EFI_SUCCESS;\r
+ }\r
+\r
+ //\r
+ // Get smram hob and save it\r
+ //\r
+ GuidHob = GetFirstGuidHob (&gEfiSmmSmramMemoryGuid);\r
+ if (GuidHob != NULL) {\r
+ SmmHob = (VOID *) (GET_GUID_HOB_DATA(GuidHob));\r
+ mSmramHob = AllocatePool (GET_GUID_HOB_DATA_SIZE(GuidHob));\r
+ if (mSmramHob == NULL) {\r
+ return EFI_OUT_OF_RESOURCES;\r
+ }\r
+ CopyMem (mSmramHob, SmmHob, GET_GUID_HOB_DATA_SIZE(GuidHob));\r
+ } else {\r
+ return EFI_NOT_FOUND;\r
+ }\r
+\r
+ //\r
+ // Get SMM register hob and save it\r
+ //\r
+ GuidHob = GetFirstGuidHob (&gSmmRegisterInfoGuid);\r
+ if (GuidHob != NULL) {\r
+ SmmHob = (VOID *) (GET_GUID_HOB_DATA(GuidHob));\r
+ mSmmRegisterHob = AllocatePool (GET_GUID_HOB_DATA_SIZE(GuidHob));\r
+ if (mSmmRegisterHob == NULL) {\r
+ return EFI_OUT_OF_RESOURCES;\r
+ }\r
+ CopyMem (mSmmRegisterHob, SmmHob, GET_GUID_HOB_DATA_SIZE(GuidHob));\r
+ } else {\r
+ return EFI_NOT_FOUND;\r
+ }\r
+\r
+ //\r
+ // Get the Sw dispatch protocol and register SMI handler.\r
+ //\r
+ Status = gSmst->SmmLocateProtocol (&gEfiSmmSwDispatch2ProtocolGuid, NULL, (VOID**)&SwDispatch);\r
+ if (EFI_ERROR (Status)) {\r
+ return Status;\r
+ }\r
+ SwContext.SwSmiInputValue = (UINTN) -1;\r
+ Status = SwDispatch->Register (SwDispatch, BlSwSmiHandler, &SwContext, &SwHandle);\r
+ if (EFI_ERROR (Status)) {\r
+ DEBUG ((DEBUG_ERROR, "Registering S3 smi handler failed: %r\n", Status));\r
+ return Status;\r
+ }\r
+\r
+ //\r
+ // Register SMM ready to lock callback\r
+ //\r
+ Status = gSmst->SmmRegisterProtocolNotify (\r
+ &gEfiSmmReadyToLockProtocolGuid,\r
+ BlSupportSmmReadyToLockCallback,\r
+ &Registration\r
+ );\r
+ ASSERT_EFI_ERROR (Status);\r
+\r
+ SaveSmmInfoForS3 ((UINT8)SwContext.SwSmiInputValue);\r
+\r
+ return EFI_SUCCESS;\r
+}\r
+\r