/** @file
SMI handler

Copyright (c) 2015, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php.

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include "StmRuntime.h"

STM_HANDLER  mStmHandlerSmi[VmExitReasonMax];

/**

This function initialize STM handle for SMI.

**/
VOID
	InitStmHandlerSmi (
	VOID
	)
{
	UINT32  Index;

	for (Index = 0; Index < VmExitReasonMax; Index++) {
		mStmHandlerSmi[Index] = UnknownHandlerSmi;
	}

	mStmHandlerSmi[VmExitReasonIoSmi] = SmiEventHandler;
	mStmHandlerSmi[VmExitReasonOtherSmi] = SmiEventHandler;
	mStmHandlerSmi[VmExitReasonVmCall] = SmiVmcallHandler;
    AsmWbinvd ();  // make sure all see it
}

/**

This function is unknown handler for SMI.

@param Index CPU index

**/
VOID
	UnknownHandlerSmi (
	IN UINT32 Index
	)
{
	AcquireSpinLock (&mHostContextCommon.DebugLock);

	DEBUG ((EFI_D_ERROR, "%ld !!!UnknownHandlerSmi\n", (UINTN)Index));
	DumpVmcsAllField (Index);

	ReleaseSpinLock (&mHostContextCommon.DebugLock);

	CpuDeadLoop ();
}

/**

This function checks Pending Mtf before resume.

@param Index CPU index

**/
VOID
	CheckPendingMtf (
	IN UINT32  Index
	)
{
	VM_EXIT_INFO_INTERRUPTION           VmEntryControlInterrupt;

	//
	// Check pending MTF
	//
	if (mGuestContextCommonSmi.GuestContextPerCpu[Index].InfoBasic.Bits.PendingMtf == 0) {
		return ;
	}

	//
	// In this case, prior to resuming the interrupted guest, the STM must set the VMENTRY
	// interrupt-information field in the interrupted contexts VMCS to 80000070H (inject
	// "other event" number 0). This will cause an MTF VMEXIT to be pended and delivered
	// immediately after completion of the VMRESUME from the STM.
	//
	// If the STM doesn't do this re-injection, the guest will execute two instructions, rather
	// than one, before the MTF VMEXIT occurs. This may have undesirable effects on the
	// MLE and must be avoided.
	//
	VmEntryControlInterrupt.Uint32 = 0;
	VmEntryControlInterrupt.Bits.InterruptType = INTERRUPT_TYPE_OTHER_EVENT;
	VmEntryControlInterrupt.Bits.Valid = 1;
	VmWrite32 (VMCS_32_CONTROL_VMENTRY_INTERRUPTION_INFO_INDEX, VmEntryControlInterrupt.Uint32);
}

/**

This function is STM handler for SMI.

@param Register X86 register context

**/
VOID
	StmHandlerSmi (
	IN X86_REGISTER *Register
	)
{
	UINT32              Index;
	UINTN               Rflags;
	VM_EXIT_INFO_BASIC  InfoBasic;
	X86_REGISTER        *Reg;

	Index = ApicToIndex (ReadLocalApicId ()); 
	InfoBasic.Uint32 = VmRead32 (VMCS_32_RO_EXIT_REASON_INDEX);

	STM_PERF_START (Index, InfoBasic.Bits.Reason, "OsSmiHandler", "StmHandlerSmi");

	Reg = &mGuestContextCommonSmi.GuestContextPerCpu[Index].Register;
	Register->Rsp = VmReadN (VMCS_N_GUEST_RSP_INDEX);
	CopyMem (Reg, Register, sizeof(X86_REGISTER));
#if 0
	DEBUG ((EFI_D_INFO, "%ld - !!!StmHandlerSmi InfoBasic %x reason %x \n", 
		(UINTN)Index,
		InfoBasic.Uint32,
		InfoBasic.Bits.Reason));
#endif
	//
	// Dispatch
	//
	if (InfoBasic.Bits.Reason >= VmExitReasonMax) {
		DEBUG ((EFI_D_ERROR, "%ld !!!UnknownReason: %d!!!\n", Index, InfoBasic.Bits.Reason));
		DumpVmcsAllField (Index);

		CpuDeadLoop ();
	}
	mGuestContextCommonSmi.GuestContextPerCpu[Index].InfoBasic.Uint32 = InfoBasic.Uint32;

	//
	// Call dispatch handler
	//

	mStmHandlerSmi[InfoBasic.Bits.Reason] (Index);

	VmWriteN (VMCS_N_GUEST_RSP_INDEX, Reg->Rsp); // sync RSP

	STM_PERF_END (Index, "OsSmiHandler", "StmHandlerSmi");

	CheckPendingMtf (Index);

	//  clear caches
	AsmWbinvd();  // flush caches
	//
	// Resume
	//
	Rflags = AsmVmResume (&mGuestContextCommonSmi.GuestContextPerCpu[Index].Register);
	// BUGBUG: - AsmVmLaunch if AsmVmResume fail
	if (VmRead32 (VMCS_32_RO_VM_INSTRUCTION_ERROR_INDEX) == VmxFailErrorVmResumeWithNonLaunchedVmcs) {
		//    DEBUG ((EFI_D_ERROR, "(STM):o(\n", (UINTN)Index));
		Rflags = AsmVmLaunch (&mGuestContextCommonSmi.GuestContextPerCpu[Index].Register);
	}

	AcquireSpinLock (&mHostContextCommon.DebugLock);

	DEBUG ((EFI_D_ERROR, "%ld StmHandlerSmi - !!!ResumeGuestSmi FAIL!!!", (UINTN)Index));
	DEBUG ((EFI_D_ERROR, "%ld StmHandlerSmi - Rflags: %08x\n", Index, Rflags));
	DEBUG ((EFI_D_ERROR, "%ld StmHandlerSmi - VMCS_32_RO_VM_INSTRUCTION_ERROR: %08x\n", Index, (UINTN)VmRead32 (VMCS_32_RO_VM_INSTRUCTION_ERROR_INDEX)));
	DumpVmcsAllField (Index);
	DumpRegContext (&mGuestContextCommonSmi.GuestContextPerCpu[Index].Register, Index);
	DumpGuestStack(Index);
	ReleaseSpinLock (&mHostContextCommon.DebugLock);

	CpuDeadLoop ();

	return ;
}
