/** * Disassembles the instruction at RIP and if it's a hypercall * instruction, performs the hypercall. * * @param pVCpu The cross context virtual CPU structure. * @param pCtx Pointer to the guest-CPU context. * @param pcbInstr Where to store the disassembled instruction length. * Optional, can be NULL. * * @todo This interface should disappear when IEM/REM execution engines * handle VMCALL/VMMCALL instructions to call into GIM when * required. See @bugref{7270#c168}. */ VMM_INT_DECL(VBOXSTRICTRC) GIMExecHypercallInstr(PVMCPU pVCpu, PCPUMCTX pCtx, uint8_t *pcbInstr) { PVM pVM = pVCpu->CTX_SUFF(pVM); VMCPU_ASSERT_EMT(pVCpu); if (RT_UNLIKELY(!GIMIsEnabled(pVM))) return VERR_GIM_NOT_ENABLED; unsigned cbInstr; DISCPUSTATE Dis; int rc = EMInterpretDisasCurrent(pVM, pVCpu, &Dis, &cbInstr); if (RT_SUCCESS(rc)) { if (pcbInstr) *pcbInstr = (uint8_t)cbInstr; switch (pVM->gim.s.enmProviderId) { case GIMPROVIDERID_HYPERV: return gimHvExecHypercallInstr(pVCpu, pCtx, &Dis); case GIMPROVIDERID_KVM: return gimKvmExecHypercallInstr(pVCpu, pCtx, &Dis); default: AssertMsgFailed(("GIMExecHypercallInstr: for provider %u not available/implemented\n", pVM->gim.s.enmProviderId)); return VERR_GIM_HYPERCALLS_NOT_AVAILABLE; } } Log(("GIM: GIMExecHypercallInstr: Failed to disassemble CS:RIP=%04x:%08RX64. rc=%Rrc\n", pCtx->cs.Sel, pCtx->rip, rc)); return rc; }
/** * Returns a pointer to the MMIO2 regions supported by Hyper-V. * * @returns Pointer to an array of MMIO2 regions. * @param pVM Pointer to the VM. * @param pcRegions Where to store the number of regions in the array. */ VMMR3_INT_DECL(PGIMMMIO2REGION) gimR3HvGetMmio2Regions(PVM pVM, uint32_t *pcRegions) { Assert(GIMIsEnabled(pVM)); PGIMHV pHv = &pVM->gim.s.u.Hv; *pcRegions = RT_ELEMENTS(pHv->aMmio2Regions); Assert(*pcRegions <= UINT8_MAX); /* See PGMR3PhysMMIO2Register(). */ return pHv->aMmio2Regions; }
/** * Does ring-0 per-VM GIM Hyper-V termination. * * @returns VBox status code. * @param pVM Pointer to the VM. */ VMMR0_INT_DECL(int) gimR0HvTermVM(PVM pVM) { AssertPtr(pVM); Assert(GIMIsEnabled(pVM)); PGIMHV pHv = &pVM->gim.s.u.Hv; RTSpinlockDestroy(pHv->hSpinlockR0); pHv->hSpinlockR0 = NIL_RTSPINLOCK; return VINF_SUCCESS; }
/** * Does ring-0 per-VM GIM Hyper-V initialization. * * @returns VBox status code. * @param pVM Pointer to the VM. */ VMMR0_INT_DECL(int) gimR0HvInitVM(PVM pVM) { AssertPtr(pVM); Assert(GIMIsEnabled(pVM)); PGIMHV pHv = &pVM->gim.s.u.Hv; Assert(pHv->hSpinlockR0 == NIL_RTSPINLOCK); int rc = RTSpinlockCreate(&pHv->hSpinlockR0, RTSPINLOCK_FLAGS_INTERRUPT_UNSAFE, "Hyper-V"); return rc; }
/** * Does ring-0 per-VM GIM termination. * * @returns VBox status code. * @param pVM Pointer to the VM. */ VMMR0_INT_DECL(int) GIMR0TermVM(PVM pVM) { if (!GIMIsEnabled(pVM)) return VINF_SUCCESS; switch (pVM->gim.s.enmProviderId) { case GIMPROVIDERID_HYPERV: return GIMR0HvTermVM(pVM); default: break; } return VINF_SUCCESS; }
/** * Returns whether the guest has configured and enabled calls to the hypervisor. * * @returns true if hypercalls are enabled and usable, false otherwise. * @param pVCpu Pointer to the VMCPU. */ VMM_INT_DECL(bool) GIMAreHypercallsEnabled(PVMCPU pVCpu) { PVM pVM = pVCpu->CTX_SUFF(pVM); if (!GIMIsEnabled(pVM)) return false; switch (pVM->gim.s.enmProviderId) { case GIMPROVIDERID_HYPERV: return GIMHvAreHypercallsEnabled(pVCpu); default: return false; } }
/** * Invokes the read-MSR handler for the GIM provider configured for the VM. * * @returns Strict VBox status code like CPUMQueryGuestMsr. * @retval VINF_CPUM_R3_MSR_READ * @retval VERR_CPUM_RAISE_GP_0 * * @param pVCpu Pointer to the VMCPU. * @param idMsr The MSR to read. * @param pRange The range this MSR belongs to. * @param puValue Where to store the MSR value read. */ VMM_INT_DECL(VBOXSTRICTRC) GIMReadMsr(PVMCPU pVCpu, uint32_t idMsr, PCCPUMMSRRANGE pRange, uint64_t *puValue) { Assert(pVCpu); PVM pVM = pVCpu->CTX_SUFF(pVM); Assert(GIMIsEnabled(pVM)); VMCPU_ASSERT_EMT(pVCpu); switch (pVM->gim.s.enmProviderId) { case GIMPROVIDERID_HYPERV: return GIMHvReadMsr(pVCpu, idMsr, pRange, puValue); default: AssertMsgFailed(("GIMReadMsr: for unknown provider %u idMsr=%#RX32 -> #GP(0)", pVM->gim.s.enmProviderId, idMsr)); return VERR_CPUM_RAISE_GP_0; } }
/** * Exception handler for \#UD when requested by the GIM provider. * * @returns Strict VBox status code. * @retval VINF_SUCCESS if the hypercall succeeded (even if its operation * failed). * @retval VINF_GIM_R3_HYPERCALL restart the hypercall from ring-3. * @retval VINF_GIM_HYPERCALL_CONTINUING continue hypercall without updating * RIP. * @retval VERR_GIM_HYPERCALL_ACCESS_DENIED CPL is insufficient. * @retval VERR_GIM_INVALID_HYPERCALL_INSTR instruction at RIP is not a valid * hypercall instruction. * * @param pVCpu The cross context virtual CPU structure. * @param pCtx Pointer to the guest-CPU context. * @param pDis Pointer to the disassembled instruction state at RIP. * If NULL is passed, it implies the disassembly of the * the instruction at RIP is the responsibility of the * GIM provider. * @param pcbInstr Where to store the instruction length of the hypercall * instruction. Optional, can be NULL. * * @thread EMT(pVCpu). */ VMM_INT_DECL(VBOXSTRICTRC) GIMXcptUD(PVMCPU pVCpu, PCPUMCTX pCtx, PDISCPUSTATE pDis, uint8_t *pcbInstr) { PVM pVM = pVCpu->CTX_SUFF(pVM); Assert(GIMIsEnabled(pVM)); Assert(pDis || pcbInstr); switch (pVM->gim.s.enmProviderId) { case GIMPROVIDERID_KVM: return gimKvmXcptUD(pVCpu, pCtx, pDis, pcbInstr); case GIMPROVIDERID_HYPERV: return gimHvXcptUD(pVCpu, pCtx, pDis, pcbInstr); default: return VERR_GIM_OPERATION_FAILED; } }
/** * Whether \#UD exceptions in the guest needs to be intercepted by the GIM * provider. * * At the moment, the reason why this isn't a more generic interface wrt to * exceptions is because of performance (each VM-exit would have to manually * check whether or not GIM needs to be notified). Left as a todo for later if * really required. * * @returns true if needed, false otherwise. * @param pVCpu The cross context virtual CPU structure. */ VMM_INT_DECL(bool) GIMShouldTrapXcptUD(PVMCPU pVCpu) { PVM pVM = pVCpu->CTX_SUFF(pVM); if (!GIMIsEnabled(pVM)) return false; switch (pVM->gim.s.enmProviderId) { case GIMPROVIDERID_KVM: return gimKvmShouldTrapXcptUD(pVCpu); case GIMPROVIDERID_HYPERV: return gimHvShouldTrapXcptUD(pVCpu); default: return false; } }
/** * Implements a GIM hypercall with the provider configured for the VM. * * @returns VBox status code. * @param pVCpu Pointer to the VMCPU. * @param pCtx Pointer to the guest-CPU context. */ VMM_INT_DECL(int) GIMHypercall(PVMCPU pVCpu, PCPUMCTX pCtx) { PVM pVM = pVCpu->CTX_SUFF(pVM); VMCPU_ASSERT_EMT(pVCpu); if (RT_UNLIKELY(!GIMIsEnabled(pVM))) return VERR_GIM_NOT_ENABLED; switch (pVM->gim.s.enmProviderId) { case GIMPROVIDERID_HYPERV: return GIMHvHypercall(pVCpu, pCtx); default: AssertMsgFailed(("GIMHypercall: for unknown provider %u\n", pVM->gim.s.enmProviderId)); return VERR_GIM_IPE_3; } }
/** * Implements a GIM hypercall with the provider configured for the VM. * * @returns Strict VBox status code. * @retval VINF_SUCCESS if the hypercall succeeded (even if its operation * failed). * @retval VINF_GIM_R3_HYPERCALL re-start the hypercall from ring-3. * @retval VERR_GIM_HYPERCALL_ACCESS_DENIED CPL is insufficient. * @retval VERR_GIM_HYPERCALLS_NOT_AVAILABLE hypercalls unavailable. * @retval VERR_GIM_NOT_ENABLED GIM is not enabled (shouldn't really happen) * @retval VERR_GIM_HYPERCALL_MEMORY_READ_FAILED hypercall failed while reading * memory. * @retval VERR_GIM_HYPERCALL_MEMORY_WRITE_FAILED hypercall failed while * writing memory. * * @param pVCpu The cross context virtual CPU structure. * @param pCtx Pointer to the guest-CPU context. * * @thread EMT. */ VMM_INT_DECL(VBOXSTRICTRC) GIMHypercall(PVMCPU pVCpu, PCPUMCTX pCtx) { PVM pVM = pVCpu->CTX_SUFF(pVM); VMCPU_ASSERT_EMT(pVCpu); if (RT_UNLIKELY(!GIMIsEnabled(pVM))) return VERR_GIM_NOT_ENABLED; switch (pVM->gim.s.enmProviderId) { case GIMPROVIDERID_HYPERV: return gimHvHypercall(pVCpu, pCtx); case GIMPROVIDERID_KVM: return gimKvmHypercall(pVCpu, pCtx); default: AssertMsgFailed(("GIMHypercall: for provider %u not available/implemented\n", pVM->gim.s.enmProviderId)); return VERR_GIM_HYPERCALLS_NOT_AVAILABLE; } }
/** * Updates Hyper-V's reference TSC page. * * @returns VBox status code. * @param pVM Pointer to the VM. * @param u64Offset The computed TSC offset. * @thread EMT. */ VMM_INT_DECL(int) gimR0HvUpdateParavirtTsc(PVM pVM, uint64_t u64Offset) { Assert(GIMIsEnabled(pVM)); bool fHvTscEnabled = MSR_GIM_HV_REF_TSC_IS_ENABLED(pVM->gim.s.u.Hv.u64TscPageMsr); if (RT_UNLIKELY(!fHvTscEnabled)) return VERR_GIM_PVTSC_NOT_ENABLED; PCGIMHV pcHv = &pVM->gim.s.u.Hv; PCGIMMMIO2REGION pcRegion = &pcHv->aMmio2Regions[GIM_HV_REF_TSC_PAGE_REGION_IDX]; PGIMHVREFTSC pRefTsc = (PGIMHVREFTSC)pcRegion->CTX_SUFF(pvPage); Assert(pRefTsc); /* * Hyper-V reports the reference time in 100 nanosecond units. */ uint64_t u64Tsc100Ns = TMCpuTicksPerSecond(pVM) / RT_NS_10MS; int64_t i64TscOffset = (int64_t)u64Offset / u64Tsc100Ns; /* * The TSC page can be simulatenously read by other VCPUs in the guest. The * spinlock is only for protecting simultaneous hypervisor writes from other * EMTs. */ RTSpinlockAcquire(pcHv->hSpinlockR0); if (pRefTsc->i64TscOffset != i64TscOffset) { if (pRefTsc->u32TscSequence < UINT32_C(0xfffffffe)) ASMAtomicIncU32(&pRefTsc->u32TscSequence); else ASMAtomicWriteU32(&pRefTsc->u32TscSequence, 1); ASMAtomicWriteS64(&pRefTsc->i64TscOffset, i64TscOffset); } RTSpinlockRelease(pcHv->hSpinlockR0); Assert(pRefTsc->u32TscSequence != 0); Assert(pRefTsc->u32TscSequence != UINT32_C(0xffffffff)); return VINF_SUCCESS; }