VBoxUsbFlt.cpp revision 6cb6e43f3a668826e9b9d00102f7dee1299589e7
/* $Id$ */
/** @file
* VBox USB Monitor Device Filtering functionality
*/
/*
* 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;
* 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 *
*******************************************************************************/
#include "VBoxUsbMon.h"
#include "../cmn/VBoxUsbTool.h"
#include <stdio.h>
#include "usbdi.h"
#pragma warning(default : 4200)
#include "usbdlib.h"
#include "VBoxUSBFilterMgr.h"
#include <devguid.h>
/*
* Note: Must match the VID & PID in the USB driver .inf file!!
*/
/*
BusQueryDeviceID USB\Vid_80EE&Pid_CAFE
BusQueryInstanceID 2
BusQueryHardwareIDs USB\Vid_80EE&Pid_CAFE&Rev_0100
BusQueryHardwareIDs USB\Vid_80EE&Pid_CAFE
BusQueryCompatibleIDs USB\Class_ff&SubClass_00&Prot_00
BusQueryCompatibleIDs USB\Class_ff&SubClass_00
BusQueryCompatibleIDs USB\Class_ff
*/
#define szBusQueryDeviceId L"USB\\Vid_80EE&Pid_CAFE"
#define szBusQueryHardwareIDs L"USB\\Vid_80EE&Pid_CAFE&Rev_0100\0USB\\Vid_80EE&Pid_CAFE\0\0"
#define szBusQueryCompatibleIDs L"USB\\Class_ff&SubClass_00&Prot_00\0USB\\Class_ff&SubClass_00\0USB\\Class_ff\0\0"
#define szDeviceTextDescription L"VirtualBox USB"
/* Possible USB bus driver names. */
{
L"\\Driver\\usbhub",
};
/*
* state transitions:
*
* (we are not filtering this device )
* ADDED --> UNCAPTURED ------------------------------->-
* | |
* | (we are filtering this device, | (the device is being
* | waiting for our device driver | re-plugged to perform
* | to pick it up) | capture-uncapture transition)
* |-> CAPTURING -------------------------------->|---> REPLUGGING -----
* ^ | (device driver picked | |
* | | up the device) | (remove cased | (device is removed
* | ->---> CAPTURED ---------------------->| by "real" removal | the device info is removed form the list)
* | | |------------------->->--> REMOVED
* | | |
* |-----------<->---> USED_BY_GUEST ------->|
* | |
* |------------------------<-
*
* NOTE: the order of enums DOES MATTER!!
* Do not blindly modify!! as the code assumes the state is ordered this way.
*/
typedef enum
{
VBOXUSBFLT_DEVSTATE_32BIT_HACK = 0x7fffffff
typedef struct VBOXUSBFLT_DEVICE
{
/* auxiliary list to be used for gathering devices to be re-plugged
* only thread that puts the device to the REPLUGGING state can use this list */
/* Owning session. Each matched device has an owning session. */
struct VBOXUSBFLTCTX *pOwner;
/* filter id - if NULL AND device has an owner - the filter is destroyed */
/* true iff device is filtered with a one-shot filter */
bool fIsFilterOneShot;
/* The device state. If the non-owner session is requesting the state while the device is grabbed,
* the USBDEVICESTATE_USED_BY_HOST is returned. */
char szSerial[MAX_USB_SERIAL_STRING];
char szMfgName[MAX_USB_SERIAL_STRING];
char szProduct[MAX_USB_SERIAL_STRING];
#if 0
char szDrvKeyName[512];
#endif
#define PVBOXUSBFLT_DEVICE_FROM_LE(_pLe) ( (PVBOXUSBFLT_DEVICE)( ((uint8_t*)(_pLe)) - RT_OFFSETOF(VBOXUSBFLT_DEVICE, GlobalLe) ) )
#define PVBOXUSBFLT_DEVICE_FROM_REPLUGGINGLE(_pLe) ( (PVBOXUSBFLT_DEVICE)( ((uint8_t*)(_pLe)) - RT_OFFSETOF(VBOXUSBFLT_DEVICE, RepluggingLe) ) )
#define PVBOXUSBFLTCTX_FROM_LE(_pLe) ( (PVBOXUSBFLTCTX)( ((uint8_t*)(_pLe)) - RT_OFFSETOF(VBOXUSBFLTCTX, ListEntry) ) )
typedef struct VBOXUSBFLT_LOCK
{
#define VBOXUSBFLT_LOCK_INIT() \
#define VBOXUSBFLT_LOCK_TERM() do { } while (0)
#define VBOXUSBFLT_LOCK_ACQUIRE() \
#define VBOXUSBFLT_LOCK_RELEASE() \
typedef struct VBOXUSBFLTGLOBALS
{
static VBOXUSBFLTGLOBALS g_VBoxUsbFltGlobals;
{
}
{
}
{
if (!cRefs)
{
}
}
static void vboxUsbFltDevOwnerSetLocked(PVBOXUSBFLT_DEVICE pDevice, PVBOXUSBFLTCTX pContext, uintptr_t uFltId, bool fIsOneShot)
{
}
{
}
static void vboxUsbFltDevOwnerUpdateLocked(PVBOXUSBFLT_DEVICE pDevice, PVBOXUSBFLTCTX pContext, uintptr_t uFltId, bool fIsOneShot)
{
{
if (pContext)
}
else if (pContext)
{
}
}
{
#ifdef DEBUG_mista
{
{
}
}
#endif
{
return pDevice;
}
return NULL;
}
{
{
}
else
{
}
return pDevice;
}
{
return Status;
}
static PVBOXUSBFLTCTX vboxUsbFltDevMatchLocked(PVBOXUSBFLT_DEVICE pDevice, uintptr_t *puId, bool fRemoveFltIfOneShot, bool *pfFilter, bool *pfIsOneShot)
{
/* Run filters on the thing. */
*puId = 0;
*pfFilter = false;
*pfIsOneShot = false;
PVBOXUSBFLTCTX pOwner = VBoxUSBFilterMatchEx(&DevFlt, puId, fRemoveFltIfOneShot, pfFilter, pfIsOneShot);
return pOwner;
}
{
}
{
}
{
}
static NTSTATUS vboxUsbFltDevPopulate(PVBOXUSBFLT_DEVICE pDevice, PDEVICE_OBJECT pDo /*, BOOLEAN bPopulateNonFilterProps*/)
{
{
AssertMsgFailed(("Failed to alloc mem for urb\n"));
return STATUS_INSUFFICIENT_RESOURCES;
}
do
{
if (!NT_SUCCESS(Status))
{
LogRel(("VBoxUSBGetDeviceDescription: getting device descriptor failed\n"));
break;
}
/* If there are no strings, don't even try to get any string descriptors. */
{
int langId;
if (!NT_SUCCESS(Status))
{
break;
}
if (pDevDr->iSerialNumber)
{
Status = VBoxUsbToolGetStringDescriptorA(pDo, pDevice->szSerial, sizeof (pDevice->szSerial), pDevDr->iSerialNumber, langId);
if (!NT_SUCCESS(Status))
{
break;
}
}
if (pDevDr->iManufacturer)
{
Status = VBoxUsbToolGetStringDescriptorA(pDo, pDevice->szMfgName, sizeof (pDevice->szMfgName), pDevDr->iManufacturer, langId);
if (!NT_SUCCESS(Status))
{
break;
}
}
{
Status = VBoxUsbToolGetStringDescriptorA(pDo, pDevice->szProduct, sizeof (pDevice->szProduct), pDevDr->iProduct, langId);
if (!NT_SUCCESS(Status))
{
break;
}
}
#if 0
{
&cbRegKeyBuf);
if (!NT_SUCCESS(Status))
{
break;
}
if (!NT_SUCCESS(Status))
{
break;
}
if (!NT_SUCCESS(Status))
{
break;
}
}
#endif
}
} while (0);
return Status;
}
static void vboxUsbFltSignalChangeLocked()
{
{
/* the removed context can not be in a list */
if (pCtx->pChangeEvent)
{
0, /* increment*/
FALSE /* wait */);
}
}
}
{
/* check if device is already replugging */
{
/* it is, do nothing */
return false;
}
{
/* this device is owned by another context, we're not allowed to do anything */
return false;
}
bool bNeedReplug = false;
bool fFilter = false;
bool fIsOneShot = false;
false, /* do not remove a one-shot filter */
&fFilter, &fIsOneShot);
{
/* the device is owned by another owner, we can not change the owner here */
return false;
}
if (!fFilter)
{
/* the device should NOT be filtered, check the current state */
{
/* no changes */
if (fIsOneShot)
{
/* remove a one-shot filter and keep the original filter data */
{
/* update owner for one-shot if the owner is changed (i.e. assigned) */
}
}
else
{
if (pNewOwner)
{
}
}
}
else
{
/* the device is currently filtered, we should release it only if
* 1. device does not have an owner
* or
* 2. it should be released bue to a one-shot filter
* or
* 3. it is NOT grabbed by a one-shot filter */
{
bNeedReplug = true;
}
}
}
else
{
/* the device should be filtered, check the current state */
{
/* the device is filtered */
{
/* no changes */
if (fIsOneShot)
{
/* remove a one-shot filter and keep the original filter data */
}
else
{
}
}
else
{
/* the device needs to be filtered, but the owner changes, replug needed */
bNeedReplug = true;
}
}
else
{
/* the device is currently NOT filtered,
* we should replug it only if
* 1. device does not have an owner
* or
* 2. it should be captured due to a one-shot filter
* or
* 3. it is NOT released by a one-shot filter */
{
bNeedReplug = true;
}
}
}
if (bNeedReplug)
{
}
return bNeedReplug;
}
{
{
}
}
{
for (int i=0;i<RT_ELEMENTS(lpszStandardControllerName);i++)
{
szStandardControllerName[i].Length = 0;
szStandardControllerName[i].Buffer = 0;
}
for (int i = 0; i < 16; i++)
{
char szHubName[32];
UnicodeName.Length = 0;
if (Status == STATUS_SUCCESS)
{
if (pHubDevObj->DriverObject
)
{
for (int j = 0; j < RT_ELEMENTS(lpszStandardControllerName); ++j)
{
if (!RtlCompareUnicodeString(&szStandardControllerName[j], &pHubDevObj->DriverObject->DriverName, TRUE /* case insensitive */))
{
#ifdef DEBUG
Log(("Associated driver "));
#endif
{
{
if (!pDevObj->DriverObject
{
AssertFailed();
continue;
}
bool fIsHub = false;
for (int z = 0; z < RT_ELEMENTS(lpszStandardControllerName); ++z)
{
if (!RtlCompareUnicodeString(&szStandardControllerName[z], &pDevObj->DriverObject->DriverName, TRUE /* case insensitive */))
{
fIsHub = true;
break;
}
}
if (fIsHub)
{
continue;
}
if (pDevice)
{
if (bReplug)
{
/* do not dereference object since we will use it later */
}
else
{
}
--cReplugPdos;
continue;
}
Status = vboxUsbFltDevPopulate(&Device, pDevRelations->Objects[k] /*, FALSE /* only need filter properties */);
if (NT_SUCCESS(Status))
{
bool fFilter = false;
bool fIsOneShot = false;
false, /* do not remove a one-shot filter */
&fFilter, &fIsOneShot);
if (!fFilter)
{
/* this device should not be filtered, and it's not */
--cReplugPdos;
continue;
}
/* this device needs to be filtered, but it's not,
* leave the PDO in array to issue a replug request for it
* later on */
}
}
if (cReplugPdos)
{
{
if (!pDevRelations->Objects[k])
continue;
if (!--cReplugPdos)
break;
}
}
}
}
}
}
}
}
return STATUS_SUCCESS;
}
{
if (pContext->pChangeEvent)
{
0, /* increment*/
FALSE /* wait */);
}
/* now re-arrange the filters */
/* 1. remove filters */
/* 2. check if there are devices owned */
{
continue;
{
/* retain to ensure the device is not removed before we issue a replug */
/* keep the PDO alive */
}
}
/* this should replug all devices that were either skipped or grabbed due to the context's */
return STATUS_SUCCESS;
}
{
return STATUS_SUCCESS;
}
{
*pId = 0;
/* Log the filter details. */
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>"));
#ifdef DEBUG
Log(("VBoxUSBClient::addFilter: idVendor=%#x idProduct=%#x bcdDevice=%#x bDeviceClass=%#x bDeviceSubClass=%#x bDeviceProtocol=%#x bBus=%#x bPort=%#x\n",
#endif
/* Add the filter. */
if (RT_SUCCESS(rc))
{
#endif
}
else
{
}
return rc;
}
{
if (!RT_SUCCESS(rc))
{
AssertFailed();
return rc;
}
{
if (pDevice->fIsFilterOneShot)
{
}
continue;
continue;
/* clear the fIsFilterOneShot flag to ensure the device is replugged on the next VBoxUsbFltFilterCheck call */
pDevice->fIsFilterOneShot = false;
}
if (RT_SUCCESS(rc))
{
#endif
}
return rc;
}
{
if (hEvent)
{
NULL);
if (!NT_SUCCESS(Status))
return Status;
}
if (pOldEvent)
{
}
return STATUS_SUCCESS;
}
{
/* the device is filtered, or replugging */
{
AssertFailed();
/* no user state for this, we should not return it tu the user */
return USBDEVICESTATE_USED_BY_HOST;
}
/* the device is filtered, if owner differs from the context, return as USED_BY_HOST */
/* the id can be null if a filter is removed */
// Assert(pDevice->uFltId);
return USBDEVICESTATE_USED_BY_HOST;
{
return USBDEVICESTATE_HELD_BY_PROXY;
return USBDEVICESTATE_USED_BY_GUEST;
default:
AssertFailed();
return USBDEVICESTATE_UNSUPPORTED;
}
}
static void vboxUsbDevToUserInfo(PVBOXUSBFLTCTX pContext, PVBOXUSBFLT_DEVICE pDevice, PUSBSUP_DEVINFO pDevInfo)
{
#if 0
{
/* this is the only case where we return the obj name to the client */
}
#endif
}
NTSTATUS VBoxUsbFltGetDevice(PVBOXUSBFLTCTX pContext, HVBOXUSBDEVUSR hDevice, PUSBSUP_GETDEV_MON pInfo)
{
{
continue;
return STATUS_SUCCESS;
}
/* this should not occur */
AssertFailed();
return STATUS_INVALID_PARAMETER;
}
{
*pbFiltered = FALSE;
/* first check if device is in the a already */
if (pDevice)
{
return STATUS_SUCCESS;
}
if (!pDevice)
{
AssertFailed();
return STATUS_NO_MEMORY;
}
if (!NT_SUCCESS(Status))
{
AssertFailed();
return Status;
}
bool fFilter = false;
bool fIsOneShot = false;
/* (paranoia) re-check the device is still not here */
if (pTmpDev)
{
return STATUS_SUCCESS;
}
true, /* remove a one-shot filter */
&fFilter, &fIsOneShot);
if (fFilter)
{
}
else
{
}
if (pCtx)
/* do not need to signal anything here -
* going to do that once the proxy device object starts */
*pbFiltered = fFilter;
return STATUS_SUCCESS;
}
{
return STATUS_SUCCESS;
}
{
if (pDevice)
{
}
return enmState >= VBOXUSBFLT_DEVSTATE_CAPTURING;
}
{
if (pDevice)
{
{
}
else
{
/* the device *should* reappear, do signlling on re-appear only
* to avoid extra signaling. still there might be a situation
* when the device will not re-appear if it gets physically removed
* before it re-appears
* @todo: set a timer callback to do a notification from it */
}
}
if (pDevice)
return STATUS_SUCCESS;
}
{
{
}
else
{
AssertFailed();
}
return pDevice;
}
{
{
/* this is due to devie was physically removed */
}
else
{
}
}
{
int rc = VBoxUSBFilterInit();
if (RT_FAILURE(rc))
return STATUS_UNSUCCESSFUL;
return STATUS_SUCCESS;
}
{
bool bBusy = false;
do
{
{
AssertFailed();
bBusy = true;
break;
}
{
{
AssertFailed();
bBusy = true;
break;
}
}
} while (0);
if (bBusy)
{
return STATUS_DEVICE_BUSY;
}
{
}
return STATUS_SUCCESS;
}