+\r
+/**\r
+ Place the handler routine for the first SMIs of hot-added CPUs at\r
+ (SMM_DEFAULT_SMBASE + SMM_HANDLER_OFFSET).\r
+\r
+ Note that this effects an "SMRAM to SMRAM" copy.\r
+\r
+ Additionally, shut the APIC ID gate in FIRST_SMI_HANDLER_CONTEXT.\r
+\r
+ This function may only be called from the entry point function of the driver,\r
+ and only after PcdQ35SmramAtDefaultSmbase has been determined to be TRUE.\r
+**/\r
+VOID\r
+SmbaseInstallFirstSmiHandler (\r
+ VOID\r
+ )\r
+{\r
+ FIRST_SMI_HANDLER_CONTEXT *Context;\r
+\r
+ CopyMem ((VOID *)(UINTN)(SMM_DEFAULT_SMBASE + SMM_HANDLER_OFFSET),\r
+ mFirstSmiHandler, mFirstSmiHandlerSize);\r
+\r
+ Context = (VOID *)(UINTN)SMM_DEFAULT_SMBASE;\r
+ Context->ApicIdGate = MAX_UINT64;\r
+}\r
+\r
+/**\r
+ Relocate the SMBASE on a hot-added CPU. Then pen the hot-added CPU in the\r
+ normal RAM reserved memory page, set up earlier with\r
+ SmbaseAllocatePostSmmPen() and SmbaseReinstallPostSmmPen().\r
+\r
+ The SMM Monarch is supposed to call this function from the root MMI handler.\r
+\r
+ The SMM Monarch is responsible for calling SmbaseInstallFirstSmiHandler(),\r
+ SmbaseAllocatePostSmmPen(), and SmbaseReinstallPostSmmPen() before calling\r
+ this function.\r
+\r
+ If the OS maliciously boots the hot-added CPU ahead of letting the ACPI CPU\r
+ hotplug event handler broadcast the CPU hotplug MMI, then the hot-added CPU\r
+ returns to the OS rather than to the pen, upon RSM. In that case, this\r
+ function will hang forever (unless the OS happens to signal back through the\r
+ last byte of the pen page).\r
+\r
+ @param[in] ApicId The APIC ID of the hot-added CPU whose SMBASE should\r
+ be relocated.\r
+\r
+ @param[in] Smbase The new SMBASE address. The root MMI handler is\r
+ responsible for passing in a free ("unoccupied")\r
+ SMBASE address that was pre-configured by\r
+ PiSmmCpuDxeSmm in CPU_HOT_PLUG_DATA.\r
+\r
+ @param[in] PenAddress The address of the Post-SMM Pen for hot-added CPUs, as\r
+ returned by SmbaseAllocatePostSmmPen(), and installed\r
+ by SmbaseReinstallPostSmmPen().\r
+\r
+ @retval EFI_SUCCESS The SMBASE of the hot-added CPU with APIC ID\r
+ ApicId has been relocated to Smbase. The\r
+ hot-added CPU has reported back about leaving\r
+ SMM.\r
+\r
+ @retval EFI_PROTOCOL_ERROR Synchronization bug encountered around\r
+ FIRST_SMI_HANDLER_CONTEXT.ApicIdGate.\r
+\r
+ @retval EFI_INVALID_PARAMETER Smbase does not fit in 32 bits. No relocation\r
+ has been attempted.\r
+**/\r
+EFI_STATUS\r
+SmbaseRelocate (\r
+ IN APIC_ID ApicId,\r
+ IN UINTN Smbase,\r
+ IN UINT32 PenAddress\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ volatile UINT8 *SmmVacated;\r
+ volatile FIRST_SMI_HANDLER_CONTEXT *Context;\r
+ UINT64 ExchangeResult;\r
+\r
+ if (Smbase > MAX_UINT32) {\r
+ Status = EFI_INVALID_PARAMETER;\r
+ DEBUG ((DEBUG_ERROR, "%a: ApicId=" FMT_APIC_ID " Smbase=0x%Lx: %r\n",\r
+ __FUNCTION__, ApicId, (UINT64)Smbase, Status));\r
+ return Status;\r
+ }\r
+\r
+ SmmVacated = (UINT8 *)(UINTN)PenAddress + (EFI_PAGE_SIZE - 1);\r
+ Context = (VOID *)(UINTN)SMM_DEFAULT_SMBASE;\r
+\r
+ //\r
+ // Clear AboutToLeaveSmm, so we notice when the hot-added CPU is just about\r
+ // to reach RSM, and we can proceed to polling the last byte of the reserved\r
+ // page (which could be attacked by the OS).\r
+ //\r
+ Context->AboutToLeaveSmm = 0;\r
+\r
+ //\r
+ // Clear the last byte of the reserved page, so we notice when the hot-added\r
+ // CPU checks back in from the pen.\r
+ //\r
+ *SmmVacated = 0;\r
+\r
+ //\r
+ // Boot the hot-added CPU.\r
+ //\r
+ // If the OS is benign, and so the hot-added CPU is still in RESET state,\r
+ // then the broadcast SMI is still pending for it; it will now launch\r
+ // directly into SMM.\r
+ //\r
+ // If the OS is malicious, the hot-added CPU has been booted already, and so\r
+ // it is already spinning on the APIC ID gate. In that case, the\r
+ // INIT-SIPI-SIPI below will be ignored.\r
+ //\r
+ SendInitSipiSipi (ApicId, PenAddress);\r
+\r
+ //\r
+ // Expose the desired new SMBASE value to the hot-added CPU.\r
+ //\r
+ Context->NewSmbase = (UINT32)Smbase;\r
+\r
+ //\r
+ // Un-gate SMBASE relocation for the hot-added CPU whose APIC ID is ApicId.\r
+ //\r
+ ExchangeResult = InterlockedCompareExchange64 (&Context->ApicIdGate,\r
+ MAX_UINT64, ApicId);\r
+ if (ExchangeResult != MAX_UINT64) {\r
+ Status = EFI_PROTOCOL_ERROR;\r
+ DEBUG ((DEBUG_ERROR, "%a: ApicId=" FMT_APIC_ID " ApicIdGate=0x%Lx: %r\n",\r
+ __FUNCTION__, ApicId, ExchangeResult, Status));\r
+ return Status;\r
+ }\r
+\r
+ //\r
+ // Wait until the hot-added CPU is just about to execute RSM.\r
+ //\r
+ while (Context->AboutToLeaveSmm == 0) {\r
+ CpuPause ();\r
+ }\r
+\r
+ //\r
+ // Now wait until the hot-added CPU reports back from the pen (or the OS\r
+ // attacks the last byte of the reserved page).\r
+ //\r
+ while (*SmmVacated == 0) {\r
+ CpuPause ();\r
+ }\r
+\r
+ Status = EFI_SUCCESS;\r
+ return Status;\r
+}\r