/* $Id: PDMUsb.cpp $ */
/** @file
 * PDM - Pluggable Device and Driver Manager, USB part.
 */

/*
 * Copyright (C) 2006-2007 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_PDM_DRIVER
#include "PDMInternal.h"
#include <VBox/vmm/pdm.h>
#include <VBox/vusb.h>
#include <VBox/vmm/mm.h>
#include <VBox/vmm/cfgm.h>
#include <VBox/vmm/vmm.h>
#include <VBox/sup.h>
#include <VBox/vmm/vm.h>
#include <VBox/version.h>
#include <VBox/err.h>

#include <VBox/log.h>
#include <iprt/assert.h>
#include <iprt/thread.h>
#include <iprt/string.h>
#include <iprt/asm.h>
#include <iprt/alloc.h>
#include <iprt/alloca.h>
#include <iprt/path.h>
#include <iprt/uuid.h>


/*******************************************************************************
*   Structures and Typedefs                                                    *
*******************************************************************************/
/**
 * Internal callback structure pointer.
 *
 * The main purpose is to define the extra data we associate
 * with PDMUSBREGCB so we can find the VM instance and so on.
 */
typedef struct PDMUSBREGCBINT
{
    /** The callback structure. */
    PDMUSBREGCB     Core;
    /** A bit of padding. */
    uint32_t        u32[4];
    /** VM Handle. */
    PVM             pVM;
} PDMUSBREGCBINT, *PPDMUSBREGCBINT;
typedef const PDMUSBREGCBINT *PCPDMUSBREGCBINT;


/*******************************************************************************
*   Defined Constants And Macros                                               *
*******************************************************************************/
/** @def PDMUSB_ASSERT_USBINS
 * Asserts the validity of the USB device instance.
 */
#ifdef VBOX_STRICT
# define PDMUSB_ASSERT_USBINS(pUsbIns) \
    do { \
        AssertPtr(pUsbIns); \
        Assert(pUsbIns->u32Version == PDM_USBINS_VERSION); \
        Assert(pUsbIns->pvInstanceDataR3 == (void *)&pUsbIns->achInstanceData[0]); \
    } while (0)
#else
# define PDMUSB_ASSERT_USBINS(pUsbIns)   do { } while (0)
#endif


/*******************************************************************************
*   Internal Functions                                                         *
*******************************************************************************/
static void pdmR3UsbDestroyDevice(PVM pVM, PPDMUSBINS pUsbIns);


/*******************************************************************************
*   Global Variables                                                           *
*******************************************************************************/
extern const PDMUSBHLP g_pdmR3UsbHlp;


AssertCompile(sizeof(PDMUSBINSINT) <= RT_SIZEOFMEMB(PDMUSBINS, Internal.padding));


/**
 * Registers a USB hub driver.
 *
 * @returns VBox status code.
 * @param   pVM             The VM handle.
 * @param   pDrvIns         The driver instance of the hub.
 * @param   fVersions       Indicates the kinds of USB devices that can be attached to this HUB.
 * @param   cPorts          The number of ports.
 * @param   pUsbHubReg      The hub callback structure that PDMUsb uses to interact with it.
 * @param   ppUsbHubHlp     The helper callback structure that the hub uses to talk to PDMUsb.
 * @thread  EMT
 */
int pdmR3UsbRegisterHub(PVM pVM, PPDMDRVINS pDrvIns, uint32_t fVersions, uint32_t cPorts, PCPDMUSBHUBREG pUsbHubReg, PPCPDMUSBHUBHLP ppUsbHubHlp)
{
    /*
     * Validate input.
     */
    /* The driver must be in the USB class. */
    if (!(pDrvIns->pReg->fClass & PDM_DRVREG_CLASS_USB))
    {
        LogRel(("pdmR3UsbRegisterHub: fClass=%#x expected %#x to be set\n", pDrvIns->pReg->fClass, PDM_DRVREG_CLASS_USB));
        return VERR_INVALID_PARAMETER;
    }
    AssertMsgReturn(!(fVersions & ~(VUSB_STDVER_11 | VUSB_STDVER_20)), ("%#x\n", fVersions), VERR_INVALID_PARAMETER);
    AssertPtrReturn(ppUsbHubHlp, VERR_INVALID_POINTER);
    AssertPtrReturn(pUsbHubReg, VERR_INVALID_POINTER);
    AssertReturn(pUsbHubReg->u32Version == PDM_USBHUBREG_VERSION, VERR_INVALID_MAGIC);
    AssertReturn(pUsbHubReg->u32TheEnd == PDM_USBHUBREG_VERSION, VERR_INVALID_MAGIC);
    AssertPtrReturn(pUsbHubReg->pfnAttachDevice, VERR_INVALID_PARAMETER);
    AssertPtrReturn(pUsbHubReg->pfnDetachDevice, VERR_INVALID_PARAMETER);

    /*
     * Check for duplicate registration and find the last hub for FIFO registration.
     */
    PPDMUSBHUB pPrev = NULL;
    for (PPDMUSBHUB pCur = pVM->pdm.s.pUsbHubs; pCur; pCur = pCur->pNext)
    {
        if (pCur->pDrvIns == pDrvIns)
            return VERR_PDM_USB_HUB_EXISTS;
        pPrev = pCur;
    }

    /*
     * Create an internal USB hub structure.
     */
    PPDMUSBHUB pHub = (PPDMUSBHUB)MMR3HeapAlloc(pVM, MM_TAG_PDM_DRIVER, sizeof(*pHub));
    if (!pHub)
        return VERR_NO_MEMORY;

    pHub->fVersions = fVersions;
    pHub->cPorts = cPorts;
    pHub->cAvailablePorts = cPorts;
    pHub->pDrvIns = pDrvIns;
    pHub->Reg = *pUsbHubReg;
    pHub->pNext = NULL;

    /* link it */
    if (pPrev)
        pPrev->pNext = pHub;
    else
        pVM->pdm.s.pUsbHubs = pHub;

    Log(("PDM: Registered USB hub %p/%s\n", pDrvIns, pDrvIns->pReg->szName));
    return VINF_SUCCESS;
}


/**
 * Loads one device module and call the registration entry point.
 *
 * @returns VBox status code.
 * @param   pVM             VM handle.
 * @param   pRegCB          The registration callback stuff.
 * @param   pszFilename     Module filename.
 * @param   pszName         Module name.
 */
static int pdmR3UsbLoad(PVM pVM, PCPDMUSBREGCBINT pRegCB, const char *pszFilename, const char *pszName)
{
    /*
     * Load it.
     */
    int rc = pdmR3LoadR3U(pVM->pUVM, pszFilename, pszName);
    if (RT_SUCCESS(rc))
    {
        /*
         * Get the registration export and call it.
         */
        FNPDMVBOXUSBREGISTER *pfnVBoxUsbRegister;
        rc = PDMR3LdrGetSymbolR3(pVM, pszName, "VBoxUsbRegister", (void **)&pfnVBoxUsbRegister);
        if (RT_SUCCESS(rc))
        {
            Log(("PDM: Calling VBoxUsbRegister (%p) of %s (%s)\n", pfnVBoxUsbRegister, pszName, pszFilename));
            rc = pfnVBoxUsbRegister(&pRegCB->Core, VBOX_VERSION);
            if (RT_SUCCESS(rc))
                Log(("PDM: Successfully loaded device module %s (%s).\n", pszName, pszFilename));
            else
                AssertMsgFailed(("VBoxDevicesRegister failed with rc=%Rrc for module %s (%s)\n", rc, pszName, pszFilename));
        }
        else
        {
            AssertMsgFailed(("Failed to locate 'VBoxUsbRegister' in %s (%s) rc=%Rrc\n", pszName, pszFilename, rc));
            if (rc == VERR_SYMBOL_NOT_FOUND)
                rc = VERR_PDM_NO_REGISTRATION_EXPORT;
        }
    }
    else
        AssertMsgFailed(("Failed to load VBoxDD!\n"));
    return rc;
}



/**
 * @interface_method_impl{PDMUSBREGCB,pfnRegister}
 */
static DECLCALLBACK(int) pdmR3UsbReg_Register(PCPDMUSBREGCB pCallbacks, PCPDMUSBREG pReg)
{
    /*
     * Validate the registration structure.
     */
    Assert(pReg);
    AssertMsgReturn(pReg->u32Version == PDM_USBREG_VERSION,
                    ("Unknown struct version %#x!\n", pReg->u32Version),
                    VERR_PDM_UNKNOWN_USBREG_VERSION);
    AssertMsgReturn(    pReg->szName[0]
                    &&  strlen(pReg->szName) < sizeof(pReg->szName)
                    &&  pdmR3IsValidName(pReg->szName),
                    ("Invalid name '%.s'\n", sizeof(pReg->szName), pReg->szName),
                    VERR_PDM_INVALID_USB_REGISTRATION);
    AssertMsgReturn(pReg->fFlags == 0, ("fFlags=%#x\n", pReg->fFlags), VERR_PDM_INVALID_USB_REGISTRATION);
    AssertMsgReturn(pReg->cMaxInstances > 0,
                    ("Max instances %u! (USB Device %s)\n", pReg->cMaxInstances, pReg->szName),
                    VERR_PDM_INVALID_USB_REGISTRATION);
    AssertMsgReturn(pReg->cbInstance <= _1M,
                    ("Instance size %d bytes! (USB Device %s)\n", pReg->cbInstance, pReg->szName),
                    VERR_PDM_INVALID_USB_REGISTRATION);
    AssertMsgReturn(pReg->pfnConstruct, ("No constructor! (USB Device %s)\n", pReg->szName),
                    VERR_PDM_INVALID_USB_REGISTRATION);

    /*
     * Check for duplicate and find FIFO entry at the same time.
     */
    PCPDMUSBREGCBINT pRegCB = (PCPDMUSBREGCBINT)pCallbacks;
    PPDMUSB pUsbPrev = NULL;
    PPDMUSB pUsb = pRegCB->pVM->pdm.s.pUsbDevs;
    for (; pUsb; pUsbPrev = pUsb, pUsb = pUsb->pNext)
        AssertMsgReturn(strcmp(pUsb->pReg->szName, pReg->szName),
                        ("USB Device '%s' already exists\n", pReg->szName),
                        VERR_PDM_USB_NAME_CLASH);

    /*
     * Allocate new device structure and insert it into the list.
     */
    pUsb = (PPDMUSB)MMR3HeapAlloc(pRegCB->pVM, MM_TAG_PDM_DEVICE, sizeof(*pUsb));
    if (pUsb)
    {
        pUsb->pNext = NULL;
        pUsb->iNextInstance = 0;
        pUsb->pInstances = NULL;
        pUsb->pReg = pReg;
        pUsb->cchName = (RTUINT)strlen(pReg->szName);

        if (pUsbPrev)
            pUsbPrev->pNext = pUsb;
        else
            pRegCB->pVM->pdm.s.pUsbDevs = pUsb;
        Log(("PDM: Registered USB device '%s'\n", pReg->szName));
        return VINF_SUCCESS;
    }
    return VERR_NO_MEMORY;
}


/**
 * Load USB Device modules.
 *
 * This is called by pdmR3DevInit() after it has loaded it's device modules.
 *
 * @returns VBox status code.
 * @param   pVM         The VM handle.
 */
int pdmR3UsbLoadModules(PVM pVM)
{
    LogFlow(("pdmR3UsbLoadModules:\n"));

    AssertRelease(!(RT_OFFSETOF(PDMUSBINS, achInstanceData) & 15));
    AssertRelease(sizeof(pVM->pdm.s.pUsbInstances->Internal.s) <= sizeof(pVM->pdm.s.pUsbInstances->Internal.padding));

    /*
     * Initialize the callback structure.
     */
    PDMUSBREGCBINT RegCB;
    RegCB.Core.u32Version = PDM_USBREG_CB_VERSION;
    RegCB.Core.pfnRegister = pdmR3UsbReg_Register;
    RegCB.pVM = pVM;

    /*
     * Load the builtin module
     */
    PCFGMNODE pUsbNode = CFGMR3GetChild(CFGMR3GetRoot(pVM), "PDM/USB/");
    bool fLoadBuiltin;
    int rc = CFGMR3QueryBool(pUsbNode, "LoadBuiltin", &fLoadBuiltin);
    if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT)
        fLoadBuiltin = true;
    else if (RT_FAILURE(rc))
    {
        AssertMsgFailed(("Configuration error: Querying boolean \"LoadBuiltin\" failed with %Rrc\n", rc));
        return rc;
    }
    if (fLoadBuiltin)
    {
        /* make filename */
        char *pszFilename = pdmR3FileR3("VBoxDD", true /*fShared*/);
        if (!pszFilename)
            return VERR_NO_TMP_MEMORY;
        rc = pdmR3UsbLoad(pVM, &RegCB, pszFilename, "VBoxDD");
        RTMemTmpFree(pszFilename);
        if (RT_FAILURE(rc))
            return rc;
    }

    /*
     * Load additional device modules.
     */
    PCFGMNODE pCur;
    for (pCur = CFGMR3GetFirstChild(pUsbNode); pCur; pCur = CFGMR3GetNextChild(pCur))
    {
        /*
         * Get the name and path.
         */
        char szName[PDMMOD_NAME_LEN];
        rc = CFGMR3GetName(pCur, &szName[0], sizeof(szName));
        if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
        {
            AssertMsgFailed(("configuration error: The module name is too long, cchName=%zu.\n", CFGMR3GetNameLen(pCur)));
            return VERR_PDM_MODULE_NAME_TOO_LONG;
        }
        else if (RT_FAILURE(rc))
        {
            AssertMsgFailed(("CFGMR3GetName -> %Rrc.\n", rc));
            return rc;
        }

        /* the path is optional, if no path the module name + path is used. */
        char szFilename[RTPATH_MAX];
        rc = CFGMR3QueryString(pCur, "Path", &szFilename[0], sizeof(szFilename));
        if (rc == VERR_CFGM_VALUE_NOT_FOUND)
            strcpy(szFilename, szName);
        else if (RT_FAILURE(rc))
        {
            AssertMsgFailed(("configuration error: Failure to query the module path, rc=%Rrc.\n", rc));
            return rc;
        }

        /* prepend path? */
        if (!RTPathHavePath(szFilename))
        {
            char *psz = pdmR3FileR3(szFilename, false /*fShared*/);
            if (!psz)
                return VERR_NO_TMP_MEMORY;
            size_t cch = strlen(psz) + 1;
            if (cch > sizeof(szFilename))
            {
                RTMemTmpFree(psz);
                AssertMsgFailed(("Filename too long! cch=%d '%s'\n", cch, psz));
                return VERR_FILENAME_TOO_LONG;
            }
            memcpy(szFilename, psz, cch);
            RTMemTmpFree(psz);
        }

        /*
         * Load the module and register it's devices.
         */
        rc = pdmR3UsbLoad(pVM, &RegCB, szFilename, szName);
        if (RT_FAILURE(rc))
            return rc;
    }

    return VINF_SUCCESS;
}


/**
 * Send the init-complete notification to all the USB devices.
 *
 * This is called from pdmR3DevInit() after it has do its notification round.
 *
 * @returns VBox status code.
 * @param   pVM         The VM handle.
 */
int pdmR3UsbVMInitComplete(PVM pVM)
{
    for (PPDMUSBINS pUsbIns = pVM->pdm.s.pUsbInstances; pUsbIns; pUsbIns = pUsbIns->Internal.s.pNext)
    {
        if (pUsbIns->pReg->pfnVMInitComplete)
        {
            int rc = pUsbIns->pReg->pfnVMInitComplete(pUsbIns);
            if (RT_FAILURE(rc))
            {
                AssertMsgFailed(("InitComplete on USB device '%s'/%d failed with rc=%Rrc\n",
                                 pUsbIns->pReg->szName, pUsbIns->iInstance, rc));
                return rc;
            }
        }
    }
    return VINF_SUCCESS;
}


/**
 * Lookups a device structure by name.
 * @internal
 */
PPDMUSB pdmR3UsbLookup(PVM pVM, const char *pszName)
{
    size_t cchName = strlen(pszName);
    for (PPDMUSB pUsb = pVM->pdm.s.pUsbDevs; pUsb; pUsb = pUsb->pNext)
        if (    pUsb->cchName == cchName
            &&  !strcmp(pUsb->pReg->szName, pszName))
            return pUsb;
    return NULL;
}


/**
 * Locates a suitable hub for the specified kind of device.
 *
 * @returns VINF_SUCCESS and *ppHub on success.
 *          VERR_PDM_NO_USB_HUBS or VERR_PDM_NO_USB_PORTS on failure.
 * @param   pVM             The VM handle.
 * @param   iUsbVersion     The USB device version.
 * @param   ppHub           Where to store the pointer to the USB hub.
 */
static int pdmR3UsbFindHub(PVM pVM, uint32_t iUsbVersion, PPDMUSBHUB *ppHub)
{
    *ppHub = NULL;
    if (!pVM->pdm.s.pUsbHubs)
        return VERR_PDM_NO_USB_HUBS;

    for (PPDMUSBHUB pCur = pVM->pdm.s.pUsbHubs; pCur; pCur = pCur->pNext)
        if (    pCur->cAvailablePorts > 0
            &&  (   (pCur->fVersions & iUsbVersion)
                 || pCur->fVersions == VUSB_STDVER_11))
        {
            *ppHub = pCur;
            if (pCur->fVersions & iUsbVersion)
                break;
        }
    if (*ppHub)
        return VINF_SUCCESS;
    return VERR_PDM_NO_USB_PORTS;
}


/**
 * Creates the device.
 *
 * @returns VBox status code.
 * @param   pVM             The VM handle.
 * @param   pUsbDev         The USB device emulation.
 * @param   iInstance       -1 if not called by pdmR3UsbInstantiateDevices().
 * @param   pUuid           The UUID for this device.
 * @param   pInstanceNode   The instance CFGM node. NULL if not called by pdmR3UsbInstantiateDevices().
 * @param   ppConfig        Pointer to the device configuration pointer. This is set to NULL if inserted
 *                          into the tree or cleaned up.
 *
 *                          In the pdmR3UsbInstantiateDevices() case (pInstanceNode != NULL) this is
 *                          the actual config node and will not be cleaned up.
 *
 * @parma   iUsbVersion     The USB version preferred by the device.
 */
static int pdmR3UsbCreateDevice(PVM pVM, PPDMUSBHUB pHub, PPDMUSB pUsbDev, int iInstance, PCRTUUID pUuid, PCFGMNODE pInstanceNode, PCFGMNODE *ppConfig, uint32_t iUsbVersion)
{
    const bool fAtRuntime = pInstanceNode == NULL;
    int rc;

    /*
     * If not called by pdmR3UsbInstantiateDevices(), we'll have to fix
     * the configuration now.
     */
    /* USB device node. */
    PCFGMNODE pDevNode = CFGMR3GetChildF(CFGMR3GetRoot(pVM), "USB/%s/", pUsbDev->pReg->szName);
    if (!pDevNode)
    {
        rc = CFGMR3InsertNodeF(CFGMR3GetRoot(pVM), &pDevNode, "USB/%s/", pUsbDev->pReg->szName);
        AssertRCReturn(rc, rc);
    }

    /* The instance node and number. */
    if (!pInstanceNode)
    {
        for (unsigned c = 0; c < _2M; c++)
        {
            iInstance = pUsbDev->iNextInstance++;
            rc = CFGMR3InsertNodeF(pDevNode, &pInstanceNode, "%d/", iInstance);
            if (rc != VERR_CFGM_NODE_EXISTS)
                break;
        }
        AssertRCReturn(rc, rc);
    }
    else
    {
        Assert(iInstance >= 0);
        if (iInstance >= (int)pUsbDev->iNextInstance)
            pUsbDev->iNextInstance = iInstance + 1;
    }

    /* The instance config node. */
    PCFGMNODE pConfigToDelete = NULL;
    PCFGMNODE pConfig = NULL;
    if (!ppConfig || !*ppConfig)
    {
        rc = CFGMR3InsertNode(pInstanceNode, "Config", &pConfig);
        AssertRCReturn(rc, rc);
    }
    else if (fAtRuntime)
    {
        rc = CFGMR3InsertSubTree(pInstanceNode, "Config", *ppConfig, &pConfig);
        AssertRCReturn(rc, rc);
        *ppConfig = NULL;
        pConfigToDelete = pConfig;
    }
    else
        pConfig = *ppConfig;
    Assert(CFGMR3GetChild(pInstanceNode, "Config") == pConfig);

    /* The global device config node. */
    PCFGMNODE pGlobalConfig = CFGMR3GetChild(pDevNode, "GlobalConfig");
    if (!pGlobalConfig)
    {
        rc = CFGMR3InsertNode(pDevNode, "GlobalConfig", &pGlobalConfig);
        if (RT_FAILURE(rc))
        {
            CFGMR3RemoveNode(pConfigToDelete);
            AssertRCReturn(rc, rc);
        }
    }

    /*
     * Allocate the device instance.
     */
    size_t cb = RT_OFFSETOF(PDMUSBINS, achInstanceData[pUsbDev->pReg->cbInstance]);
    cb = RT_ALIGN_Z(cb, 16);
    PPDMUSBINS pUsbIns;
    rc = MMR3HeapAllocZEx(pVM, MM_TAG_PDM_USB, cb, (void **)&pUsbIns);
    if (RT_FAILURE(rc))
    {
        AssertMsgFailed(("Failed to allocate %d bytes of instance data for USB device '%s'. rc=%Rrc\n",
                         cb, pUsbDev->pReg->szName, rc));
        CFGMR3RemoveNode(pConfigToDelete);
        return rc;
    }

    /*
     * Initialize it.
     */
    pUsbIns->u32Version                     = PDM_USBINS_VERSION;
    //pUsbIns->Internal.s.pNext               = NULL;
    //pUsbIns->Internal.s.pPerDeviceNext      = NULL;
    pUsbIns->Internal.s.pUsbDev             = pUsbDev;
    pUsbIns->Internal.s.pVM                 = pVM;
    //pUsbIns->Internal.s.pLuns               = NULL;
    pUsbIns->Internal.s.pCfg                = pInstanceNode;
    pUsbIns->Internal.s.pCfgDelete          = pConfigToDelete;
    pUsbIns->Internal.s.pCfgGlobal          = pGlobalConfig;
    pUsbIns->Internal.s.Uuid                = *pUuid;
    //pUsbIns->Internal.s.pHub                = NULL;
    pUsbIns->Internal.s.iPort               = UINT32_MAX; /* to be determined. */
    pUsbIns->Internal.s.fVMSuspended        = true;
    //pUsbIns->Internal.s.pfnAsyncNotify      = NULL;
    pUsbIns->pHlpR3                         = &g_pdmR3UsbHlp;
    pUsbIns->pReg                           = pUsbDev->pReg;
    pUsbIns->pCfg                           = pConfig;
    pUsbIns->pCfgGlobal                     = pGlobalConfig;
    pUsbIns->iInstance                      = iInstance;
    pUsbIns->pvInstanceDataR3               = &pUsbIns->achInstanceData[0];
    pUsbIns->pszName                        = RTStrDup(pUsbDev->pReg->szName);

    /*
     * Link it into all the lists.
     */
    /* The global instance FIFO. */
    PPDMUSBINS pPrev1 = pVM->pdm.s.pUsbInstances;
    if (!pPrev1)
        pVM->pdm.s.pUsbInstances = pUsbIns;
    else
    {
        while (pPrev1->Internal.s.pNext)
        {
            Assert(pPrev1->u32Version == PDM_USBINS_VERSION);
            pPrev1 = pPrev1->Internal.s.pNext;
        }
        pPrev1->Internal.s.pNext = pUsbIns;
    }

    /* The per device instance FIFO. */
    PPDMUSBINS pPrev2 = pUsbDev->pInstances;
    if (!pPrev2)
        pUsbDev->pInstances = pUsbIns;
    else
    {
        while (pPrev2->Internal.s.pPerDeviceNext)
        {
            Assert(pPrev2->u32Version == PDM_USBINS_VERSION);
            pPrev2 = pPrev2->Internal.s.pPerDeviceNext;
        }
        pPrev2->Internal.s.pPerDeviceNext = pUsbIns;
    }

    /*
     * Call the constructor.
     */
    Log(("PDM: Constructing USB device '%s' instance %d...\n", pUsbIns->pReg->szName, pUsbIns->iInstance));
    rc = pUsbIns->pReg->pfnConstruct(pUsbIns, pUsbIns->iInstance, pUsbIns->pCfg, pUsbIns->pCfgGlobal);
    if (RT_SUCCESS(rc))
    {
        /*
         * Attach it to the hub.
         */
        Log(("PDM: Attaching it...\n"));
        rc = pHub->Reg.pfnAttachDevice(pHub->pDrvIns, pUsbIns, &pUsbIns->Internal.s.iPort);
        if (RT_SUCCESS(rc))
        {
            pHub->cAvailablePorts--;
            Assert((int32_t)pHub->cAvailablePorts >= 0 && pHub->cAvailablePorts < pHub->cPorts);
            pUsbIns->Internal.s.pHub = pHub;

            /* Send the hot-plugged notification if applicable. */
            if (fAtRuntime && pUsbIns->pReg->pfnHotPlugged)
                pUsbIns->pReg->pfnHotPlugged(pUsbIns);

            Log(("PDM: Successfully attached USB device '%s' instance %d to hub %p\n",
                 pUsbIns->pReg->szName, pUsbIns->iInstance, pHub));
            return VINF_SUCCESS;
        }

        LogRel(("PDM: Failed to attach USB device '%s' instance %d to hub %p: %Rrc\n",
                pUsbIns->pReg->szName, pUsbIns->iInstance, pHub, rc));
    }
    else
    {
        AssertMsgFailed(("Failed to construct '%s'/%d! %Rra\n", pUsbIns->pReg->szName, pUsbIns->iInstance, rc));
        if (rc == VERR_VERSION_MISMATCH)
            rc = VERR_PDM_DRIVER_VERSION_MISMATCH;
    }
    if (fAtRuntime)
        pdmR3UsbDestroyDevice(pVM, pUsbIns);
    /* else: destructors are invoked later. */
    return rc;
}


/**
 * Instantiate USB devices.
 *
 * This is called by pdmR3DevInit() after it has instantiated the
 * other devices and their drivers. If there aren't any hubs
 * around, we'll silently skip the USB devices.
 *
 * @returns VBox status code.
 * @param   pVM
 */
int pdmR3UsbInstantiateDevices(PVM pVM)
{
    /*
     * Any hubs?
     */
    if (!pVM->pdm.s.pUsbHubs)
    {
        Log(("PDM: No USB hubs, skipping USB device instantiation.\n"));
        return VINF_SUCCESS;
    }

    /*
     * Count the device instances.
     */
    PCFGMNODE pCur;
    PCFGMNODE pUsbNode = CFGMR3GetChild(CFGMR3GetRoot(pVM), "USB/");
    PCFGMNODE pInstanceNode;
    unsigned cUsbDevs = 0;
    for (pCur = CFGMR3GetFirstChild(pUsbNode); pCur; pCur = CFGMR3GetNextChild(pCur))
    {
        PCFGMNODE pGlobal = CFGMR3GetChild(pCur, "GlobalConfig/");
        for (pInstanceNode = CFGMR3GetFirstChild(pCur); pInstanceNode; pInstanceNode = CFGMR3GetNextChild(pInstanceNode))
            if (pInstanceNode != pGlobal)
                cUsbDevs++;
    }
    if (!cUsbDevs)
    {
        Log(("PDM: No USB devices were configured!\n"));
        return VINF_SUCCESS;
    }
    Log2(("PDM: cUsbDevs=%d!\n", cUsbDevs));

    /*
     * Collect info on each USB device instance.
     */
    struct USBDEVORDER
    {
        /** Configuration node. */
        PCFGMNODE   pNode;
        /** Pointer to the USB device. */
        PPDMUSB     pUsbDev;
        /** Init order. */
        uint32_t    u32Order;
        /** VBox instance number. */
        uint32_t    iInstance;
    } *paUsbDevs = (struct USBDEVORDER *)alloca(sizeof(paUsbDevs[0]) * (cUsbDevs + 1)); /* (One extra for swapping) */
    Assert(paUsbDevs);
    int rc;
    unsigned i = 0;
    for (pCur = CFGMR3GetFirstChild(pUsbNode); pCur; pCur = CFGMR3GetNextChild(pCur))
    {
        /* Get the device name. */
        char szName[sizeof(paUsbDevs[0].pUsbDev->pReg->szName)];
        rc = CFGMR3GetName(pCur, szName, sizeof(szName));
        AssertMsgRCReturn(rc, ("Configuration error: device name is too long (or something)! rc=%Rrc\n", rc), rc);

        /* Find the device. */
        PPDMUSB pUsbDev = pdmR3UsbLookup(pVM, szName);
        AssertMsgReturn(pUsbDev, ("Configuration error: device '%s' not found!\n", szName), VERR_PDM_DEVICE_NOT_FOUND);

        /* Configured priority or use default? */
        uint32_t u32Order;
        rc = CFGMR3QueryU32(pCur, "Priority", &u32Order);
        if (rc == VERR_CFGM_VALUE_NOT_FOUND)
            u32Order = i << 4;
        else
            AssertMsgRCReturn(rc, ("Configuration error: reading \"Priority\" for the '%s' USB device failed rc=%Rrc!\n", szName, rc), rc);

        /* Global config. */
        PCFGMNODE pGlobal = CFGMR3GetChild(pCur, "GlobalConfig/");
        if (!pGlobal)
        {
            rc = CFGMR3InsertNode(pCur, "GlobalConfig/", &pGlobal);
            AssertMsgRCReturn(rc, ("Failed to create GlobalConfig node! rc=%Rrc\n", rc), rc);
            CFGMR3SetRestrictedRoot(pGlobal);
        }

        /* Enumerate the device instances. */
        for (pInstanceNode = CFGMR3GetFirstChild(pCur); pInstanceNode; pInstanceNode = CFGMR3GetNextChild(pInstanceNode))
        {
            if (pInstanceNode == pGlobal)
                continue;

            paUsbDevs[i].pNode = pInstanceNode;
            paUsbDevs[i].pUsbDev = pUsbDev;
            paUsbDevs[i].u32Order = u32Order;

            /* Get the instance number. */
            char szInstance[32];
            rc = CFGMR3GetName(pInstanceNode, szInstance, sizeof(szInstance));
            AssertMsgRCReturn(rc, ("Configuration error: instance name is too long (or something)! rc=%Rrc\n", rc), rc);
            char *pszNext = NULL;
            rc = RTStrToUInt32Ex(szInstance, &pszNext, 0, &paUsbDevs[i].iInstance);
            AssertMsgRCReturn(rc, ("Configuration error: RTStrToInt32Ex failed on the instance name '%s'! rc=%Rrc\n", szInstance, rc), rc);
            AssertMsgReturn(!*pszNext, ("Configuration error: the instance name '%s' isn't all digits. (%s)\n", szInstance, pszNext), VERR_INVALID_PARAMETER);

            /* next instance */
            i++;
        }
    } /* devices */
    Assert(i == cUsbDevs);

    /*
     * Sort the device array ascending on u32Order. (bubble)
     */
    unsigned c = cUsbDevs - 1;
    while (c)
    {
        unsigned j = 0;
        for (i = 0; i < c; i++)
            if (paUsbDevs[i].u32Order > paUsbDevs[i + 1].u32Order)
            {
                paUsbDevs[cUsbDevs] = paUsbDevs[i + 1];
                paUsbDevs[i + 1] = paUsbDevs[i];
                paUsbDevs[i] = paUsbDevs[cUsbDevs];
                j = i;
            }
        c = j;
    }

    /*
     * Instantiate the devices.
     */
    for (i = 0; i < cUsbDevs; i++)
    {
        /*
         * Make sure there is a config node and mark it as restricted.
         */
        PCFGMNODE pConfigNode = CFGMR3GetChild(paUsbDevs[i].pNode, "Config/");
        if (!pConfigNode)
        {
            rc = CFGMR3InsertNode(paUsbDevs[i].pNode, "Config", &pConfigNode);
            AssertMsgRCReturn(rc, ("Failed to create Config node! rc=%Rrc\n", rc), rc);
        }
        CFGMR3SetRestrictedRoot(pConfigNode);

        /** @todo
         * Figure out the USB version from the USB device registration and the configuration.
         */
        uint32_t iUsbVersion = VUSB_STDVER_11;

        /*
         * Find a suitable hub with free ports.
         */
        PPDMUSBHUB pHub;
        rc = pdmR3UsbFindHub(pVM, iUsbVersion, &pHub);
        if (RT_FAILURE(rc))
        {
            Log(("pdmR3UsbFindHub failed %Rrc\n", rc));
            return rc;
        }

        /*
         * Create and attach the device.
         */
        RTUUID Uuid;
        rc = RTUuidCreate(&Uuid);
        AssertRCReturn(rc, rc);
        rc = pdmR3UsbCreateDevice(pVM, pHub, paUsbDevs[i].pUsbDev, paUsbDevs[i].iInstance, &Uuid, paUsbDevs[i].pNode, &pConfigNode, iUsbVersion);
        if (RT_FAILURE(rc))
            return rc;
    } /* for device instances */

    return VINF_SUCCESS;
}


/**
 * Creates a USB proxy device instance.
 *
 * This will find an appropriate HUB for the USB device, create the necessary CFGM stuff
 * and try instantiate the proxy device.
 *
 * @returns VBox status code.
 * @param   pVM             The VM handle.
 * @param   pUuid           The UUID to be associated with the device.
 * @param   fRemote         Whether it's a remove or local device.
 * @param   pszAddress      The address string.
 * @param   pvBackend       Pointer to the backend.
 * @param   iUsbVersion     The preferred USB version.
 * @param   fMaskedIfs      The interfaces to hide from the guest.
 */
VMMR3DECL(int) PDMR3USBCreateProxyDevice(PVM pVM, PCRTUUID pUuid, bool fRemote, const char *pszAddress, void *pvBackend,
                                         uint32_t iUsbVersion, uint32_t fMaskedIfs)
{
    /*
     * Validate input.
     */
    VM_ASSERT_EMT(pVM);
    AssertPtrReturn(pUuid, VERR_INVALID_POINTER);
    AssertPtrReturn(pszAddress, VERR_INVALID_POINTER);
    AssertReturn(    iUsbVersion == VUSB_STDVER_20
                 ||  iUsbVersion == VUSB_STDVER_11, VERR_INVALID_PARAMETER);

    /*
     * Find the USBProxy driver.
     */
    PPDMUSB pUsbDev = pdmR3UsbLookup(pVM, "USBProxy");
    if (!pUsbDev)
    {
        LogRel(("PDMR3USBCreateProxyDevice: The USBProxy device class wasn't found\n"));
        return VERR_PDM_NO_USBPROXY;
    }

    /*
     * Find a suitable hub with free ports.
     */
    PPDMUSBHUB pHub;
    int rc = pdmR3UsbFindHub(pVM, iUsbVersion, &pHub);
    if (RT_FAILURE(rc))
    {
        Log(("pdmR3UsbFindHub: failed %Rrc\n", rc));
        return rc;
    }

    /*
     * Create the CFGM configuration node.
     */
    PCFGMNODE pConfig = CFGMR3CreateTree(pVM);
    AssertReturn(pConfig, VERR_NO_MEMORY);
    do /* break loop */
    {
        rc = CFGMR3InsertString(pConfig,  "Address", pszAddress);               AssertRCBreak(rc);
        char szUuid[RTUUID_STR_LENGTH];
        rc = RTUuidToStr(pUuid, &szUuid[0], sizeof(szUuid));                    AssertRCBreak(rc);
        rc = CFGMR3InsertString(pConfig,  "UUID", szUuid);                      AssertRCBreak(rc);
        rc = CFGMR3InsertInteger(pConfig, "Remote", fRemote);                   AssertRCBreak(rc);
        rc = CFGMR3InsertInteger(pConfig, "USBVersion", iUsbVersion);           AssertRCBreak(rc);
        rc = CFGMR3InsertInteger(pConfig, "pvBackend", (uintptr_t)pvBackend);   AssertRCBreak(rc);
        rc = CFGMR3InsertInteger(pConfig, "MaskedIfs", fMaskedIfs);             AssertRCBreak(rc);
        rc = CFGMR3InsertInteger(pConfig, "Force11Device", !(pHub->fVersions & iUsbVersion)); AssertRCBreak(rc);
    } while (0); /* break loop */
    if (RT_FAILURE(rc))
    {
        CFGMR3RemoveNode(pConfig);
        LogRel(("PDMR3USBCreateProxyDevice: failed to setup CFGM config, rc=%Rrc\n", rc));
        return rc;
    }

    /*
     * Finally, try create it.
     */
    rc = pdmR3UsbCreateDevice(pVM, pHub, pUsbDev, -1, pUuid, NULL, &pConfig, iUsbVersion);
    if (RT_FAILURE(rc) && pConfig)
        CFGMR3RemoveNode(pConfig);
    return rc;
}


/**
 * Destroys a hot-plugged USB device.
 *
 * The device must be detached from the HUB at this point.
 *
 * @param   pVM             Pointer to the shared VM structure.
 * @param   pUsbIns         The USB device instance to destroy.
 * @thread  EMT
 */
static void pdmR3UsbDestroyDevice(PVM pVM, PPDMUSBINS pUsbIns)
{
    Assert(!pUsbIns->Internal.s.pHub);

    /*
     * Do the unplug notification.
     */
    /** @todo what about the drivers? */
    if (pUsbIns->pReg->pfnHotUnplugged)
        pUsbIns->pReg->pfnHotUnplugged(pUsbIns);

    /*
     * Destroy the luns with their driver chains and call the device destructor.
     */
    while (pUsbIns->Internal.s.pLuns)
    {
        PPDMLUN pLun = pUsbIns->Internal.s.pLuns;
        pUsbIns->Internal.s.pLuns = pLun->pNext;
        if (pLun->pTop)
            pdmR3DrvDestroyChain(pLun->pTop, PDM_TACH_FLAGS_NOT_HOT_PLUG); /* Hotplugging is handled differently here atm. */
        MMR3HeapFree(pLun);
    }

    /* finally, the device. */
    if (pUsbIns->pReg->pfnDestruct)
    {
        Log(("PDM: Destructing USB device '%s' instance %d...\n", pUsbIns->pReg->szName, pUsbIns->iInstance));
        pUsbIns->pReg->pfnDestruct(pUsbIns);
    }
    TMR3TimerDestroyUsb(pVM, pUsbIns);
    //SSMR3DeregisterUsb(pVM, pUsbIns, NULL, 0);
    pdmR3ThreadDestroyUsb(pVM, pUsbIns);

    /*
     * Unlink it.
     */
    /* The global instance FIFO. */
    if (pVM->pdm.s.pUsbInstances == pUsbIns)
        pVM->pdm.s.pUsbInstances = pUsbIns->Internal.s.pNext;
    else
    {
        PPDMUSBINS pPrev = pVM->pdm.s.pUsbInstances;
        while (pPrev && pPrev->Internal.s.pNext != pUsbIns)
        {
            Assert(pPrev->u32Version == PDM_USBINS_VERSION);
            pPrev = pPrev->Internal.s.pNext;
        }
        Assert(pPrev); Assert(pPrev != pUsbIns);
        if (pPrev)
            pPrev->Internal.s.pNext = pUsbIns->Internal.s.pNext;
    }

    /* The per device instance FIFO. */
    PPDMUSB pUsbDev = pUsbIns->Internal.s.pUsbDev;
    if (pUsbDev->pInstances == pUsbIns)
        pUsbDev->pInstances = pUsbIns->Internal.s.pPerDeviceNext;
    else
    {
        PPDMUSBINS pPrev = pUsbDev->pInstances;
        while (pPrev && pPrev->Internal.s.pPerDeviceNext != pUsbIns)
        {
            Assert(pPrev->u32Version == PDM_USBINS_VERSION);
            pPrev = pPrev->Internal.s.pPerDeviceNext;
        }
        Assert(pPrev); Assert(pPrev != pUsbIns);
        if (pPrev)
            pPrev->Internal.s.pPerDeviceNext = pUsbIns->Internal.s.pPerDeviceNext;
    }

    /*
     * Trash it.
     */
    pUsbIns->u32Version = 0;
    pUsbIns->pReg = NULL;
    if (pUsbIns->pszName)
    {
        RTStrFree(pUsbIns->pszName);
        pUsbIns->pszName = NULL;
    }
    CFGMR3RemoveNode(pUsbIns->Internal.s.pCfgDelete);
    MMR3HeapFree(pUsbIns);
}


/**
 * Detaches and destroys a USB device.
 *
 * @returns VBox status code.
 * @param   pVM             The VM handle.
 * @param   pUuid           The UUID associated with the device to detach.
 * @thread  EMT
 */
VMMR3DECL(int) PDMR3USBDetachDevice(PVM pVM, PCRTUUID pUuid)
{
    /*
     * Validate input.
     */
    VM_ASSERT_EMT(pVM);
    AssertPtrReturn(pUuid, VERR_INVALID_POINTER);
    AssertPtrReturn(pVM, VERR_INVALID_POINTER);

    /*
     * Search the global list for it.
     */
    PPDMUSBINS pUsbIns = pVM->pdm.s.pUsbInstances;
    for ( ; pUsbIns; pUsbIns = pUsbIns->Internal.s.pNext)
        if (!RTUuidCompare(&pUsbIns->Internal.s.Uuid, pUuid))
            break;
    if (!pUsbIns)
        return VERR_PDM_DEVICE_INSTANCE_NOT_FOUND; /** @todo VERR_PDM_USB_INSTANCE_NOT_FOUND */

    /*
     * Detach it from the HUB (if it's actually attached to one).
     */
    PPDMUSBHUB pHub = pUsbIns->Internal.s.pHub;
    if (pHub)
    {
        int rc = pHub->Reg.pfnDetachDevice(pHub->pDrvIns, pUsbIns, pUsbIns->Internal.s.iPort);
        if (RT_FAILURE(rc))
        {
            LogRel(("PDM: Failed to detach USB device '%s' instance %d from %p: %Rrc\n",
                    pUsbIns->pReg->szName, pUsbIns->iInstance, pHub, rc));
            return rc;
        }

        pHub->cAvailablePorts++;
        Assert(pHub->cAvailablePorts > 0 && pHub->cAvailablePorts <= pHub->cPorts);
        pUsbIns->Internal.s.pHub = NULL;
    }

    /*
     * Notify about unplugging and destroy the device with it's drivers.
     */
    pdmR3UsbDestroyDevice(pVM, pUsbIns);

    return VINF_SUCCESS;
}


/**
 * Checks if there are any USB hubs attached.
 *
 * @returns true / false accordingly.
 * @param   pVM     Pointer to the shared VM structure.
 */
VMMR3DECL(bool) PDMR3USBHasHub(PVM pVM)
{
    return pVM->pdm.s.pUsbHubs != NULL;
}


/** @name USB Device Helpers
 * @{
 */

/** @interface_method_impl{PDMUSBHLPR3,pfnDriverAttach} */
static DECLCALLBACK(int) pdmR3UsbHlp_DriverAttach(PPDMUSBINS pUsbIns, RTUINT iLun, PPDMIBASE pBaseInterface, PPDMIBASE *ppBaseInterface, const char *pszDesc)
{
    PDMUSB_ASSERT_USBINS(pUsbIns);
    PVM pVM = pUsbIns->Internal.s.pVM;
    VM_ASSERT_EMT(pVM);
    LogFlow(("pdmR3UsbHlp_DriverAttach: caller='%s'/%d: iLun=%d pBaseInterface=%p ppBaseInterface=%p pszDesc=%p:{%s}\n",
             pUsbIns->pReg->szName, pUsbIns->iInstance, iLun, pBaseInterface, ppBaseInterface, pszDesc, pszDesc));

    /*
     * Lookup the LUN, it might already be registered.
     */
    PPDMLUN pLunPrev = NULL;
    PPDMLUN pLun = pUsbIns->Internal.s.pLuns;
    for (; pLun; pLunPrev = pLun, pLun = pLun->pNext)
        if (pLun->iLun == iLun)
            break;

    /*
     * Create the LUN if if wasn't found, else check if driver is already attached to it.
     */
    if (!pLun)
    {
        if (    !pBaseInterface
            ||  !pszDesc
            ||  !*pszDesc)
        {
            Assert(pBaseInterface);
            Assert(pszDesc || *pszDesc);
            return VERR_INVALID_PARAMETER;
        }

        pLun = (PPDMLUN)MMR3HeapAlloc(pVM, MM_TAG_PDM_LUN, sizeof(*pLun));
        if (!pLun)
            return VERR_NO_MEMORY;

        pLun->iLun      = iLun;
        pLun->pNext     = pLunPrev ? pLunPrev->pNext : NULL;
        pLun->pTop      = NULL;
        pLun->pBottom   = NULL;
        pLun->pDevIns   = NULL;
        pLun->pUsbIns   = pUsbIns;
        pLun->pszDesc   = pszDesc;
        pLun->pBase     = pBaseInterface;
        if (!pLunPrev)
            pUsbIns->Internal.s.pLuns = pLun;
        else
            pLunPrev->pNext = pLun;
        Log(("pdmR3UsbHlp_DriverAttach: Registered LUN#%d '%s' with device '%s'/%d.\n",
             iLun, pszDesc, pUsbIns->pReg->szName, pUsbIns->iInstance));
    }
    else if (pLun->pTop)
    {
        AssertMsgFailed(("Already attached! The device should keep track of such things!\n"));
        LogFlow(("pdmR3UsbHlp_DriverAttach: caller='%s'/%d: returns %Rrc\n", pUsbIns->pReg->szName, pUsbIns->iInstance, VERR_PDM_DRIVER_ALREADY_ATTACHED));
        return VERR_PDM_DRIVER_ALREADY_ATTACHED;
    }
    Assert(pLun->pBase == pBaseInterface);


    /*
     * Get the attached driver configuration.
     */
    int rc;
    PCFGMNODE pNode = CFGMR3GetChildF(pUsbIns->Internal.s.pCfg, "LUN#%u", iLun);
    if (pNode)
        rc = pdmR3DrvInstantiate(pVM, pNode, pBaseInterface, NULL /*pDrvAbove*/, pLun, ppBaseInterface);
    else
        rc = VERR_PDM_NO_ATTACHED_DRIVER;


    LogFlow(("pdmR3UsbHlp_DriverAttach: caller='%s'/%d: returns %Rrc\n", pUsbIns->pReg->szName, pUsbIns->iInstance, rc));
    return rc;
}


/** @interface_method_impl{PDMUSBHLP,pfnAssertEMT} */
static DECLCALLBACK(bool) pdmR3UsbHlp_AssertEMT(PPDMUSBINS pUsbIns, const char *pszFile, unsigned iLine, const char *pszFunction)
{
    PDMUSB_ASSERT_USBINS(pUsbIns);
    if (VM_IS_EMT(pUsbIns->Internal.s.pVM))
        return true;

    char szMsg[100];
    RTStrPrintf(szMsg, sizeof(szMsg), "AssertEMT '%s'/%d\n", pUsbIns->pReg->szName, pUsbIns->iInstance);
    RTAssertMsg1Weak(szMsg, iLine, pszFile, pszFunction);
    AssertBreakpoint();
    return false;
}


/** @interface_method_impl{PDMUSBHLP,pfnAssertOther} */
static DECLCALLBACK(bool) pdmR3UsbHlp_AssertOther(PPDMUSBINS pUsbIns, const char *pszFile, unsigned iLine, const char *pszFunction)
{
    PDMUSB_ASSERT_USBINS(pUsbIns);
    if (!VM_IS_EMT(pUsbIns->Internal.s.pVM))
        return true;

    char szMsg[100];
    RTStrPrintf(szMsg, sizeof(szMsg), "AssertOther '%s'/%d\n", pUsbIns->pReg->szName, pUsbIns->iInstance);
    RTAssertMsg1Weak(szMsg, iLine, pszFile, pszFunction);
    AssertBreakpoint();
    return false;
}


/** @interface_method_impl{PDMUSBHLP,pfnDBGFStopV} */
static DECLCALLBACK(int) pdmR3UsbHlp_DBGFStopV(PPDMUSBINS pUsbIns, const char *pszFile, unsigned iLine, const char *pszFunction, const char *pszFormat, va_list args)
{
    PDMUSB_ASSERT_USBINS(pUsbIns);
#ifdef LOG_ENABLED
    va_list va2;
    va_copy(va2, args);
    LogFlow(("pdmR3UsbHlp_DBGFStopV: caller='%s'/%d: pszFile=%p:{%s} iLine=%d pszFunction=%p:{%s} pszFormat=%p:{%s} (%N)\n",
             pUsbIns->pReg->szName, pUsbIns->iInstance, pszFile, pszFile, iLine, pszFunction, pszFunction, pszFormat, pszFormat, pszFormat, &va2));
    va_end(va2);
#endif

    PVM pVM = pUsbIns->Internal.s.pVM;
    VM_ASSERT_EMT(pVM);
    int rc = DBGFR3EventSrcV(pVM, DBGFEVENT_DEV_STOP, pszFile, iLine, pszFunction, pszFormat, args);
    if (rc == VERR_DBGF_NOT_ATTACHED)
        rc = VINF_SUCCESS;

    LogFlow(("pdmR3UsbHlp_DBGFStopV: caller='%s'/%d: returns %Rrc\n", pUsbIns->pReg->szName, pUsbIns->iInstance, rc));
    return rc;
}


/** @interface_method_impl{PDMUSBHLP,pfnDBGFInfoRegister} */
static DECLCALLBACK(int) pdmR3UsbHlp_DBGFInfoRegister(PPDMUSBINS pUsbIns, const char *pszName, const char *pszDesc, PFNDBGFHANDLERUSB pfnHandler)
{
    PDMUSB_ASSERT_USBINS(pUsbIns);
    LogFlow(("pdmR3UsbHlp_DBGFInfoRegister: caller='%s'/%d: pszName=%p:{%s} pszDesc=%p:{%s} pfnHandler=%p\n",
             pUsbIns->pReg->szName, pUsbIns->iInstance, pszName, pszName, pszDesc, pszDesc, pfnHandler));

    PVM pVM = pUsbIns->Internal.s.pVM;
    VM_ASSERT_EMT(pVM);
    /** @todo int rc = DBGFR3InfoRegisterUsb(pVM, pszName, pszDesc, pfnHandler, pUsbIns); */
    int rc = VERR_NOT_IMPLEMENTED; AssertFailed();

    LogFlow(("pdmR3UsbHlp_DBGFInfoRegister: caller='%s'/%d: returns %Rrc\n", pUsbIns->pReg->szName, pUsbIns->iInstance, rc));
    return rc;
}


/** @interface_method_impl{PDMUSBHLP,pfnMMHeapAlloc} */
static DECLCALLBACK(void *) pdmR3UsbHlp_MMHeapAlloc(PPDMUSBINS pUsbIns, size_t cb)
{
    PDMUSB_ASSERT_USBINS(pUsbIns);
    LogFlow(("pdmR3UsbHlp_MMHeapAlloc: caller='%s'/%d: cb=%#x\n", pUsbIns->pReg->szName, pUsbIns->iInstance, cb));

    void *pv = MMR3HeapAlloc(pUsbIns->Internal.s.pVM, MM_TAG_PDM_USB_USER, cb);

    LogFlow(("pdmR3UsbHlp_MMHeapAlloc: caller='%s'/%d: returns %p\n", pUsbIns->pReg->szName, pUsbIns->iInstance, pv));
    return pv;
}


/** @interface_method_impl{PDMUSBHLP,pfnMMHeapAllocZ} */
static DECLCALLBACK(void *) pdmR3UsbHlp_MMHeapAllocZ(PPDMUSBINS pUsbIns, size_t cb)
{
    PDMUSB_ASSERT_USBINS(pUsbIns);
    LogFlow(("pdmR3UsbHlp_MMHeapAllocZ: caller='%s'/%d: cb=%#x\n", pUsbIns->pReg->szName, pUsbIns->iInstance, cb));

    void *pv = MMR3HeapAllocZ(pUsbIns->Internal.s.pVM, MM_TAG_PDM_USB_USER, cb);

    LogFlow(("pdmR3UsbHlp_MMHeapAllocZ: caller='%s'/%d: returns %p\n", pUsbIns->pReg->szName, pUsbIns->iInstance, pv));
    return pv;
}


/** @interface_method_impl{PDMUSBHLP,pfnPDMQueueCreate} */
static DECLCALLBACK(int) pdmR3UsbHlp_PDMQueueCreate(PPDMUSBINS pUsbIns, RTUINT cbItem, RTUINT cItems, uint32_t cMilliesInterval,
                                                    PFNPDMQUEUEUSB pfnCallback, const char *pszName, PPDMQUEUE *ppQueue)
{
    PDMUSB_ASSERT_USBINS(pUsbIns);
    LogFlow(("pdmR3UsbHlp_PDMQueueCreate: caller='%s'/%d: cbItem=%#x cItems=%#x cMilliesInterval=%u pfnCallback=%p pszName=%p:{%s} ppQueue=%p\n",
             pUsbIns->pReg->szName, pUsbIns->iInstance, cbItem, cItems, cMilliesInterval, pfnCallback, pszName, pszName, ppQueue));

    PVM pVM = pUsbIns->Internal.s.pVM;
    VM_ASSERT_EMT(pVM);

    if (pUsbIns->iInstance > 0)
    {
        pszName = MMR3HeapAPrintf(pVM, MM_TAG_PDM_DEVICE_DESC, "%s_%u", pszName, pUsbIns->iInstance);
        AssertLogRelReturn(pszName, VERR_NO_MEMORY);
    }

    /** @todo int rc = PDMR3QueueCreateUsb(pVM, pUsbIns, cbItem, cItems, cMilliesInterval, pfnCallback, fGCEnabled, pszName, ppQueue); */
    int rc = VERR_NOT_IMPLEMENTED; AssertFailed();

    LogFlow(("pdmR3UsbHlp_PDMQueueCreate: caller='%s'/%d: returns %Rrc *ppQueue=%p\n", pUsbIns->pReg->szName, pUsbIns->iInstance, rc, *ppQueue));
    return rc;
}


/** @interface_method_impl{PDMUSBHLP,pfnSSMRegister} */
static DECLCALLBACK(int) pdmR3UsbHlp_SSMRegister(PPDMUSBINS pUsbIns, uint32_t uVersion, size_t cbGuess,
                                                 PFNSSMUSBLIVEPREP pfnLivePrep, PFNSSMUSBLIVEEXEC pfnLiveExec, PFNSSMUSBLIVEVOTE pfnLiveVote,
                                                 PFNSSMUSBSAVEPREP pfnSavePrep, PFNSSMUSBSAVEEXEC pfnSaveExec, PFNSSMUSBSAVEDONE pfnSaveDone,
                                                 PFNSSMUSBLOADPREP pfnLoadPrep, PFNSSMUSBLOADEXEC pfnLoadExec, PFNSSMUSBLOADDONE pfnLoadDone)
{
    PDMUSB_ASSERT_USBINS(pUsbIns);
    VM_ASSERT_EMT(pUsbIns->Internal.s.pVM);
    LogFlow(("pdmR3UsbHlp_SSMRegister: caller='%s'/%d: uVersion=#x cbGuess=%#x\n"
             "    pfnLivePrep=%p pfnLiveExec=%p pfnLiveVote=%p pfnSavePrep=%p pfnSaveExec=%p pfnSaveDone=%p pszLoadPrep=%p pfnLoadExec=%p pfnLoadDone=%p\n",
             pUsbIns->pReg->szName, pUsbIns->iInstance, uVersion, cbGuess,
             pfnLivePrep, pfnLiveExec, pfnLiveVote,
             pfnSavePrep, pfnSaveExec, pfnSaveDone,
             pfnLoadPrep, pfnLoadExec, pfnLoadDone));

    /** @todo
    int rc = SSMR3RegisterUsb(pUsbIns->Internal.s.pVM, pUsbIns, pUsbIns->pReg->szName, pUsbIns->iInstance,
                              uVersion, cbGuess,
                              pfnLivePrep, pfnLiveExec, pfnLiveVote,
                              pfnSavePrep, pfnSaveExec, pfnSaveDone,
                              pfnLoadPrep, pfnLoadExec, pfnLoadDone); */
    int rc = VERR_NOT_IMPLEMENTED; AssertFailed();

    LogFlow(("pdmR3UsbHlp_SSMRegister: caller='%s'/%d: returns %Rrc\n", pUsbIns->pReg->szName, pUsbIns->iInstance, rc));
    return rc;
}


/** @interface_method_impl{PDMUSBHLP,pfnSTAMRegisterV} */
static DECLCALLBACK(void) pdmR3UsbHlp_STAMRegisterV(PPDMUSBINS pUsbIns, void *pvSample, STAMTYPE enmType, STAMVISIBILITY enmVisibility,
                                                    STAMUNIT enmUnit, const char *pszDesc, const char *pszName, va_list args)
{
    PDMUSB_ASSERT_USBINS(pUsbIns);
    PVM pVM = pUsbIns->Internal.s.pVM;
    VM_ASSERT_EMT(pVM);

    int rc = STAMR3RegisterV(pVM, pvSample, enmType, enmVisibility, enmUnit, pszDesc, pszName, args);
    AssertRC(rc);

    NOREF(pVM);
}


/** @interface_method_impl{PDMUSBHLP,pfnTMTimerCreate} */
static DECLCALLBACK(int) pdmR3UsbHlp_TMTimerCreate(PPDMUSBINS pUsbIns, TMCLOCK enmClock, PFNTMTIMERUSB pfnCallback, void *pvUser,
                                                   uint32_t fFlags, const char *pszDesc, PPTMTIMERR3 ppTimer)
{
    PDMUSB_ASSERT_USBINS(pUsbIns);
    PVM pVM = pUsbIns->Internal.s.pVM;
    VM_ASSERT_EMT(pVM);
    LogFlow(("pdmR3UsbHlp_TMTimerCreate: caller='%s'/%d: enmClock=%d pfnCallback=%p pvUser=%p fFlags=%#x pszDesc=%p:{%s} ppTimer=%p\n",
             pUsbIns->pReg->szName, pUsbIns->iInstance, enmClock, pfnCallback, pvUser, fFlags, pszDesc, pszDesc, ppTimer));

    if (pUsbIns->iInstance > 0) /** @todo use a string cache here later. */
    {
         char *pszDesc2 = MMR3HeapAPrintf(pVM, MM_TAG_PDM_USB_DESC, "%s [%u]", pszDesc, pUsbIns->iInstance);
         if (pszDesc2)
             pszDesc = pszDesc2;
    }

    int rc = TMR3TimerCreateUsb(pVM, pUsbIns, enmClock, pfnCallback, pvUser, fFlags, pszDesc, ppTimer);

    LogFlow(("pdmR3UsbHlp_TMTimerCreate: caller='%s'/%d: returns %Rrc\n", pUsbIns->pReg->szName, pUsbIns->iInstance, rc));
    return rc;
}


/** @interface_method_impl{PDMUSBHLP,pfnVMSetErrorV} */
static DECLCALLBACK(int) pdmR3UsbHlp_VMSetErrorV(PPDMUSBINS pUsbIns, int rc, RT_SRC_POS_DECL, const char *pszFormat, va_list va)
{
    PDMUSB_ASSERT_USBINS(pUsbIns);
    int rc2 = VMSetErrorV(pUsbIns->Internal.s.pVM, rc, RT_SRC_POS_ARGS, pszFormat, va); Assert(rc2 == rc); NOREF(rc2);
    return rc;
}


/** @interface_method_impl{PDMUSBHLP,pfnVMSetRuntimeErrorV} */
static DECLCALLBACK(int) pdmR3UsbHlp_VMSetRuntimeErrorV(PPDMUSBINS pUsbIns, uint32_t fFlags, const char *pszErrorId, const char *pszFormat, va_list va)
{
    PDMUSB_ASSERT_USBINS(pUsbIns);
    int rc = VMSetRuntimeErrorV(pUsbIns->Internal.s.pVM, fFlags, pszErrorId, pszFormat, va);
    return rc;
}


/** @interface_method_impl{PDMUSBHLP,pfnVMState} */
static DECLCALLBACK(VMSTATE) pdmR3UsbHlp_VMState(PPDMUSBINS pUsbIns)
{
    PDMUSB_ASSERT_USBINS(pUsbIns);

    VMSTATE enmVMState = VMR3GetState(pUsbIns->Internal.s.pVM);

    LogFlow(("pdmR3UsbHlp_VMState: caller='%s'/%d: returns %d (%s)\n", pUsbIns->pReg->szName, pUsbIns->iInstance,
             enmVMState, VMR3GetStateName(enmVMState)));
    return enmVMState;
}

/** @interface_method_impl{PDMUSBHLP,pfnThreadCreate} */
static DECLCALLBACK(int) pdmR3UsbHlp_ThreadCreate(PPDMUSBINS pUsbIns, PPPDMTHREAD ppThread, void *pvUser, PFNPDMTHREADUSB pfnThread,
                                                  PFNPDMTHREADWAKEUPUSB pfnWakeup, size_t cbStack, RTTHREADTYPE enmType, const char *pszName)
{
    PDMUSB_ASSERT_USBINS(pUsbIns);
    VM_ASSERT_EMT(pUsbIns->Internal.s.pVM);
    LogFlow(("pdmR3UsbHlp_ThreadCreate: caller='%s'/%d: ppThread=%p pvUser=%p pfnThread=%p pfnWakeup=%p cbStack=%#zx enmType=%d pszName=%p:{%s}\n",
             pUsbIns->pReg->szName, pUsbIns->iInstance, ppThread, pvUser, pfnThread, pfnWakeup, cbStack, enmType, pszName, pszName));

    int rc = pdmR3ThreadCreateUsb(pUsbIns->Internal.s.pVM, pUsbIns, ppThread, pvUser, pfnThread, pfnWakeup, cbStack, enmType, pszName);

    LogFlow(("pdmR3UsbHlp_ThreadCreate: caller='%s'/%d: returns %Rrc *ppThread=%RTthrd\n", pUsbIns->pReg->szName, pUsbIns->iInstance,
             rc, *ppThread));
    return rc;
}


/** @interface_method_impl{PDMUSBHLP,pfnSetAsyncNotification} */
static DECLCALLBACK(int) pdmR3UsbHlp_SetAsyncNotification(PPDMUSBINS pUsbIns, PFNPDMUSBASYNCNOTIFY pfnAsyncNotify)
{
    PDMUSB_ASSERT_USBINS(pUsbIns);
    VM_ASSERT_EMT0(pUsbIns->Internal.s.pVM);
    LogFlow(("pdmR3UsbHlp_SetAsyncNotification: caller='%s'/%d: pfnAsyncNotify=%p\n", pUsbIns->pReg->szName, pUsbIns->iInstance, pfnAsyncNotify));

    int rc = VINF_SUCCESS;
    AssertStmt(pfnAsyncNotify, rc = VERR_INVALID_PARAMETER);
    AssertStmt(!pUsbIns->Internal.s.pfnAsyncNotify, rc = VERR_WRONG_ORDER);
    AssertStmt(pUsbIns->Internal.s.fVMSuspended || pUsbIns->Internal.s.fVMReset, rc = VERR_WRONG_ORDER);
    VMSTATE enmVMState = VMR3GetState(pUsbIns->Internal.s.pVM);
    AssertStmt(   enmVMState == VMSTATE_SUSPENDING
               || enmVMState == VMSTATE_SUSPENDING_EXT_LS
               || enmVMState == VMSTATE_SUSPENDING_LS
               || enmVMState == VMSTATE_RESETTING
               || enmVMState == VMSTATE_RESETTING_LS
               || enmVMState == VMSTATE_POWERING_OFF
               || enmVMState == VMSTATE_POWERING_OFF_LS,
               rc = VERR_INVALID_STATE);

    if (RT_SUCCESS(rc))
        pUsbIns->Internal.s.pfnAsyncNotify = pfnAsyncNotify;

    LogFlow(("pdmR3UsbHlp_SetAsyncNotification: caller='%s'/%d: returns %Rrc\n", pUsbIns->pReg->szName, pUsbIns->iInstance, rc));
    return rc;
}


/** @interface_method_impl{PDMUSBHLP,pfnAsyncNotificationCompleted} */
static DECLCALLBACK(void) pdmR3UsbHlp_AsyncNotificationCompleted(PPDMUSBINS pUsbIns)
{
    PDMUSB_ASSERT_USBINS(pUsbIns);
    PVM pVM = pUsbIns->Internal.s.pVM;

    VMSTATE enmVMState = VMR3GetState(pVM);
    if (   enmVMState == VMSTATE_SUSPENDING
        || enmVMState == VMSTATE_SUSPENDING_EXT_LS
        || enmVMState == VMSTATE_SUSPENDING_LS
        || enmVMState == VMSTATE_RESETTING
        || enmVMState == VMSTATE_RESETTING_LS
        || enmVMState == VMSTATE_POWERING_OFF
        || enmVMState == VMSTATE_POWERING_OFF_LS)
    {
        LogFlow(("pdmR3UsbHlp_AsyncNotificationCompleted: caller='%s'/%d:\n", pUsbIns->pReg->szName, pUsbIns->iInstance));
        VMR3AsyncPdmNotificationWakeupU(pVM->pUVM);
    }
    else
        LogFlow(("pdmR3UsbHlp_AsyncNotificationCompleted: caller='%s'/%d: enmVMState=%d\n", pUsbIns->pReg->szName, pUsbIns->iInstance, enmVMState));
}


/**
 * The USB device helper structure.
 */
const PDMUSBHLP g_pdmR3UsbHlp =
{
    PDM_USBHLP_VERSION,
    pdmR3UsbHlp_DriverAttach,
    pdmR3UsbHlp_AssertEMT,
    pdmR3UsbHlp_AssertOther,
    pdmR3UsbHlp_DBGFStopV,
    pdmR3UsbHlp_DBGFInfoRegister,
    pdmR3UsbHlp_MMHeapAlloc,
    pdmR3UsbHlp_MMHeapAllocZ,
    pdmR3UsbHlp_PDMQueueCreate,
    pdmR3UsbHlp_SSMRegister,
    pdmR3UsbHlp_STAMRegisterV,
    pdmR3UsbHlp_TMTimerCreate,
    pdmR3UsbHlp_VMSetErrorV,
    pdmR3UsbHlp_VMSetRuntimeErrorV,
    pdmR3UsbHlp_VMState,
    pdmR3UsbHlp_ThreadCreate,
    pdmR3UsbHlp_SetAsyncNotification,
    pdmR3UsbHlp_AsyncNotificationCompleted,
    PDM_USBHLP_VERSION
};

/** @}  */
