CC'ing Mike and Jiewen On 10/13/15 12:32, Paolo Bonzini wrote: > > > On 09/10/2015 23:58, Laszlo Ersek wrote: >> diff --git a/OvmfPkg/SmmControl2Dxe/SmmControl2Dxe.c >> b/OvmfPkg/SmmControl2Dxe/SmmControl2Dxe.c >> new file mode 100644 >> index 0000000..e895fd6 >> --- /dev/null >> +++ b/OvmfPkg/SmmControl2Dxe/SmmControl2Dxe.c >> @@ -0,0 +1,365 @@ >> +/** @file >> + >> + A DXE_RUNTIME_DRIVER providing synchronous SMI activations via the >> + EFI_SMM_CONTROL2_PROTOCOL. >> + >> + 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.<BR> >> + Copyright (c) 2009 - 2010, Intel Corporation. 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 <IndustryStandard/Q35MchIch9.h> >> +#include <Library/DebugLib.h> >> +#include <Library/IoLib.h> >> +#include <Library/PcdLib.h> >> +#include <Library/PciLib.h> >> +#include <Library/QemuFwCfgLib.h> >> +#include <Library/UefiBootServicesTableLib.h> >> +#include <Protocol/S3SaveState.h> >> +#include <Protocol/SmmControl2.h> >> + >> +// >> +// The absolute IO port address of the SMI Control and Enable Register. It >> is >> +// only used to carry information from the entry point function to the >> +// S3SaveState protocol installation callback, strictly before the runtime >> +// phase. >> +// >> +STATIC UINTN mSmiEnable; >> + >> +// >> +// Event signaled when an S3SaveState protocol interface is installed. >> +// >> +STATIC EFI_EVENT mS3SaveStateInstalled; >> + >> +/** >> + Invokes SMI activation from either the preboot or runtime environment. >> + >> + This function generates an SMI. >> + >> + @param[in] This The EFI_SMM_CONTROL2_PROTOCOL instance. >> + @param[in,out] CommandPort The value written to the command port. >> + @param[in,out] DataPort The value written to the data port. >> + @param[in] Periodic Optional mechanism to engender a >> periodic >> + stream. >> + @param[in] ActivationInterval Optional parameter to repeat at this >> + period one time or, if the Periodic >> + Boolean is set, periodically. >> + >> + @retval EFI_SUCCESS The SMI/PMI has been engendered. >> + @retval EFI_DEVICE_ERROR The timing is unsupported. >> + @retval EFI_INVALID_PARAMETER The activation period is unsupported. >> + @retval EFI_INVALID_PARAMETER The last periodic activation has not been >> + cleared. >> + @retval EFI_NOT_STARTED The SMM base service has not been >> initialized. >> +**/ >> +STATIC >> +EFI_STATUS >> +EFIAPI >> +SmmControl2DxeTrigger ( >> + IN CONST EFI_SMM_CONTROL2_PROTOCOL *This, >> + IN OUT UINT8 *CommandPort OPTIONAL, >> + IN OUT UINT8 *DataPort OPTIONAL, >> + IN BOOLEAN Periodic OPTIONAL, >> + IN UINTN ActivationInterval OPTIONAL >> + ) >> +{ >> + // >> + // No support for queued or periodic activation. >> + // >> + if (Periodic || ActivationInterval > 0) { >> + return EFI_DEVICE_ERROR; >> + } >> + >> + // >> + // 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, DataPort == NULL ? 0 : *DataPort); >> + IoWrite8 (ICH9_APM_CNT, CommandPort == NULL ? 0 : *CommandPort); >> + return EFI_SUCCESS; >> +} > > I tested multiprocessor SMM with your branch, and we're really close. > KVM doesn't handle SMIs correctly (ironically I fixed the same bug in > QEMU with commit a9bad65, "target-i386: wake up processors that > receive an SMI", 2015-05-19). TCG boots... but it takes about 5 > minutes. > > This is because the SMI handler, and in particular > SmmWaitForApArrival, expects that SmmControl2DxeTrigger triggers an > SMI IPI on all processors rather than just the BSP. QEMU's port 0xb2 > only affects the current CPU, thus all SMM invocations loop for a > second (the default value of PcdCpuSmmApSyncTimeout) before > SmmWaitForApArrival sends another SMI IPI to the APs. > > This could also be fixed by setting > gUefiCpuPkgTokenSpaceGuid.PcdCpuSmmSyncMode to 1 (aka > SmmCpuSyncModeRelaxedAp), but this only takes effect on X64 CPUs which > also ironically we don't support yet. Therefore, the following seems > to be the correct fix. With it, TCG still takes a while to boot, but > KVM gets to the UEFI shell very fast. > > [pbonz...@redhat.com: Use LocalApicLib to send trigger an SMI on all > processors] > Contributed-under: TianoCore Contribution Agreement 1.0 > Signed-off-by: Paolo Bonzini <pbonz...@redhat.com> > > diff --git a/OvmfPkg/SmmControl2Dxe/SmmControl2Dxe.c > b/OvmfPkg/SmmControl2Dxe/SmmControl2Dxe.c > index e895fd6..17fbb88 100644 > --- a/OvmfPkg/SmmControl2Dxe/SmmControl2Dxe.c > +++ b/OvmfPkg/SmmControl2Dxe/SmmControl2Dxe.c > @@ -28,6 +28,7 @@ > #include <IndustryStandard/Q35MchIch9.h> > #include <Library/DebugLib.h> > #include <Library/IoLib.h> > +#include <Library/LocalApicLib.h> > #include <Library/PcdLib.h> > #include <Library/PciLib.h> > #include <Library/QemuFwCfgLib.h> > @@ -88,6 +89,15 @@ SmmControl2DxeTrigger ( > } > > // > + // The write to the control register is synchronous and only affects the > + // current CPU, so bring in the APs first. The SMI handler expects that > + // all APs will rendez-vous within one PcdCpuSmmApSyncTimeout (though it > + // helpfully tries sending SMI IPIs to the missing processors if there are > + // any). > + // > + SendSmiIpiAllExcludingSelf (); > + > + // > // 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 > diff --git a/OvmfPkg/SmmControl2Dxe/SmmControl2Dxe.inf > b/OvmfPkg/SmmControl2Dxe/SmmControl2Dxe.inf > index bc66a27..f9c4821 100644 > --- a/OvmfPkg/SmmControl2Dxe/SmmControl2Dxe.inf > +++ b/OvmfPkg/SmmControl2Dxe/SmmControl2Dxe.inf > @@ -44,10 +44,12 @@ > [Packages] > MdePkg/MdePkg.dec > OvmfPkg/OvmfPkg.dec > + UefiCpuPkg/UefiCpuPkg.dec > > [LibraryClasses] > DebugLib > IoLib > + LocalApicLib > PcdLib > PciLib > QemuFwCfgLib >
Thank you. This looks very effective, and it's also easy to squash into my patch (with the right attribution of course). However, it raises a number of interesting questions. First of all, if the edk2 reference code (in the SMM core and in PiSmmCpuDxeSmm) depends on such behavior justifiedly, then I think we have a bug in the PI specification. Namely, version 1.4 thereof does not seem to require that EFI_SMM_CONTROL2_PROTOCOL.Trigger() raise an SMI on *all* processors. (See volume 4, section 5.4.) In particular, the EFI_SMM_CONTROL2_PROTOCOL.Trigger() specification makes many references to SMI handling and dispatching. But I cannot make a mental connection between an SMI "broadcast", and "handling and dispatching" in edk2, since all "handling and dispatching" code in edk2 that is exposed via protocols, is non-reentrant with regard to multiple VCPUs, to my knowledge. One could argue that whatever code handles the SMI on the BP is responsible for bringing the APs into SMM as well, before doing any sensitive work. I'm not sure. Second, if writing to ioport 0xb2 should *automatically* raise an SMI on all processors, then the QEMU code could be incorrect. However I could never derive such an "imperative" from the ICH9 spec. Third, we can look at the implementation of EFI_SMM_CONTROL2_PROTOCOL in the Quark package. It is in the file QuarkSocPkg/QuarkNorthCluster/Smm/Dxe/SmmControlDxe/SmmControlDriver.c and the relevant function is called Activate(). ( For easy sharing, I just googled the filename, hoping for at least one hit, and yes there is one: https://kernel.googlesource.com/pub/scm/linux/kernel/git/jejb/Quark_EDKII/+/master/QuarkSocPkg/QuarkNorthCluster/Smm/Dxe/SmmControlDxe/SmmControlDriver.c ) It is very similar to my code (although any similarity is purely unintended); it doesn't seem to send SMI IPIs to APs. On the other hand, I don't know if Quark is a UP vs. MP system to begin with. So, I think we're missing something in the design, and in the "interplay" between the PI spec and the edk2 reference code (SMM core, PiSmmCpuDxeSmm). Again, I'd be very glad (and thankful) to pick up this hunk (and to give credit), but first I'd like to understand why we have to resort to this, given that my implementation (without this fixup) *should* already conform to the PI spec. Let's look at the context where EFI_SMM_CONTROL2_PROTOCOL.Trigger() is invoked. First, the setup. When PiSmmCpuSmmDxe loads, it installs the EFI_SMM_CONFIGURATION_PROTOCOL interface. This happens in PiCpuSmmEntry(), file "UefiCpuPkg/PiSmmCpuDxeSmm/PiSmmCpuDxeSmm.c". The one member function exposed by that interface is RegisterSmmEntry(). Now, when this protocol iface is installed, the SMM core gets a protocol notify callback -- see the SmmIplSmmConfigurationEventNotify() function in "MdeModulePkg/Core/PiSmmCore/PiSmmIpl.c" -- and the SMM core calls the RegisterSmmEntry() function of PiSmmCpuSmmDxe right away. The function pointer argument that is passed in comes from SmmMain() [MdeModulePkg/Core/PiSmmCore/PiSmmCore.c], and it points to SmmEntryPoint() [same file]. This in turn will result in PiSmmCpuSmmDxe stashing a pointer to the SmmEntryPoint() function of the SMM core in "gSmmCpuPrivate->SmmCoreEntry". See the RegisterSmmEntry() function in "UefiCpuPkg/PiSmmCpuDxeSmm/MpService.c". So, for the setup, we can come up with the following call trees: - When the SMM core launches: SmmMain() [MdeModulePkg/Core/PiSmmCore/PiSmmCore.c] gSmmCorePrivate->SmmEntryPoint = SmmEntryPoint - When PiSmmCpuDxeSmm is loaded into SMRAM and started: PiCpuSmmEntry() [UefiCpuPkg/PiSmmCpuDxeSmm/PiSmmCpuDxeSmm.c] InstallMultipleProtocolInterfaces() [...] SmmIplSmmConfigurationEventNotify() [MdeModulePkg/Core/PiSmmCore/PiSmmIpl.c] RegisterSmmEntry() [UefiCpuPkg/PiSmmCpuDxeSmm/MpService.c] gSmmCpuPrivate->SmmCoreEntry = SmmEntryPoint Second, the handling and dispatching. The only location that matters for us is the EFI_SMM_CONTROL2_PROTOCOL.Trigger() call in "MdeModulePkg/Core/PiSmmCore/PiSmmIpl.c", the SmmCommunicationCommunicate() function, which is the implementation of the EFI_SMM_COMMUNICATION_PROTOCOL.Communicate() member. The sole purpose of this function is to "call into SMM" with a preformatted "communication buffer" (a message) from the outside. All the drivers that are split into non-priv and priv halves rely on this service to call their priv halves. So, assuming a driver wishes to make such a privileged call, it ends up in SmmCommunicationCommunicate(). From then onwards: SmmCommunicationCommunicate() [MdeModulePkg/Core/PiSmmCore/PiSmmIpl.c] SmmControl2DxeTrigger() [OvmfPkg/SmmControl2Dxe/SmmControl2Dxe.c] // // by way of SMI -- privileged from here on // _SmiEntryPoint() [UefiCpuPkg/PiSmmCpuDxeSmm/Ia32/SmiEntry.S] SmiRendezvous() [UefiCpuPkg/PiSmmCpuDxeSmm/MpService.c] BSPHandler() [UefiCpuPkg/PiSmmCpuDxeSmm/MpService.c] SmmEntryPoint() [MdeModulePkg/Core/PiSmmCore/PiSmmCore.c] SmiManage() [MdeModulePkg/Core/PiSmmCore/Smi.c] // // dispatch // APHandler() [UefiCpuPkg/PiSmmCpuDxeSmm/MpService.c] ... But, you are completely right. SmiRendezvous() expects to be executed concurrently, by several processors. I just don't have the slightest clue why the APs would *be there*. So, something is missing. Either the EFI_SMM_CONTROL2_PROTOCOL.Trigger() spec is incomplete in PI, or QEMU's emulation of APM_CNT is not appropriate. Either way, I'm okay with this fix, but I'd like to understand the expectations. Mike, Jiewen, can you please help investigate this? Thanks! Laszlo _______________________________________________ edk2-devel mailing list edk2-devel@lists.01.org https://lists.01.org/mailman/listinfo/edk2-devel