/** @file CPU MP Initialize helper function for AMD SEV. Copyright (c) 2021, AMD Inc. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "MpLib.h" #include /** Get Protected mode code segment with 16-bit default addressing from current GDT table. @return Protected mode 16-bit code segment value. **/ STATIC UINT16 GetProtectedMode16CS ( VOID ) { IA32_DESCRIPTOR GdtrDesc; IA32_SEGMENT_DESCRIPTOR *GdtEntry; UINTN GdtEntryCount; UINT16 Index; Index = (UINT16)-1; AsmReadGdtr (&GdtrDesc); GdtEntryCount = (GdtrDesc.Limit + 1) / sizeof (IA32_SEGMENT_DESCRIPTOR); GdtEntry = (IA32_SEGMENT_DESCRIPTOR *)GdtrDesc.Base; for (Index = 0; Index < GdtEntryCount; Index++) { if ((GdtEntry->Bits.L == 0) && (GdtEntry->Bits.DB == 0) && (GdtEntry->Bits.Type > 8)) { break; } GdtEntry++; } ASSERT (Index != GdtEntryCount); return Index * 8; } /** Get Protected mode code segment with 32-bit default addressing from current GDT table. @return Protected mode 32-bit code segment value. **/ STATIC UINT16 GetProtectedMode32CS ( VOID ) { IA32_DESCRIPTOR GdtrDesc; IA32_SEGMENT_DESCRIPTOR *GdtEntry; UINTN GdtEntryCount; UINT16 Index; Index = (UINT16)-1; AsmReadGdtr (&GdtrDesc); GdtEntryCount = (GdtrDesc.Limit + 1) / sizeof (IA32_SEGMENT_DESCRIPTOR); GdtEntry = (IA32_SEGMENT_DESCRIPTOR *)GdtrDesc.Base; for (Index = 0; Index < GdtEntryCount; Index++) { if ((GdtEntry->Bits.L == 0) && (GdtEntry->Bits.DB == 1) && (GdtEntry->Bits.Type > 8)) { break; } GdtEntry++; } ASSERT (Index != GdtEntryCount); return Index * 8; } /** Reset an AP when in SEV-ES mode. If successful, this function never returns. @param[in] Ghcb Pointer to the GHCB @param[in] CpuMpData Pointer to CPU MP Data **/ VOID MpInitLibSevEsAPReset ( IN GHCB *Ghcb, IN CPU_MP_DATA *CpuMpData ) { EFI_STATUS Status; UINTN ProcessorNumber; UINT16 Code16, Code32; AP_RESET *APResetFn; UINTN BufferStart; UINTN StackStart; Status = GetProcessorNumber (CpuMpData, &ProcessorNumber); ASSERT_EFI_ERROR (Status); Code16 = GetProtectedMode16CS (); Code32 = GetProtectedMode32CS (); APResetFn = (AP_RESET *)(CpuMpData->WakeupBufferHigh + CpuMpData->AddressMap.SwitchToRealNoNxOffset); BufferStart = CpuMpData->MpCpuExchangeInfo->BufferStart; StackStart = CpuMpData->SevEsAPResetStackStart - (AP_RESET_STACK_SIZE * ProcessorNumber); // // This call never returns. // APResetFn (BufferStart, Code16, Code32, StackStart); } /** Allocate the SEV-ES AP jump table buffer. @param[in, out] CpuMpData The pointer to CPU MP Data structure. **/ VOID AllocateSevEsAPMemory ( IN OUT CPU_MP_DATA *CpuMpData ) { if (CpuMpData->SevEsAPBuffer == (UINTN)-1) { CpuMpData->SevEsAPBuffer = CpuMpData->SevEsIsEnabled ? GetSevEsAPMemory () : 0; } } /** Program the SEV-ES AP jump table buffer. @param[in] SipiVector The SIPI vector used for the AP Reset **/ VOID SetSevEsJumpTable ( IN UINTN SipiVector ) { SEV_ES_AP_JMP_FAR *JmpFar; UINT32 Offset, InsnByte; UINT8 LoNib, HiNib; JmpFar = (SEV_ES_AP_JMP_FAR *)(UINTN)FixedPcdGet32 (PcdSevEsWorkAreaBase); ASSERT (JmpFar != NULL); // // Obtain the address of the Segment/Rip location in the workarea. // This will be set to a value derived from the SIPI vector and will // be the memory address used for the far jump below. // Offset = FixedPcdGet32 (PcdSevEsWorkAreaBase); Offset += sizeof (JmpFar->InsnBuffer); LoNib = (UINT8)Offset; HiNib = (UINT8)(Offset >> 8); // // Program the workarea (which is the initial AP boot address) with // far jump to the SIPI vector (where XX and YY represent the // address of where the SIPI vector is stored. // // JMP FAR [CS:XXYY] => 2E FF 2E YY XX // InsnByte = 0; JmpFar->InsnBuffer[InsnByte++] = 0x2E; // CS override prefix JmpFar->InsnBuffer[InsnByte++] = 0xFF; // JMP (FAR) JmpFar->InsnBuffer[InsnByte++] = 0x2E; // ModRM (JMP memory location) JmpFar->InsnBuffer[InsnByte++] = LoNib; // YY offset ... JmpFar->InsnBuffer[InsnByte++] = HiNib; // XX offset ... // // Program the Segment/Rip based on the SIPI vector (always at least // 16-byte aligned, so Rip is set to 0). // JmpFar->Rip = 0; JmpFar->Segment = (UINT16)(SipiVector >> 4); } /** The function puts the AP in halt loop. @param[in] CpuMpData The pointer to CPU MP Data structure. **/ VOID SevEsPlaceApHlt ( CPU_MP_DATA *CpuMpData ) { MSR_SEV_ES_GHCB_REGISTER Msr; GHCB *Ghcb; UINT64 Status; BOOLEAN DoDecrement; BOOLEAN InterruptState; DoDecrement = (BOOLEAN)(CpuMpData->InitFlag == ApInitConfig); while (TRUE) { Msr.GhcbPhysicalAddress = AsmReadMsr64 (MSR_SEV_ES_GHCB); Ghcb = Msr.Ghcb; CcExitVmgInit (Ghcb, &InterruptState); if (DoDecrement) { DoDecrement = FALSE; // // Perform the delayed decrement just before issuing the first // VMGEXIT with AP_RESET_HOLD. // InterlockedDecrement ((UINT32 *)&CpuMpData->MpCpuExchangeInfo->NumApsExecuting); } Status = CcExitVmgExit (Ghcb, SVM_EXIT_AP_RESET_HOLD, 0, 0); if ((Status == 0) && (Ghcb->SaveArea.SwExitInfo2 != 0)) { CcExitVmgDone (Ghcb, InterruptState); break; } CcExitVmgDone (Ghcb, InterruptState); } // // Awakened in a new phase? Use the new CpuMpData // if (CpuMpData->NewCpuMpData != NULL) { CpuMpData = CpuMpData->NewCpuMpData; } MpInitLibSevEsAPReset (Ghcb, CpuMpData); } /** The function fills the exchange data for the AP. @param[in] ExchangeInfo The pointer to CPU Exchange Data structure **/ VOID FillExchangeInfoDataSevEs ( IN volatile MP_CPU_EXCHANGE_INFO *ExchangeInfo ) { UINT32 StdRangeMax; AsmCpuid (CPUID_SIGNATURE, &StdRangeMax, NULL, NULL, NULL); if (StdRangeMax >= CPUID_EXTENDED_TOPOLOGY) { CPUID_EXTENDED_TOPOLOGY_EBX ExtTopoEbx; AsmCpuid (CPUID_EXTENDED_TOPOLOGY, NULL, &ExtTopoEbx.Uint32, NULL, NULL); ExchangeInfo->ExtTopoAvail = !!ExtTopoEbx.Bits.LogicalProcessors; } }