Start VM/PE frpm non-VM/PE processor

Current implementation of STM/PE requires that the VM/PE and any
run commands that affect the VM/PE be done on the VM/PE processor.
On most operating systems, the VM/PE processor has been hot
unplugged from the available processor set.  Getting commands to
the VM/PE requires some modification of the operating system for
this action to be supported and this implemantation is fragile
across releases.

This patch allows VM/PE commands to issued via a vmcall on any
processor.  The STM will setup a SMI timer interrupt to cause
the command to be passed to the VM/PE processor.  This greatly
reduces and simplifies the changes needed for the O/S.

Signed-off-by: Eugene Myers <edmyers@cyberpackventures.com>
This commit is contained in:
Eugene Myers 2023-02-01 15:20:17 -05:00
parent 449be74faf
commit fa187042a9
5 changed files with 178 additions and 68 deletions

View File

@ -34,16 +34,16 @@ extern UINT32 GetMPState;
static int CpuGetState = 0;
void SetEndOfSmi(void);
void StartTimer(void);
void StartPeriodicTimer(void);
void StopTimer(void);
void ClearTimerSTS(void);
void SetMaxSwTimerInt(void);
void SetMinSwTimerInt(void);
void SetTimerRate(UINT16 value);
void SetPeriodicTimerRate(UINT16 value);
extern void MapVmcs();
void LaunchPeVm(UINT32 PeType, UINT32 CpuIndex);
void AckTimer(void);
void AckPeriodicTimer(void);
UINT16 get_pmbase(void);
UINT32 save_Inter_PeVm(UINT32 CpuIndex);
@ -130,7 +130,7 @@ void LaunchPeVm(UINT32 PeType, UINT32 CpuIndex)
if(0 >= EndSize)
{
DEBUG((EFI_D_ERROR,
"%ld LaunchPeVM - VM/PE heap space not cleared because of DoNotClearSize too large\n",
"%ld LaunchPeVm - VM/PE heap space not cleared because of DoNotClearSize too large\n",
CpuIndex));
}
else
@ -171,7 +171,7 @@ void LaunchPeVm(UINT32 PeType, UINT32 CpuIndex)
// save the state, process the SMI, then start the VM/PE afterwards
DEBUG((EFI_D_INFO,
"%ld LaunchPeVM - SMI being processed - faking NMI - PeSmiState: %ld\n",
"%ld LaunchPeVm - SMI being processed - faking NMI - PeSmiState: %ld\n",
CpuIndex,
PeSmiControl.PeSmiState));
}
@ -219,14 +219,15 @@ void LaunchPeVm(UINT32 PeType, UINT32 CpuIndex)
SHARED_PAGE_STM_HEADER * SharedPageStmHeader = (SHARED_PAGE_STM_HEADER *) (UINT64*) PeVmData[PeType].SharedPageStm;
SharedPageStmHeader->RunCount = PeVmData[PeType].UserModule.RunCount;
SharedPageStmHeader->ExecProcessor = CpuIndex;
SharedPageStmHeader->NumProcessors = mHostContextCommon.CpuNum;
DEBUG((EFI_D_INFO,
"%ld LaunchPeVM - Initiating PE/VM run number: %d\n",
"%ld LaunchPeVm - Initiating PE/VM run number: %d\n",
CpuIndex,
PeVmData[PeType].UserModule.RunCount));
DEBUG((EFI_D_INFO,
"%ld LaunchPeVM - SharedPageStm 0x%016llx 0x%016llx 0x%016llx\n",
"%ld LaunchPeVm - SharedPageStm NumProcessors: %d RunCount: %d ExecProc: %d\n",
CpuIndex,
SharedPageStmHeader->NumProcessors,
SharedPageStmHeader->RunCount,
@ -264,11 +265,11 @@ void LaunchPeVm(UINT32 PeType, UINT32 CpuIndex)
// which will cause the SMI for this processor to be fired
DEBUG((EFI_D_INFO,
"%ld LaunchPeVM - SMI detected during build - delaying launch to handle SMI\n",
"%ld LaunchPeVm - SMI detected during build - delaying launch to handle SMI\n",
CpuIndex));
save_Inter_PeVm(CpuIndex);
DEBUG((EFI_D_ERROR,
"%ld LaunchPeVM - Warning: Return from non-returnable function\n",
"%ld LaunchPeVm - Warning: Return from non-returnable function\n",
CpuIndex));
}
@ -277,7 +278,7 @@ void LaunchPeVm(UINT32 PeType, UINT32 CpuIndex)
if(NMIReceived > 1)
{
DEBUG((EFI_D_INFO,
"%ld LaunchPeVM - NMI detected during build - delaying launch to handle SMI\n",
"%ld LaunchPeVm - NMI detected during build - delaying launch to handle SMI\n",
CpuIndex));
// This will cause the current PE/VM state to be saved and fake a return to the MLE
@ -286,13 +287,13 @@ void LaunchPeVm(UINT32 PeType, UINT32 CpuIndex)
save_Inter_PeVm(CpuIndex);
DEBUG((EFI_D_ERROR,
"%ld LaunchPeVM - Warning: Return from non-returnable function\n",
"%ld LaunchPeVm - Warning: Return from non-returnable function\n",
CpuIndex));
// this function should not return
}
DEBUG((EFI_D_INFO,
"%ld LaunchPeVM - Launching PE/VM - NMIReceived: %d\n",
"%ld LaunchPeVm - Launching PE/VM - NMIReceived: %d\n",
CpuIndex,
NMIReceived));
@ -302,7 +303,7 @@ void LaunchPeVm(UINT32 PeType, UINT32 CpuIndex)
"%ld LaunchPeVm - !!!LaunchGuestSmm fail for PeVm!!!\n",
CpuIndex));
DEBUG ((EFI_D_ERROR,
"%ld LaunchPeVm - Rflags: (UINTN)CpuIndex, %08llx\n",
"%ld LaunchPeVm - Rflags: %08llx\n",
CpuIndex,
Rflags));
DEBUG ((EFI_D_ERROR, "%ld LaunchPeVm - VMCS_32_RO_VM_INSTRUCTION_ERROR: %08x\n",
@ -340,6 +341,7 @@ STM_STATUS RunPermVM(UINT32 CpuIndex)
{
UINT32 rc;
UINT32 PeType = PE_PERM; // can only restart perm vms...
UINT32 SetupMode = RESTART_VM;
// (for now) start the VM...
@ -371,6 +373,8 @@ STM_STATUS RunPermVM(UINT32 CpuIndex)
}
return rc;
}
else if(PeVmData[PeType].PeVmState == PE_VM_WAIT_START)
SetupMode = NEW_VM;
PeVmData[PeType].PeVmState = PE_VM_ACTIVE;
@ -387,7 +391,7 @@ STM_STATUS RunPermVM(UINT32 CpuIndex)
rc = SetupProtExecVm(CpuIndex,
PeVmData[PE_PERM].UserModule.VmConfig,
RESTART_VM,
SetupMode,
PeType); // can only restart PERM_VM
if(rc != PE_SUCCESS) // did we have a problem
@ -401,6 +405,8 @@ STM_STATUS RunPermVM(UINT32 CpuIndex)
LaunchPeVm(PeType, CpuIndex); // Launch the PE/VM
DEBUG((EFI_D_ERROR, "%ld - Error in launching PE VM\n", CpuIndex));
PeVmData[PE_PERM].PeVmState = PE_VM_AVAIL; // not there anymore
mHostContextCommon.HostContextPerCpu[CpuIndex].GuestVmType = SMI_HANDLER;
return rc;
@ -526,10 +532,10 @@ UINT32 PostPeVmProc(UINT32 rc, UINT32 CpuIndex, UINT32 mode)
// turn on the timer
SetTimerRate(PeriodicSmi16Sec);
StartTimer();
SetPeriodicTimerRate(PeriodicSmi16Sec);
StartPeriodicTimer();
AckTimer();
AckPeriodicTimer();
}
}
}

View File

@ -15,7 +15,7 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#include "StmRuntime.h"
#include "PeStm.h"
void SetTimerRate(UINT16 value);
void SetPeriodicTimerRate(UINT16 value);
// interval timer support
@ -132,23 +132,33 @@ UINT16 get_pmbase(void)
return pmbase;
}
void StartTimer(void)
void StartTimer(UINT32 enable, UINT32 status)
{
UINT16 pmbase = get_pmbase();
UINT32 smi_en = IoRead32(pmbase + SMI_EN);
UINT32 smi_sts = IoRead32(pmbase + SMI_STS);
smi_en |= PERIODIC_EN;
#if 0
DEBUG((EFI_D_INFO,
"StartTimer - smi_en: 0x%08lx smi_sts: 0x%08lx\n",
smi_en,
smi_sts));
#endif
IoWrite32(pmbase + SMI_STS, PERIODIC_STS);
smi_en |= enable;
IoWrite32(pmbase + SMI_STS, status);
IoWrite32(pmbase + SMI_EN, smi_en);
}
void StartPeriodicTimer(void)
{
StartTimer(PERIODIC_EN, PERIODIC_STS);
}
void StartSwSmiTimer(void)
{
StartTimer(SWSMI_TMR_EN, SWSMI_TMR_STS);
}
void SetEndOfSmi(void)
{
@ -180,7 +190,7 @@ void PrintSmiEnRegister(UINT32 Index)
IoRead32(pmbase + SMI_STS)));
}
void AckTimer(void)
void AckPeriodicTimer(void)
{
UINT16 pmbase = get_pmbase();
@ -193,15 +203,25 @@ void AckTimer(void)
#endif
}
void StopSwTimer(void)
void StopTimer(UINT32 enable)
{
UINT16 pmbase = get_pmbase();
UINT32 smi_en = IoRead32(pmbase + SMI_EN);
smi_en &= ~PERIODIC_EN;
smi_en &= ~enable;
IoWrite32(pmbase + SMI_EN, smi_en);
}
void StopPeriodicTimer(void)
{
StopTimer(PERIODIC_EN);
}
void StopSwSmiTimer(void)
{
StopTimer(SWSMI_TMR_EN);
}
/*
* CheckTimerSTS
* Input:
@ -217,18 +237,23 @@ int CheckTimerSTS(UINT32 Index)
{
UINT16 pmbase = get_pmbase();
UINT32 smi_sts = IoRead32(pmbase + SMI_STS);
UINT32 smi_en = IoRead32(pmbase + SMI_EN);
// only care about the ones that are enabled
smi_sts = smi_en & smi_sts;
#if 0
DEBUG((EFI_D_ERROR, "%ld CheckTimerSTS - 0x%08lx\n", Index, smi_sts));
#endif
if((smi_sts & PERIODIC_STS) == PERIODIC_STS)
{
UINT32 smi_en = IoRead32(pmbase + SMI_EN);
UINT32 other_smi = (smi_en & smi_sts) & ~PERIODIC_STS;
UINT32 other_smi = smi_sts & ~PERIODIC_STS;
if(other_smi == 0)
{
if(other_smi == 0)
{
DEBUG((EFI_D_INFO,
"%ld CheckTimerSTS - Timer Interrupt Detected\n",
"%ld CheckTimerSTS - Periodic Timer SMI\n",
Index,
smi_sts));
return 1;
@ -236,14 +261,36 @@ int CheckTimerSTS(UINT32 Index)
else
{
DEBUG((EFI_D_INFO,
"%ld CheckTimerSTS - Timer + other SMI found\n",
"%ld CheckTimerSTS - Periodic Timer + other SMI\n",
Index,
smi_sts));
return 2;
}
}
else
if((smi_sts & SWSMI_TMR_STS) == SWSMI_TMR_STS)
{
UINT32 other_smi = smi_sts & ~SWSMI_TMR_STS;
StopSwSmiTimer();
if(other_smi == 0)
{
DEBUG((EFI_D_INFO,
"%ld CheckTimerSTS - SWSMI\n",
Index,
smi_sts));
return 1;
}
else
{
DEBUG((EFI_D_INFO,
"%ld CheckTimerSTS - SWSMI + other SMI found\n",
Index,
smi_sts));
return 2;
}
}
#if 0
DEBUG((EFI_D_INFO,
"%ld CheckTimerSTS - No Timer Interrupt Detected\n",
@ -251,10 +298,9 @@ int CheckTimerSTS(UINT32 Index)
smi_sts));
#endif
return 0;
}
}
void ClearTimerSTS()
void ClearPeriodicTimerSTS()
{
UINT16 pmbase = get_pmbase();
@ -262,17 +308,17 @@ void ClearTimerSTS()
IoWrite32(pmbase + SMI_STS, PERIODIC_STS);
}
void SetMaxSwTimerInt()
void SetMaxPeriodicTimerInt()
{
SetTimerRate(3);
SetPeriodicTimerRate(3);
}
void SetMinSwTimerInt()
void SetMinPeriodicTimerInt()
{
SetTimerRate(0);
SetPeriodicTimerRate(0);
}
void SetTimerRate(UINT16 value)
void SetPeriodicTimerRate(UINT16 value)
{
UINT16 Reg16;
UINT16 TimeOut;
@ -287,3 +333,19 @@ void SetTimerRate(UINT16 value)
Reg16 = pcie_read_config16(PcuDev, D31F0_GEN_PMCON_1);
pcie_write_config16(PcuDev, D31F0_GEN_PMCON_1, Reg16|TimeOut);
}
void SetSwSmiTimerRate(UINT16 value)
{
UINT16 Reg16;
UINT16 TimeOut;
device_t PcuDev = get_pcu_dev();
if( value > 3)
{
value = 3;
}
TimeOut = (value << 6);
Reg16 = pcie_read_config16(PcuDev, D31F0_GEN_PMCON_3);
pcie_write_config16(PcuDev, D31F0_GEN_PMCON_3, Reg16|TimeOut);
}

View File

@ -24,7 +24,7 @@ extern int CheckAndGetState(UINT32 CpuIndex);
extern void CpuReadySync(UINT32 Index);
extern PE_VM_DATA PeVmData[4]; // right now support a max of 3 PE VM (VM 0 is the SMI_HANDLER)
extern int CheckTimerSTS(UINT32 Index);
extern void StopSwTimer(void);
extern void StopPeriodicTimer(void);
extern void SetEndOfSmi(void);
extern void PrintVmxState(UINT32 CpuIndex, ROOT_VMX_STATE * RootState);
@ -130,7 +130,7 @@ UINT32 PeSmiHandler(UINT32 CpuIndex)
if (PeSmiControl.PeCpuIndex == (INT32)CpuIndex)
{
InterlockedCompareExchange32(&PeSmiControl.PeWaitTimer, 1, 0);
StopSwTimer();
StopPeriodicTimer();
// start the VM/PE
PeVmData[PeType].StartMode = PEVM_PRESTART_SMI; // starting from SMI

View File

@ -10,6 +10,8 @@ extern void print_region_list(UINT32 PeType, UINT32 CpuIndex);
extern UINT32 SetupProtExecVm(UINT32 CpuIndex, UINT32 VM_Configuration, UINT32 mode, UINT32 PeType);
extern void LaunchPeVm(UINT32 PeType, UINT32 CpuIndex);
extern VOID EptDumpPageTable (IN EPT_POINTER *EptPointer );
extern void SetSwSmiTimerRate(UINT16 value);
extern void StartSwSmiTimer(void);
static UINT32 setupModulepages(UINT32 PeType, UINT32 CpuIndex);
STM_STATUS AddPeVm(UINT32 CpuIndex, PE_MODULE_INFO * callerDataStructure, UINT32 PeType, UINT32 RunVm)
@ -78,6 +80,13 @@ STM_STATUS AddPeVm(UINT32 CpuIndex, PE_MODULE_INFO * callerDataStructure, UINT32
PeVmData[PeType].UserModule.DoNotClearSize = callerDataStructure->DoNotClearSize;
PeVmData[PeType].UserModule.RunCount = 0;
if(callerDataStructure->Processor < mHostContextCommon.CpuNum)
PeVmData[PeType].UserModule.Processor = callerDataStructure->Processor;
else
PeVmData[PeType].UserModule.Processor = 0;
PeVmData[PeType].UserModule.LastRunStatus = 0;
#if defined (MDE_CPU_X64)
sourceBuffer = (UINTN *)((PeVmData[PeType].UserModule.ModuleAddress));
#else
@ -163,6 +172,17 @@ STM_STATUS AddPeVm(UINT32 CpuIndex, PE_MODULE_INFO * callerDataStructure, UINT32
// calculate the location within the allocated space to place the module
destBuffer = PeVmData[PeType].SmmBuffer + PeVmData[PeType].UserModule.AddressSpaceStart -
PeVmData[PeType].UserModule.ModuleLoadAddress;
// sanity check - make sure the module lands in the allocated buffer
if( (destBuffer < PeVmData[PeType].SmmBuffer) ||
(destBuffer + PeVmData[PeType].UserModule.ModuleSize >
PeVmData[PeType].SmmBuffer + (numModulePages << 12)))
{
FreePE_DataStructures(PeType);
PeVmData[PeType].PeVmState = PE_VM_AVAIL;
return(PE_MODULE_TOO_LARGE);
}
}
// make sure that the size of the module will fit into the allocated space
@ -390,36 +410,55 @@ STM_STATUS AddPeVm(UINT32 CpuIndex, PE_MODULE_INFO * callerDataStructure, UINT32
// (for now) start the VM...
PeVmData[PeType].StartMode = PEVM_START_VMCALL;
rc = SetupProtExecVm(CpuIndex, PeVmData[PeType].UserModule.VmConfig, NEW_VM, PeType);
if(rc != PE_SUCCESS) // did we have a problem
{
DEBUG((EFI_D_ERROR, "%ld AddPeVm - Error in configuring PE VM\n", CpuIndex));
FreePE_DataStructures(PeType);
//setPEerrorCode(rc, StmVmm); // tell the caller of the problem
PeVmData[PeType].PeVmState = PE_VM_AVAIL; // not there anymore
// StmVmm->NonSmiHandler = 0; // no longer an PE VM
AsmVmPtrLoad(&mGuestContextCommonSmi.GuestContextPerCpu[CpuIndex].Vmcs);
/// at this point we should return to the MLE as per the Intel method...
AsmVmClear(&mGuestContextCommonSmm[PeType].GuestContextPerCpu[0].Vmcs);
mHostContextCommon.HostContextPerCpu[CpuIndex].GuestVmType = SMI_HANDLER;
return(rc);
}
DEBUG((EFI_D_ERROR, "%ld AddPeVm - sucessfully completed - PeApicId: 0x%llx PeType: %d\n", CpuIndex, PeSmiControl.PeApicId, PeType));
DEBUG((EFI_D_ERROR, "%ld AddPeVm - sucessfully completed - PeApicId: 0x%llx PeType: %d\n",
CpuIndex, PeSmiControl.PeApicId, PeType));
if(RunVm == 1)
{
PeVmData[PeType].StartMode = PEVM_START_VMCALL;
LaunchPeVm(PeType, CpuIndex); // launch the PE/VM
if((PeVmData[PeType].UserModule.Processor == 0) ||
(PeVmData[PeType].UserModule.Processor == CpuIndex))
{
//Execution is only on this processor
rc = SetupProtExecVm(CpuIndex, PeVmData[PeType].UserModule.VmConfig, NEW_VM, PeType);
// if we get to this point the PeVm has failed to launch so we need clean up the mess
// and return the error to the caller
FreePE_DataStructures(PeType);
DEBUG((EFI_D_ERROR, "%ld AddPeVm - VM/PE Launch Failure\n", CpuIndex));
rc = PE_VMLAUNCH_ERROR;
PeVmData[PeType].PeVmState = PE_VM_AVAIL; // not there anymore
if(rc != PE_SUCCESS) // did we have a problem
{
DEBUG((EFI_D_ERROR, "%ld AddPeVm - Error in configuring PE VM\n", CpuIndex));
FreePE_DataStructures(PeType);
PeVmData[PeType].PeVmState = PE_VM_AVAIL; // not there anymore
AsmVmPtrLoad(&mGuestContextCommonSmi.GuestContextPerCpu[CpuIndex].Vmcs);
AsmVmClear(&mGuestContextCommonSmm[PeType].GuestContextPerCpu[0].Vmcs);
mHostContextCommon.HostContextPerCpu[CpuIndex].GuestVmType = SMI_HANDLER;
return(rc);
}
PeVmData[PeType].StartMode = PEVM_START_VMCALL;
LaunchPeVm(PeType, CpuIndex); // launch the PE/VM
// if we get to this point the PeVm has failed to launch so
// we need to clean up and return the error to the caller
FreePE_DataStructures(PeType);
DEBUG((EFI_D_ERROR, "%ld AddPeVm - VM/PE Launch Failure\n", CpuIndex));
rc = PE_VMLAUNCH_ERROR;
PeVmData[PeType].PeVmState = PE_VM_AVAIL; // not there anymore
}
else
{
// execution will not be on this processor, setup to get it
// to where it belongs
DEBUG((EFI_D_ERROR, "%ld AddPeVM - Execution is to be on processor %ld\n",
CpuIndex, PeVmData[PeType].UserModule.Processor));
PeSmiControl.PeCpuIndex = PeVmData[PeType].UserModule.Processor;
mHostContextCommon.HostContextPerCpu[PeSmiControl.PeCpuIndex].NonSmiHandler = PeType;
InterlockedCompareExchange32(&PeSmiControl.PeWaitTimer, 0, 1);
PeVmData[PeType].PeVmState = PE_VM_WAIT_START;
PeVmData[PeType].StartMode = PEVM_PRESTART_SMI;
SetSwSmiTimerRate(0); // 1.5ms
StartSwSmiTimer();
AsmWbinvd();
rc = STM_SUCCESS;
}
}
else
{

View File

@ -43,18 +43,21 @@ typedef struct
UINT64 AddressSpaceStart; // start of guest physical address space (page aligned)
UINT32 AddressSpaceSize; // size of guest physical address space
UINT32 VmConfig; // Options to the configuration of the PE/VM
UINT64 Cr3Load; // CR3
UINT64 SharedPage; // writeable pages for sharing between the PE Module and kernel space
// can be multible pages and is located in mail memory
UINT64 Cr3Load; // CR3
UINT64 SharedPage; // writeable pages for sharing between the PE Module and kernel space
// can be multible pages and is located in mail memory
PE_REGION_LIST *Segment; // list of read only regions (contained within a page)
UINT32 SharedPageSize; // size of]SharedPage/region
UINT32 DoNotClearSize; // area at beginning of memory not to be cleared
UINT64 ModuleDataSection; // Location of Module Data Section for VM/PE
UINT32 Processor; // assigned processor (0 = use current processor)
UINT32 reserved;
// data areas local to the STM go after this point
UINT64 SharedStmPage; // page shared between PE/VM and the STM
UINT64 RunCount; // count of runs starting with one (1)
UINT64 LastRunStatus; // Status of last VM/PE run
// UINTN DataRegionStart; // data space after text region
UINTN DataRegionSize; // data space size
UINTN FrontDataRegionSize; // data space size before text region