VBoxUsbLib-win.cpp revision c7814cf6e1240a519cbec0441e033d0e2470ed00
/* $Id$ */
/** @file
* VBox USB ring-3 Driver Interface library, Windows.
*/
/*
* Copyright (C) 2011-2012 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* 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_DRV_USBPROXY
#include <windows.h>
#include <VBox/usblib-win.h>
#include <VBox/VBoxDrvCfg-win.h>
#include <stdio.h>
#include <setupapi.h>
#include <usbdi.h>
#include <hidsdi.h>
#define VBOX_USB_USE_DEVICE_NOTIFICATION
# include <Dbt.h>
#endif
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
typedef struct _USB_INTERFACE_DESCRIPTOR2
{
typedef struct VBOXUSBGLOBALSTATE
{
#endif
typedef struct VBOXUSB_STRING_DR_ENTRY
{
struct VBOXUSB_STRING_DR_ENTRY *pNext;
/**
* This represents VBoxUsb device instance
*/
typedef struct VBOXUSB_DEV
{
struct VBOXUSB_DEV *pNext;
char szName[512];
char szDriverRegName[512];
} VBOXUSB_DEV, *PVBOXUSB_DEV;
/*******************************************************************************
* Global Variables *
*******************************************************************************/
static VBOXUSBGLOBALSTATE g_VBoxUsbGlobal;
{
hOut = CreateFile(pVuDev->szName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL,
if (hOut == INVALID_HANDLE_VALUE)
{
return VERR_GENERAL_FAILURE;
}
USBSUP_VERSION version = {0};
DWORD cbReturned = 0;
int rc = VERR_VERSION_MISMATCH;
do
{
if (!DeviceIoControl(hOut, SUPUSB_IOCTL_GET_VERSION, NULL, 0,&version, sizeof(version), &cbReturned, NULL))
{
AssertMsgFailed(("DeviceIoControl SUPUSB_IOCTL_GET_VERSION failed with LastError=%Rwa\n", GetLastError()));
break;
}
{
AssertMsgFailed(("Invalid version %d:%d vs %d:%d\n", version.u32Major, version.u32Minor, USBDRV_MAJOR_VERSION, USBDRV_MINOR_VERSION));
break;
}
{
AssertMsgFailed(("DeviceIoControl SUPUSB_IOCTL_IS_OPERATIONAL failed with LastError=%Rwa\n", GetLastError()));
break;
}
rc = VINF_SUCCESS;
} while (0);
return rc;
}
static int usbLibVuDevicePopulate(PVBOXUSB_DEV pVuDev, HDEVINFO hDevInfo, PSP_DEVICE_INTERFACE_DATA pIfData)
{
int rc = VINF_SUCCESS;
NULL, /* OUT PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData */
0, /* IN DWORD DeviceInterfaceDetailDataSize */
);
PSP_DEVICE_INTERFACE_DETAIL_DATA pIfDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)RTMemAllocZ(cbIfDetailData);
if (!pIfDetailData)
{
AssertMsgFailed(("RTMemAllocZ failed\n"));
return VERR_OUT_OF_RESOURCES;
}
/* the cbSize should contain the sizeof a fixed-size part according to the docs */
do
{
&DevInfoData))
{
AssertMsgFailed(("SetupDiGetDeviceInterfaceDetail, cbRequired (%d), was (%d), winEr (%d)\n", cbDbgRequired, cbIfDetailData, winEr));
break;
}
NULL, /* OUT PDWORD PropertyRegDataType */
sizeof (pVuDev->szDriverRegName),
{
AssertMsgFailed(("SetupDiGetDeviceRegistryPropertyA, cbRequired (%d), was (%d), winEr (%d)\n", cbDbgRequired, sizeof (pVuDev->szDriverRegName), winEr));
break;
}
} while (0);
return rc;
}
{
while (pDevInfos)
{
}
}
{
*pcVuDevs = 0;
NULL, /* IN PCTSTR Enumerator */
NULL, /* IN HWND hwndParent */
);
if (hDevInfo == INVALID_HANDLE_VALUE)
{
return VERR_GENERAL_FAILURE;
}
for (int i = 0; ; ++i)
{
NULL, /* IN PSP_DEVINFO_DATA DeviceInfoData */
&GUID_CLASS_VBOXUSB, /* IN LPGUID InterfaceClassGuid */
i,
&IfData))
{
if (winEr == ERROR_NO_MORE_ITEMS)
break;
continue;
}
/* we've now got the IfData */
if (!pVuDev)
{
AssertMsgFailed(("RTMemAllocZ failed, resuming\n"));
continue;
}
if (!RT_SUCCESS(rc))
{
continue;
}
++*pcVuDevs;
}
return VINF_SUCCESS;
}
{
if (pDevice->pszManufacturer)
if (pDevice->pszProduct)
if (pDevice->pszSerialNumber)
}
{
while (pDevice)
{
}
}
static int usbLibDevPopulate(PUSBDEVICE pDev, PUSB_NODE_CONNECTION_INFORMATION_EX pConInfo, ULONG iPort, LPCSTR lpszDrvKeyName, LPCSTR lpszHubName, PVBOXUSB_STRING_DR_ENTRY pDrList)
{
/** @todo check which devices are used for primary input (keyboard & mouse) */
if (!lpszDrvKeyName || *lpszDrvKeyName == 0)
else
pDev->bNumConfigurations = 0;
pDev->u64SerialHash = 0;
{
char ** lppszString = NULL;
if (pConInfo->DeviceDescriptor.iManufacturer && pDrList->iDr == pConInfo->DeviceDescriptor.iManufacturer)
{
}
else if (pConInfo->DeviceDescriptor.iProduct && pDrList->iDr == pConInfo->DeviceDescriptor.iProduct)
{
}
else if (pConInfo->DeviceDescriptor.iSerialNumber && pDrList->iDr == pConInfo->DeviceDescriptor.iSerialNumber)
{
}
if (lppszString)
{
/** @todo r=bird: This code is making bad asumptions that strings are sane and
* that stuff succeeds:
*
* */
if (RT_FAILURE(rc))
{
continue;
}
{
}
}
}
return VINF_SUCCESS;
}
{
}
{
DWORD cbReturned = 0;
if (!DeviceIoControl(hHub, IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME, &Name, sizeof (Name), &Name, sizeof (Name), &cbReturned, NULL))
{
#endif
return VERR_GENERAL_FAILURE;
}
{
AssertFailed();
return VERR_OUT_OF_RESOURCES;
}
PUSB_NODE_CONNECTION_DRIVERKEY_NAME pName = (PUSB_NODE_CONNECTION_DRIVERKEY_NAME)RTMemAllocZ(Name.ActualLength);
if (!pName)
{
AssertFailed();
return VERR_OUT_OF_RESOURCES;
}
int rc = VINF_SUCCESS;
if (DeviceIoControl(hHub, IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME, pName, Name.ActualLength, pName, Name.ActualLength, &cbReturned, NULL))
{
rc = RTUtf16ToUtf8Ex((PCRTUTF16)pName->DriverKeyName, pName->ActualLength / sizeof (WCHAR), plpszName, 0, NULL);
if (RT_SUCCESS(rc))
rc = VINF_SUCCESS;
}
else
{
}
return rc;
}
{
DWORD cbReturned = 0;
if (!DeviceIoControl(hHub, IOCTL_USB_GET_NODE_CONNECTION_NAME, &Name, sizeof (Name), &Name, sizeof (Name), &cbReturned, NULL))
{
AssertFailed();
return VERR_GENERAL_FAILURE;
}
{
AssertFailed();
return VERR_OUT_OF_RESOURCES;
}
if (!pName)
{
AssertFailed();
return VERR_OUT_OF_RESOURCES;
}
int rc = VINF_SUCCESS;
if (DeviceIoControl(hHub, IOCTL_USB_GET_NODE_CONNECTION_NAME, pName, Name.ActualLength, pName, Name.ActualLength, &cbReturned, NULL))
{
rc = RTUtf16ToUtf8Ex((PCRTUTF16)pName->NodeName, pName->ActualLength / sizeof (WCHAR), plpszName, 0, NULL);
if (RT_SUCCESS(rc))
rc = VINF_SUCCESS;
}
else
{
AssertFailed();
}
return rc;
}
{
DWORD cbReturned = 0;
if (!DeviceIoControl(hCtl, IOCTL_USB_GET_ROOT_HUB_NAME, NULL, 0, &HubName, sizeof (HubName), &cbReturned, NULL))
{
return VERR_GENERAL_FAILURE;
}
if (!pHubName)
return VERR_OUT_OF_RESOURCES;
int rc = VINF_SUCCESS;
if (DeviceIoControl(hCtl, IOCTL_USB_GET_ROOT_HUB_NAME, NULL, 0, pHubName, HubName.ActualLength, &cbReturned, NULL))
{
rc = RTUtf16ToUtf8Ex((PCRTUTF16)pHubName->RootHubName, pHubName->ActualLength / sizeof (WCHAR), plpszName, 0, NULL);
if (RT_SUCCESS(rc))
rc = VINF_SUCCESS;
}
else
{
}
return rc;
}
static int usbLibDevCfgDrGet(HANDLE hHub, ULONG iPort, ULONG iDr, PUSB_CONFIGURATION_DESCRIPTOR *ppDr)
{
DWORD cbReturned = 0;
&cbReturned, NULL))
{
AssertFailed();
#endif
return VERR_GENERAL_FAILURE;
}
if (sizeof (Buf) != cbReturned)
{
AssertFailed();
return VERR_GENERAL_FAILURE;
}
{
AssertFailed();
return VERR_GENERAL_FAILURE;
}
if (!pRq)
return VERR_OUT_OF_RESOURCES;
int rc = VERR_GENERAL_FAILURE;
do
{
&cbReturned, NULL))
{
AssertFailed();
#endif
break;
}
if (cbRq != cbReturned)
{
AssertFailed();
break;
}
{
AssertFailed();
break;
}
return VINF_SUCCESS;
} while (0);
return rc;
}
{
}
static int usbLibDevStrDrEntryGet(HANDLE hHub, ULONG iPort, ULONG iDr, USHORT idLang, PVBOXUSB_STRING_DR_ENTRY *ppList)
{
DWORD cbReturned = 0;
&cbReturned, NULL))
{
AssertFailed();
#endif
return VERR_GENERAL_FAILURE;
}
{
AssertFailed();
return VERR_GENERAL_FAILURE;
}
{
AssertFailed();
return VERR_GENERAL_FAILURE;
}
{
AssertFailed();
return VERR_GENERAL_FAILURE;
}
{
AssertFailed();
return VERR_GENERAL_FAILURE;
}
PVBOXUSB_STRING_DR_ENTRY pEntry = (PVBOXUSB_STRING_DR_ENTRY)RTMemAllocZ(sizeof (*pEntry) + pDr->bLength + 2);
if (!pEntry)
{
return VERR_OUT_OF_RESOURCES;
}
return VINF_SUCCESS;
}
{
}
{
while (pDr)
{
}
}
static int usbLibDevStrDrEntryGetForLangs(HANDLE hHub, ULONG iPort, ULONG iDr, ULONG cIdLang, const USHORT *pIdLang, PVBOXUSB_STRING_DR_ENTRY *ppList)
{
{
}
return VINF_SUCCESS;
}
static int usbLibDevStrDrEntryGetAll(HANDLE hHub, ULONG iPort, PUSB_DEVICE_DESCRIPTOR pDevDr, PUSB_CONFIGURATION_DESCRIPTOR pCfgDr, PVBOXUSB_STRING_DR_ENTRY *ppList)
{
#endif
if (RT_FAILURE(rc))
return rc;
ULONG cIdLang = (pLandStrDr->bLength - RT_OFFSETOF(USB_STRING_DESCRIPTOR, bString)) / sizeof (*pIdLang);
if (pDevDr->iManufacturer)
{
}
{
}
if (pDevDr->iSerialNumber)
{
}
{
{
AssertFailed();
break;
}
switch (pCmnDr->bDescriptorType)
{
{
{
AssertFailed();
break;
}
if (!pCurCfgDr->iConfiguration)
break;
rc = usbLibDevStrDrEntryGetForLangs(hHub, iPort, pCurCfgDr->iConfiguration, cIdLang, pIdLang, ppList);
break;
}
{
if (pCmnDr->bLength != sizeof (USB_INTERFACE_DESCRIPTOR) && pCmnDr->bLength != sizeof (USB_INTERFACE_DESCRIPTOR2))
{
AssertFailed();
break;
}
if (!pCurIfDr->iInterface)
break;
break;
}
default:
break;
}
}
return VINF_SUCCESS;
}
static int usbLibDevGetHubPortDevices(HANDLE hHub, LPCSTR lpcszHubName, ULONG iPort, PUSBDEVICE *ppDevs, uint32_t *pcDevs)
{
int rc = VINF_SUCCESS;
DWORD cbReturned = 0;
&cbReturned, NULL))
{
AssertMsg(winEr == ERROR_DEVICE_NOT_CONNECTED, (__FUNCTION__": DeviceIoControl failed winEr (%d)\n", winEr));
return VERR_GENERAL_FAILURE;
}
{
/* just ignore & return success */
return VWRN_INVALID_HANDLE;
}
if (pConInfo->DeviceIsHub)
{
if (RT_SUCCESS(rc))
{
return rc;
}
/* ignore this err */
return VINF_SUCCESS;
}
bool fFreeNameBuf = true;
char nameEmptyBuf = '\0';
if (!lpszName)
{
lpszName = &nameEmptyBuf;
fFreeNameBuf = false;
}
if (pCfgDr)
{
#endif
}
if (RT_SUCCESS(rc))
{
++*pcDevs;
}
if (pCfgDr)
if (fFreeNameBuf)
{
}
if (pList)
return VINF_SUCCESS;
}
{
if (!lpszDevName)
{
AssertFailed();
return VERR_OUT_OF_RESOURCES;
}
int rc = VINF_SUCCESS;
do
{
DWORD cbReturned = 0;
if (hDev == INVALID_HANDLE_VALUE)
{
AssertFailed();
break;
}
&cbReturned, NULL))
{
AssertFailed();
break;
}
{
}
} while (0);
if (hDev != INVALID_HANDLE_VALUE)
return rc;
}
{
char CtlName[16];
int rc = VINF_SUCCESS;
for (int i = 0; i < 10; ++i)
{
if (hCtl != INVALID_HANDLE_VALUE)
{
char* lpszName;
if (RT_SUCCESS(rc))
{
}
if (RT_FAILURE(rc))
break;
}
}
return VINF_SUCCESS;
}
{
if (!pRq)
return NULL;
return pRq;
}
{
int iDiff;
return iDiff;
}
static int usbLibMonDevicesUpdate(PVBOXUSBGLOBALSTATE pGlobal, PUSBDEVICE pDevs, uint32_t cDevs, PVBOXUSB_DEV pDevInfos, uint32_t cDevInfos)
{
{
{
continue;
if (!pDevInfos->szDriverRegName[0])
{
AssertFailed();
break;
}
USBSUP_GETDEV Dev = {0};
HANDLE hDev = CreateFile(pDevInfos->szName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL,
if (hDev == INVALID_HANDLE_VALUE)
{
AssertFailed();
break;
}
DWORD cbReturned = 0;
if (!DeviceIoControl(hDev, SUPUSB_IOCTL_GET_DEVICE, &Dev, sizeof (Dev), &Dev, sizeof (Dev), &cbReturned, NULL))
{
/* ERROR_DEVICE_NOT_CONNECTED -> device was removed just now */
AssertMsg(winEr == ERROR_DEVICE_NOT_CONNECTED, (__FUNCTION__": DeviceIoControl failed winEr (%d)\n", winEr));
#endif
Log(("SUPUSB_IOCTL_GET_DEVICE: DeviceIoControl no longer connected\n"));
break;
}
/* we must not close the handle until we request for the device state from the monitor to ensure
* the device handle returned by the device driver does not disappear */
if (!DeviceIoControl(pGlobal->hMonitor, SUPUSBFLT_IOCTL_GET_DEVICE, &hDevice, sizeof (hDevice), &MonInfo, sizeof (MonInfo), &cbReturned, NULL))
{
/* ERROR_DEVICE_NOT_CONNECTED -> device was removed just now */
Log(("SUPUSBFLT_IOCTL_GET_DEVICE: DeviceIoControl no longer connected\n"));
break;
}
/* success!! update device info */
/* ensure the state returned is valid */
/* The following is not 100% accurate but we only care about high-speed vs. non-high-speed */
{
/* only set the interface name if device can be grabbed */
}
else
{
/* dbg breakpoint */
Assert(0);
}
#endif
/* we've found the device, break in any way */
break;
}
}
return VINF_SUCCESS;
}
{
*pcDevs = 0;
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
}
return VINF_SUCCESS;
}
return rc;
}
{
FALSE, /* BOOL bWaitAll */
cMillies);
switch (dwResult)
{
case WAIT_OBJECT_0:
return VINF_SUCCESS;
case WAIT_OBJECT_0 + 1:
return VERR_INTERRUPTED;
case WAIT_TIMEOUT:
return VERR_TIMEOUT;
default:
{
return VERR_GENERAL_FAILURE;
}
}
}
{
}
{
if (!bRc)
{
return VERR_GENERAL_FAILURE;
}
return VINF_SUCCESS;
}
{
return usbLibInterruptWaitChange(&g_VBoxUsbGlobal);
}
/*
USBLIB_DECL(bool) USBLibHasPendingDeviceChanges(void)
{
int rc = USBLibWaitChange(0);
return rc == VINF_SUCCESS;
}
*/
{
}
{
DWORD cbReturned = 0;
{
AssertFailed();
#endif
return NULL;
}
Log(("usblibInsertFilter: Manufacturer=%s Product=%s Serial=%s\n",
USBFilterGetString(pFilter, USBFILTERIDX_MANUFACTURER_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_MANUFACTURER_STR) : "<null>",
USBFilterGetString(pFilter, USBFILTERIDX_PRODUCT_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_PRODUCT_STR) : "<null>",
USBFilterGetString(pFilter, USBFILTERIDX_SERIAL_NUMBER_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_SERIAL_NUMBER_STR) : "<null>"));
&cbReturned, NULL))
{
return NULL;
}
{
return NULL;
}
}
{
DWORD cbReturned = 0;
{
AssertFailed();
#endif
return;
}
if (!DeviceIoControl(g_VBoxUsbGlobal.hMonitor, SUPUSBFLT_IOCTL_REMOVE_FILTER, &uId, sizeof(uId), NULL, 0,&cbReturned, NULL))
}
USBLIB_DECL(int) USBLibRunFilters()
{
DWORD cbReturned = 0;
NULL, 0,
NULL, 0,
&cbReturned, NULL))
{
return RTErrConvertFromWin32(winEr);
}
return VINF_SUCCESS;
}
{
}
static void usbLibOnDeviceChange(void)
{
/* we're getting series of events like that especially on device re-attach
* (i.e. first for device detach and then for device attach)
* unfortunately the event does not tell us what actually happened.
* To avoid extra notifications, we delay the SetEvent via a timer
* and update the timer if additional notification comes before the timer fires
* */
if (g_VBoxUsbGlobal.hTimer)
{
{
}
}
NULL,
500, /* ms*/
0,
{
/* call it directly */
}
}
{
switch (uMsg)
{
case WM_DEVICECHANGE:
if (wParam == DBT_DEVNODES_CHANGED)
{
* and let the client decide whether the usb change actually happened
* so far this is more clean than reporting events from the Monitor
* and by the time PDO is created, device can not
* be yet started and fully functional,
* so usblib won't be able to pick it up
* */
}
break;
case WM_DESTROY:
return 0;
}
}
/** @todo r=bird: Use an IPRT thread? */
{
/*
* Register the Window Class and the hitten window create.
*/
wc.cbClsExtra = 0;
wc.cbWndExtra = sizeof(void *);
if (atomWindowClass != 0)
else
/*
* Signal the creator thread.
*/
if (g_VBoxUsbGlobal.hWnd)
{
/* Make sure it's really hidden. */
/*
* The message pump.
*/
{
}
/*
* Clean up.
*/
}
if (atomWindowClass != NULL)
return 0;
}
#endif /* VBOX_USB_USE_DEVICE_NOTIFICATION */
/**
* Initialize the USB library
*
* @returns VBox status code.
*/
USBLIB_DECL(int) USBLibInit(void)
{
int rc = VERR_GENERAL_FAILURE;
Log(("usbproxy: usbLibInit\n"));
/*
* Create the notification and interrupt event before opening the device.
*/
FALSE, /* BOOL bManualReset */
#ifndef VBOX_USB_USE_DEVICE_NOTIFICATION
TRUE, /* BOOL bInitialState */
#else
FALSE, /* set to false since it will be initially used for notification thread startup sync */
#endif
NULL /* LPCTSTR lpName */);
{
FALSE, /* BOOL bManualReset */
FALSE, /* BOOL bInitialState */
NULL /* LPCTSTR lpName */);
{
/*
* Open the USB monitor device, starting if needed.
*/
NULL,
NULL);
{
{
NULL,
NULL);
{
}
}
}
{
/*
* Check the USB monitor version.
*
* Drivers are backwards compatible within the same major
* number. We consider the minor version number this library
* is compiled with to be the minimum required by the driver.
* This is by reasoning that the library uses the full feature
* set of the driver it's written for.
*/
USBSUP_VERSION Version = {0};
DWORD cbReturned = 0;
NULL, 0,
&cbReturned, NULL))
{
{
#ifndef VBOX_USB_USE_DEVICE_NOTIFICATION
/*
* Tell the monitor driver which event object to use
* for notifications.
*/
USBSUP_SET_NOTIFY_EVENT SetEvent = {0};
&cbReturned, NULL))
{
if (RT_SUCCESS(rc))
{
/*
* We're DONE!
*/
return VINF_SUCCESS;
}
}
else
{
}
#else
/*
* the function device driver may still do some initialization, which might result in
* notifying too early.
* Instead we use WM_DEVICECHANGE + DBT_DEVNODES_CHANGED to make Windows notify us about
* Since WM_DEVICECHANGE is a window message, create a dedicated thread to be used for WndProc and stuff.
* The thread would create a window, track windows messages and call usbLibOnDeviceChange on WM_DEVICECHANGE arrival.
* See comments in usbLibOnDeviceChange function for detail about using the timer queue.
*/
{
NULL, /*__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, */
0, /*__in SIZE_T dwStackSize, */
usbLibMsgThreadProc, /*__in LPTHREAD_START_ROUTINE lpStartAddress,*/
NULL, /*__in_opt LPVOID lpParameter,*/
0, /*__in DWORD dwCreationFlags,*/
NULL /*__out_opt LPDWORD lpThreadId*/
);
if (g_VBoxUsbGlobal.hThread)
{
if (g_VBoxUsbGlobal.hWnd)
{
/*
* We're DONE!
*
* Juse ensure that the event is set so the
* first "wait change" request is processed.
*/
return VINF_SUCCESS;
}
}
else
{
}
}
else
{
}
#endif
}
else
{
AssertFailed();
#endif
}
}
else
{
}
}
else
{
AssertFailed();
#endif
}
}
else
{
}
}
else
{
}
/* since main calls us even if USBLibInit fails,
* we use hMonitor == INVALID_HANDLE_VALUE as a marker to indicate whether the lib is inited */
return rc;
}
/**
* Terminate the USB library
*
* @returns VBox status code.
*/
USBLIB_DECL(int) USBLibTerm(void)
{
{
return VINF_SUCCESS;
}
{
}
if (g_VBoxUsbGlobal.hTimer)
{
INVALID_HANDLE_VALUE); /* <-- to block until the timer is completed */
}
{
INVALID_HANDLE_VALUE); /* <-- to block until all timers are completed */
}
#endif /* VBOX_USB_USE_DEVICE_NOTIFICATION */
return VINF_SUCCESS;
}