diff --git a/OvmfPkg/SmmControl2Dxe/MmControlPei.c b/OvmfPkg/SmmControl2Dxe/MmControlPei.c new file mode 100644 index 0000000000..8feac62e32 --- /dev/null +++ b/OvmfPkg/SmmControl2Dxe/MmControlPei.c @@ -0,0 +1,271 @@ +/** @file + + A PEIM providing synchronous SMI activations via the + EFI_PEI_MM_CONTROL_PPI. + + We expect the PEI phase to have covered the following: + - ensure that the underlying QEMU machine type be Q35 + (responsible: OvmfPkg/SmmAccess/SmmAccessPei.inf) + - ensure that the ACPI PM IO space be configured + (responsible: OvmfPkg/PlatformPei/PlatformPei.inf) + + Our own entry point is responsible for confirming the SMI feature and for + configuring it. + + Copyright (C) 2013, 2015, Red Hat, Inc.
+ Copyright (c) 2009 - 2010, Intel Corporation. All rights reserved.
+ Copyright (c) Microsoft Corporation. + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "SmiFeatures.h" + +// +// The absolute IO port address of the SMI Control and Enable Register. +// +STATIC UINTN mSmiEnable; + +/** + Invokes PPI activation from the PI PEI environment. + + @param PeiServices An indirect pointer to the PEI Services Table published by the PEI Foundation. + @param This The PEI_MM_CONTROL_PPI instance. + @param ArgumentBuffer The value passed to the MMI handler. This value corresponds to the + SwMmiInputValue in the RegisterContext parameter for the Register() + function in the EFI_MM_SW_DISPATCH_PROTOCOL and in the Context parameter + in the call to the DispatchFunction + @param ArgumentBufferSize The size of the data passed in ArgumentBuffer or NULL if ArgumentBuffer is NULL. + @param Periodic An optional mechanism to periodically repeat activation. + @param ActivationInterval An optional parameter to repeat at this period one + time or, if the Periodic Boolean is set, periodically. + + @retval EFI_SUCCESS The MMI has been engendered. + @retval EFI_DEVICE_ERROR The timing is unsupported. + @retval EFI_INVALID_PARAMETER The activation period is unsupported. + @retval EFI_NOT_STARTED The MM base service has not been initialized. + +**/ +STATIC +EFI_STATUS +EFIAPI +MmControlPeiTrigger ( + IN EFI_PEI_SERVICES **PeiServices, + IN EFI_PEI_MM_CONTROL_PPI *This, + IN OUT INT8 *ArgumentBuffer OPTIONAL, + IN OUT UINTN *ArgumentBufferSize OPTIONAL, + IN BOOLEAN Periodic OPTIONAL, + IN UINTN ActivationInterval OPTIONAL + ) +{ + UINT8 Command; + UINT8 Data; + + // + // No support for queued or periodic activation. + // + if (Periodic || (ActivationInterval > 0)) { + return EFI_DEVICE_ERROR; + } + + if ((ArgumentBufferSize != NULL) && (ArgumentBuffer == NULL)) { + return EFI_INVALID_PARAMETER; + } + + // + // The 0th byte as command, 1st as data... Right? + // + if ((NULL == ArgumentBuffer) || (*ArgumentBufferSize == 0)) { + Command = 0; + Data = 0; + } else if (*ArgumentBufferSize == 1) { + Command = *ArgumentBuffer; + Data = 0; + } else if (*ArgumentBufferSize == 2) { + Command = ArgumentBuffer[0]; + Data = ArgumentBuffer[1]; + } else { + return EFI_INVALID_PARAMETER; + } + + // + // The so-called "Advanced Power Management Status Port Register" is in fact + // a generic data passing register, between the caller and the SMI + // dispatcher. The ICH9 spec calls it "scratchpad register" -- calling it + // "status" elsewhere seems quite the misnomer. Status registers usually + // report about hardware status, while this register is fully governed by + // software. + // + // Write to the status register first, as this won't trigger the SMI just + // yet. Then write to the control register. + // + IoWrite8 (ICH9_APM_STS, Data); + IoWrite8 (ICH9_APM_CNT, Command); + return EFI_SUCCESS; +} + +/** + Clears any system state that was created in response to the Trigger() call. + + @param PeiServices General purpose services available to every PEIM. + @param This The PEI_MM_CONTROL_PPI instance. + @param Periodic Optional parameter to repeat at this period one + time or, if the Periodic Boolean is set, periodically. + + @retval EFI_SUCCESS The MMI has been engendered. + @retval EFI_DEVICE_ERROR The source could not be cleared. + @retval EFI_INVALID_PARAMETER The service did not support the Periodic input argument. + +**/ +STATIC +EFI_STATUS +EFIAPI +MmControlPeiClear ( + IN EFI_PEI_SERVICES **PeiServices, + IN EFI_PEI_MM_CONTROL_PPI *This, + IN BOOLEAN Periodic OPTIONAL + ) +{ + if (Periodic) { + return EFI_INVALID_PARAMETER; + } + + // + // The PI spec v1.4 explains that Clear() is only supposed to clear software + // status; it is not in fact responsible for deasserting the SMI. It gives + // two reasons for this: (a) many boards clear the SMI automatically when + // entering SMM, (b) if Clear() actually deasserted the SMI, then it could + // incorrectly suppress an SMI that was asynchronously asserted between the + // last return of the SMI handler and the call made to Clear(). + // + // In fact QEMU automatically deasserts CPU_INTERRUPT_SMI in: + // - x86_cpu_exec_interrupt() [target-i386/seg_helper.c], and + // - kvm_arch_pre_run() [target-i386/kvm.c]. + // + // So, nothing to do here. + // + return EFI_SUCCESS; +} + +STATIC EFI_PEI_MM_CONTROL_PPI mMmControl = +{ + .Trigger = MmControlPeiTrigger, + .Clear = MmControlPeiClear +}; + +STATIC EFI_PEI_PPI_DESCRIPTOR mSmmControlPpiList = +{ + (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), + &gEfiPeiMmControlPpiGuid, + &mMmControl +}; + +// +// Entry point of this driver. +// +EFI_STATUS +EFIAPI +MmControlPeiEntryPoint ( + IN EFI_PEI_FILE_HANDLE FileHandle, + IN CONST EFI_PEI_SERVICES **PeiServices + ) +{ + UINT32 PmBase; + UINT32 SmiEnableVal; + EFI_STATUS Status; + + // + // This module should only be included if SMRAM support is required. + // + ASSERT (FeaturePcdGet (PcdSmmSmramRequire)); + + // + // Calculate the absolute IO port address of the SMI Control and Enable + // Register. (As noted at the top, the PEI phase has left us with a working + // ACPI PM IO space.) + // + PmBase = PciRead32 (POWER_MGMT_REGISTER_Q35 (ICH9_PMBASE)) & + ICH9_PMBASE_MASK; + mSmiEnable = PmBase + ICH9_PMBASE_OFS_SMI_EN; + + // + // If APMC_EN is pre-set in SMI_EN, that's QEMU's way to tell us that SMI + // support is not available. (For example due to KVM lacking it.) Otherwise, + // this bit is clear after each reset. + // + SmiEnableVal = IoRead32 (mSmiEnable); + if ((SmiEnableVal & ICH9_SMI_EN_APMC_EN) != 0) { + DEBUG (( + DEBUG_ERROR, + "%a: this Q35 implementation lacks SMI\n", + __func__ + )); + goto FatalError; + } + + // + // Otherwise, configure the board to inject an SMI when ICH9_APM_CNT is + // written to. (See the Trigger() method above.) + // + SmiEnableVal |= ICH9_SMI_EN_APMC_EN | ICH9_SMI_EN_GBL_SMI_EN; + IoWrite32 (mSmiEnable, SmiEnableVal); + + // + // Prevent software from undoing the above (until platform reset). + // + PciOr16 ( + POWER_MGMT_REGISTER_Q35 (ICH9_GEN_PMCON_1), + ICH9_GEN_PMCON_1_SMI_LOCK + ); + + // + // If we can clear GBL_SMI_EN now, that means QEMU's SMI support is not + // appropriate. + // + IoWrite32 (mSmiEnable, SmiEnableVal & ~(UINT32)ICH9_SMI_EN_GBL_SMI_EN); + if (IoRead32 (mSmiEnable) != SmiEnableVal) { + DEBUG (( + DEBUG_ERROR, + "%a: failed to lock down GBL_SMI_EN\n", + __func__ + )); + goto FatalError; + } + + // + // We have no pointers to convert to virtual addresses. The handle itself + // doesn't matter, as protocol services are not accessible at runtime. + // + Status = (*PeiServices)->InstallPpi (PeiServices, &mSmmControlPpiList); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "%a: InstallMultipleProtocolInterfaces: %r\n", + __func__, + Status + )); + goto FatalError; + } + + return EFI_SUCCESS; + +FatalError: + // + // We really don't want to continue in this case. + // + ASSERT (FALSE); + CpuDeadLoop (); + return EFI_UNSUPPORTED; +} diff --git a/OvmfPkg/SmmControl2Dxe/MmControlPei.inf b/OvmfPkg/SmmControl2Dxe/MmControlPei.inf new file mode 100644 index 0000000000..cd14ae337f --- /dev/null +++ b/OvmfPkg/SmmControl2Dxe/MmControlPei.inf @@ -0,0 +1,70 @@ +## @file +# A PEIM providing synchronous SMI activations via the +# EFI_PEI_MM_CONTROL_PPI. +# +# We expect the PEI phase to have covered the following: +# - ensure that the underlying QEMU machine type be Q35 +# (responsible: QemuQ35Pkg/SmmAccess/SmmAccessPei.inf) +# - ensure that the ACPI PM IO space be configured +# (responsible: QemuQ35Pkg/PlatformPei/PlatformPei.inf) +# +# Our own entry point is responsible for confirming the SMI feature and for +# configuring it. +# +# Copyright (C) 2013, 2015, Red Hat, Inc. +# Copyright (c) Microsoft Corporation. +# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = MmControlPei + FILE_GUID = A0DBE438-701C-44AF-AB4A-25545B86B565 + MODULE_TYPE = PEIM + VERSION_STRING = 1.0 + ENTRY_POINT = MmControlPeiEntryPoint + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 +# + +[Sources] + SmiFeatures.h + SmiFeatures.c + MmControlPei.c + +[Packages] + MdePkg/MdePkg.dec + OvmfPkg/OvmfPkg.dec + UefiCpuPkg/UefiCpuPkg.dec + +[LibraryClasses] + BaseLib + DebugLib + IoLib + MemEncryptSevLib + MemoryAllocationLib + PcdLib + PciLib + QemuFwCfgLib + QemuFwCfgS3Lib + PeimEntryPoint + +[Ppis] + gEfiPeiMmControlPpiGuid ## PRODUCES + +[Pcd] + gUefiCpuPkgTokenSpaceGuid.PcdCpuSmmApSyncTimeout ## SOMETIMES_PRODUCES + gUefiCpuPkgTokenSpaceGuid.PcdCpuSmmApSyncTimeout2 ## SOMETIMES_PRODUCES + gUefiCpuPkgTokenSpaceGuid.PcdCpuSmmSyncMode ## SOMETIMES_PRODUCES + +[FeaturePcd] + gUefiOvmfPkgTokenSpaceGuid.PcdSmmSmramRequire + gUefiOvmfPkgTokenSpaceGuid.PcdStandaloneMmEnable + +[Depex] + TRUE