/* $Id: USBProxyDevice-solaris.cpp $ */
/** @file
 * USB device proxy - the Solaris backend.
 */

/*
 * Copyright (C) 2009-2024 Oracle and/or its affiliates.
 *
 * This file is part of VirtualBox base platform packages, as
 * available from https://www.virtualbox.org.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation, in version 3 of the
 * License.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <https://www.gnu.org/licenses>.
 *
 * SPDX-License-Identifier: GPL-3.0-only
 */


/*********************************************************************************************************************************
*   Header Files                                                                                                                 *
*********************************************************************************************************************************/
#define LOG_GROUP LOG_GROUP_DRV_USBPROXY
#include <sys/poll.h>
#include <errno.h>
#include <strings.h>
#include <limits.h>

#include <VBox/log.h>
#include <VBox/err.h>
#include <VBox/vmm/pdm.h>

#include <iprt/string.h>
#include <iprt/critsect.h>
#include <iprt/time.h>
#include <iprt/file.h>
#include <iprt/mem.h>
#include <iprt/pipe.h>
#include "../USBProxyDevice.h"
#include <VBox/usblib.h>


/*********************************************************************************************************************************
*   Defined Constants And Macros                                                                                                 *
*********************************************************************************************************************************/
/** Log Prefix. */
#define USBPROXY              "USBProxy"


/*********************************************************************************************************************************
*   Structures and Typedefs                                                                                                      *
*********************************************************************************************************************************/
/**
 * Wrapper around the solaris urb request structure.
 * This is required to track in-flight and landed URBs.
 */
typedef struct USBPROXYURBSOL
{
    /** Pointer to the Solaris device. */
    struct USBPROXYDEVSOL         *pDevSol;
    /** Pointer to the VUSB URB (set to NULL if canceled). */
    PVUSBURB                       pVUsbUrb;
    /** Pointer to the next solaris URB. */
    struct USBPROXYURBSOL         *pNext;
    /** Pointer to the previous solaris URB. */
    struct USBPROXYURBSOL         *pPrev;
} USBPROXYURBSOL, *PUSBPROXYURBSOL;

/**
 * Data for the solaris usb proxy backend.
 */
typedef struct USBPROXYDEVSOL
{
    /** Path of the USB device in the devices tree (persistent). */
    char                          *pszDevicePath;
    /** The connection to the client driver. */
    RTFILE                         hFile;
    /** Pointer to the proxy device instance. */
    PUSBPROXYDEV                   pProxyDev;
    /** Critical section protecting the two lists. */
    RTCRITSECT                     CritSect;
    /** The list of free solaris URBs. Singly linked. */
    PUSBPROXYURBSOL                pFreeHead;
    /** The list of active solaris URBs. Doubly linked.
     * We must maintain this so we can properly reap URBs of a detached device.
     * Only the split head will appear in this list. */
    PUSBPROXYURBSOL                pInFlightHead;
    /** The list of landed solaris URBs. Doubly linked.
     * Only the split head will appear in this list. */
    PUSBPROXYURBSOL                pTaxingHead;
    /** The tail of the landed solaris URBs. */
    PUSBPROXYURBSOL                pTaxingTail;
    /** Pipe handle for waking up - writing end. */
    RTPIPE                         hPipeWakeupW;
    /** Pipe handle for waking up - reading end. */
    RTPIPE                         hPipeWakeupR;
} USBPROXYDEVSOL, *PUSBPROXYDEVSOL;

static PVUSBURB usbProxySolarisUrbComplete(PUSBPROXYDEVSOL pDevSol);


/**
 * Allocates a Solaris URB request structure.
 *
 * @returns Pointer to an active URB request.
 * @returns NULL on failure.
 *
 * @param   pDevSol         The solaris USB device.
 */
static PUSBPROXYURBSOL usbProxySolarisUrbAlloc(PUSBPROXYDEVSOL pDevSol)
{
    PUSBPROXYURBSOL pUrbSol;

    RTCritSectEnter(&pDevSol->CritSect);

    /*
     * Try remove a Solaris URB from the free list, if none there allocate a new one.
     */
    pUrbSol = pDevSol->pFreeHead;
    if (pUrbSol)
        pDevSol->pFreeHead = pUrbSol->pNext;
    else
    {
        RTCritSectLeave(&pDevSol->CritSect);
        pUrbSol = (PUSBPROXYURBSOL)RTMemAlloc(sizeof(*pUrbSol));
        if (!pUrbSol)
            return NULL;
        RTCritSectEnter(&pDevSol->CritSect);
    }
    pUrbSol->pVUsbUrb = NULL;
    pUrbSol->pDevSol = pDevSol;

    /*
     * Link it into the active list
     */
    pUrbSol->pPrev = NULL;
    pUrbSol->pNext = pDevSol->pInFlightHead;
    if (pUrbSol->pNext)
        pUrbSol->pNext->pPrev = pUrbSol;
    pDevSol->pInFlightHead = pUrbSol;

    RTCritSectLeave(&pDevSol->CritSect);
    return pUrbSol;
}


/**
 * Frees a Solaris URB request structure.
 *
 * @param   pDevSol         The Solaris USB device.
 * @param   pUrbSol         The Solaris URB to free.
 */
static void usbProxySolarisUrbFree(PUSBPROXYDEVSOL pDevSol, PUSBPROXYURBSOL pUrbSol)
{
    RTCritSectEnter(&pDevSol->CritSect);

    /*
     * Remove from the active or taxing list.
     */
    if (pUrbSol->pNext)
        pUrbSol->pNext->pPrev   = pUrbSol->pPrev;
    else if (pDevSol->pTaxingTail == pUrbSol)
        pDevSol->pTaxingTail    = pUrbSol->pPrev;

    if (pUrbSol->pPrev)
        pUrbSol->pPrev->pNext   = pUrbSol->pNext;
    else if (pDevSol->pTaxingHead == pUrbSol)
        pDevSol->pTaxingHead    = pUrbSol->pNext;
    else if (pDevSol->pInFlightHead == pUrbSol)
        pDevSol->pInFlightHead  = pUrbSol->pNext;
    else
        AssertFailed();

    /*
     * Link it into the free list.
     */
    pUrbSol->pPrev = NULL;
    pUrbSol->pNext = pDevSol->pFreeHead;
    pDevSol->pFreeHead = pUrbSol;

    pUrbSol->pVUsbUrb = NULL;
    pUrbSol->pDevSol = NULL;

    RTCritSectLeave(&pDevSol->CritSect);
}


/*
 * Close the connection to the USB client driver.
 *
 * This is required because our userland enumeration relies on drivers/device trees
 * to recognize active devices, and hence if this device is unplugged we should no
 * longer keep the client driver loaded.
 */
static void usbProxySolarisCloseFile(PUSBPROXYDEVSOL pDevSol)
{
    RTFileClose(pDevSol->hFile);
    pDevSol->hFile = NIL_RTFILE;
}


/**
 * The client driver IOCtl Wrapper function.
 *
 * @returns VBox status code.
 * @param   pDevSol         The Solaris device instance.
 * @param   Function        The Function.
 * @param   pvData          Opaque pointer to the data.
 * @param   cbData          Size of the data pointed to by pvData.
 */
static int usbProxySolarisIOCtl(PUSBPROXYDEVSOL pDevSol, unsigned Function, void *pvData, size_t cbData)
{
    if (RT_UNLIKELY(pDevSol->hFile == NIL_RTFILE))
    {
        LogFlow((USBPROXY ":usbProxySolarisIOCtl: Connection to driver gone!\n"));
        return VERR_VUSB_DEVICE_NOT_ATTACHED;
    }

    VBOXUSBREQ Req;
    Req.u32Magic = VBOXUSB_MAGIC;
    Req.rc = -1;
    Req.cbData = cbData;
    Req.pvDataR3 = pvData;

    int Ret = -1;
    int rc = RTFileIoCtl(pDevSol->hFile, Function, &Req, sizeof(Req), &Ret);
    if (RT_SUCCESS(rc))
    {
        if (RT_FAILURE(Req.rc))
        {
            if (Req.rc == VERR_VUSB_DEVICE_NOT_ATTACHED)
            {
                pDevSol->pProxyDev->fDetached = true;
                usbProxySolarisCloseFile(pDevSol);
                LogRel((USBPROXY ": Command %#x failed, USB Device '%s' disconnected!\n", Function,
                        pDevSol->pProxyDev->pUsbIns->pszName));
            }
            else
                LogRel((USBPROXY ": Command %#x failed. Req.rc=%Rrc\n", Function, Req.rc));
        }

        return Req.rc;
    }

    LogRel((USBPROXY ": Function %#x failed. rc=%Rrc\n", Function, rc));
    return rc;
}


/**
 * Get the active configuration from the device. The first time this is called
 * our client driver would returned the cached configuration since the device is first plugged in.
 * Subsequent get configuration requests are passed on to the device.
 *
 * @returns VBox status code.
 * @param   pDevSol         The Solaris device instance.
 *
 */
static inline int usbProxySolarisGetActiveConfig(PUSBPROXYDEVSOL pDevSol)
{
    VBOXUSBREQ_GET_CONFIG GetConfigReq;
    bzero(&GetConfigReq, sizeof(GetConfigReq));
    int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_GET_CONFIG, &GetConfigReq, sizeof(GetConfigReq));
    if (RT_SUCCESS(rc))
    {
        pDevSol->pProxyDev->iActiveCfg = GetConfigReq.bConfigValue;
        pDevSol->pProxyDev->cIgnoreSetConfigs = 0;
    }
    else
    {
        if (rc != VERR_VUSB_DEVICE_NOT_ATTACHED)
            LogRel((USBPROXY ": Failed to get configuration. rc=%Rrc\n", rc));

        pDevSol->pProxyDev->iActiveCfg = -1;
        pDevSol->pProxyDev->cIgnoreSetConfigs = 0;
    }
    return rc;
}


/**
 * Opens the USB device.
 *
 * @returns VBox status code.
 * @param   pProxyDev       The device instance.
 * @param   pszAddress      The unique device identifier.
 *                          The format of this string is "VendorId:ProducIt:Release:StaticPath".
 */
static DECLCALLBACK(int) usbProxySolarisOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress)
{
    PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);

    LogFlowFunc((USBPROXY ":usbProxySolarisOpen: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress));

    /*
     * Initialize our USB R3 lib.
     */
    int rc = USBLibInit();
    if (RT_SUCCESS(rc))
    {
        /*
         * Allocate and initialize the solaris backend data.
         */
        AssertCompile(PATH_MAX >= MAXPATHLEN);
        char szDeviceIdent[PATH_MAX+48];
        rc = RTStrPrintf(szDeviceIdent, sizeof(szDeviceIdent), "%s", pszAddress);
        if (RT_SUCCESS(rc))
        {
            rc = RTCritSectInit(&pDevSol->CritSect);
            if (RT_SUCCESS(rc))
            {
                /*
                 * Create wakeup pipe.
                 */
                rc = RTPipeCreate(&pDevSol->hPipeWakeupR, &pDevSol->hPipeWakeupW, 0);
                if (RT_SUCCESS(rc))
                {
                    int Instance;
                    char *pszDevicePath = NULL;
                    rc = USBLibGetClientInfo(szDeviceIdent, &pszDevicePath, &Instance);
                    if (RT_SUCCESS(rc))
                    {
                        pDevSol->pszDevicePath = pszDevicePath;

                        /*
                         * Open the client driver.
                         */
                        RTFILE hFile;
                        rc = RTFileOpen(&hFile, pDevSol->pszDevicePath, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
                        if (RT_SUCCESS(rc))
                        {
                            pDevSol->hFile = hFile;
                            pDevSol->pProxyDev = pProxyDev;

                            /*
                             * Verify client driver version.
                             */
                            VBOXUSBREQ_GET_VERSION GetVersionReq;
                            bzero(&GetVersionReq, sizeof(GetVersionReq));
                            rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_GET_VERSION, &GetVersionReq, sizeof(GetVersionReq));
                            if (RT_SUCCESS(rc))
                            {
                                if (   GetVersionReq.u32Major == VBOXUSB_VERSION_MAJOR
                                    && GetVersionReq.u32Minor >= VBOXUSB_VERSION_MINOR)
                                {
                                    /*
                                     * Try & get the current cached config from Solaris.
                                     */
                                    usbProxySolarisGetActiveConfig(pDevSol);
                                    return VINF_SUCCESS;
                                }
                                else
                                {
                                    LogRel((USBPROXY ": Version mismatch, Driver v%d.%d expecting ~v%d.%d\n", GetVersionReq.u32Major,
                                            GetVersionReq.u32Minor, VBOXUSB_VERSION_MAJOR, VBOXUSB_VERSION_MINOR));
                                    rc = VERR_VERSION_MISMATCH;
                                }
                            }
                            else
                                LogRel((USBPROXY ": Failed to query driver version. rc=%Rrc\n", rc));

                            RTFileClose(pDevSol->hFile);
                            pDevSol->hFile = NIL_RTFILE;
                            pDevSol->pProxyDev = NULL;
                        }
                        else
                            LogRel((USBPROXY ": Failed to open device. rc=%Rrc pszDevicePath=%s\n", rc, pDevSol->pszDevicePath));

                        RTStrFree(pDevSol->pszDevicePath);
                        pDevSol->pszDevicePath = NULL;
                    }
                    else
                    {
                        LogRel((USBPROXY ": Failed to get client info. rc=%Rrc szDeviceIdent=%s\n", rc, szDeviceIdent));
                        if (rc == VERR_NOT_FOUND)
                            rc = VERR_OPEN_FAILED;
                    }
                    RTPipeClose(pDevSol->hPipeWakeupR);
                    RTPipeClose(pDevSol->hPipeWakeupW);
                }

                RTCritSectDelete(&pDevSol->CritSect);
            }
            else
                LogRel((USBPROXY ": RTCritSectInit failed. rc=%Rrc pszAddress=%s\n", rc, pszAddress));
        }
        else
            LogRel((USBPROXY ": RTStrAPrintf failed. rc=%Rrc pszAddress=%s\n", rc, pszAddress));
    }
    else
        LogRel((USBPROXY ": USBLibInit failed. rc=%Rrc\n", rc));

    USBLibTerm();
    return rc;
}


/**
 * Close the USB device.
 *
 * @param   pProxyDev   The device instance.
 */
static DECLCALLBACK(void) usbProxySolarisClose(PUSBPROXYDEV pProxyDev)
{
    LogFlow((USBPROXY ":usbProxySolarisClose: pProxyDev=%p\n", pProxyDev));

    PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);

    /* Close the device (do not re-enumerate). */
    VBOXUSBREQ_CLOSE_DEVICE CloseReq;
    CloseReq.ResetLevel = VBOXUSB_RESET_LEVEL_CLOSE;
    usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_CLOSE_DEVICE, &CloseReq, sizeof(CloseReq));

    pProxyDev->fDetached = true;
    usbProxySolarisCloseFile(pDevSol);

    /*
     * Now we can close it and free all the resources.
     */
    RTCritSectDelete(&pDevSol->CritSect);

    PUSBPROXYURBSOL pUrbSol = NULL;
    while ((pUrbSol = pDevSol->pInFlightHead) != NULL)
    {
        pDevSol->pInFlightHead = pUrbSol->pNext;
        RTMemFree(pUrbSol);
    }

    while ((pUrbSol = pDevSol->pFreeHead) != NULL)
    {
        pDevSol->pFreeHead = pUrbSol->pNext;
        RTMemFree(pUrbSol);
    }

    RTPipeClose(pDevSol->hPipeWakeupR);
    RTPipeClose(pDevSol->hPipeWakeupW);

    RTStrFree(pDevSol->pszDevicePath);
    pDevSol->pszDevicePath = NULL;

    USBLibTerm();
}


/**
 * Reset the device.
 *
 * @returns VBox status code.
 * @param   pProxyDev           The device to reset.
 * @param   fRootHubReset       Is this a root hub reset or device specific reset request.
 */
static DECLCALLBACK(int) usbProxySolarisReset(PUSBPROXYDEV pProxyDev, bool fRootHubReset)
{
    LogFlowFunc((USBPROXY ": usbProxySolarisReset: pProxyDev=%s fRootHubReset=%d\n", pProxyDev->pUsbIns->pszName, fRootHubReset));

    /** Pass all resets to the device. The Trekstor USB (1.1) stick requires this to work. */
    PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);

    /* Soft reset the device. */
    VBOXUSBREQ_CLOSE_DEVICE CloseReq;
    CloseReq.ResetLevel = VBOXUSB_RESET_LEVEL_SOFT;
    int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_CLOSE_DEVICE, &CloseReq, sizeof(CloseReq));
    if (RT_SUCCESS(rc))
    {
        /* Get the active config. Solaris USBA sets a default config. */
        usbProxySolarisGetActiveConfig(pDevSol);
    }
    else if (rc != VERR_VUSB_DEVICE_NOT_ATTACHED)
        LogRel((USBPROXY ": usbProxySolarisReset: Failed! rc=%d\n", rc));

    return rc;
}


/**
 * Set the active configuration.
 *
 * The caller makes sure that it's not called first time after open or reset
 * with the active interface.
 *
 * @returns success indicator.
 * @param   pProxyDev       The device instance data.
 * @param   iCfg            The configuration value to set.
 */
static DECLCALLBACK(int) usbProxySolarisSetConfig(PUSBPROXYDEV pProxyDev, int iCfg)
{
    LogFlowFunc((USBPROXY ": usbProxySolarisSetConfig: pProxyDev=%p iCfg=%#x\n", pProxyDev, iCfg));

    PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);
    AssertPtrReturn(pDevSol, VERR_INVALID_POINTER);

    VBOXUSBREQ_SET_CONFIG SetConfigReq;
    SetConfigReq.bConfigValue = iCfg;
    int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_SET_CONFIG, &SetConfigReq, sizeof(SetConfigReq));
    if (   RT_FAILURE(rc)
        && rc != VERR_VUSB_DEVICE_NOT_ATTACHED)
        LogRel((USBPROXY ": usbProxySolarisSetConfig: Failed! rc=%Rrc\n", rc));

    return rc;
}


/**
 * Claims an interface.
 *
 * This is a stub on Solaris since we release/claim all interfaces at
 * as and when required with endpoint opens.
 *
 * @returns success indicator (always true).
 */
static DECLCALLBACK(int) usbProxySolarisClaimInterface(PUSBPROXYDEV pProxyDev, int iIf)
{
    return VINF_SUCCESS;
}


/**
 * Releases an interface.
 *
 * This is a stub on Solaris since we release/claim all interfaces at
 * as and when required with endpoint opens.
 *
 * @returns success indicator.
 */
static DECLCALLBACK(int) usbProxySolarisReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf)
{
    return VINF_SUCCESS;
}


/**
 * Specify an alternate setting for the specified interface of the current configuration.
 *
 * @returns success indicator.
 */
static DECLCALLBACK(int) usbProxySolarisSetInterface(PUSBPROXYDEV pProxyDev, int bIf, int bAlt)
{
    LogFlowFunc((USBPROXY ": usbProxySolarisSetInterface: pProxyDev=%p bIf=%#x iAlt=%#x\n", pProxyDev, bIf, bAlt));

    PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);
    AssertPtrReturn(pDevSol, VERR_INVALID_POINTER);

    VBOXUSBREQ_SET_INTERFACE SetInterfaceReq;
    SetInterfaceReq.bInterface = bIf;
    SetInterfaceReq.bAlternate = bAlt;
    int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_SET_INTERFACE, &SetInterfaceReq, sizeof(SetInterfaceReq));
    if (   RT_FAILURE(rc)
        && rc != VERR_VUSB_DEVICE_NOT_ATTACHED)
        LogRel((USBPROXY ": usbProxySolarisSetInterface: Failed! rc=%Rrc\n", rc));

    return rc;
}


/**
 * Clears the halted endpoint 'EndPt'.
 */
static DECLCALLBACK(int) usbProxySolarisClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int EndPt)
{
    LogFlowFunc((USBPROXY ": usbProxySolarisClearHaltedEp: pProxyDev=%p EndPt=%#x\n", pProxyDev, EndPt));

    PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);
    AssertPtrReturn(pDevSol, VERR_INVALID_POINTER);

    VBOXUSBREQ_CLEAR_EP ClearEpReq;
    ClearEpReq.bEndpoint = EndPt;
    int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_CLEAR_EP, &ClearEpReq, sizeof(ClearEpReq));
    if (   RT_FAILURE(rc)
        && rc != VERR_VUSB_DEVICE_NOT_ATTACHED)
        LogRel((USBPROXY ": usbProxySolarisClearHaltedEp: Failed! rc=%Rrc\n", rc));

    return rc;
}


/**
 * @interface_method_impl{USBPROXYBACK,pfnUrbQueue}
 */
static DECLCALLBACK(int) usbProxySolarisUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)
{
    PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);

    LogFlowFunc((USBPROXY ": usbProxySolarisUrbQueue: pProxyDev=%s pUrb=%p pszDesc=%s EndPt=%#x enmDir=%d cbData=%d pvData=%p\n",
             pProxyDev->pUsbIns->pszName, pUrb, pUrb->pszDesc, pUrb->EndPt, pUrb->enmDir, pUrb->cbData, pUrb->abData));

    PUSBPROXYURBSOL pUrbSol = usbProxySolarisUrbAlloc(pDevSol);
    if (RT_UNLIKELY(!pUrbSol))
    {
        LogRel((USBPROXY ": usbProxySolarisUrbQueue: Failed to allocate URB\n"));
        return VERR_NO_MEMORY;
    }

    pUrbSol->pVUsbUrb = pUrb;
    pUrbSol->pDevSol = pDevSol;

    uint8_t EndPt = pUrb->EndPt;
    if (EndPt)
        EndPt |= pUrb->enmDir == VUSBDIRECTION_IN ? VUSB_DIR_TO_HOST : VUSB_DIR_TO_DEVICE;

    VBOXUSBREQ_URB UrbReq;
    UrbReq.pvUrbR3      = pUrbSol;
    UrbReq.bEndpoint    = EndPt;
    UrbReq.enmType      = pUrb->enmType;
    UrbReq.enmDir       = pUrb->enmDir;
    UrbReq.enmStatus    = pUrb->enmStatus;
    UrbReq.fShortOk     = !pUrb->fShortNotOk;
    UrbReq.cbData       = pUrb->cbData;
    UrbReq.pvData       = &pUrb->abData[0];

    Log6((USBPROXY ": Sending: EndPt=%#x Dir=%d cbData=%u\n", pUrb->EndPt, pUrb->enmDir, pUrb->cbData));

    if (pUrb->enmType == VUSBXFERTYPE_ISOC)
    {
        UrbReq.cIsocPkts = pUrb->cIsocPkts;
        for (unsigned i = 0; i < pUrb->cIsocPkts; i++)
        {
            UrbReq.aIsocPkts[i].cbPkt = pUrb->aIsocPkts[i].cb;
            UrbReq.aIsocPkts[i].cbActPkt = 0;
            UrbReq.aIsocPkts[i].enmStatus = VUSBSTATUS_INVALID;
        }
    }

    int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_SEND_URB, &UrbReq, sizeof(UrbReq));
    if (RT_SUCCESS(rc))
    {
        if (pUrb->enmType == VUSBXFERTYPE_ISOC)
            LogFlow((USBPROXY ":usbProxySolarisUrbQueue: Success cbData=%d\n", pUrb->cbData));
        pUrb->Dev.pvPrivate = pUrbSol;
        return VINF_SUCCESS;
    }

    if (rc != VERR_VUSB_DEVICE_NOT_ATTACHED)
    {
        LogRel((USBPROXY ": usbProxySolarisUrbQueue: Failed! pProxyDev=%s pUrb=%p EndPt=%#x bEndpoint=%#x enmType=%d "
                "enmDir=%d cbData=%u rc=%Rrc\n", pProxyDev->pUsbIns->pszName, pUrb, pUrb->EndPt,
                UrbReq.bEndpoint, pUrb->enmType, pUrb->enmDir, pUrb->cbData, rc));
    }

    return rc;
}


/**
 * Cancels a URB.
 *
 * The URB requires reaping, so we don't change its state.
 * @remark  There isn't any way to cancel a specific asynchronous request
 *          on Solaris. So we just abort pending URBs on the pipe.
 */
static DECLCALLBACK(int) usbProxySolarisUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)
{
    PUSBPROXYURBSOL pUrbSol = (PUSBPROXYURBSOL)pUrb->Dev.pvPrivate;
    PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);
    AssertPtrReturn(pDevSol, VERR_INVALID_POINTER);

    LogFlowFunc((USBPROXY ": usbProxySolarisUrbCancel: pUrb=%p pUrbSol=%p pDevSol=%p\n", pUrb, pUrbSol, pUrbSol->pDevSol));

    /* Aborting the control pipe isn't supported, pretend success. */
    if (!pUrb->EndPt)
        return VINF_SUCCESS;

    VBOXUSBREQ_ABORT_PIPE AbortPipeReq;
    AbortPipeReq.bEndpoint = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? VUSB_DIR_TO_HOST : VUSB_DIR_TO_DEVICE);
    int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_ABORT_PIPE, &AbortPipeReq, sizeof(AbortPipeReq));
    if (   RT_FAILURE(rc)
        && rc != VERR_VUSB_DEVICE_NOT_ATTACHED)
        LogRel((USBPROXY ": usbProxySolarisUrbCancel: Failed to abort pipe. rc=%Rrc\n", rc));

    LogFlow((USBPROXY ": usbProxySolarisUrbCancel: returns rc=%Rrc\n", rc));
    return rc;
}


/**
 * Reap URBs in-flight on a device.
 *
 * @returns Pointer to a completed URB.
 * @returns NULL if no URB was completed.
 * @param   pProxyDev   The device.
 * @param   cMillies    Number of milliseconds to wait. Use 0 to not wait at all.
 */
static DECLCALLBACK(PVUSBURB) usbProxySolarisUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies)
{
    LogFlowFunc((USBPROXY ":usbProxySolarisUrbReap pProxyDev=%p cMillies=%u\n", pProxyDev, cMillies));

    PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);

    /*
     * Don't block if nothing is in the air.
     */
    if (!pDevSol->pInFlightHead)
        return NULL;

    /*
     * Deque URBs inflight or those landed.
     */
    if (cMillies > 0)
    {
        for (;;)
        {
            int cMilliesWait = cMillies == RT_INDEFINITE_WAIT ? -1 : (int)cMillies;

            struct pollfd aFd[2];
            size_t const  cFds = RT_ELEMENTS(aFd);

            aFd[0].fd      = RTFileToNative(pDevSol->hFile);
            aFd[0].events  = POLLIN;
            aFd[0].revents = 0;

            aFd[1].fd      = RTPipeToNative(pDevSol->hPipeWakeupR);
            aFd[1].events  = POLLIN;
            aFd[1].revents = 0;

            int rc = poll(&aFd[0], cFds, cMilliesWait);
            if (rc > 0)
            {
                if (aFd[0].revents & POLLHUP)
                {
                    LogRel((USBPROXY ": USB Device '%s' disconnected!\n", pDevSol->pProxyDev->pUsbIns->pszName));
                    pProxyDev->fDetached = true;
                    usbProxySolarisCloseFile(pDevSol);
                }

                if (aFd[1].revents & POLLIN)
                {
                    /* Got woken up, drain pipe. */
                    uint8_t bRead;
                    size_t cbIgnored = 0;
                    RTPipeRead(pDevSol->hPipeWakeupR, &bRead, 1, &cbIgnored);

                    /*
                     * It is possible that we got woken up and have an URB pending
                     * for completion. Do it on the way out. Otherwise return
                     * immediately to the caller.
                     */
                    if (!(aFd[0].revents & POLLIN))
                        return NULL;
                }
                break;
            }
            else if (rc == 0)
                return NULL;
            else if (errno != EAGAIN)
            {
                LogFlow((USBPROXY ":usbProxySolarisUrbReap Poll rc=%d errno=%d\n", rc, errno));
                return NULL;
            }
        }
    }

    usbProxySolarisUrbComplete(pDevSol);

    /*
     * Any URBs pending delivery?
     */
    PVUSBURB pUrb = NULL;
    while (    pDevSol->pTaxingHead
           && !pUrb)
    {
        RTCritSectEnter(&pDevSol->CritSect);

        PUSBPROXYURBSOL pUrbSol = pDevSol->pTaxingHead;
        if (pUrbSol)
        {
            pUrb = pUrbSol->pVUsbUrb;
            if (pUrb)
            {
                /*
                 * Remove it from the taxing list and move it to the free list.
                 */
                pUrb->Dev.pvPrivate = NULL;
                usbProxySolarisUrbFree(pDevSol, pUrbSol);
            }
        }
        RTCritSectLeave(&pDevSol->CritSect);
    }

    return pUrb;
}


/**
 * Reads a completed/error'd URB from the client driver (no waiting).
 *
 * @param   pDevSol         The Solaris device instance.
 */
static PVUSBURB usbProxySolarisUrbComplete(PUSBPROXYDEVSOL pDevSol)
{
    LogFlowFunc((USBPROXY ": usbProxySolarisUrbComplete: pDevSol=%p\n", pDevSol));

    VBOXUSBREQ_URB UrbReq;
    bzero(&UrbReq, sizeof(UrbReq));

    int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_REAP_URB, &UrbReq, sizeof(UrbReq));
    if (RT_SUCCESS(rc))
    {
        if (UrbReq.pvUrbR3)
        {
            PUSBPROXYURBSOL pUrbSol = (PUSBPROXYURBSOL)UrbReq.pvUrbR3;
            PVUSBURB pUrb           = pUrbSol->pVUsbUrb;
            if (RT_LIKELY(pUrb))
            {
                Assert(pUrb->u32Magic == VUSBURB_MAGIC);

                /*
                 * Update the URB.
                 */
                if (   pUrb->enmType == VUSBXFERTYPE_ISOC
                    && pUrb->enmDir == VUSBDIRECTION_IN)
                {
                    size_t cbData = 0;
                    for (unsigned i = 0; i < UrbReq.cIsocPkts; i++)
                    {
                        pUrb->aIsocPkts[i].cb = UrbReq.aIsocPkts[i].cbActPkt;
                        cbData += UrbReq.aIsocPkts[i].cbActPkt;
                        pUrb->aIsocPkts[i].enmStatus = UrbReq.aIsocPkts[i].enmStatus;
                    }

                    LogFlow((USBPROXY ":usbProxySolarisUrbComplete: Isoc cbData=%d cbActPktSum=%d\n", pUrb->cbData, cbData));
                    pUrb->cbData = cbData;
                    pUrb->enmStatus = UrbReq.enmStatus;
                }
                else
                {
                    pUrb->cbData    = UrbReq.cbData;
                    pUrb->enmStatus = UrbReq.enmStatus;
                }

                RTCritSectEnter(&pDevSol->CritSect);

                /*
                 * Remove from the active list.
                 */
                if (pUrbSol->pNext)
                    pUrbSol->pNext->pPrev = pUrbSol->pPrev;
                if (pUrbSol->pPrev)
                    pUrbSol->pPrev->pNext = pUrbSol->pNext;
                else
                {
                    Assert(pDevSol->pInFlightHead == pUrbSol);
                    pDevSol->pInFlightHead = pUrbSol->pNext;
                }

                /*
                 * Add to the tail of the taxing list.
                 */
                pUrbSol->pNext = NULL;
                pUrbSol->pPrev = pDevSol->pTaxingTail;
                if (pDevSol->pTaxingTail)
                    pDevSol->pTaxingTail->pNext = pUrbSol;
                else
                    pDevSol->pTaxingHead = pUrbSol;
                pDevSol->pTaxingTail = pUrbSol;

                RTCritSectLeave(&pDevSol->CritSect);

                Log6((USBPROXY ": Reaping: EndPt=%#x Dir=%d cbData=%u\n", pUrb->EndPt, pUrb->enmDir, pUrb->cbData));
                if (pUrb->cbData < 1024)
                    Log6(("%.*Rhxd\n", pUrb->cbData, pUrb->abData));
                return pUrb;
            }
        }
    }
    else
    {
        if (rc != VERR_VUSB_DEVICE_NOT_ATTACHED)
            LogRel((USBPROXY ": Reaping URB failed. rc=%Rrc\n", rc));
    }

    return NULL;
}


static DECLCALLBACK(int) usbProxySolarisWakeup(PUSBPROXYDEV pProxyDev)
{
    PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);
    size_t cbIgnored;

    LogFlowFunc(("pProxyDev=%p\n", pProxyDev));

    return RTPipeWrite(pDevSol->hPipeWakeupW, "", 1, &cbIgnored);
}


/**
 * The Solaris USB Proxy Backend.
 */
extern const USBPROXYBACK g_USBProxyDeviceHost =
{
    /* pszName */
    "host",
    /* cbBackend */
    sizeof(USBPROXYDEVSOL),
    usbProxySolarisOpen,
    NULL,
    usbProxySolarisClose,
    usbProxySolarisReset,
    usbProxySolarisSetConfig,
    usbProxySolarisClaimInterface,
    usbProxySolarisReleaseInterface,
    usbProxySolarisSetInterface,
    usbProxySolarisClearHaltedEp,
    usbProxySolarisUrbQueue,
    usbProxySolarisUrbCancel,
    usbProxySolarisUrbReap,
    usbProxySolarisWakeup,
    0
};

