| /** @file | |
| CPU MP Initialize helper function for AMD SEV. | |
| Copyright (c) 2021, AMD Inc. All rights reserved.<BR> | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include "MpLib.h" | |
| #include <Library/CcExitLib.h> | |
| /** | |
| 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; | |
| AsmCpuidEx ( | |
| CPUID_EXTENDED_TOPOLOGY, | |
| 0, | |
| NULL, | |
| &ExtTopoEbx.Uint32, | |
| NULL, | |
| NULL | |
| ); | |
| ExchangeInfo->ExtTopoAvail = !!ExtTopoEbx.Bits.LogicalProcessors; | |
| } | |
| } | |
| /** | |
| Get pointer to CPU MP Data structure from GUIDed HOB. | |
| @param[in] CpuMpData The pointer to CPU MP Data structure. | |
| **/ | |
| VOID | |
| AmdSevUpdateCpuMpData ( | |
| IN CPU_MP_DATA *CpuMpData | |
| ) | |
| { | |
| CPU_MP_DATA *OldCpuMpData; | |
| OldCpuMpData = GetCpuMpDataFromGuidedHob (); | |
| OldCpuMpData->NewCpuMpData = CpuMpData; | |
| } |