HostUSBDeviceImpl.cpp revision b9769010e0bf9346865942a20da204bd1dbc8cb6
/** @file
*
* VirtualBox IHostUSBDevice COM interface implementation
*/
/*
* Copyright (C) 2006-2007 innotek GmbH
*
* 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 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.
*
* If you received this file as part of a commercial VirtualBox
* distribution, then only the terms of your commercial VirtualBox
* license agreement apply instead of the previous paragraph.
*/
#include "HostUSBDeviceImpl.h"
#include "MachineImpl.h"
#include "VirtualBoxErrorInfoImpl.h"
#include "USBProxyService.h"
#include "Logging.h"
#include <iprt/cpputils.h>
// constructor / destructor
/////////////////////////////////////////////////////////////////////////////
{
return S_OK;
}
void HostUSBDevice::FinalRelease()
{
uninit();
}
// public initializer/uninitializer for internal purposes only
/////////////////////////////////////////////////////////////////////////////
/**
* Initializes the USB device object.
*
* @returns COM result indicator
* @param aUsb Pointer to the usb device structure for which the object is to be a wrapper.
* This structure is now fully owned by the HostUSBDevice object and will be
* freed when it is destructed.
* @param aUSBProxyService Pointer to the USB Proxy Service object.
*/
{
/* Enclose the state transition NotReady->InInit->Ready */
AutoInitSpan autoInitSpan (this);
/*
* We need a unique ID for this VBoxSVC session.
* The UUID isn't stored anywhere.
*/
/*
* Convert from USBDEVICESTATE to USBDeviceState.
*
* Note that not all proxy backend can detect the HELD_BY_PROXY
* and USED_BY_GUEST states. But that shouldn't matter much.
*/
{
default:
break;
break;
break;
case USBDEVICESTATE_UNUSED:
break;
break;
/* @todo USBDEVICESTATE_USED_BY_GUEST seems not to be used
* anywhere in the proxy code; it's quite logical because the
* proxy doesn't know anything about guest VMs. */
break;
}
/* Other data members */
mIsStatePending = false;
/* Confirm the successful initialization */
return S_OK;
}
/**
* Uninitializes the instance and sets the ready flag to FALSE.
* Called either from FinalRelease() or by the parent when it gets destroyed.
*/
void HostUSBDevice::uninit()
{
/* Enclose the state transition Ready->InUninit->NotReady */
AutoUninitSpan autoUninitSpan (this);
if (autoUninitSpan.uninitDone())
return;
{
}
}
// IUSBDevice properties
/////////////////////////////////////////////////////////////////////////////
{
if (!aId)
return E_INVALIDARG;
AutoCaller autoCaller (this);
/* mId is constant during life time, no need to lock */
return S_OK;
}
{
if (!aVendorId)
return E_INVALIDARG;
AutoCaller autoCaller (this);
AutoReaderLock alock (this);
return S_OK;
}
{
if (!aProductId)
return E_INVALIDARG;
AutoCaller autoCaller (this);
AutoReaderLock alock (this);
return S_OK;
}
{
if (!aRevision)
return E_INVALIDARG;
AutoCaller autoCaller (this);
AutoReaderLock alock (this);
return S_OK;
}
{
if (!aManufacturer)
return E_INVALIDARG;
AutoCaller autoCaller (this);
AutoReaderLock alock (this);
return S_OK;
}
{
if (!aProduct)
return E_INVALIDARG;
AutoCaller autoCaller (this);
AutoReaderLock alock (this);
return S_OK;
}
{
if (!aSerialNumber)
return E_INVALIDARG;
AutoCaller autoCaller (this);
AutoReaderLock alock (this);
return S_OK;
}
{
if (!aAddress)
return E_INVALIDARG;
AutoCaller autoCaller (this);
AutoReaderLock alock (this);
return S_OK;
}
{
if (!aPort)
return E_INVALIDARG;
AutoCaller autoCaller (this);
AutoReaderLock alock (this);
///@todo implement
aPort = 0;
return S_OK;
}
{
if (!aRemote)
return E_INVALIDARG;
AutoCaller autoCaller (this);
AutoReaderLock alock (this);
return S_OK;
}
// IHostUSBDevice properties
/////////////////////////////////////////////////////////////////////////////
{
if (!aState)
return E_POINTER;
AutoCaller autoCaller (this);
AutoReaderLock alock (this);
return S_OK;
}
// public methods only for internal purposes
////////////////////////////////////////////////////////////////////////////////
/**
* @note Locks this object for reading.
*/
{
AutoCaller autoCaller (this);
AutoReaderLock alock (this);
if (haveManufacturer && haveProduct)
mUsb->pszProduct);
else if (haveManufacturer)
else if (haveProduct)
else
name = "<unknown>";
return name;
}
/**
* Requests the USB proxy service to capture the device and sets the pending
* state to Captured.
*
* If the state change may be performed immediately (for example, Hold ->
* Captured), then the machine is informed before this method returns.
*
* @param aMachine Machine that will capture this device on success.
* @return @c false if the device could be immediately captured
* but the VM process refused to grab it;
* @c true otherwise.
*
* @note Must be called from under the object write lock.
*
* @note May lock the given machine object for reading.
*/
{
LogFlowThisFunc (("\n"));
AssertReturn (aMachine, false);
AssertReturn (isLockedOnCurrentThread(), false);
AssertReturn (mIsStatePending == false, false);
false);
if (mState == USBDeviceState_USBDeviceHeld)
{
/* can perform immediate capture, inform the VM process */
ComPtr <IUSBDevice> d = this;
mIsStatePending = true;
/* the VM process will query the object, so leave the lock */
LogFlowThisFunc (("Calling machine->onUSBDeviceAttach()...\n"));
/* The VM process has a legal reason to fail (for example, incorrect
* usbfs permissions or out of virtual USB ports). More over, the VM
* process might have been accidentially crashed and not accessible
* any more (so that calling an uninitialized SessionMachine returns a
* failure). So don't assert. */
mIsStatePending = false;
{
return true;
}
return false;
}
mIsStatePending = true;
mUSBProxyService->captureDevice (this);
return true;
}
/**
* Requests the USB proxy service to release the device and sets the pending
* state to Available.
*
* If the state change may be performed immediately (for example, the current
* state is Busy), this method does nothing.
*
* @note Must be called from under the object write lock.
*/
void HostUSBDevice::requestRelease()
{
LogFlowThisFunc (("\n"));
AssertReturnVoid (mIsStatePending == false);
if (mState != USBDeviceState_USBDeviceHeld)
return;
mIsStatePending = true;
mUSBProxyService->releaseDevice (this);
}
/**
* Requests the USB proxy service to release the device, sets the pending
* state to Held and removes the machine association if any.
*
* If the state change may be performed immediately (for example, the current
* state is already Held), this method does nothing but removes the machine
* association.
*
* @note Must be called from under the object write lock.
*/
void HostUSBDevice::requestHold()
{
LogFlowThisFunc (("\n"));
AssertReturnVoid (mIsStatePending == false);
if (mState == USBDeviceState_USBDeviceHeld)
return;
mIsStatePending = true;
mUSBProxyService->captureDevice (this);
}
/**
* Sets the device state from Captured to Held and resets the machine
* association (if any). Usually called before applying filters.
*
* @note Must be called from under the object write lock.
*/
void HostUSBDevice::setHeld()
{
LogFlowThisFunc (("\n"));
AssertReturnVoid (mIsStatePending == false);
}
/**
* Resets all device data and informs the machine (if any) about the
* detachment. Must be called when this device is physically detached from
* the host.
*
* @note Must be called from under the object write lock.
*/
void HostUSBDevice::reset()
{
LogFlowThisFunc (("\n"));
{
/* the device is captured by a machine, instruct it to release */
mIsStatePending = true;
/* the VM process will query the object, so leave the lock */
LogFlowThisFunc (("Calling machine->onUSBDeviceDetach()...\n"));
/* This call may expectedly fail with rc = NS_ERROR_FAILURE (on XPCOM)
* if the VM process requests device release right before termination
* and then terminates before onUSBDeviceDetach() reached
* it. Therefore, we don't assert here. On MS COM, there should be
* something similar (with the different error code). More over, the
* VM process might have been accidentially crashed and not accessible
* any more (so that calling an uninitialized SessionMachine returns a
* failure). So don't assert. */
/* Reset all fields. Tthe object should have been
* uninitialized after this method returns, so it doesn't really
* matter what state we put it in. */
mIsStatePending = false;
}
}
/**
* Handles the finished pending state change and informs the VM process if
* necessary.
*
* @note Must be called from under the object write lock.
*/
{
LogFlowThisFunc (("\n"));
AssertReturnVoid (mIsStatePending == true);
bool wasCapture = false;
switch (mPendingState)
{
{
if (mState == USBDeviceState_USBDeviceHeld)
{
wasCapture = true;
else
{
/* it is a canceled capture request. Give the device back
* to the host. */
mUSBProxyService->releaseDevice (this);
}
}
else
{
/* couldn't capture the device, will report an error */
wasCapture = true;
/// @todo more detailed error message depending on the state?
errorText = Utf8StrFmt (
tr ("USB device '%s' with UUID {%Vuuid} is being accessed by the host "
"computer and cannot be attached to the virtual machine."
"Please try later"),
}
break;
}
{
if (mState == USBDeviceState_USBDeviceHeld)
{
/* couldn't release the device (give it back to the host).
* there is nobody to report an error to (the machine has
* already been deassociated because VMM has already detached
* the device before requesting a release). */
}
else
{
/* it is a canceled release request. Leave at the host */
/// @todo we may want to re-run all filters in this case
}
break;
}
{
if (mState == USBDeviceState_USBDeviceHeld)
{
/* All right, the device is now held (due to some global
* filter). */
break;
}
else
{
/* couldn't capture the device requested by the global
* filter, there is nobody to report an error to. */
}
break;
}
default:
AssertFailed();
}
{
LogFlowThisFunc (("Request failed, requestRC=%08X, text='%ls'\n",
}
if (wasCapture)
{
/* inform the VM process */
ComPtr <IUSBDevice> d = this;
/* the VM process will query the object, so leave the lock */
LogFlowThisFunc (("Calling machine->onUSBDeviceAttach()...\n"));
/* The VM process has a legal reason to fail (for example, incorrect
* usbfs permissions or out of virtual USB ports). More over, the VM
* process might have been accidentially crashed and not accessible
* any more (so that calling an uninitialized SessionMachine returns a
* failure). So don't assert. */
/// @todo we will probably want to re-run all filters on failure
{
mIsStatePending = false;
return;
}
/* on failure, either from the proxy or from the VM process,
* deassociate from the machine */
}
mIsStatePending = false;
}
/**
* Cancels pending state change due to machine termination.
*
* @note Must be called from under the object write lock.
*/
void HostUSBDevice::cancelPendingState()
{
LogFlowThisFunc (("\n"));
AssertReturnVoid (mIsStatePending == true);
switch (mPendingState)
{
{
/* reset mMachine to deassociate it from the filter and tell
* handlePendingStateChange() what to do */
break;
}
default:
AssertFailed();
}
}
/**
* Returns true if this device matches the given filter data.
*
* @note It is assumed, that the filter data owner is appropriately
* locked before calling this method.
*
* @note
* This method MUST correlate with
* USBController::hasMatchingFilter (IUSBDevice *)
* in the sense of the device matching logic.
*
* @note Locks this object for reading.
*/
{
AutoCaller autoCaller (this);
AutoReaderLock alock (this);
return false;
{
LogFlowThisFunc (("vendor not match %04X\n",
return false;
}
{
LogFlowThisFunc (("product id not match %04X\n",
return false;
}
{
LogFlowThisFunc (("rev not match %04X\n",
return false;
}
#if !defined (__WIN__)
// these filters are temporarily ignored on Win32
return false;
return false;
return false;
/// @todo (dmik) pusPort is yet absent
// if (!aData.mPort.isMatch (Bstr (mUsb->pusPort)))
// return false;
#endif
// Host USB devices are local, so remote is always FALSE
{
LogFlowMember (("Host::HostUSBDevice: remote not match FALSE\n"));
return false;
}
/// @todo (dmik): bird, I assumed isMatch() is called only for devices
// is just attached it first goes to our filter driver, and only after applying
// filters goes back to the system when appropriate). So the below
// doesn't look too correct; moreover, currently there is no determinable
// "any match" state for intervalic filters, and it will be not so easy
// to determine this state for an arbitrary regexp expression...
// For now, I just check that the string filter is empty (which doesn't
// actually reflect all possible "any match" filters).
//
// bird: This method was called for any device some weeks back, and it most certainly
// should be called for 'busy' devices still. However, we do *not* want 'busy' devices
// to match empty filters (because that will for instance capture all USB keyboards & mice).
// You assumption about a filter driver is not correct on linux. We're racing with
// everyone else in the system there - see your problem with usbfs access.
//
// The customer *requires* a way of matching all devices which the host isn't using,
// if that is now difficult or the below method opens holes in the matching, this *must*
// be addresses immediately.
/*
* If all the criteria is empty, devices which are used by the host will not match.
*/
return false;
LogFlowThisFunc (("returns true\n"));
return true;
}
/**
* Compares this device with a USBDEVICE and decides which comes first.
*
* If the device has a pending state request, a non-strict comparison is made
* (to properly detect a re-attached device). Otherwise, a strict comparison
* is performed.
*
* @param aDev2 Device 2.
*
* @return < 0 if this should come before aDev2.
* @return 0 if this and aDev2 are equal.
* @return > 0 if this should come after aDev2.
*
* @note Must be called from under the object write lock.
*/
{
}
/**
* Compares two USB devices and decides which comes first.
*
* If @a aIsStrict is @c true then the comparison will indicate a difference
* even if the same physical device (represented by @a aDev1) has been just
* re-attached to the host computer (represented by @a aDev2) and got a
* different address from the host OS, etc.
*
* If @a aIsStrict is @c false, then such a re-attached device will be
* considered equal to the previous device definition and this function will
* retrun 0.
*
* @param aDev1 Device 1.
* @param aDev2 Device 2.
* @param aIsStrict @c true to do a strict check and @c false otherwise.
* @return < 0 if aDev1 should come before aDev2.
* @return 0 if aDev1 and aDev2 are equal.
* @return > 0 if aDev1 should come after aDev2.
*/
/*static*/
bool aIsStrict /* = true */)
{
if (iDiff)
return iDiff;
if (iDiff)
return iDiff;
if (!aIsStrict)
return 0;
/* The rest is considered as a strict check. */
}
/**
* Updates the state of the device.
*
* If this method returns @c true, Host::onUSBDeviceStateChanged() will be
* called to process the state change (complete the state change request,
* inform the VM process etc.).
*
* If this method returns @c false, it is assumed that the given state change
* is "minor": it doesn't require any further action other than update the
* mState field with the actual state value.
*
* @param aDev The current device state as seen by the proxy backend.
*
* @note Locks this object for writing.
*/
{
LogFlowThisFunc (("\n"));
AssertReturn (isLockedOnCurrentThread(), false);
AutoCaller autoCaller (this);
bool isImportant = false;
/*
* We have to be pretty conservative here because the proxy backend
* doesn't necessarily know everything that's going on. So, rather
* be overly careful than changing the state once when we shouldn't!
*
* In particular, we treat changing between three states Unavailable, Busy
* and Available as non-important (because they all mean that the device
* is owned by the host) and return false in this case. We may want to
* change it later and, e.g. re-run all USB filters when the device goes from
* from Busy to Available).
*/
{
default:
return false;
switch (mState)
{
return false;
/* the following state changes don't require any action for now */
isImportant = false;
break;
default:
isImportant = true;
}
LogFlowThisFunc (("%d -> %d\n",
return isImportant;
switch (mState)
{
return false;
/* the following state changes don't require any action for now */
isImportant = false;
break;
default:
isImportant = true;
}
LogFlowThisFunc (("%d -> %d\n",
return isImportant;
case USBDEVICESTATE_UNUSED:
switch (mState)
{
return false;
if (!mIsStatePending)
return false;
isImportant = true;
break;
#endif
/* the following state changes don't require any action for now */
isImportant = false;
break;
default:
isImportant = true;
}
LogFlowThisFunc (("%d -> %d\n",
return isImportant;
switch (mState)
{
return false;
default:
LogFlowThisFunc (("%d -> %d\n",
return true;
}
break;
/* @todo USBDEVICESTATE_USED_BY_GUEST seems not to be used
* anywhere in the proxy code; it's quite logical because the
* proxy doesn't know anything about guest VMs. */
AssertFailed();
#if 0
switch (mState)
{
/* the proxy may confuse following state(s) with captured */
return false;
default:
LogFlowThisFunc (("%d -> %d\n",
return true;
}
#endif
break;
}
return false;
}