--- /dev/null
+/**@file\r
+ Negotiate SMI features with QEMU, and configure UefiCpuPkg/PiSmmCpuDxeSmm\r
+ accordingly.\r
+\r
+ Copyright (C) 2016-2017, Red Hat, Inc.\r
+\r
+ This program and the accompanying materials are licensed and made available\r
+ under the terms and conditions of the BSD License which accompanies this\r
+ distribution. The full text of the license may be found at\r
+ http://opensource.org/licenses/bsd-license.php\r
+\r
+ THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT\r
+ WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
+**/\r
+\r
+#include <Library/BaseLib.h>\r
+#include <Library/DebugLib.h>\r
+#include <Library/MemoryAllocationLib.h>\r
+#include <Library/PcdLib.h>\r
+#include <Library/QemuFwCfgLib.h>\r
+\r
+#include "SmiFeatures.h"\r
+\r
+//\r
+// The following bit value stands for "broadcast SMI" in the\r
+// "etc/smi/supported-features" and "etc/smi/requested-features" fw_cfg files.\r
+//\r
+#define ICH9_LPC_SMI_F_BROADCAST BIT0\r
+\r
+//\r
+// Provides a scratch buffer (allocated in EfiReservedMemoryType type memory)\r
+// for the S3 boot script fragment to write to and read from. The buffer\r
+// captures a combined fw_cfg item selection + write command using the DMA\r
+// access method. Note that we don't trust the runtime OS to preserve the\r
+// contents of the buffer, the boot script will first rewrite it.\r
+//\r
+#pragma pack (1)\r
+typedef struct {\r
+ FW_CFG_DMA_ACCESS Access;\r
+ UINT64 Features;\r
+} SCRATCH_BUFFER;\r
+#pragma pack ()\r
+\r
+//\r
+// These carry the selector keys of the "etc/smi/requested-features" and\r
+// "etc/smi/features-ok" fw_cfg files from NegotiateSmiFeatures() to\r
+// SaveSmiFeatures().\r
+//\r
+STATIC FIRMWARE_CONFIG_ITEM mRequestedFeaturesItem;\r
+STATIC FIRMWARE_CONFIG_ITEM mFeaturesOkItem;\r
+\r
+//\r
+// Carries the negotiated SMI features from NegotiateSmiFeatures() to\r
+// SaveSmiFeatures().\r
+//\r
+STATIC UINT64 mSmiFeatures;\r
+\r
+/**\r
+ Negotiate SMI features with QEMU.\r
+\r
+ @retval FALSE If SMI feature negotiation is not supported by QEMU. This is\r
+ not an error, it just means that SaveSmiFeatures() should not\r
+ be called.\r
+\r
+ @retval TRUE SMI feature negotiation is supported, and it has completed\r
+ successfully as well. (Failure to negotiate is a fatal error\r
+ and the function never returns in that case.)\r
+**/\r
+BOOLEAN\r
+NegotiateSmiFeatures (\r
+ VOID\r
+ )\r
+{\r
+ FIRMWARE_CONFIG_ITEM SupportedFeaturesItem;\r
+ UINTN SupportedFeaturesSize;\r
+ UINTN RequestedFeaturesSize;\r
+ UINTN FeaturesOkSize;\r
+\r
+ //\r
+ // Look up the fw_cfg files used for feature negotiation. The selector keys\r
+ // of "etc/smi/requested-features" and "etc/smi/features-ok" are saved\r
+ // statically. If the files are missing, then QEMU doesn't support SMI\r
+ // feature negotiation.\r
+ //\r
+ if (RETURN_ERROR (QemuFwCfgFindFile ("etc/smi/supported-features",\r
+ &SupportedFeaturesItem, &SupportedFeaturesSize)) ||\r
+ RETURN_ERROR (QemuFwCfgFindFile ("etc/smi/requested-features",\r
+ &mRequestedFeaturesItem, &RequestedFeaturesSize)) ||\r
+ RETURN_ERROR (QemuFwCfgFindFile ("etc/smi/features-ok",\r
+ &mFeaturesOkItem, &FeaturesOkSize))) {\r
+ DEBUG ((DEBUG_INFO, "%a: SMI feature negotiation unavailable\n",\r
+ __FUNCTION__));\r
+ return FALSE;\r
+ }\r
+\r
+ //\r
+ // If the files are present but their sizes disagree with us, that's a fatal\r
+ // error (we can't trust the behavior of SMIs either way).\r
+ //\r
+ if (SupportedFeaturesSize != sizeof mSmiFeatures ||\r
+ RequestedFeaturesSize != sizeof mSmiFeatures ||\r
+ FeaturesOkSize != sizeof (UINT8)) {\r
+ DEBUG ((DEBUG_ERROR, "%a: size mismatch in feature negotiation\n",\r
+ __FUNCTION__));\r
+ goto FatalError;\r
+ }\r
+\r
+ //\r
+ // Get the features supported by the host.\r
+ //\r
+ QemuFwCfgSelectItem (SupportedFeaturesItem);\r
+ QemuFwCfgReadBytes (sizeof mSmiFeatures, &mSmiFeatures);\r
+\r
+ //\r
+ // We want broadcast SMI and nothing else.\r
+ //\r
+ mSmiFeatures &= ICH9_LPC_SMI_F_BROADCAST;\r
+ QemuFwCfgSelectItem (mRequestedFeaturesItem);\r
+ QemuFwCfgWriteBytes (sizeof mSmiFeatures, &mSmiFeatures);\r
+\r
+ //\r
+ // Invoke feature validation in QEMU. If the selection is accepted, the\r
+ // features will be locked down. If the selection is rejected, feature\r
+ // negotiation remains open; however we don't know what to do in that case,\r
+ // so that's a fatal error.\r
+ //\r
+ QemuFwCfgSelectItem (mFeaturesOkItem);\r
+ if (QemuFwCfgRead8 () != 1) {\r
+ DEBUG ((DEBUG_ERROR, "%a: negotiation failed for feature bitmap 0x%Lx\n",\r
+ __FUNCTION__, mSmiFeatures));\r
+ goto FatalError;\r
+ }\r
+\r
+ if ((mSmiFeatures & ICH9_LPC_SMI_F_BROADCAST) == 0) {\r
+ //\r
+ // If we can't get broadcast SMIs from QEMU, that's acceptable too,\r
+ // although not optimal.\r
+ //\r
+ DEBUG ((DEBUG_INFO, "%a: SMI broadcast unavailable\n", __FUNCTION__));\r
+ } else {\r
+ //\r
+ // Configure the traditional AP sync / SMI delivery mode for\r
+ // PiSmmCpuDxeSmm. Effectively, restore the UefiCpuPkg defaults, from which\r
+ // the original QEMU behavior (i.e., unicast SMI) used to differ.\r
+ //\r
+ if (RETURN_ERROR (PcdSet64S (PcdCpuSmmApSyncTimeout, 1000000)) ||\r
+ RETURN_ERROR (PcdSet8S (PcdCpuSmmSyncMode, 0x00))) {\r
+ DEBUG ((DEBUG_ERROR, "%a: PiSmmCpuDxeSmm PCD configuration failed\n",\r
+ __FUNCTION__));\r
+ goto FatalError;\r
+ }\r
+ DEBUG ((DEBUG_INFO, "%a: using SMI broadcast\n", __FUNCTION__));\r
+ }\r
+\r
+ //\r
+ // Negotiation successful (although we may not have gotten the optimal\r
+ // feature set).\r
+ //\r
+ return TRUE;\r
+\r
+FatalError:\r
+ ASSERT (FALSE);\r
+ CpuDeadLoop ();\r
+ //\r
+ // Keep the compiler happy.\r
+ //\r
+ return FALSE;\r
+}\r
+\r
+/**\r
+ Append a boot script fragment that will re-select the previously negotiated\r
+ SMI features during S3 resume.\r
+\r
+ @param[in] S3SaveState The EFI_S3_SAVE_STATE_PROTOCOL instance to append to\r
+ the S3 boot script with.\r
+**/\r
+VOID\r
+SaveSmiFeatures (\r
+ IN EFI_S3_SAVE_STATE_PROTOCOL *S3SaveState\r
+ )\r
+{\r
+ SCRATCH_BUFFER *ScratchBuffer;\r
+ EFI_STATUS Status;\r
+ UINT64 AccessAddress;\r
+ UINT32 ControlPollData;\r
+ UINT32 ControlPollMask;\r
+ UINT16 FeaturesOkItemAsUint16;\r
+ UINT8 FeaturesOkData;\r
+ UINT8 FeaturesOkMask;\r
+\r
+ ScratchBuffer = AllocateReservedPool (sizeof *ScratchBuffer);\r
+ if (ScratchBuffer == NULL) {\r
+ DEBUG ((DEBUG_ERROR, "%a: scratch buffer allocation failed\n",\r
+ __FUNCTION__));\r
+ goto FatalError;\r
+ }\r
+\r
+ //\r
+ // Populate the scratch buffer with a select + write fw_cfg DMA command that\r
+ // will write the negotiated feature bitmap into\r
+ // "etc/smi/requested-features".\r
+ //\r
+ ScratchBuffer->Access.Control = SwapBytes32 (\r
+ (UINT32)mRequestedFeaturesItem << 16 |\r
+ FW_CFG_DMA_CTL_SELECT |\r
+ FW_CFG_DMA_CTL_WRITE\r
+ );\r
+ ScratchBuffer->Access.Length = SwapBytes32 (\r
+ (UINT32)sizeof ScratchBuffer->Features);\r
+ ScratchBuffer->Access.Address = SwapBytes64 (\r
+ (UINTN)&ScratchBuffer->Features);\r
+ ScratchBuffer->Features = mSmiFeatures;\r
+\r
+ //\r
+ // Copy the scratch buffer into the boot script. When replayed, this\r
+ // EFI_BOOT_SCRIPT_MEM_WRITE_OPCODE will restore the current contents of the\r
+ // scratch buffer, in-place.\r
+ //\r
+ Status = S3SaveState->Write (\r
+ S3SaveState, // This\r
+ EFI_BOOT_SCRIPT_MEM_WRITE_OPCODE, // OpCode\r
+ EfiBootScriptWidthUint8, // Width\r
+ (UINT64)(UINTN)ScratchBuffer, // Address\r
+ sizeof *ScratchBuffer, // Count\r
+ (VOID*)ScratchBuffer // Buffer\r
+ );\r
+ if (EFI_ERROR (Status)) {\r
+ DEBUG ((DEBUG_ERROR, "%a:%d: EFI_BOOT_SCRIPT_MEM_WRITE_OPCODE: %r\n",\r
+ __FUNCTION__, __LINE__, Status));\r
+ goto FatalError;\r
+ }\r
+\r
+ //\r
+ // Append an opcode that will write the address of the scratch buffer to the\r
+ // fw_cfg DMA address register, which consists of two 32-bit IO ports. The\r
+ // second (highest address, least significant) write will start the transfer.\r
+ //\r
+ AccessAddress = SwapBytes64 ((UINTN)&ScratchBuffer->Access);\r
+ Status = S3SaveState->Write (\r
+ S3SaveState, // This\r
+ EFI_BOOT_SCRIPT_IO_WRITE_OPCODE, // OpCode\r
+ EfiBootScriptWidthUint32, // Width\r
+ (UINT64)0x514, // Address\r
+ (UINTN)2, // Count\r
+ &AccessAddress // Buffer\r
+ );\r
+ if (EFI_ERROR (Status)) {\r
+ DEBUG ((DEBUG_ERROR, "%a:%d: EFI_BOOT_SCRIPT_IO_WRITE_OPCODE: %r\n",\r
+ __FUNCTION__, __LINE__, Status));\r
+ goto FatalError;\r
+ }\r
+\r
+ //\r
+ // The EFI_BOOT_SCRIPT_MEM_POLL_OPCODE will wait until the Control word reads\r
+ // as zero (transfer complete). As timeout we use MAX_UINT64 * 100ns, which\r
+ // is approximately 58494 years.\r
+ //\r
+ ControlPollData = 0;\r
+ ControlPollMask = MAX_UINT32;\r
+ Status = S3SaveState->Write (\r
+ S3SaveState, // This\r
+ EFI_BOOT_SCRIPT_MEM_POLL_OPCODE, // OpCode\r
+ EfiBootScriptWidthUint32, // Width\r
+ (UINT64)(UINTN)&ScratchBuffer->Access.Control, // Address\r
+ &ControlPollData, // Data\r
+ &ControlPollMask, // DataMask\r
+ MAX_UINT64 // Delay\r
+ );\r
+ if (EFI_ERROR (Status)) {\r
+ DEBUG ((DEBUG_ERROR, "%a:%d: EFI_BOOT_SCRIPT_MEM_POLL_OPCODE: %r\n",\r
+ __FUNCTION__, __LINE__, Status));\r
+ goto FatalError;\r
+ }\r
+\r
+ //\r
+ // Select the "etc/smi/features-ok" fw_cfg file, which invokes the feature\r
+ // validation & lockdown. (The validation succeeded at first boot.)\r
+ //\r
+ FeaturesOkItemAsUint16 = (UINT16)mFeaturesOkItem;\r
+ Status = S3SaveState->Write (\r
+ S3SaveState, // This\r
+ EFI_BOOT_SCRIPT_IO_WRITE_OPCODE, // OpCode\r
+ EfiBootScriptWidthUint16, // Width\r
+ (UINT64)0x510, // Address\r
+ (UINTN)1, // Count\r
+ &FeaturesOkItemAsUint16 // Buffer\r
+ );\r
+ if (EFI_ERROR (Status)) {\r
+ DEBUG ((DEBUG_ERROR, "%a:%d: EFI_BOOT_SCRIPT_IO_WRITE_OPCODE: %r\n",\r
+ __FUNCTION__, __LINE__, Status));\r
+ goto FatalError;\r
+ }\r
+\r
+ //\r
+ // Read the contents (one byte) of "etc/smi/features-ok". If the value is\r
+ // one, we're good. Otherwise, continue reading the data port: QEMU returns 0\r
+ // past the end of the fw_cfg item, so this will hang the resume process,\r
+ // which matches our intent.\r
+ //\r
+ FeaturesOkData = 1;\r
+ FeaturesOkMask = MAX_UINT8;\r
+ Status = S3SaveState->Write (\r
+ S3SaveState, // This\r
+ EFI_BOOT_SCRIPT_IO_POLL_OPCODE, // OpCode\r
+ EfiBootScriptWidthUint8, // Width\r
+ (UINT64)(UINTN)0x511, // Address\r
+ &FeaturesOkData, // Data\r
+ &FeaturesOkMask, // DataMask\r
+ MAX_UINT64 // Delay\r
+ );\r
+ if (EFI_ERROR (Status)) {\r
+ DEBUG ((DEBUG_ERROR, "%a:%d: EFI_BOOT_SCRIPT_IO_POLL_OPCODE: %r\n",\r
+ __FUNCTION__, __LINE__, Status));\r
+ goto FatalError;\r
+ }\r
+\r
+ DEBUG ((DEBUG_VERBOSE, "%a: ScratchBuffer@%p\n", __FUNCTION__,\r
+ (VOID *)ScratchBuffer));\r
+ return;\r
+\r
+FatalError:\r
+ ASSERT (FALSE);\r
+ CpuDeadLoop ();\r
+}\r