/* $Id: IOMR0Mmio.cpp $ */
/** @file
 * IOM - Host Context Ring 0, MMIO.
 */

/*
 * Copyright (C) 2006-2020 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */


/*********************************************************************************************************************************
*   Header Files                                                                                                                 *
*********************************************************************************************************************************/
#define LOG_GROUP LOG_GROUP_IOM_MMIO
#include <VBox/vmm/iom.h>
#include "IOMInternal.h"
#include <VBox/vmm/pdmdev.h>
#include <VBox/vmm/vmcc.h>
#include <VBox/err.h>
#include <VBox/log.h>
#include <iprt/assert.h>
#include <iprt/mem.h>
#include <iprt/memobj.h>
#include <iprt/process.h>
#include <iprt/string.h>



/**
 * Initializes the MMIO related members.
 *
 * @param   pGVM    Pointer to the global VM structure.
 */
void iomR0MmioInitPerVMData(PGVM pGVM)
{
    pGVM->iomr0.s.hMmioMapObj      = NIL_RTR0MEMOBJ;
    pGVM->iomr0.s.hMmioMemObj      = NIL_RTR0MEMOBJ;
#ifdef VBOX_WITH_STATISTICS
    pGVM->iomr0.s.hMmioStatsMapObj = NIL_RTR0MEMOBJ;
    pGVM->iomr0.s.hMmioStatsMemObj = NIL_RTR0MEMOBJ;
#endif
}


/**
 * Cleans up MMIO related resources.
 */
void iomR0MmioCleanupVM(PGVM pGVM)
{
    RTR0MemObjFree(pGVM->iomr0.s.hMmioMapObj, true /*fFreeMappings*/);
    pGVM->iomr0.s.hMmioMapObj      = NIL_RTR0MEMOBJ;
    RTR0MemObjFree(pGVM->iomr0.s.hMmioMemObj, true /*fFreeMappings*/);
    pGVM->iomr0.s.hMmioMemObj      = NIL_RTR0MEMOBJ;
#ifdef VBOX_WITH_STATISTICS
    RTR0MemObjFree(pGVM->iomr0.s.hMmioStatsMapObj, true /*fFreeMappings*/);
    pGVM->iomr0.s.hMmioStatsMapObj = NIL_RTR0MEMOBJ;
    RTR0MemObjFree(pGVM->iomr0.s.hMmioStatsMemObj, true /*fFreeMappings*/);
    pGVM->iomr0.s.hMmioStatsMemObj = NIL_RTR0MEMOBJ;
#endif
}


/**
 * Implements PDMDEVHLPR0::pfnMmioSetUpContext.
 *
 * @param   pGVM            The global (ring-0) VM structure.
 * @param   pDevIns         The device instance.
 * @param   hRegion         The MMIO region handle (already registered in
 *                          ring-3).
 * @param   pfnWrite        The write handler callback, optional.
 * @param   pfnRead         The read handler callback, optional.
 * @param   pfnFill         The fill handler callback, optional.
 * @param   pvUser          User argument for the callbacks.
 * @thread  EMT(0)
 * @note    Only callable at VM creation time.
 */
VMMR0_INT_DECL(int)  IOMR0MmioSetUpContext(PGVM pGVM, PPDMDEVINS pDevIns, IOMMMIOHANDLE hRegion, PFNIOMMMIONEWWRITE pfnWrite,
                                           PFNIOMMMIONEWREAD pfnRead, PFNIOMMMIONEWFILL pfnFill, void *pvUser)
{
    /*
     * Validate input and state.
     */
    VM_ASSERT_EMT0_RETURN(pGVM, VERR_VM_THREAD_NOT_EMT);
    VM_ASSERT_STATE_RETURN(pGVM, VMSTATE_CREATING, VERR_VM_INVALID_VM_STATE);
    AssertReturn(hRegion < pGVM->iomr0.s.cMmioAlloc, VERR_IOM_INVALID_MMIO_HANDLE);
    AssertReturn(hRegion < pGVM->iom.s.cMmioRegs, VERR_IOM_INVALID_MMIO_HANDLE);
    AssertPtrReturn(pDevIns, VERR_INVALID_HANDLE);
    AssertReturn(pDevIns->pDevInsForR3 != NIL_RTR3PTR && !(pDevIns->pDevInsForR3 & PAGE_OFFSET_MASK), VERR_INVALID_PARAMETER);
    AssertReturn(pGVM->iomr0.s.paMmioRing3Regs[hRegion].pDevIns == pDevIns->pDevInsForR3, VERR_IOM_INVALID_MMIO_HANDLE);
    AssertReturn(pGVM->iomr0.s.paMmioRegs[hRegion].pDevIns == NULL, VERR_WRONG_ORDER);
    Assert(pGVM->iomr0.s.paMmioRegs[hRegion].idxSelf == hRegion);

    AssertReturn(pfnWrite || pfnRead || pfnFill, VERR_INVALID_PARAMETER);
    AssertPtrNullReturn(pfnWrite, VERR_INVALID_POINTER);
    AssertPtrNullReturn(pfnRead, VERR_INVALID_POINTER);
    AssertPtrNullReturn(pfnFill, VERR_INVALID_POINTER);

    uint32_t const fFlags   = pGVM->iomr0.s.paMmioRing3Regs[hRegion].fFlags;
    RTGCPHYS const cbRegion = pGVM->iomr0.s.paMmioRing3Regs[hRegion].cbRegion;
    AssertMsgReturn(cbRegion > 0 && cbRegion <= _1T, ("cbRegion=%#RGp\n", cbRegion), VERR_IOM_INVALID_MMIO_HANDLE);

    /*
     * Do the job.
     */
    pGVM->iomr0.s.paMmioRegs[hRegion].cbRegion          = cbRegion;
    pGVM->iomr0.s.paMmioRegs[hRegion].pvUser            = pvUser;
    pGVM->iomr0.s.paMmioRegs[hRegion].pDevIns           = pDevIns;
    pGVM->iomr0.s.paMmioRegs[hRegion].pfnWriteCallback  = pfnWrite;
    pGVM->iomr0.s.paMmioRegs[hRegion].pfnReadCallback   = pfnRead;
    pGVM->iomr0.s.paMmioRegs[hRegion].pfnFillCallback   = pfnFill;
    pGVM->iomr0.s.paMmioRegs[hRegion].fFlags            = fFlags;
#ifdef VBOX_WITH_STATISTICS
    uint16_t const idxStats = pGVM->iomr0.s.paMmioRing3Regs[hRegion].idxStats;
    pGVM->iomr0.s.paMmioRegs[hRegion].idxStats          = (uint32_t)idxStats < pGVM->iomr0.s.cMmioStatsAllocation
                                                        ? idxStats : UINT16_MAX;
#else
    pGVM->iomr0.s.paMmioRegs[hRegion].idxStats          = UINT16_MAX;
#endif

    pGVM->iomr0.s.paMmioRing3Regs[hRegion].fRing0 = true;

    return VINF_SUCCESS;
}


/**
 * Grows the MMIO registration (all contexts) and lookup tables.
 *
 * @returns VBox status code.
 * @param   pGVM            The global (ring-0) VM structure.
 * @param   cReqMinEntries  The minimum growth (absolute).
 * @thread  EMT(0)
 * @note    Only callable at VM creation time.
 */
VMMR0_INT_DECL(int) IOMR0MmioGrowRegistrationTables(PGVM pGVM, uint64_t cReqMinEntries)
{
    /*
     * Validate input and state.
     */
    VM_ASSERT_EMT0_RETURN(pGVM, VERR_VM_THREAD_NOT_EMT);
    VM_ASSERT_STATE_RETURN(pGVM, VMSTATE_CREATING, VERR_VM_INVALID_VM_STATE);
    AssertReturn(cReqMinEntries <= _4K, VERR_IOM_TOO_MANY_MMIO_REGISTRATIONS);
    uint32_t cNewEntries = (uint32_t)cReqMinEntries;
    AssertReturn(cNewEntries >= pGVM->iom.s.cMmioAlloc, VERR_IOM_MMIO_IPE_1);
    uint32_t const cOldEntries = pGVM->iomr0.s.cMmioAlloc;
    ASMCompilerBarrier();
    AssertReturn(cNewEntries >= cOldEntries, VERR_IOM_MMIO_IPE_2);
    AssertReturn(pGVM->iom.s.cMmioRegs >= pGVM->iomr0.s.cMmioMax, VERR_IOM_MMIO_IPE_3);

    /*
     * Allocate the new tables.  We use a single allocation for the three tables (ring-0,
     * ring-3, lookup) and does a partial mapping of the result to ring-3.
     */
    uint32_t const cbRing0  = RT_ALIGN_32(cNewEntries * sizeof(IOMMMIOENTRYR0),     PAGE_SIZE);
    uint32_t const cbRing3  = RT_ALIGN_32(cNewEntries * sizeof(IOMMMIOENTRYR3),     PAGE_SIZE);
    uint32_t const cbShared = RT_ALIGN_32(cNewEntries * sizeof(IOMMMIOLOOKUPENTRY), PAGE_SIZE);
    uint32_t const cbNew    = cbRing0 + cbRing3 + cbShared;

    /* Use the rounded up space as best we can. */
    cNewEntries = RT_MIN(RT_MIN(cbRing0 / sizeof(IOMMMIOENTRYR0), cbRing3 / sizeof(IOMMMIOENTRYR3)),
                         cbShared / sizeof(IOMMMIOLOOKUPENTRY));

    RTR0MEMOBJ hMemObj;
    int rc = RTR0MemObjAllocPage(&hMemObj, cbNew, false /*fExecutable*/);
    if (RT_SUCCESS(rc))
    {
        /*
         * Zero and map it.
         */
        RT_BZERO(RTR0MemObjAddress(hMemObj), cbNew);

        RTR0MEMOBJ hMapObj;
        rc = RTR0MemObjMapUserEx(&hMapObj, hMemObj, (RTR3PTR)-1, PAGE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE,
                                 RTR0ProcHandleSelf(), cbRing0, cbNew - cbRing0);
        if (RT_SUCCESS(rc))
        {
            PIOMMMIOENTRYR0       const paRing0    = (PIOMMMIOENTRYR0)RTR0MemObjAddress(hMemObj);
            PIOMMMIOENTRYR3       const paRing3    = (PIOMMMIOENTRYR3)((uintptr_t)paRing0 + cbRing0);
            PIOMMMIOLOOKUPENTRY   const paLookup   = (PIOMMMIOLOOKUPENTRY)((uintptr_t)paRing3 + cbRing3);
            RTR3UINTPTR           const uAddrRing3 = RTR0MemObjAddressR3(hMapObj);

            /*
             * Copy over the old info and initialize the idxSelf and idxStats members.
             */
            if (pGVM->iomr0.s.paMmioRegs != NULL)
            {
                memcpy(paRing0,  pGVM->iomr0.s.paMmioRegs,      sizeof(paRing0[0])  * cOldEntries);
                memcpy(paRing3,  pGVM->iomr0.s.paMmioRing3Regs, sizeof(paRing3[0])  * cOldEntries);
                memcpy(paLookup, pGVM->iomr0.s.paMmioLookup,    sizeof(paLookup[0]) * cOldEntries);
            }

            size_t i = cbRing0 / sizeof(*paRing0);
            while (i-- > cOldEntries)
            {
                paRing0[i].idxSelf  = (uint16_t)i;
                paRing0[i].idxStats = UINT16_MAX;
            }
            i = cbRing3 / sizeof(*paRing3);
            while (i-- > cOldEntries)
            {
                paRing3[i].idxSelf  = (uint16_t)i;
                paRing3[i].idxStats = UINT16_MAX;
            }

            /*
             * Switch the memory handles.
             */
            RTR0MEMOBJ hTmp = pGVM->iomr0.s.hMmioMapObj;
            pGVM->iomr0.s.hMmioMapObj = hMapObj;
            hMapObj = hTmp;

            hTmp = pGVM->iomr0.s.hMmioMemObj;
            pGVM->iomr0.s.hMmioMemObj = hMemObj;
            hMemObj = hTmp;

            /*
             * Update the variables.
             */
            pGVM->iomr0.s.paMmioRegs      = paRing0;
            pGVM->iomr0.s.paMmioRing3Regs = paRing3;
            pGVM->iomr0.s.paMmioLookup    = paLookup;
            pGVM->iom.s.paMmioRegs        = uAddrRing3;
            pGVM->iom.s.paMmioLookup      = uAddrRing3 + cbRing3;
            pGVM->iom.s.cMmioAlloc        = cNewEntries;
            pGVM->iomr0.s.cMmioAlloc      = cNewEntries;

            /*
             * Free the old allocation.
             */
            RTR0MemObjFree(hMapObj, true /*fFreeMappings*/);
        }
        RTR0MemObjFree(hMemObj, true /*fFreeMappings*/);
    }

    return rc;
}


/**
 * Grows the MMIO statistics table.
 *
 * @returns VBox status code.
 * @param   pGVM            The global (ring-0) VM structure.
 * @param   cReqMinEntries  The minimum growth (absolute).
 * @thread  EMT(0)
 * @note    Only callable at VM creation time.
 */
VMMR0_INT_DECL(int) IOMR0MmioGrowStatisticsTable(PGVM pGVM, uint64_t cReqMinEntries)
{
    /*
     * Validate input and state.
     */
    VM_ASSERT_EMT0_RETURN(pGVM, VERR_VM_THREAD_NOT_EMT);
    VM_ASSERT_STATE_RETURN(pGVM, VMSTATE_CREATING, VERR_VM_INVALID_VM_STATE);
    AssertReturn(cReqMinEntries <= _64K, VERR_IOM_TOO_MANY_MMIO_REGISTRATIONS);
    uint32_t cNewEntries = (uint32_t)cReqMinEntries;
#ifdef VBOX_WITH_STATISTICS
    uint32_t const cOldEntries = pGVM->iomr0.s.cMmioStatsAllocation;
    ASMCompilerBarrier();
#else
    uint32_t const cOldEntries = 0;
#endif
    AssertReturn(cNewEntries > cOldEntries, VERR_IOM_MMIO_IPE_1);
    AssertReturn(pGVM->iom.s.cMmioStatsAllocation == cOldEntries, VERR_IOM_MMIO_IPE_1);
    AssertReturn(pGVM->iom.s.cMmioStats <= cOldEntries, VERR_IOM_MMIO_IPE_2);
#ifdef VBOX_WITH_STATISTICS
    AssertReturn(!pGVM->iomr0.s.fMmioStatsFrozen, VERR_WRONG_ORDER);
#endif

    /*
     * Allocate a new table, zero it and map it.
     */
#ifndef VBOX_WITH_STATISTICS
    AssertFailedReturn(VERR_NOT_SUPPORTED);
#else
    uint32_t const cbNew = RT_ALIGN_32(cNewEntries * sizeof(IOMMMIOSTATSENTRY), PAGE_SIZE);
    cNewEntries = cbNew / sizeof(IOMMMIOSTATSENTRY);

    RTR0MEMOBJ hMemObj;
    int rc = RTR0MemObjAllocPage(&hMemObj, cbNew, false /*fExecutable*/);
    if (RT_SUCCESS(rc))
    {
        RT_BZERO(RTR0MemObjAddress(hMemObj), cbNew);

        RTR0MEMOBJ hMapObj;
        rc = RTR0MemObjMapUser(&hMapObj, hMemObj, (RTR3PTR)-1, PAGE_SIZE, RTMEM_PROT_READ | RTMEM_PROT_WRITE, RTR0ProcHandleSelf());
        if (RT_SUCCESS(rc))
        {
            PIOMMMIOSTATSENTRY pMmioStats = (PIOMMMIOSTATSENTRY)RTR0MemObjAddress(hMemObj);

            /*
             * Anything to copy over and free up?
             */
            if (pGVM->iomr0.s.paMmioStats)
                memcpy(pMmioStats, pGVM->iomr0.s.paMmioStats, cOldEntries * sizeof(IOMMMIOSTATSENTRY));

            /*
             * Switch the memory handles.
             */
            RTR0MEMOBJ hTmp = pGVM->iomr0.s.hMmioStatsMapObj;
            pGVM->iomr0.s.hMmioStatsMapObj = hMapObj;
            hMapObj = hTmp;

            hTmp = pGVM->iomr0.s.hMmioStatsMemObj;
            pGVM->iomr0.s.hMmioStatsMemObj = hMemObj;
            hMemObj = hTmp;

            /*
             * Update the variables.
             */
            pGVM->iomr0.s.paMmioStats          = pMmioStats;
            pGVM->iom.s.paMmioStats            = RTR0MemObjAddressR3(pGVM->iomr0.s.hMmioStatsMapObj);
            pGVM->iom.s.cMmioStatsAllocation   = cNewEntries;
            pGVM->iomr0.s.cMmioStatsAllocation = cNewEntries;

            /*
             * Free the old allocation.
             */
            RTR0MemObjFree(hMapObj, true /*fFreeMappings*/);
        }
        RTR0MemObjFree(hMemObj, true /*fFreeMappings*/);
    }
    return rc;
#endif /* VBOX_WITH_STATISTICS */
}


/**
 * Called after all devices has been instantiated to copy over the statistics
 * indices to the ring-0 MMIO registration table.
 *
 * This simplifies keeping statistics for MMIO ranges that are ring-3 only.
 *
 * @returns VBox status code.
 * @param   pGVM            The global (ring-0) VM structure.
 * @thread  EMT(0)
 * @note    Only callable at VM creation time.
 */
VMMR0_INT_DECL(int) IOMR0MmioSyncStatisticsIndices(PGVM pGVM)
{
    VM_ASSERT_EMT0_RETURN(pGVM, VERR_VM_THREAD_NOT_EMT);
    VM_ASSERT_STATE_RETURN(pGVM, VMSTATE_CREATING, VERR_VM_INVALID_VM_STATE);

#ifdef VBOX_WITH_STATISTICS
    /*
     * First, freeze the statistics array:
     */
    pGVM->iomr0.s.fMmioStatsFrozen = true;

    /*
     * Second, synchronize the indices:
     */
    uint32_t const          cRegs        = RT_MIN(pGVM->iom.s.cMmioRegs, pGVM->iomr0.s.cMmioAlloc);
    uint32_t const          cStatsAlloc  = pGVM->iomr0.s.cMmioStatsAllocation;
    PIOMMMIOENTRYR0         paMmioRegs   = pGVM->iomr0.s.paMmioRegs;
    IOMMMIOENTRYR3 const   *paMmioRegsR3 = pGVM->iomr0.s.paMmioRing3Regs;
    AssertReturn((paMmioRegs && paMmioRegsR3) || cRegs == 0, VERR_IOM_MMIO_IPE_3);

    for (uint32_t i = 0 ; i < cRegs; i++)
    {
        uint16_t idxStats = paMmioRegsR3[i].idxStats;
        paMmioRegs[i].idxStats = idxStats < cStatsAlloc ? idxStats : UINT16_MAX;
    }

#else
    RT_NOREF(pGVM);
#endif
    return VINF_SUCCESS;
}

