/**
 * Enables the Hyper-V Hypercall page.
 *
 * @returns VBox status code.
 * @param   pVM                     Pointer to the VM.
 * @param   GCPhysHypercallPage     Where to map the hypercall page.
 */
VMMR3_INT_DECL(int) gimR3HvEnableHypercallPage(PVM pVM, RTGCPHYS GCPhysHypercallPage)
{
    PPDMDEVINSR3    pDevIns = pVM->gim.s.pDevInsR3;
    PGIMMMIO2REGION pRegion = &pVM->gim.s.u.Hv.aMmio2Regions[GIM_HV_HYPERCALL_PAGE_REGION_IDX];
    AssertPtrReturn(pDevIns, VERR_GIM_DEVICE_NOT_REGISTERED);

    if (pRegion->fMapped)
    {
        /*
         * Is it already enabled at the given guest-address?
         */
        if (pRegion->GCPhysPage == GCPhysHypercallPage)
            return VINF_SUCCESS;

        /*
         * If it's mapped at a different address, unmap the previous address.
         */
        int rc2 = gimR3HvDisableHypercallPage(pVM);
        AssertRC(rc2);
    }

    /*
     * Map the hypercall-page at the specified address.
     */
    Assert(!pRegion->fMapped);
    int rc = GIMR3Mmio2Map(pVM, pRegion, GCPhysHypercallPage);
    if (RT_SUCCESS(rc))
    {
        Assert(pRegion->GCPhysPage == GCPhysHypercallPage);

        /*
         * Patch the hypercall-page.
         */
        size_t cbWritten = 0;
        rc = VMMPatchHypercall(pVM, pRegion->pvPageR3, PAGE_SIZE, &cbWritten);
        if (   RT_SUCCESS(rc)
            && cbWritten < PAGE_SIZE)
        {
            uint8_t *pbLast = (uint8_t *)pRegion->pvPageR3 + cbWritten;
            *pbLast = 0xc3;  /* RET */

            /*
             * Notify VMM that hypercalls are now enabled for all VCPUs.
             */
            for (VMCPUID i = 0; i < pVM->cCpus; i++)
                VMMHypercallsEnable(&pVM->aCpus[i]);

            LogRel(("GIM: HyperV: Enabled hypercalls at %#RGp\n", GCPhysHypercallPage));
            return VINF_SUCCESS;
        }
        else
        {
            if (rc == VINF_SUCCESS)
                rc = VERR_GIM_OPERATION_FAILED;
            LogRel(("GIM: HyperV: VMMPatchHypercall failed. rc=%Rrc cbWritten=%u\n", rc, cbWritten));
        }

        GIMR3Mmio2Unmap(pVM, pRegion);
    }

    LogRel(("GIM: HyperV: GIMR3Mmio2Map failed. rc=%Rrc\n", rc));
    return rc;
}
/**
 * Exception handler for #UD.
 *
 * @param   pVCpu       Pointer to the VMCPU.
 * @param   pCtx        Pointer to the guest-CPU context.
 * @param   pDis        Pointer to the disassembled instruction state at RIP.
 *                      Optional, can be NULL.
 */
VMM_INT_DECL(int) gimKvmXcptUD(PVMCPU pVCpu, PCPUMCTX pCtx, PDISCPUSTATE pDis)
{
    /*
     * If we didn't ask for #UD to be trapped, bail.
     */
    PVM     pVM  = pVCpu->CTX_SUFF(pVM);
    PGIMKVM pKvm = &pVM->gim.s.u.Kvm;
    if (RT_UNLIKELY(!pVM->gim.s.u.Kvm.fTrapXcptUD))
        return VERR_GIM_OPERATION_FAILED;

    /*
     * Make sure guest ring-0 is the one making the hypercall.
     */
    if (CPUMGetGuestCPL(pVCpu))
        return VERR_GIM_HYPERCALL_ACCESS_DENIED;

    int rc = VINF_SUCCESS;
    if (!pDis)
    {
        /*
         * Disassemble the instruction at RIP to figure out if it's the Intel VMCALL instruction
         * or the AMD VMMCALL instruction and if so, handle it as a hypercall.
         */
        DISCPUSTATE Dis;
        rc = EMInterpretDisasCurrent(pVM, pVCpu, &Dis, NULL /* pcbInstr */);
        pDis = &Dis;
    }

    if (RT_SUCCESS(rc))
    {
        /*
         * Patch the instruction to so we don't have to spend time disassembling it each time.
         * Makes sense only for HM as with raw-mode we will be getting a #UD regardless.
         */
        if (   pDis->pCurInstr->uOpcode == OP_VMCALL
            || pDis->pCurInstr->uOpcode == OP_VMMCALL)
        {
            if (   pDis->pCurInstr->uOpcode != pKvm->uOpCodeNative
                && HMIsEnabled(pVM))
            {
                uint8_t abHypercall[3];
                size_t  cbWritten = 0;
                rc = VMMPatchHypercall(pVM, &abHypercall, sizeof(abHypercall), &cbWritten);
                AssertRC(rc);
                Assert(sizeof(abHypercall) == pDis->cbInstr);
                Assert(sizeof(abHypercall) == cbWritten);

                rc = PGMPhysSimpleWriteGCPtr(pVCpu, pCtx->rip, &abHypercall, sizeof(abHypercall));
            }

            /*
             * Perform the hypercall and update RIP.
             *
             * For HM, we can simply resume guest execution without performing the hypercall now and
             * do it on the next VMCALL/VMMCALL exit handler on the patched instruction.
             *
             * For raw-mode we need to do this now anyway. So we do it here regardless with an added
             * advantage is that it saves one world-switch for the HM case.
             */
            if (RT_SUCCESS(rc))
            {
                int rc2 = gimKvmHypercall(pVCpu, pCtx);
                AssertRC(rc2);
                pCtx->rip += pDis->cbInstr;
            }
            return rc;
        }
    }

    return VERR_GIM_OPERATION_FAILED;
}