/* $Id: VBoxUsbRt.cpp $ */
/** @file
 * VBox USB R0 runtime
 */
/*
 * Copyright (C) 2011 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.
 */

#include "VBoxUsbCmn.h"
#include "../cmn/VBoxUsbIdc.h"
#include "../cmn/VBoxUsbTool.h"

#include <VBox/usblib-win.h>
#include <iprt/assert.h>
#include <VBox/log.h>
#define _USBD_

#define USBD_DEFAULT_PIPE_TRANSFER 0x00000008

#define VBOXUSB_MAGIC  0xABCF1423

typedef struct VBOXUSB_URB_CONTEXT
{
    PURB pUrb;
    PMDL pMdlBuf;
    PVBOXUSBDEV_EXT pDevExt;
    PVOID pOut;
    ULONG ulTransferType;
    ULONG ulMagic;
} VBOXUSB_URB_CONTEXT, * PVBOXUSB_URB_CONTEXT;

typedef struct VBOXUSB_SETUP
{
    uint8_t  bmRequestType;
    uint8_t  bRequest;
    uint16_t wValue;
    uint16_t wIndex;
    uint16_t wLength;
} VBOXUSB_SETUP, *PVBOXUSB_SETUP;

static bool vboxUsbRtCtxSetOwner(PVBOXUSBDEV_EXT pDevExt, PFILE_OBJECT pFObj)
{
    bool bRc = ASMAtomicCmpXchgPtr(&pDevExt->Rt.pOwner, pFObj, NULL);
    if (bRc)
    {
        Log((__FUNCTION__": pDevExt (0x%x) Owner(0x%x) acquired\n", pFObj));
    }
    else
    {
        Log((__FUNCTION__": pDevExt (0x%x) Owner(0x%x) FAILED!!\n", pFObj));
    }
    return bRc;
}

static bool vboxUsbRtCtxReleaseOwner(PVBOXUSBDEV_EXT pDevExt, PFILE_OBJECT pFObj)
{
    bool bRc = ASMAtomicCmpXchgPtr(&pDevExt->Rt.pOwner, NULL, pFObj);
    if (bRc)
    {
        Log((__FUNCTION__": pDevExt (0x%x) Owner(0x%x) released\n", pFObj));
    }
    else
    {
        Log((__FUNCTION__": pDevExt (0x%x) Owner(0x%x) release: is NOT an owner\n", pFObj));
    }
    return bRc;
}

static bool vboxUsbRtCtxIsOwner(PVBOXUSBDEV_EXT pDevExt, PFILE_OBJECT pFObj)
{
    PFILE_OBJECT pOwner = (PFILE_OBJECT)ASMAtomicReadPtr((void *volatile *)(&pDevExt->Rt.pOwner));
    return pOwner == pFObj;
}

static NTSTATUS vboxUsbRtIdcSubmit(ULONG uCtl, void *pvBuffer)
{
    /* we just reuse the standard usb tooling for simplicity here */
    NTSTATUS Status = VBoxUsbToolIoInternalCtlSendSync(g_VBoxUsbGlobals.RtIdc.pDevice, uCtl, pvBuffer, NULL);
    Assert(Status == STATUS_SUCCESS);
    return Status;
}

static NTSTATUS vboxUsbRtIdcInit()
{
    UNICODE_STRING UniName;
    RtlInitUnicodeString(&UniName, USBMON_DEVICE_NAME_NT);
    NTSTATUS Status = IoGetDeviceObjectPointer(&UniName, FILE_ALL_ACCESS, &g_VBoxUsbGlobals.RtIdc.pFile, &g_VBoxUsbGlobals.RtIdc.pDevice);
    if (NT_SUCCESS(Status))
    {
        VBOXUSBIDC_VERSION Version;
        vboxUsbRtIdcSubmit(VBOXUSBIDC_INTERNAL_IOCTL_GET_VERSION, &Version);
        if (NT_SUCCESS(Status))
        {
            if (Version.u32Major == VBOXUSBIDC_VERSION_MAJOR
                    && Version.u32Minor >= VBOXUSBIDC_VERSION_MINOR)
                return STATUS_SUCCESS;
            AssertFailed();
        }
        else
        {
            AssertFailed();
        }

        /* this will as well dereference the dev obj */
        ObDereferenceObject(g_VBoxUsbGlobals.RtIdc.pFile);
    }
    else
    {
        AssertFailed();
    }

    memset(&g_VBoxUsbGlobals.RtIdc, 0, sizeof (g_VBoxUsbGlobals.RtIdc));
    return Status;
}

static VOID vboxUsbRtIdcTerm()
{
    Assert(g_VBoxUsbGlobals.RtIdc.pFile);
    Assert(g_VBoxUsbGlobals.RtIdc.pDevice);
    ObDereferenceObject(g_VBoxUsbGlobals.RtIdc.pFile);
    memset(&g_VBoxUsbGlobals.RtIdc, 0, sizeof (g_VBoxUsbGlobals.RtIdc));
}

static NTSTATUS vboxUsbRtIdcReportDevStart(PDEVICE_OBJECT pPDO, HVBOXUSBIDCDEV *phDev)
{
    VBOXUSBIDC_PROXY_STARTUP Start;
    Start.u.pPDO = pPDO;

    *phDev = NULL;

    NTSTATUS Status = vboxUsbRtIdcSubmit(VBOXUSBIDC_INTERNAL_IOCTL_PROXY_STARTUP, &Start);
    Assert(Status == STATUS_SUCCESS);
    if (!NT_SUCCESS(Status))
        return Status;

    *phDev = Start.u.hDev;
    return STATUS_SUCCESS;
}

static NTSTATUS vboxUsbRtIdcReportDevStop(HVBOXUSBIDCDEV hDev)
{
    VBOXUSBIDC_PROXY_TEARDOWN Stop;
    Stop.hDev = hDev;

    NTSTATUS Status = vboxUsbRtIdcSubmit(VBOXUSBIDC_INTERNAL_IOCTL_PROXY_TEARDOWN, &Stop);
    Assert(Status == STATUS_SUCCESS);
    return Status;
}


DECLHIDDEN(NTSTATUS) vboxUsbRtGlobalsInit()
{
    return vboxUsbRtIdcInit();
}

DECLHIDDEN(VOID) vboxUsbRtGlobalsTerm()
{
    vboxUsbRtIdcTerm();
}


DECLHIDDEN(NTSTATUS) vboxUsbRtInit(PVBOXUSBDEV_EXT pDevExt)
{
    RtlZeroMemory(&pDevExt->Rt, sizeof (pDevExt->Rt));
    NTSTATUS Status = IoRegisterDeviceInterface(pDevExt->pPDO, &GUID_CLASS_VBOXUSB,
                                NULL, /* IN PUNICODE_STRING ReferenceString OPTIONAL */
                                &pDevExt->Rt.IfName);
    Assert(Status == STATUS_SUCCESS);
    if (NT_SUCCESS(Status))
    {
        Status = vboxUsbRtIdcReportDevStart(pDevExt->pPDO, &pDevExt->Rt.hMonDev);
        Assert(Status == STATUS_SUCCESS);
        if (NT_SUCCESS(Status))
        {
            Assert(pDevExt->Rt.hMonDev);
            return STATUS_SUCCESS;
        }

        NTSTATUS tmpStatus = IoSetDeviceInterfaceState(&pDevExt->Rt.IfName, FALSE);
        Assert(tmpStatus == STATUS_SUCCESS);
        if (NT_SUCCESS(tmpStatus))
        {
            RtlFreeUnicodeString(&pDevExt->Rt.IfName);
        }
    }
    return Status;
}

/**
 * Free cached USB device/configuration descriptors
 *
 * @param   pDevExt             USB DevExt pointer
 */
static void vboxUsbRtFreeCachedDescriptors(PVBOXUSBDEV_EXT pDevExt)
{
    if (pDevExt->Rt.devdescr)
    {
        vboxUsbMemFree(pDevExt->Rt.devdescr);
        pDevExt->Rt.devdescr = NULL;
    }
    for (ULONG i = 0; i < VBOXUSBRT_MAX_CFGS; ++i)
    {
        if (pDevExt->Rt.cfgdescr[i])
        {
            vboxUsbMemFree(pDevExt->Rt.cfgdescr[i]);
            pDevExt->Rt.cfgdescr[i] = NULL;
        }
    }
}

/**
 * Free per-device interface info
 *
 * @param   pDevExt             USB DevExt pointer
 * @param   fAbortPipes         If true, also abort any open pipes
 */
static void vboxUsbRtFreeInterfaces(PVBOXUSBDEV_EXT pDevExt, BOOLEAN fAbortPipes)
{
    unsigned i;
    unsigned j;

    /*
     * Free old interface info
     */
    if (pDevExt->Rt.pVBIfaceInfo)
    {
        for (i=0;i<pDevExt->Rt.uNumInterfaces;i++)
        {
            if (pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo)
            {
                if (fAbortPipes)
                {
                    for(j=0; j<pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->NumberOfPipes; j++)
                    {
                        Log(("Aborting Pipe %d handle %x address %x\n", j,
                                 pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->Pipes[j].PipeHandle,
                                 pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->Pipes[j].EndpointAddress));
                        VBoxUsbToolPipeClear(pDevExt->pLowerDO, pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->Pipes[j].PipeHandle, FALSE);
                    }
                }
                vboxUsbMemFree(pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo);
            }
            pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo = NULL;
            if (pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo)
                vboxUsbMemFree(pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo);
            pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo = NULL;
        }
        vboxUsbMemFree(pDevExt->Rt.pVBIfaceInfo);
        pDevExt->Rt.pVBIfaceInfo = NULL;
    }
}

DECLHIDDEN(VOID) vboxUsbRtClear(PVBOXUSBDEV_EXT pDevExt)
{
    vboxUsbRtFreeCachedDescriptors(pDevExt);
    vboxUsbRtFreeInterfaces(pDevExt, FALSE);
}

DECLHIDDEN(NTSTATUS) vboxUsbRtRm(PVBOXUSBDEV_EXT pDevExt)
{
    if (!pDevExt->Rt.IfName.Buffer)
        return STATUS_SUCCESS;

    NTSTATUS Status = vboxUsbRtIdcReportDevStop(pDevExt->Rt.hMonDev);
    Assert(Status == STATUS_SUCCESS);
    Status = IoSetDeviceInterfaceState(&pDevExt->Rt.IfName, FALSE);
    Assert(Status == STATUS_SUCCESS);
    if (NT_SUCCESS(Status))
    {
        RtlFreeUnicodeString(&pDevExt->Rt.IfName);
        pDevExt->Rt.IfName.Buffer = NULL;
    }
    return Status;
}

DECLHIDDEN(NTSTATUS) vboxUsbRtStart(PVBOXUSBDEV_EXT pDevExt)
{
    NTSTATUS Status = IoSetDeviceInterfaceState(&pDevExt->Rt.IfName, TRUE);
    Assert(Status == STATUS_SUCCESS);
    return Status;
}

static NTSTATUS vboxUsbRtCacheDescriptors(PVBOXUSBDEV_EXT pDevExt)
{
    NTSTATUS Status = STATUS_INSUFFICIENT_RESOURCES;
//    uint32_t uTotalLength;
//    unsigned                        i;

    /* Read device descriptor */
    Assert(!pDevExt->Rt.devdescr);
    pDevExt->Rt.devdescr = (PUSB_DEVICE_DESCRIPTOR)vboxUsbMemAlloc(sizeof (USB_DEVICE_DESCRIPTOR));
    if (pDevExt->Rt.devdescr)
    {
        memset(pDevExt->Rt.devdescr, 0, sizeof (USB_DEVICE_DESCRIPTOR));
        Status = VBoxUsbToolGetDescriptor(pDevExt->pLowerDO, pDevExt->Rt.devdescr, sizeof (USB_DEVICE_DESCRIPTOR), USB_DEVICE_DESCRIPTOR_TYPE, 0, 0, RT_INDEFINITE_WAIT);
        if (NT_SUCCESS(Status))
        {
            Assert(pDevExt->Rt.devdescr->bNumConfigurations > 0);
            PUSB_CONFIGURATION_DESCRIPTOR pDr = (PUSB_CONFIGURATION_DESCRIPTOR)vboxUsbMemAlloc(sizeof (USB_CONFIGURATION_DESCRIPTOR));
            Assert(pDr);
            if (pDr)
            {
                UCHAR i = 0;
                for (; i < pDevExt->Rt.devdescr->bNumConfigurations; ++i)
                {
                    Status = VBoxUsbToolGetDescriptor(pDevExt->pLowerDO, pDr, sizeof (USB_CONFIGURATION_DESCRIPTOR), USB_CONFIGURATION_DESCRIPTOR_TYPE, i, 0, RT_INDEFINITE_WAIT);
                    if (!NT_SUCCESS(Status))
                    {
                        break;
                    }

                    USHORT uTotalLength = pDr->wTotalLength;
                    pDevExt->Rt.cfgdescr[i] = (PUSB_CONFIGURATION_DESCRIPTOR)vboxUsbMemAlloc(uTotalLength);
                    if (!pDevExt->Rt.cfgdescr[i])
                    {
                        Status = STATUS_INSUFFICIENT_RESOURCES;
                        break;
                    }

                    Status = VBoxUsbToolGetDescriptor(pDevExt->pLowerDO, pDevExt->Rt.cfgdescr[i], uTotalLength, USB_CONFIGURATION_DESCRIPTOR_TYPE, i, 0, RT_INDEFINITE_WAIT);
                    if (!NT_SUCCESS(Status))
                    {
                        break;
                    }
                }

                vboxUsbMemFree(pDr);

                if (NT_SUCCESS(Status))
                    return Status;

                /* recources will be freed in vboxUsbRtFreeCachedDescriptors below */
            }
        }

        vboxUsbRtFreeCachedDescriptors(pDevExt);
    }

    /* shoud be only on fail here */
    Assert(!NT_SUCCESS(Status));
    return Status;
}

static NTSTATUS vboxUsbRtDispatchClaimDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp)
{
    PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp);
    PFILE_OBJECT pFObj = pSl->FileObject;
    PUSBSUP_CLAIMDEV pDev  = (PUSBSUP_CLAIMDEV)pIrp->AssociatedIrp.SystemBuffer;
    ULONG cbOut = 0;
    NTSTATUS Status = STATUS_SUCCESS;

    do
    {
        if (!pFObj)
        {
            AssertFailed();
            Status = STATUS_INVALID_PARAMETER;
            break;
        }

        if (  !pDev
            || pSl->Parameters.DeviceIoControl.InputBufferLength != sizeof (*pDev)
            || pSl->Parameters.DeviceIoControl.OutputBufferLength != sizeof (*pDev))
        {
            AssertFailed();
            Status = STATUS_INVALID_PARAMETER;
            break;
        }

        if (!vboxUsbRtCtxSetOwner(pDevExt, pFObj))
        {
            AssertFailed();
            pDev->fClaimed = false;
            cbOut = sizeof (*pDev);
            break;
        }

        vboxUsbRtFreeCachedDescriptors(pDevExt);
        Status = vboxUsbRtCacheDescriptors(pDevExt);
        if (NT_SUCCESS(Status))
        {
            pDev->fClaimed = true;
            cbOut = sizeof (*pDev);
        }
    } while (0);

    Assert(Status != STATUS_PENDING);
    VBoxDrvToolIoComplete(pIrp, Status, cbOut);
    vboxUsbDdiStateRelease(pDevExt);
    return Status;
}

static NTSTATUS vboxUsbRtDispatchReleaseDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp)
{
    PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp);
    PFILE_OBJECT pFObj = pSl->FileObject;
    NTSTATUS Status= STATUS_SUCCESS;

    if (vboxUsbRtCtxIsOwner(pDevExt, pFObj))
    {
        vboxUsbRtFreeCachedDescriptors(pDevExt);
        bool bRc = vboxUsbRtCtxReleaseOwner(pDevExt, pFObj);
        Assert(bRc);
    }
    else
    {
        AssertFailed();
        Status = STATUS_ACCESS_DENIED;
    }

    VBoxDrvToolIoComplete(pIrp, STATUS_SUCCESS, 0);
    vboxUsbDdiStateRelease(pDevExt);
    return STATUS_SUCCESS;
}

static NTSTATUS vboxUsbRtGetDeviceDescription(PVBOXUSBDEV_EXT pDevExt)
{
    NTSTATUS Status = STATUS_INSUFFICIENT_RESOURCES;
    PUSB_DEVICE_DESCRIPTOR pDr = (PUSB_DEVICE_DESCRIPTOR)vboxUsbMemAllocZ(sizeof (USB_DEVICE_DESCRIPTOR));
    if (pDr)
    {
        Status = VBoxUsbToolGetDescriptor(pDevExt->pLowerDO, pDr, sizeof(*pDr), USB_DEVICE_DESCRIPTOR_TYPE, 0, 0, RT_INDEFINITE_WAIT);
        if (NT_SUCCESS(Status))
        {
            pDevExt->Rt.idVendor    = pDr->idVendor;
            pDevExt->Rt.idProduct   = pDr->idProduct;
            pDevExt->Rt.bcdDevice   = pDr->bcdDevice;
            pDevExt->Rt.szSerial[0] = 0;

            if (pDr->iSerialNumber
#ifdef DEBUG
                    || pDr->iProduct || pDr->iManufacturer
#endif
               )
            {
                int langId;
                Status = VBoxUsbToolGetLangID(pDevExt->pLowerDO, &langId, RT_INDEFINITE_WAIT);
                if (NT_SUCCESS(Status))
                {
                    Status = VBoxUsbToolGetStringDescriptorA(pDevExt->pLowerDO, pDevExt->Rt.szSerial, sizeof (pDevExt->Rt.szSerial), pDr->iSerialNumber, langId, RT_INDEFINITE_WAIT);
                }
                else
                {
                    Status = STATUS_SUCCESS;
                }
            }
        }
        vboxUsbMemFree(pDr);
    }

    return Status;
}

static NTSTATUS vboxUsbRtDispatchGetDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp)
{
    PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp);
    PUSBSUP_GETDEV pDev  = (PUSBSUP_GETDEV)pIrp->AssociatedIrp.SystemBuffer;
    NTSTATUS Status = STATUS_SUCCESS;
    ULONG cbOut = 0;

    /* don't check for owner since this request is allowed for non-owners as well */

    if (pDev && pSl->Parameters.DeviceIoControl.InputBufferLength == sizeof (*pDev)
             && pSl->Parameters.DeviceIoControl.OutputBufferLength == sizeof (*pDev))
    {
        Status = VBoxUsbToolGetDeviceSpeed(pDevExt->pLowerDO, &pDevExt->Rt.fIsHighSpeed);
        if (NT_SUCCESS(Status))
        {
            pDev->hDevice = pDevExt->Rt.hMonDev;
            pDev->fAttached = true;
            pDev->fHiSpeed = pDevExt->Rt.fIsHighSpeed;
            cbOut = sizeof (*pDev);
        }
    }
    else
    {
        Status = STATUS_INVALID_PARAMETER;
    }

    Assert(Status != STATUS_PENDING);
    VBoxDrvToolIoComplete(pIrp, Status, cbOut);
    vboxUsbDdiStateRelease(pDevExt);
    return Status;
}

static NTSTATUS vboxUsbRtDispatchUsbReset(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp)
{
    PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp);
    PFILE_OBJECT pFObj = pSl->FileObject;
    PUSBSUP_GETDEV pDev  = (PUSBSUP_GETDEV)pIrp->AssociatedIrp.SystemBuffer;
    NTSTATUS Status = STATUS_SUCCESS;

    do
    {
        if (!pFObj)
        {
            AssertFailed();
            Status = STATUS_INVALID_PARAMETER;
            break;
        }

        if (!vboxUsbRtCtxIsOwner(pDevExt, pFObj))
        {
            AssertFailed();
            Status = STATUS_ACCESS_DENIED;
            break;
        }

        if (pIrp->AssociatedIrp.SystemBuffer
                || pSl->Parameters.DeviceIoControl.InputBufferLength
                || pSl->Parameters.DeviceIoControl.OutputBufferLength)
        {
            AssertFailed();
            Status = STATUS_INVALID_PARAMETER;
            break;
        }

        Status = VBoxUsbToolIoInternalCtlSendSync(pDevExt->pLowerDO, IOCTL_INTERNAL_USB_RESET_PORT, NULL, NULL);
        Assert(NT_SUCCESS(Status));
    } while (0);

    Assert(Status != STATUS_PENDING);
    VBoxDrvToolIoComplete(pIrp, Status, 0);
    vboxUsbDdiStateRelease(pDevExt);
    return Status;
}

static PUSB_CONFIGURATION_DESCRIPTOR vboxUsbRtFindConfigDesc(PVBOXUSBDEV_EXT pDevExt, uint8_t uConfiguration)
{
    PUSB_CONFIGURATION_DESCRIPTOR pCfgDr = NULL;

    for (ULONG i = 0; i < VBOXUSBRT_MAX_CFGS; ++i)
    {
        if (pDevExt->Rt.cfgdescr[i])
        {
            if (pDevExt->Rt.cfgdescr[i]->bConfigurationValue == uConfiguration)
            {
                pCfgDr = pDevExt->Rt.cfgdescr[i];
                break;
            }
        }
    }

    return pCfgDr;
}

static NTSTATUS vboxUsbRtSetConfig(PVBOXUSBDEV_EXT pDevExt, uint8_t uConfiguration)
{
    PURB pUrb = NULL;
    NTSTATUS Status = STATUS_SUCCESS;
    uint32_t i;

    if (!uConfiguration)
    {
        pUrb = VBoxUsbToolUrbAllocZ(URB_FUNCTION_SELECT_CONFIGURATION, sizeof (struct _URB_SELECT_CONFIGURATION));
        if(!pUrb)
        {
            AssertMsgFailed((__FUNCTION__": VBoxUsbToolUrbAlloc failed\n"));
            return STATUS_INSUFFICIENT_RESOURCES;
        }

        vboxUsbRtFreeInterfaces(pDevExt, TRUE);

        pUrb->UrbSelectConfiguration.ConfigurationDescriptor = NULL;

        Status = VBoxUsbToolUrbPost(pDevExt->pLowerDO, pUrb, RT_INDEFINITE_WAIT);
        if(NT_SUCCESS(Status) && USBD_SUCCESS(pUrb->UrbHeader.Status))
        {
            pDevExt->Rt.hConfiguration = pUrb->UrbSelectConfiguration.ConfigurationHandle;
            pDevExt->Rt.uConfigValue = uConfiguration;
        }
        else
        {
            AssertMsgFailed((__FUNCTION__": VBoxUsbToolUrbPost failed Status (0x%x), usb Status (0x%x)\n", Status, pUrb->UrbHeader.Status));
        }

        VBoxUsbToolUrbFree(pUrb);

        return Status;
    }

    PUSB_CONFIGURATION_DESCRIPTOR pCfgDr = vboxUsbRtFindConfigDesc(pDevExt, uConfiguration);
    if (!pCfgDr)
    {
        AssertMsgFailed((__FUNCTION__": VBoxUSBFindConfigDesc did not find cfg (%d)\n", uConfiguration));
        return STATUS_INVALID_PARAMETER;
    }

    PUSBD_INTERFACE_LIST_ENTRY pIfLe = (PUSBD_INTERFACE_LIST_ENTRY)vboxUsbMemAllocZ((pCfgDr->bNumInterfaces + 1) * sizeof(USBD_INTERFACE_LIST_ENTRY));
    if (!pIfLe)
    {
        AssertMsgFailed((__FUNCTION__": vboxUsbMemAllocZ for pIfLe failed\n"));
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    for (i = 0; i < pCfgDr->bNumInterfaces; i++)
    {
        pIfLe[i].InterfaceDescriptor = USBD_ParseConfigurationDescriptorEx(pCfgDr, pCfgDr, i, 0, -1, -1, -1);
        if (!pIfLe[i].InterfaceDescriptor)
        {
            AssertMsgFailed((__FUNCTION__": interface %d not found\n", i));
            Status = STATUS_INVALID_PARAMETER;
            break;
        }
    }

    if (NT_SUCCESS(Status))
    {
        pUrb = USBD_CreateConfigurationRequestEx(pCfgDr, pIfLe);
        if (pUrb)
        {
            Status = VBoxUsbToolUrbPost(pDevExt->pLowerDO, pUrb, RT_INDEFINITE_WAIT);
            if (NT_SUCCESS(Status) && USBD_SUCCESS(pUrb->UrbHeader.Status))
            {
                vboxUsbRtFreeInterfaces(pDevExt, FALSE);

                pDevExt->Rt.hConfiguration = pUrb->UrbSelectConfiguration.ConfigurationHandle;
                pDevExt->Rt.uConfigValue = uConfiguration;
                pDevExt->Rt.uNumInterfaces = pCfgDr->bNumInterfaces;

                pDevExt->Rt.pVBIfaceInfo = (VBOXUSB_IFACE_INFO*)vboxUsbMemAllocZ(pDevExt->Rt.uNumInterfaces * sizeof (VBOXUSB_IFACE_INFO));
                if (pDevExt->Rt.pVBIfaceInfo)
                {
                    Assert(NT_SUCCESS(Status));
                    for (i = 0; i < pDevExt->Rt.uNumInterfaces; i++)
                    {
                        uint32_t uTotalIfaceInfoLength = sizeof (struct _URB_SELECT_INTERFACE) + ((pIfLe[i].Interface->NumberOfPipes > 0) ? (pIfLe[i].Interface->NumberOfPipes - 1) : 0) * sizeof(USBD_PIPE_INFORMATION);
                        pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo = (PUSBD_INTERFACE_INFORMATION)vboxUsbMemAlloc(uTotalIfaceInfoLength);
                        if (!pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo)
                        {
                            AssertMsgFailed((__FUNCTION__": vboxUsbMemAlloc failed\n"));
                            Status = STATUS_INSUFFICIENT_RESOURCES;
                            break;
                        }

                        if (pIfLe[i].Interface->NumberOfPipes > 0)
                        {
                            pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo = (VBOXUSB_PIPE_INFO *)vboxUsbMemAlloc(pIfLe[i].Interface->NumberOfPipes * sizeof(VBOXUSB_PIPE_INFO));
                            if (!pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo)
                            {
                                AssertMsgFailed((__FUNCTION__": vboxUsbMemAlloc failed\n"));
                                Status = STATUS_NO_MEMORY;
                                break;
                            }
                        }
                        else
                        {
                            pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo = NULL;
                        }

                        *pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo = *pIfLe[i].Interface;

                        for (ULONG j = 0; j < pIfLe[i].Interface->NumberOfPipes; j++)
                        {
                            pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->Pipes[j] = pIfLe[i].Interface->Pipes[j];
                            pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo[j].EndpointAddress = pIfLe[i].Interface->Pipes[j].EndpointAddress;
                            pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo[j].NextScheduledFrame = 0;
                        }
                    }

//                    if (NT_SUCCESS(Status))
//                    {
//
//                    }
                }
                else
                {
                    AssertMsgFailed((__FUNCTION__": vboxUsbMemAllocZ failed\n"));
                    Status = STATUS_NO_MEMORY;
                }
            }
            else
            {
                AssertMsgFailed((__FUNCTION__": VBoxUsbToolUrbPost failed Status (0x%x), usb Status (0x%x)\n", Status, pUrb->UrbHeader.Status));
            }
            ExFreePool(pUrb);
        }
        else
        {
            AssertMsgFailed((__FUNCTION__": USBD_CreateConfigurationRequestEx failed\n"));
            Status = STATUS_INSUFFICIENT_RESOURCES;
        }
    }

    vboxUsbMemFree(pIfLe);

    return Status;
}

static NTSTATUS vboxUsbRtDispatchUsbSetConfig(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp)
{
    PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp);
    PFILE_OBJECT pFObj = pSl->FileObject;
    PUSBSUP_SET_CONFIG pCfg  = (PUSBSUP_SET_CONFIG)pIrp->AssociatedIrp.SystemBuffer;
    NTSTATUS Status = STATUS_SUCCESS;

    do
    {
        if (!pFObj)
        {
            AssertFailed();
            Status = STATUS_INVALID_PARAMETER;
            break;
        }

        if (!vboxUsbRtCtxIsOwner(pDevExt, pFObj))
        {
            AssertFailed();
            Status = STATUS_ACCESS_DENIED;
            break;
        }

        if (      !pCfg
                || pSl->Parameters.DeviceIoControl.InputBufferLength != sizeof (*pCfg)
                || pSl->Parameters.DeviceIoControl.OutputBufferLength != 0)
        {
            AssertMsgFailed((__FUNCTION__": STATUS_INVALID_PARAMETER\n"));
            Status = STATUS_INVALID_PARAMETER;
            break;
        }

        Status = vboxUsbRtSetConfig(pDevExt, pCfg->bConfigurationValue);
    } while (0);

    Assert(Status != STATUS_PENDING);
    VBoxDrvToolIoComplete(pIrp, Status, 0);
    vboxUsbDdiStateRelease(pDevExt);
    return Status;
}

static NTSTATUS vboxUsbRtSetInterface(PVBOXUSBDEV_EXT pDevExt, uint32_t InterfaceNumber, int AlternateSetting)
{
    if (!pDevExt->Rt.uConfigValue)
    {
        AssertMsgFailed((__FUNCTION__": Can't select an interface without an active configuration\n"));
        return STATUS_INVALID_PARAMETER;
    }

    if (InterfaceNumber >= pDevExt->Rt.uNumInterfaces)
    {
        AssertMsgFailed((__FUNCTION__": InterfaceNumber %d too high!!\n", InterfaceNumber));
        return STATUS_INVALID_PARAMETER;
    }

    PUSB_CONFIGURATION_DESCRIPTOR pCfgDr = vboxUsbRtFindConfigDesc(pDevExt, pDevExt->Rt.uConfigValue);
    if (!pCfgDr)
    {
        AssertMsgFailed((__FUNCTION__": configuration %d not found!!\n", pDevExt->Rt.uConfigValue));
        return STATUS_INVALID_PARAMETER;
    }

    PUSB_INTERFACE_DESCRIPTOR pIfDr = USBD_ParseConfigurationDescriptorEx(pCfgDr, pCfgDr, InterfaceNumber, AlternateSetting, -1, -1, -1);
    if (!pIfDr)
    {
        AssertMsgFailed((__FUNCTION__": invalid interface %d or alternate setting %d\n", InterfaceNumber, AlternateSetting));
        return STATUS_UNSUCCESSFUL;
    }

    USHORT uUrbSize = GET_SELECT_INTERFACE_REQUEST_SIZE(pIfDr->bNumEndpoints);
    ULONG uTotalIfaceInfoLength = GET_USBD_INTERFACE_SIZE(pIfDr->bNumEndpoints);
    NTSTATUS Status = STATUS_SUCCESS;
    PURB pUrb = VBoxUsbToolUrbAllocZ(0, uUrbSize);
    if (!pUrb)
    {
        AssertMsgFailed((__FUNCTION__": VBoxUsbToolUrbAlloc failed\n"));
        return STATUS_NO_MEMORY;
    }

    /*
     * Free old interface and pipe info, allocate new again
     */
    if (pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo)
    {
        /* Clear pipes associated with the interface, else Windows may hang. */
        for(ULONG i = 0; i < pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo->NumberOfPipes; i++)
        {
            VBoxUsbToolPipeClear(pDevExt->pLowerDO, pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo->Pipes[i].PipeHandle, FALSE);
        }
        vboxUsbMemFree(pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo);
    }

    if (pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pPipeInfo)
    {
        vboxUsbMemFree(pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pPipeInfo);
    }

    pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo = (PUSBD_INTERFACE_INFORMATION)vboxUsbMemAlloc(uTotalIfaceInfoLength);
    if (pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo)
    {
        if (pIfDr->bNumEndpoints > 0)
        {
            pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pPipeInfo = (VBOXUSB_PIPE_INFO*)vboxUsbMemAlloc(pIfDr->bNumEndpoints * sizeof(VBOXUSB_PIPE_INFO));
            if (!pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pPipeInfo)
            {
                AssertMsgFailed(("VBoxUSBSetInterface: ExAllocatePool failed!\n"));
                Status = STATUS_NO_MEMORY;
            }
        }
        else
        {
            pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pPipeInfo = NULL;
        }

        if (NT_SUCCESS(Status))
        {
            UsbBuildSelectInterfaceRequest(pUrb, uUrbSize, pDevExt->Rt.hConfiguration, InterfaceNumber, AlternateSetting);
            pUrb->UrbSelectInterface.Interface.Length = GET_USBD_INTERFACE_SIZE(pIfDr->bNumEndpoints);

            Status = VBoxUsbToolUrbPost(pDevExt->pLowerDO, pUrb, RT_INDEFINITE_WAIT);
            if (NT_SUCCESS(Status) && USBD_SUCCESS(pUrb->UrbHeader.Status))
            {
                USBD_INTERFACE_INFORMATION *pIfInfo = &pUrb->UrbSelectInterface.Interface;
                memcpy(pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo, pIfInfo, GET_USBD_INTERFACE_SIZE(pIfDr->bNumEndpoints));

                Assert(pIfInfo->NumberOfPipes == pIfDr->bNumEndpoints);
                for(ULONG i = 0; i < pIfInfo->NumberOfPipes; i++)
                {
                    pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo->Pipes[i] = pIfInfo->Pipes[i];
                    pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pPipeInfo[i].EndpointAddress = pIfInfo->Pipes[i].EndpointAddress;
                    pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pPipeInfo[i].NextScheduledFrame = 0;
                }
            }
            else
            {
                AssertMsgFailed((__FUNCTION__": VBoxUsbToolUrbPost failed Status (0x%x) usb Status (0x%x)\n", Status, pUrb->UrbHeader.Status));
            }
        }

    }
    else
    {
        AssertMsgFailed(("VBoxUSBSetInterface: ExAllocatePool failed!\n"));
        Status = STATUS_NO_MEMORY;
    }

    VBoxUsbToolUrbFree(pUrb);

    return Status;
}

static NTSTATUS vboxUsbRtDispatchUsbSelectInterface(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp)
{
    PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp);
    PFILE_OBJECT pFObj = pSl->FileObject;
    PUSBSUP_SELECT_INTERFACE pIf = (PUSBSUP_SELECT_INTERFACE)pIrp->AssociatedIrp.SystemBuffer;
    NTSTATUS Status;

    do
    {
        if (!pFObj)
        {
            AssertFailed();
            Status = STATUS_INVALID_PARAMETER;
            break;
        }

        if (!vboxUsbRtCtxIsOwner(pDevExt, pFObj))
        {
            AssertFailed();
            Status = STATUS_ACCESS_DENIED;
            break;
        }

        if (  !pIf
            || pSl->Parameters.DeviceIoControl.InputBufferLength != sizeof (*pIf)
            || pSl->Parameters.DeviceIoControl.OutputBufferLength != 0)
        {
            AssertMsgFailed((__FUNCTION__": STATUS_INVALID_PARAMETER\n"));
            Status = STATUS_INVALID_PARAMETER;
            break;
        }

        Status = vboxUsbRtSetInterface(pDevExt, pIf->bInterfaceNumber, pIf->bAlternateSetting);
    } while (0);

    Assert(Status != STATUS_PENDING);
    VBoxDrvToolIoComplete(pIrp, Status, 0);
    vboxUsbDdiStateRelease(pDevExt);
    return Status;
}

static HANDLE vboxUsbRtGetPipeHandle(PVBOXUSBDEV_EXT pDevExt, uint32_t EndPointAddress)
{
    for (ULONG i = 0; i < pDevExt->Rt.uNumInterfaces; i++)
    {
        for (ULONG j = 0; j < pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->NumberOfPipes; j++)
        {
            /* Note that bit 7 determines pipe direction, but is still significant
             * because endpoints may be numbered like 0x01, 0x81, 0x02, 0x82 etc.
             */
            if (pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->Pipes[j].EndpointAddress == EndPointAddress)
                return pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->Pipes[j].PipeHandle;
        }
    }
    return 0;
}

static VBOXUSB_PIPE_INFO* vboxUsbRtGetPipeInfo(PVBOXUSBDEV_EXT pDevExt, uint32_t EndPointAddress)
{
    for (ULONG i = 0; i < pDevExt->Rt.uNumInterfaces; i++)
    {
        for (ULONG j = 0; j < pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->NumberOfPipes; j++)
        {
            if (pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo[j].EndpointAddress == EndPointAddress)
                return &pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo[j];
        }
    }
    return NULL;
}



static NTSTATUS vboxUsbRtClearEndpoint(PVBOXUSBDEV_EXT pDevExt, uint32_t EndPointAddress, bool fReset)
{
    NTSTATUS Status = VBoxUsbToolPipeClear(pDevExt->pLowerDO, vboxUsbRtGetPipeHandle(pDevExt, EndPointAddress), fReset);
    if (!NT_SUCCESS(Status))
    {
        AssertMsgFailed((__FUNCTION__": VBoxUsbToolPipeClear failed Status (0x%x)\n", Status));
    }

    return Status;
}

static NTSTATUS vboxUsbRtDispatchUsbClearEndpoint(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp)
{
    PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp);
    PFILE_OBJECT pFObj = pSl->FileObject;
    PUSBSUP_CLEAR_ENDPOINT pCe = (PUSBSUP_CLEAR_ENDPOINT)pIrp->AssociatedIrp.SystemBuffer;
    NTSTATUS Status;

    do
    {
        if (!pFObj)
        {
            AssertFailed();
            Status = STATUS_INVALID_PARAMETER;
            break;
        }

        if (!vboxUsbRtCtxIsOwner(pDevExt, pFObj))
        {
            AssertFailed();
            Status = STATUS_ACCESS_DENIED;
            break;
        }

        if (   !pCe
             || pSl->Parameters.DeviceIoControl.InputBufferLength != sizeof (*pCe)
             || pSl->Parameters.DeviceIoControl.OutputBufferLength != 0)
        {
            AssertMsgFailed((__FUNCTION__": STATUS_INVALID_PARAMETER\n"));
            Status = STATUS_INVALID_PARAMETER;
            break;
        }

        Status = vboxUsbRtClearEndpoint(pDevExt, pCe->bEndpoint, TRUE);
    } while (0);

    Assert(Status != STATUS_PENDING);
    VBoxDrvToolIoComplete(pIrp, Status, 0);
    vboxUsbDdiStateRelease(pDevExt);
    return Status;
}

static NTSTATUS vboxUsbRtDispatchUsbAbortEndpoint(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp)
{
    PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp);
    PFILE_OBJECT pFObj = pSl->FileObject;
    PUSBSUP_CLEAR_ENDPOINT pCe = (PUSBSUP_CLEAR_ENDPOINT)pIrp->AssociatedIrp.SystemBuffer;
    NTSTATUS Status;

    do
    {
        if (!pFObj)
        {
            AssertFailed();
            Status = STATUS_INVALID_PARAMETER;
            break;
        }

        if (!vboxUsbRtCtxIsOwner(pDevExt, pFObj))
        {
            AssertFailed();
            Status = STATUS_ACCESS_DENIED;
            break;
        }

        if (  !pCe
            || pSl->Parameters.DeviceIoControl.InputBufferLength != sizeof (*pCe)
            || pSl->Parameters.DeviceIoControl.OutputBufferLength != 0)
        {
            AssertMsgFailed((__FUNCTION__": STATUS_INVALID_PARAMETER\n"));
            Status = STATUS_INVALID_PARAMETER;
            break;
        }

        Status = vboxUsbRtClearEndpoint(pDevExt, pCe->bEndpoint, FALSE);
    } while (0);

    Assert(Status != STATUS_PENDING);
    VBoxDrvToolIoComplete(pIrp, Status, 0);
    vboxUsbDdiStateRelease(pDevExt);
    return Status;
}

static NTSTATUS vboxUsbRtUrbSendCompletion(PDEVICE_OBJECT pDevObj, IRP *pIrp, void *pvContext)
{
    if (!pvContext)
    {
        AssertMsgFailed((__FUNCTION__":  context is NULL\n"));
        pIrp->IoStatus.Information = 0;
        return STATUS_CONTINUE_COMPLETION;
    }

    PVBOXUSB_URB_CONTEXT pContext = (PVBOXUSB_URB_CONTEXT)pvContext;

    if (pContext->ulMagic != VBOXUSB_MAGIC)
    {
        AssertMsgFailed((__FUNCTION__": Invalid context magic\n"));
        pIrp->IoStatus.Information = 0;
        return STATUS_CONTINUE_COMPLETION;
    }

    PURB pUrb = pContext->pUrb;
    PMDL pMdlBuf = pContext->pMdlBuf;
    PUSBSUP_URB pUrbInfo = (PUSBSUP_URB)pContext->pOut;
    PVBOXUSBDEV_EXT pDevExt = pContext->pDevExt;

    if (!pUrb || !pMdlBuf || !pUrbInfo | !pDevExt)
    {
        AssertMsgFailed((__FUNCTION__": Invalid args\n"));
        if (pDevExt)
            vboxUsbDdiStateRelease(pDevExt);
        pIrp->IoStatus.Information = 0;
        return STATUS_CONTINUE_COMPLETION;
    }

    NTSTATUS Status = pIrp->IoStatus.Status;
    if (Status == STATUS_SUCCESS)
    {
        switch(pUrb->UrbHeader.Status)
        {
            case USBD_STATUS_CRC:
                pUrbInfo->error = USBSUP_XFER_CRC;
                break;
            case USBD_STATUS_SUCCESS:
                pUrbInfo->error = USBSUP_XFER_OK;
                break;
            case USBD_STATUS_STALL_PID:
                pUrbInfo->error = USBSUP_XFER_STALL;
                break;
            case USBD_STATUS_INVALID_URB_FUNCTION:
            case USBD_STATUS_INVALID_PARAMETER:
                AssertMsgFailed((__FUNCTION__": sw error, urb Status (0x%x)\n", pUrb->UrbHeader.Status));
            case USBD_STATUS_DEV_NOT_RESPONDING:
            default:
                pUrbInfo->error = USBSUP_XFER_DNR;
            break;
        }

        switch(pContext->ulTransferType)
        {
            case USBSUP_TRANSFER_TYPE_CTRL:
            case USBSUP_TRANSFER_TYPE_MSG:
                pUrbInfo->len = pUrb->UrbControlTransfer.TransferBufferLength;
                if (pContext->ulTransferType == USBSUP_TRANSFER_TYPE_MSG)
                {
                    /* QUSB_TRANSFER_TYPE_MSG is a control transfer, but it is special
                     * the first 8 bytes of the buffer is the setup packet so the real
                     * data length is therefore urb->len - 8
                     */
                    pUrbInfo->len += sizeof (pUrb->UrbControlTransfer.SetupPacket);
                }
                break;
            case USBSUP_TRANSFER_TYPE_ISOC:
                pUrbInfo->len = pUrb->UrbIsochronousTransfer.TransferBufferLength;
                break;
            case USBSUP_TRANSFER_TYPE_BULK:
            case USBSUP_TRANSFER_TYPE_INTR:
                if (pUrbInfo->dir == USBSUP_DIRECTION_IN && pUrbInfo->error == USBSUP_XFER_OK
                        && !(pUrbInfo->flags & USBSUP_FLAG_SHORT_OK)
                        && pUrbInfo->len > pUrb->UrbBulkOrInterruptTransfer.TransferBufferLength
                        )
                {
                    /* If we don't use the USBD_SHORT_TRANSFER_OK flag, the returned buffer lengths are
                     * wrong for short transfers (always a multiple of max packet size?). So we just figure
                     * out if this was a data underrun on our own.
                     */
                    pUrbInfo->error = USBSUP_XFER_UNDERRUN;
                }
                pUrbInfo->len = pUrb->UrbBulkOrInterruptTransfer.TransferBufferLength;
                break;
            default:
                break;
        }
    }
    else
    {
        pUrbInfo->len = 0;

        Log((__FUNCTION__": URB failed Status (0x%x) urb Status (0x%x)\n", Status, pUrb->UrbHeader.Status));
#ifdef DEBUG
        switch(pContext->ulTransferType)
        {
            case USBSUP_TRANSFER_TYPE_CTRL:
            case USBSUP_TRANSFER_TYPE_MSG:
                LogRel(("Ctrl/Msg length=%d\n", pUrb->UrbControlTransfer.TransferBufferLength));
                break;
            case USBSUP_TRANSFER_TYPE_ISOC:
                LogRel(("ISOC length=%d\n", pUrb->UrbIsochronousTransfer.TransferBufferLength));
                break;
            case USBSUP_TRANSFER_TYPE_BULK:
            case USBSUP_TRANSFER_TYPE_INTR:
                LogRel(("BULK/INTR length=%d\n", pUrb->UrbBulkOrInterruptTransfer.TransferBufferLength));
                break;
        }
#endif
        switch(pUrb->UrbHeader.Status)
        {
            case USBD_STATUS_CRC:
                pUrbInfo->error = USBSUP_XFER_CRC;
                Status = STATUS_SUCCESS;
                break;
            case USBD_STATUS_STALL_PID:
                pUrbInfo->error = USBSUP_XFER_STALL;
                Status = STATUS_SUCCESS;
                break;
            case USBD_STATUS_DEV_NOT_RESPONDING:
                pUrbInfo->error = USBSUP_XFER_DNR;
                Status = STATUS_SUCCESS;
                break;
            case ((USBD_STATUS)0xC0010000L): // USBD_STATUS_CANCELED - too bad usbdi.h and usb.h aren't consistent!
                // TODO: What the heck are we really supposed to do here?
                pUrbInfo->error = USBSUP_XFER_STALL;
                Status = STATUS_SUCCESS;
                break;
            case USBD_STATUS_BAD_START_FRAME:   // This one really shouldn't happen
            case USBD_STATUS_ISOCH_REQUEST_FAILED:
                pUrbInfo->error = USBSUP_XFER_NAC;
                Status = STATUS_SUCCESS;
                break;
            default:
                AssertMsgFailed((__FUNCTION__": err Status (0x%x) (0x%x)\n", Status, pUrb->UrbHeader.Status));
                pUrbInfo->error = USBSUP_XFER_DNR;
                Status = STATUS_SUCCESS;
                break;
        }
    }
    // For isochronous transfers, always update the individual packets
    if (pContext->ulTransferType == USBSUP_TRANSFER_TYPE_ISOC)
    {
        Assert(pUrbInfo->numIsoPkts == pUrb->UrbIsochronousTransfer.NumberOfPackets);
        for (ULONG i = 0; i < pUrbInfo->numIsoPkts; ++i)
        {
            Assert(pUrbInfo->aIsoPkts[i].off == pUrb->UrbIsochronousTransfer.IsoPacket[i].Offset);
            pUrbInfo->aIsoPkts[i].cb = (uint16_t)pUrb->UrbIsochronousTransfer.IsoPacket[i].Length;
            switch (pUrb->UrbIsochronousTransfer.IsoPacket[i].Status)
            {
                case USBD_STATUS_SUCCESS:
                    pUrbInfo->aIsoPkts[i].stat = USBSUP_XFER_OK;
                    break;
                case USBD_STATUS_NOT_ACCESSED:
                    pUrbInfo->aIsoPkts[i].stat = USBSUP_XFER_NAC;
                    break;
                default:
                    pUrbInfo->aIsoPkts[i].stat = USBSUP_XFER_STALL;
                    break;
            }
        }
    }

    MmUnlockPages(pMdlBuf);
    IoFreeMdl(pMdlBuf);

    vboxUsbMemFree(pContext);

    vboxUsbDdiStateRelease(pDevExt);

    Assert(pIrp->IoStatus.Status != STATUS_IO_TIMEOUT);
    pIrp->IoStatus.Information = sizeof(*pUrbInfo);
    pIrp->IoStatus.Status = Status;
    return STATUS_CONTINUE_COMPLETION;
}

static NTSTATUS vboxUsbRtUrbSend(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp, PUSBSUP_URB pUrbInfo)
{
    NTSTATUS Status = STATUS_SUCCESS;
    PVBOXUSB_URB_CONTEXT pContext = NULL;
    PMDL pMdlBuf = NULL;
    ULONG cbUrb;

    Assert(pUrbInfo);
    if (pUrbInfo->type == USBSUP_TRANSFER_TYPE_ISOC)
    {
        Assert(pUrbInfo->numIsoPkts <= 8);
        cbUrb = GET_ISO_URB_SIZE(pUrbInfo->numIsoPkts);
    }
    else
        cbUrb = sizeof (URB);

    do
    {
        pContext = (PVBOXUSB_URB_CONTEXT)vboxUsbMemAllocZ(cbUrb + sizeof (VBOXUSB_URB_CONTEXT));
        if (!pContext)
        {
            AssertMsgFailed((__FUNCTION__": vboxUsbMemAlloc failed\n"));
            Status = STATUS_INSUFFICIENT_RESOURCES;
            break;
        }

        PURB pUrb = (PURB)(pContext + 1);
        HANDLE hPipe = NULL;
        if (pUrbInfo->ep)
        {
            hPipe = vboxUsbRtGetPipeHandle(pDevExt, pUrbInfo->ep | ((pUrbInfo->dir == USBSUP_DIRECTION_IN) ? 0x80 : 0x00));
            if (!hPipe)
            {
                AssertMsgFailed((__FUNCTION__": vboxUsbRtGetPipeHandle failed for endpoint (0x%x)\n", pUrbInfo->ep));
                Status = STATUS_INVALID_PARAMETER;
                break;
            }
        }

        pMdlBuf = IoAllocateMdl(pUrbInfo->buf, (ULONG)pUrbInfo->len, FALSE, FALSE, NULL);
        if (!pMdlBuf)
        {
            AssertMsgFailed((__FUNCTION__": IoAllocateMdl failed for buffer (0x%p) length (%d)\n", pUrbInfo->buf, pUrbInfo->len));
            Status = STATUS_INSUFFICIENT_RESOURCES;
            break;
        }

        __try
        {
            MmProbeAndLockPages(pMdlBuf, KernelMode, IoModifyAccess);
        }
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
            Status = GetExceptionCode();
            IoFreeMdl(pMdlBuf);
            pMdlBuf = NULL;
            AssertMsgFailed((__FUNCTION__": Exception Code (0x%x)\n", Status));
            break;
        }

        /* For some reason, passing a MDL in the URB does not work reliably. Notably
         * the iPhone when used with iTunes fails.
         */
        PVOID pBuffer = MmGetSystemAddressForMdlSafe(pMdlBuf, NormalPagePriority);
        if (!pBuffer)
        {
            AssertMsgFailed((__FUNCTION__": MmGetSystemAddressForMdlSafe failed\n"));
            Status = STATUS_INSUFFICIENT_RESOURCES;
            break;
        }

        switch (pUrbInfo->type)
        {
            case USBSUP_TRANSFER_TYPE_CTRL:
            case USBSUP_TRANSFER_TYPE_MSG:
            {
                pUrb->UrbHeader.Function = URB_FUNCTION_CONTROL_TRANSFER;
                pUrb->UrbHeader.Length = sizeof (struct _URB_CONTROL_TRANSFER);
                pUrb->UrbControlTransfer.PipeHandle = hPipe;
                pUrb->UrbControlTransfer.TransferBufferLength = (ULONG)pUrbInfo->len;
                pUrb->UrbControlTransfer.TransferFlags = ((pUrbInfo->dir == USBSUP_DIRECTION_IN) ? USBD_TRANSFER_DIRECTION_IN : USBD_TRANSFER_DIRECTION_OUT);
                pUrb->UrbControlTransfer.UrbLink = 0;

                if (!hPipe)
                    pUrb->UrbControlTransfer.TransferFlags |= USBD_DEFAULT_PIPE_TRANSFER;

                if (pUrbInfo->type == USBSUP_TRANSFER_TYPE_MSG)
                {
                   /* QUSB_TRANSFER_TYPE_MSG is a control transfer, but it is special
                    * the first 8 bytes of the buffer is the setup packet so the real
                    * data length is therefore pUrb->len - 8
                    */
                    PVBOXUSB_SETUP pSetup = (PVBOXUSB_SETUP)pUrb->UrbControlTransfer.SetupPacket;
                    memcpy(pUrb->UrbControlTransfer.SetupPacket, pBuffer, min(sizeof (pUrb->UrbControlTransfer.SetupPacket), pUrbInfo->len));

                    if (pUrb->UrbControlTransfer.TransferBufferLength <= sizeof (pUrb->UrbControlTransfer.SetupPacket))
                        pUrb->UrbControlTransfer.TransferBufferLength = 0;
                    else
                        pUrb->UrbControlTransfer.TransferBufferLength -= sizeof (pUrb->UrbControlTransfer.SetupPacket);

                    pUrb->UrbControlTransfer.TransferBuffer = (uint8_t *)pBuffer + sizeof(pUrb->UrbControlTransfer.SetupPacket);
                    pUrb->UrbControlTransfer.TransferBufferMDL = 0;
                    pUrb->UrbControlTransfer.TransferFlags |= USBD_SHORT_TRANSFER_OK;
                }
                else
                {
                    pUrb->UrbControlTransfer.TransferBuffer = 0;
                    pUrb->UrbControlTransfer.TransferBufferMDL = pMdlBuf;
                }
                break;
            }
            case USBSUP_TRANSFER_TYPE_ISOC:
            {
                Assert(pUrbInfo->dir == USBSUP_DIRECTION_IN || pUrbInfo->type == USBSUP_TRANSFER_TYPE_BULK);
                Assert(hPipe);
                VBOXUSB_PIPE_INFO *pPipeInfo = vboxUsbRtGetPipeInfo(pDevExt, pUrbInfo->ep | ((pUrbInfo->dir == USBSUP_DIRECTION_IN) ? 0x80 : 0x00));
                if (pPipeInfo == NULL)
                {
                    /* Can happen if the isoc request comes in too early or late. */
                    AssertMsgFailed((__FUNCTION__": pPipeInfo not found\n"));
                    Status = STATUS_INVALID_PARAMETER;
                    break;
                }

                pUrb->UrbHeader.Function = URB_FUNCTION_ISOCH_TRANSFER;
                pUrb->UrbHeader.Length = (USHORT)cbUrb;
                pUrb->UrbIsochronousTransfer.PipeHandle = hPipe;
                pUrb->UrbIsochronousTransfer.TransferBufferLength = (ULONG)pUrbInfo->len;
                pUrb->UrbIsochronousTransfer.TransferBufferMDL = 0;
                pUrb->UrbIsochronousTransfer.TransferBuffer = pBuffer;
                pUrb->UrbIsochronousTransfer.TransferFlags = ((pUrbInfo->dir == USBSUP_DIRECTION_IN) ? USBD_TRANSFER_DIRECTION_IN : USBD_TRANSFER_DIRECTION_OUT);
                pUrb->UrbIsochronousTransfer.TransferFlags |= USBD_SHORT_TRANSFER_OK;  // May be implied already
                pUrb->UrbIsochronousTransfer.NumberOfPackets = pUrbInfo->numIsoPkts;
                pUrb->UrbIsochronousTransfer.ErrorCount = 0;
                pUrb->UrbIsochronousTransfer.UrbLink = 0;

                Assert(pUrbInfo->numIsoPkts == pUrb->UrbIsochronousTransfer.NumberOfPackets);
                for (ULONG i = 0; i < pUrbInfo->numIsoPkts; ++i)
                {
                    pUrb->UrbIsochronousTransfer.IsoPacket[i].Offset = pUrbInfo->aIsoPkts[i].off;
                    pUrb->UrbIsochronousTransfer.IsoPacket[i].Length = pUrbInfo->aIsoPkts[i].cb;
                }

                /* We have to schedule the URBs ourselves. There is an ASAP flag but
                 * that can only be reliably used after pipe creation/reset, ie. it's
                 * almost completely useless.
                 */
                ULONG iFrame, iStartFrame;
                VBoxUsbToolCurrentFrame(pDevExt->pLowerDO, pIrp, &iFrame);
                iFrame += 2;
                iStartFrame = pPipeInfo->NextScheduledFrame;
                if ((iFrame < iStartFrame) || (iStartFrame > iFrame + 512))
                    iFrame = iStartFrame;
                pPipeInfo->NextScheduledFrame = iFrame + pUrbInfo->numIsoPkts;
                pUrb->UrbIsochronousTransfer.StartFrame = iFrame;
                break;
            }
            case USBSUP_TRANSFER_TYPE_BULK:
            case USBSUP_TRANSFER_TYPE_INTR:
            {
                Assert(pUrbInfo->dir != USBSUP_DIRECTION_SETUP);
                Assert(pUrbInfo->dir == USBSUP_DIRECTION_IN || pUrbInfo->type == USBSUP_TRANSFER_TYPE_BULK);
                Assert(hPipe);

                pUrb->UrbHeader.Function = URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER;
                pUrb->UrbHeader.Length = sizeof (struct _URB_BULK_OR_INTERRUPT_TRANSFER);
                pUrb->UrbBulkOrInterruptTransfer.PipeHandle = hPipe;
                pUrb->UrbBulkOrInterruptTransfer.TransferBufferLength = (ULONG)pUrbInfo->len;
                pUrb->UrbBulkOrInterruptTransfer.TransferBufferMDL = 0;
                pUrb->UrbBulkOrInterruptTransfer.TransferBuffer = pBuffer;
                pUrb->UrbBulkOrInterruptTransfer.TransferFlags = ((pUrbInfo->dir == USBSUP_DIRECTION_IN) ? USBD_TRANSFER_DIRECTION_IN : USBD_TRANSFER_DIRECTION_OUT);

                if (pUrb->UrbBulkOrInterruptTransfer.TransferFlags & USBD_TRANSFER_DIRECTION_IN)
                    pUrb->UrbBulkOrInterruptTransfer.TransferFlags |= (USBD_SHORT_TRANSFER_OK);

                pUrb->UrbBulkOrInterruptTransfer.UrbLink = 0;
                break;
            }
            default:
            {
                AssertFailed();
                Status = STATUS_INVALID_PARAMETER;
                break;
            }
        }

        if (!NT_SUCCESS(Status))
        {
            break;
        }

        pContext->pDevExt = pDevExt;
        pContext->pMdlBuf = pMdlBuf;
        pContext->pUrb = pUrb;
        pContext->pOut = pUrbInfo;
        pContext->ulTransferType = pUrbInfo->type;
        pContext->ulMagic = VBOXUSB_MAGIC;

        PIO_STACK_LOCATION pSl = IoGetNextIrpStackLocation(pIrp);
        pSl->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
        pSl->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB;
        pSl->Parameters.Others.Argument1 = pUrb;
        pSl->Parameters.Others.Argument2 = NULL;

        IoSetCompletionRoutine(pIrp, vboxUsbRtUrbSendCompletion, pContext, TRUE, TRUE, TRUE);
        IoMarkIrpPending(pIrp);
        Status = IoCallDriver(pDevExt->pLowerDO, pIrp);
        AssertMsg(NT_SUCCESS(Status), (__FUNCTION__": IoCallDriver failed Status (0x%x)\n", Status));
        return STATUS_PENDING;
    } while (0);

    Assert(!NT_SUCCESS(Status));

    if (pMdlBuf)
    {
        MmUnlockPages(pMdlBuf);
        IoFreeMdl(pMdlBuf);
    }

    if (pContext)
        vboxUsbMemFree(pContext);

    VBoxDrvToolIoComplete(pIrp, Status, 0);
    vboxUsbDdiStateRelease(pDevExt);
    return Status;
}

static NTSTATUS vboxUsbRtDispatchSendUrb(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp)
{
    PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp);
    PFILE_OBJECT pFObj = pSl->FileObject;
    PUSBSUP_URB pUrbInfo = (PUSBSUP_URB)pIrp->AssociatedIrp.SystemBuffer;
    NTSTATUS Status;

    do
    {
        if (!pFObj)
        {
            AssertFailed();
            Status = STATUS_INVALID_PARAMETER;
            break;
        }

        if (!vboxUsbRtCtxIsOwner(pDevExt, pFObj))
        {
            AssertFailed();
            Status = STATUS_ACCESS_DENIED;
            break;
        }

        if (  !pUrbInfo
            || pSl->Parameters.DeviceIoControl.InputBufferLength != sizeof (*pUrbInfo)
            || pSl->Parameters.DeviceIoControl.OutputBufferLength != sizeof (*pUrbInfo))
        {
            AssertMsgFailed((__FUNCTION__": STATUS_INVALID_PARAMETER\n"));
            Status = STATUS_INVALID_PARAMETER;
            break;
        }
        return vboxUsbRtUrbSend(pDevExt, pIrp, pUrbInfo);
    } while (0);

    Assert(Status != STATUS_PENDING);
    VBoxDrvToolIoComplete(pIrp, Status, 0);
    vboxUsbDdiStateRelease(pDevExt);
    return Status;
}

static NTSTATUS vboxUsbRtDispatchIsOperational(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp)
{
    VBoxDrvToolIoComplete(pIrp, STATUS_SUCCESS, 0);
    vboxUsbDdiStateRelease(pDevExt);
    return STATUS_SUCCESS;
}

static NTSTATUS vboxUsbRtDispatchGetVersion(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp)
{
    PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp);
    PUSBSUP_VERSION pVer= (PUSBSUP_VERSION)pIrp->AssociatedIrp.SystemBuffer;
    NTSTATUS Status = STATUS_SUCCESS;

    if (pVer && pSl->Parameters.DeviceIoControl.InputBufferLength == 0
             && pSl->Parameters.DeviceIoControl.OutputBufferLength == sizeof (*pVer))
    {
        pVer->u32Major = USBDRV_MAJOR_VERSION;
        pVer->u32Minor = USBDRV_MINOR_VERSION;
    }
    else
    {
        AssertMsgFailed((__FUNCTION__": STATUS_INVALID_PARAMETER\n"));
        Status = STATUS_INVALID_PARAMETER;
    }

    Assert(Status != STATUS_PENDING);
    VBoxDrvToolIoComplete(pIrp, Status, sizeof (*pVer));
    vboxUsbDdiStateRelease(pDevExt);
    return Status;
}

static NTSTATUS vboxUsbRtDispatchDefault(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp)
{
    VBoxDrvToolIoComplete(pIrp, STATUS_INVALID_DEVICE_REQUEST, 0);
    vboxUsbDdiStateRelease(pDevExt);
    return STATUS_INVALID_DEVICE_REQUEST;
}

DECLHIDDEN(NTSTATUS) vboxUsbRtCreate(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp)
{
    PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp);
    PFILE_OBJECT pFObj = pSl->FileObject;
    if (!pFObj)
    {
        AssertFailed();
        return STATUS_INVALID_PARAMETER;
    }

    return STATUS_SUCCESS;
}

DECLHIDDEN(NTSTATUS) vboxUsbRtClose(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp)
{
    PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp);
    PFILE_OBJECT pFObj = pSl->FileObject;
    Assert(pFObj);

    vboxUsbRtCtxReleaseOwner(pDevExt, pFObj);

    return STATUS_SUCCESS;
}

DECLHIDDEN(NTSTATUS) vboxUsbRtDispatch(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp)
{
    PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp);
    switch (pSl->Parameters.DeviceIoControl.IoControlCode)
    {
        case SUPUSB_IOCTL_USB_CLAIM_DEVICE:
        {
            return vboxUsbRtDispatchClaimDevice(pDevExt, pIrp);
        }
        case SUPUSB_IOCTL_USB_RELEASE_DEVICE:
        {
            return vboxUsbRtDispatchReleaseDevice(pDevExt, pIrp);
        }
        case SUPUSB_IOCTL_GET_DEVICE:
        {
            return vboxUsbRtDispatchGetDevice(pDevExt, pIrp);
        }
        case SUPUSB_IOCTL_USB_RESET:
        {
            return vboxUsbRtDispatchUsbReset(pDevExt, pIrp);
        }
        case SUPUSB_IOCTL_USB_SET_CONFIG:
        {
            return vboxUsbRtDispatchUsbSetConfig(pDevExt, pIrp);
        }
        case SUPUSB_IOCTL_USB_SELECT_INTERFACE:
        {
            return vboxUsbRtDispatchUsbSelectInterface(pDevExt, pIrp);
        }
        case SUPUSB_IOCTL_USB_CLEAR_ENDPOINT:
        {
            return vboxUsbRtDispatchUsbClearEndpoint(pDevExt, pIrp);
        }
        case SUPUSB_IOCTL_USB_ABORT_ENDPOINT:
        {
            return vboxUsbRtDispatchUsbAbortEndpoint(pDevExt, pIrp);
        }
        case SUPUSB_IOCTL_SEND_URB:
        {
            return vboxUsbRtDispatchSendUrb(pDevExt, pIrp);
        }
        case SUPUSB_IOCTL_IS_OPERATIONAL:
        {
            return vboxUsbRtDispatchIsOperational(pDevExt, pIrp);
        }
        case SUPUSB_IOCTL_GET_VERSION:
        {
            return vboxUsbRtDispatchGetVersion(pDevExt, pIrp);
        }
        default:
        {
            return vboxUsbRtDispatchDefault(pDevExt, pIrp);
        }
    }
}
