| /**@file | |
| Negotiate SMI features with QEMU, and configure UefiCpuPkg/PiSmmCpuDxeSmm | |
| accordingly. | |
| Copyright (C) 2016-2017, Red Hat, Inc. | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include <Library/BaseLib.h> | |
| #include <Library/DebugLib.h> | |
| #include <Library/MemEncryptSevLib.h> | |
| #include <Library/MemoryAllocationLib.h> | |
| #include <Library/PcdLib.h> | |
| #include <Library/QemuFwCfgLib.h> | |
| #include <Library/QemuFwCfgS3Lib.h> | |
| #include "SmiFeatures.h" | |
| // | |
| // The following bit value stands for "broadcast SMI" in the | |
| // "etc/smi/supported-features" and "etc/smi/requested-features" fw_cfg files. | |
| // | |
| #define ICH9_LPC_SMI_F_BROADCAST BIT0 | |
| // | |
| // The following bit value stands for "enable CPU hotplug, and inject an SMI | |
| // with control value ICH9_APM_CNT_CPU_HOTPLUG upon hotplug", in the | |
| // "etc/smi/supported-features" and "etc/smi/requested-features" fw_cfg files. | |
| // | |
| #define ICH9_LPC_SMI_F_CPU_HOTPLUG BIT1 | |
| // | |
| // The following bit value stands for "enable CPU hot-unplug, and inject an SMI | |
| // with control value ICH9_APM_CNT_CPU_HOTPLUG upon hot-unplug", in the | |
| // "etc/smi/supported-features" and "etc/smi/requested-features" fw_cfg files. | |
| // | |
| #define ICH9_LPC_SMI_F_CPU_HOT_UNPLUG BIT2 | |
| // | |
| // Provides a scratch buffer (allocated in EfiReservedMemoryType type memory) | |
| // for the S3 boot script fragment to write to and read from. | |
| // | |
| #pragma pack (1) | |
| typedef union { | |
| UINT64 Features; | |
| UINT8 FeaturesOk; | |
| } SCRATCH_BUFFER; | |
| #pragma pack () | |
| // | |
| // These carry the selector keys of the "etc/smi/requested-features" and | |
| // "etc/smi/features-ok" fw_cfg files from NegotiateSmiFeatures() to | |
| // AppendFwCfgBootScript(). | |
| // | |
| STATIC FIRMWARE_CONFIG_ITEM mRequestedFeaturesItem; | |
| STATIC FIRMWARE_CONFIG_ITEM mFeaturesOkItem; | |
| // | |
| // Carries the negotiated SMI features from NegotiateSmiFeatures() to | |
| // AppendFwCfgBootScript(). | |
| // | |
| STATIC UINT64 mSmiFeatures; | |
| /** | |
| Negotiate SMI features with QEMU. | |
| @retval FALSE If SMI feature negotiation is not supported by QEMU. This is | |
| not an error, it just means that SaveSmiFeatures() should not | |
| be called. | |
| @retval TRUE SMI feature negotiation is supported, and it has completed | |
| successfully as well. (Failure to negotiate is a fatal error | |
| and the function never returns in that case.) | |
| **/ | |
| BOOLEAN | |
| NegotiateSmiFeatures ( | |
| VOID | |
| ) | |
| { | |
| FIRMWARE_CONFIG_ITEM SupportedFeaturesItem; | |
| UINTN SupportedFeaturesSize; | |
| UINTN RequestedFeaturesSize; | |
| UINTN FeaturesOkSize; | |
| UINT64 RequestedFeaturesMask; | |
| // | |
| // Look up the fw_cfg files used for feature negotiation. The selector keys | |
| // of "etc/smi/requested-features" and "etc/smi/features-ok" are saved | |
| // statically. If the files are missing, then QEMU doesn't support SMI | |
| // feature negotiation. | |
| // | |
| if (RETURN_ERROR ( | |
| QemuFwCfgFindFile ( | |
| "etc/smi/supported-features", | |
| &SupportedFeaturesItem, | |
| &SupportedFeaturesSize | |
| ) | |
| ) || | |
| RETURN_ERROR ( | |
| QemuFwCfgFindFile ( | |
| "etc/smi/requested-features", | |
| &mRequestedFeaturesItem, | |
| &RequestedFeaturesSize | |
| ) | |
| ) || | |
| RETURN_ERROR ( | |
| QemuFwCfgFindFile ( | |
| "etc/smi/features-ok", | |
| &mFeaturesOkItem, | |
| &FeaturesOkSize | |
| ) | |
| )) | |
| { | |
| DEBUG (( | |
| DEBUG_INFO, | |
| "%a: SMI feature negotiation unavailable\n", | |
| __func__ | |
| )); | |
| return FALSE; | |
| } | |
| // | |
| // If the files are present but their sizes disagree with us, that's a fatal | |
| // error (we can't trust the behavior of SMIs either way). | |
| // | |
| if ((SupportedFeaturesSize != sizeof mSmiFeatures) || | |
| (RequestedFeaturesSize != sizeof mSmiFeatures) || | |
| (FeaturesOkSize != sizeof (UINT8))) | |
| { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: size mismatch in feature negotiation\n", | |
| __func__ | |
| )); | |
| goto FatalError; | |
| } | |
| // | |
| // Get the features supported by the host. | |
| // | |
| QemuFwCfgSelectItem (SupportedFeaturesItem); | |
| QemuFwCfgReadBytes (sizeof mSmiFeatures, &mSmiFeatures); | |
| // | |
| // We want broadcast SMI, SMI on CPU hotplug, SMI on CPU hot-unplug | |
| // and nothing else. | |
| // | |
| RequestedFeaturesMask = ICH9_LPC_SMI_F_BROADCAST; | |
| if (!MemEncryptSevIsEnabled ()) { | |
| // | |
| // For now, we only support hotplug with SEV disabled. | |
| // | |
| RequestedFeaturesMask |= ICH9_LPC_SMI_F_CPU_HOTPLUG; | |
| RequestedFeaturesMask |= ICH9_LPC_SMI_F_CPU_HOT_UNPLUG; | |
| } | |
| mSmiFeatures &= RequestedFeaturesMask; | |
| QemuFwCfgSelectItem (mRequestedFeaturesItem); | |
| QemuFwCfgWriteBytes (sizeof mSmiFeatures, &mSmiFeatures); | |
| // | |
| // Invoke feature validation in QEMU. If the selection is accepted, the | |
| // features will be locked down. If the selection is rejected, feature | |
| // negotiation remains open; however we don't know what to do in that case, | |
| // so that's a fatal error. | |
| // | |
| QemuFwCfgSelectItem (mFeaturesOkItem); | |
| if (QemuFwCfgRead8 () != 1) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: negotiation failed for feature bitmap 0x%Lx\n", | |
| __func__, | |
| mSmiFeatures | |
| )); | |
| goto FatalError; | |
| } | |
| if ((mSmiFeatures & ICH9_LPC_SMI_F_BROADCAST) == 0) { | |
| // | |
| // If we can't get broadcast SMIs from QEMU, that's acceptable too, | |
| // although not optimal. | |
| // | |
| DEBUG ((DEBUG_INFO, "%a: SMI broadcast unavailable\n", __func__)); | |
| } else { | |
| // | |
| // Configure the traditional AP sync / SMI delivery mode for | |
| // PiSmmCpuDxeSmm. Effectively, restore the UefiCpuPkg defaults, from which | |
| // the original QEMU behavior (i.e., unicast SMI) used to differ. | |
| // | |
| if (RETURN_ERROR (PcdSet64S (PcdCpuSmmApSyncTimeout, 1000000)) || | |
| RETURN_ERROR (PcdSet8S (PcdCpuSmmSyncMode, 0x00))) | |
| { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "%a: PiSmmCpuDxeSmm PCD configuration failed\n", | |
| __func__ | |
| )); | |
| goto FatalError; | |
| } | |
| DEBUG ((DEBUG_INFO, "%a: using SMI broadcast\n", __func__)); | |
| } | |
| if ((mSmiFeatures & ICH9_LPC_SMI_F_CPU_HOTPLUG) == 0) { | |
| DEBUG ((DEBUG_INFO, "%a: CPU hotplug not negotiated\n", __func__)); | |
| } else { | |
| DEBUG (( | |
| DEBUG_INFO, | |
| "%a: CPU hotplug with SMI negotiated\n", | |
| __func__ | |
| )); | |
| } | |
| if ((mSmiFeatures & ICH9_LPC_SMI_F_CPU_HOT_UNPLUG) == 0) { | |
| DEBUG ((DEBUG_INFO, "%a: CPU hot-unplug not negotiated\n", __func__)); | |
| } else { | |
| DEBUG (( | |
| DEBUG_INFO, | |
| "%a: CPU hot-unplug with SMI negotiated\n", | |
| __func__ | |
| )); | |
| } | |
| // | |
| // Negotiation successful (although we may not have gotten the optimal | |
| // feature set). | |
| // | |
| return TRUE; | |
| FatalError: | |
| ASSERT (FALSE); | |
| CpuDeadLoop (); | |
| // | |
| // Keep the compiler happy. | |
| // | |
| return FALSE; | |
| } | |
| /** | |
| FW_CFG_BOOT_SCRIPT_CALLBACK_FUNCTION provided to QemuFwCfgS3Lib. | |
| **/ | |
| STATIC | |
| VOID | |
| EFIAPI | |
| AppendFwCfgBootScript ( | |
| IN OUT VOID *Context OPTIONAL, | |
| IN OUT VOID *ExternalScratchBuffer | |
| ) | |
| { | |
| SCRATCH_BUFFER *ScratchBuffer; | |
| RETURN_STATUS Status; | |
| ScratchBuffer = ExternalScratchBuffer; | |
| // | |
| // Write the negotiated feature bitmap into "etc/smi/requested-features". | |
| // | |
| ScratchBuffer->Features = mSmiFeatures; | |
| Status = QemuFwCfgS3ScriptWriteBytes ( | |
| mRequestedFeaturesItem, | |
| sizeof ScratchBuffer->Features | |
| ); | |
| if (RETURN_ERROR (Status)) { | |
| goto FatalError; | |
| } | |
| // | |
| // Read back "etc/smi/features-ok". This invokes the feature validation & | |
| // lockdown. (The validation succeeded at first boot.) | |
| // | |
| Status = QemuFwCfgS3ScriptReadBytes ( | |
| mFeaturesOkItem, | |
| sizeof ScratchBuffer->FeaturesOk | |
| ); | |
| if (RETURN_ERROR (Status)) { | |
| goto FatalError; | |
| } | |
| // | |
| // If "etc/smi/features-ok" read as 1, we're good. Otherwise, hang the S3 | |
| // resume process. | |
| // | |
| Status = QemuFwCfgS3ScriptCheckValue ( | |
| &ScratchBuffer->FeaturesOk, | |
| sizeof ScratchBuffer->FeaturesOk, | |
| MAX_UINT8, | |
| 1 | |
| ); | |
| if (RETURN_ERROR (Status)) { | |
| goto FatalError; | |
| } | |
| DEBUG (( | |
| DEBUG_VERBOSE, | |
| "%a: SMI feature negotiation boot script saved\n", | |
| __func__ | |
| )); | |
| return; | |
| FatalError: | |
| ASSERT (FALSE); | |
| CpuDeadLoop (); | |
| } | |
| /** | |
| Append a boot script fragment that will re-select the previously negotiated | |
| SMI features during S3 resume. | |
| **/ | |
| VOID | |
| SaveSmiFeatures ( | |
| VOID | |
| ) | |
| { | |
| RETURN_STATUS Status; | |
| // | |
| // We are already running at TPL_CALLBACK, on the stack of | |
| // OnS3SaveStateInstalled(). But that's okay, we can easily queue more | |
| // notification functions while executing a notification function. | |
| // | |
| Status = QemuFwCfgS3CallWhenBootScriptReady ( | |
| AppendFwCfgBootScript, | |
| NULL, | |
| sizeof (SCRATCH_BUFFER) | |
| ); | |
| if (RETURN_ERROR (Status)) { | |
| ASSERT (FALSE); | |
| CpuDeadLoop (); | |
| } | |
| } |