VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/USBProxyBackend.cpp

Last change on this file was 99739, checked in by vboxsync, 12 months ago

*: doxygen corrections (mostly about removing @returns from functions returning void).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.2 KB
Line 
1/* $Id: USBProxyBackend.cpp 99739 2023-05-11 01:01:08Z vboxsync $ */
2/** @file
3 * VirtualBox USB Proxy Service (base) class.
4 */
5
6/*
7 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28#define LOG_GROUP LOG_GROUP_MAIN_USBPROXYBACKEND
29#include "USBProxyBackend.h"
30#include "USBProxyService.h"
31#include "HostUSBDeviceImpl.h"
32#include "HostImpl.h"
33#include "MachineImpl.h"
34#include "VirtualBoxImpl.h"
35
36#include "AutoCaller.h"
37#include "LoggingNew.h"
38
39#include <VBox/com/array.h>
40#include <iprt/errcore.h>
41#include <iprt/asm.h>
42#include <iprt/semaphore.h>
43#include <iprt/thread.h>
44#include <iprt/mem.h>
45#include <iprt/string.h>
46
47
48/**
49 * Empty constructor.
50 */
51USBProxyBackend::USBProxyBackend()
52{
53 LogFlowThisFunc(("\n"));
54}
55
56
57/**
58 * Empty destructor.
59 */
60USBProxyBackend::~USBProxyBackend()
61{
62}
63
64
65HRESULT USBProxyBackend::FinalConstruct()
66{
67 return BaseFinalConstruct();
68}
69
70void USBProxyBackend::FinalRelease()
71{
72 uninit();
73 BaseFinalRelease();
74}
75
76/**
77 * Stub needed as long as the class isn't virtual
78 */
79int USBProxyBackend::init(USBProxyService *pUsbProxyService, const com::Utf8Str &strId,
80 const com::Utf8Str &strAddress, bool fLoadingSettings)
81{
82 RT_NOREF1(fLoadingSettings);
83
84 m_pUsbProxyService = pUsbProxyService;
85 mThread = NIL_RTTHREAD;
86 mTerminate = false;
87 unconst(m_strId) = strId;
88 m_cRefs = 0;
89 unconst(m_strAddress) = strAddress;
90
91 unconst(m_strBackend) = Utf8Str::Empty;
92
93 return VINF_SUCCESS;
94}
95
96
97void USBProxyBackend::uninit()
98{
99 LogFlowThisFunc(("\n"));
100 Assert(mThread == NIL_RTTHREAD);
101 mTerminate = true;
102 m_pUsbProxyService = NULL;
103 m_llDevices.clear();
104}
105
106/**
107 * Query if the service is active and working.
108 *
109 * @returns true if the service is up running.
110 * @returns false if the service isn't running.
111 */
112bool USBProxyBackend::isActive(void)
113{
114 return mThread != NIL_RTTHREAD;
115}
116
117
118/**
119 * Returns the ID of the instance.
120 *
121 * @returns ID string for the instance.
122 */
123const com::Utf8Str &USBProxyBackend::i_getId()
124{
125 return m_strId;
126}
127
128
129/**
130 * Returns the address of the instance.
131 *
132 * @returns ID string for the instance.
133 */
134const com::Utf8Str &USBProxyBackend::i_getAddress()
135{
136 return m_strAddress;
137}
138
139
140/**
141 * Returns the backend of the instance.
142 *
143 * @returns ID string for the instance.
144 */
145const com::Utf8Str &USBProxyBackend::i_getBackend()
146{
147 return m_strBackend;
148}
149
150/**
151 * Returns the current reference counter for the backend.
152 */
153uint32_t USBProxyBackend::i_getRefCount()
154{
155 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
156 return m_cRefs;
157}
158
159
160/**
161 * A filter was inserted / loaded.
162 *
163 * @param aFilter Pointer to the inserted filter.
164 * @return ID of the inserted filter
165 */
166void *USBProxyBackend::insertFilter(PCUSBFILTER aFilter)
167{
168 // return non-NULL to fake success.
169 NOREF(aFilter);
170 return (void *)1;
171}
172
173
174/**
175 * A filter was removed.
176 *
177 * @param aId ID of the filter to remove
178 */
179void USBProxyBackend::removeFilter(void *aId)
180{
181 NOREF(aId);
182}
183
184
185/**
186 * A VM is trying to capture a device, do necessary preparations.
187 *
188 * @returns VBox status code.
189 * @param aDevice The device in question.
190 */
191int USBProxyBackend::captureDevice(HostUSBDevice *aDevice)
192{
193 NOREF(aDevice);
194 return VERR_NOT_IMPLEMENTED;
195}
196
197
198/**
199 * Notification that an async captureDevice() operation completed.
200 *
201 * This is used by the proxy to release temporary filters.
202 *
203 * @param aDevice The device in question.
204 * @param aSuccess Whether it succeeded or failed.
205 */
206void USBProxyBackend::captureDeviceCompleted(HostUSBDevice *aDevice, bool aSuccess)
207{
208 NOREF(aDevice);
209 NOREF(aSuccess);
210
211 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
212 incRef();
213}
214
215
216/**
217 * A VM is releasing a device back to the host.
218 *
219 * @returns VBox status code.
220 * @param aDevice The device in question.
221 */
222int USBProxyBackend::releaseDevice(HostUSBDevice *aDevice)
223{
224 NOREF(aDevice);
225 return VERR_NOT_IMPLEMENTED;
226}
227
228
229/**
230 * Notification that an async releaseDevice() operation completed.
231 *
232 * This is used by the proxy to release temporary filters.
233 *
234 * @param aDevice The device in question.
235 * @param aSuccess Whether it succeeded or failed.
236 */
237void USBProxyBackend::releaseDeviceCompleted(HostUSBDevice *aDevice, bool aSuccess)
238{
239 NOREF(aDevice);
240 NOREF(aSuccess);
241
242 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
243 decRef();
244}
245
246
247bool USBProxyBackend::isFakeUpdateRequired()
248{
249 return false;
250}
251
252/**
253 * Returns whether devices reported by this backend go through a de/re-attach
254 * and device re-enumeration cycle when they are captured or released.
255 */
256bool USBProxyBackend::i_isDevReEnumerationRequired()
257{
258 return false;
259}
260
261// Internals
262/////////////////////////////////////////////////////////////////////////////
263
264
265/**
266 * Starts the service.
267 *
268 * @returns VBox status code.
269 */
270int USBProxyBackend::start(void)
271{
272 int vrc = VINF_SUCCESS;
273 if (mThread == NIL_RTTHREAD)
274 {
275 /*
276 * Force update before starting the poller thread.
277 */
278 vrc = wait(0);
279 if (vrc == VERR_TIMEOUT || vrc == VERR_INTERRUPTED || RT_SUCCESS(vrc))
280 {
281 PUSBDEVICE pDevices = getDevices();
282 updateDeviceList(pDevices);
283
284 /*
285 * Create the poller thread which will look for changes.
286 */
287 mTerminate = false;
288 vrc = RTThreadCreate(&mThread, USBProxyBackend::serviceThread, this,
289 0, RTTHREADTYPE_INFREQUENT_POLLER, RTTHREADFLAGS_WAITABLE, "USBPROXY");
290 AssertRC(vrc);
291 if (RT_SUCCESS(vrc))
292 LogFlowThisFunc(("started mThread=%RTthrd\n", mThread));
293 else
294 mThread = NIL_RTTHREAD;
295 }
296 }
297 else
298 LogFlowThisFunc(("already running, mThread=%RTthrd\n", mThread));
299 return vrc;
300}
301
302
303/**
304 * Stops the service.
305 *
306 * @returns VBox status code.
307 */
308int USBProxyBackend::stop(void)
309{
310 int vrc = VINF_SUCCESS;
311 if (mThread != NIL_RTTHREAD)
312 {
313 /*
314 * Mark the thread for termination and kick it.
315 */
316 ASMAtomicXchgSize(&mTerminate, true);
317 vrc = interruptWait();
318 AssertRC(vrc);
319
320 /*
321 * Wait for the thread to finish and then update the state.
322 */
323 vrc = RTThreadWait(mThread, 60000, NULL);
324 if (vrc == VERR_INVALID_HANDLE)
325 vrc = VINF_SUCCESS;
326 if (RT_SUCCESS(vrc))
327 {
328 LogFlowThisFunc(("stopped mThread=%RTthrd\n", mThread));
329 mThread = NIL_RTTHREAD;
330 mTerminate = false;
331 }
332 else
333 AssertRC(vrc);
334 }
335 else
336 LogFlowThisFunc(("not active\n"));
337
338 /* Make sure there is no device from us in the list anymore. */
339 updateDeviceList(NULL);
340
341 return vrc;
342}
343
344
345/**
346 * The service thread created by start().
347 *
348 * @param Thread The thread handle.
349 * @param pvUser Pointer to the USBProxyBackend instance.
350 */
351/*static*/ DECLCALLBACK(int) USBProxyBackend::serviceThread(RTTHREAD /* Thread */, void *pvUser)
352{
353 USBProxyBackend *pThis = (USBProxyBackend *)pvUser;
354 LogFlowFunc(("pThis=%p\n", pThis));
355 pThis->serviceThreadInit();
356 int vrc = VINF_SUCCESS;
357
358 /*
359 * Processing loop.
360 */
361 for (;;)
362 {
363 vrc = pThis->wait(RT_INDEFINITE_WAIT);
364 if (RT_FAILURE(vrc) && vrc != VERR_INTERRUPTED && vrc != VERR_TIMEOUT)
365 break;
366 if (pThis->mTerminate)
367 break;
368
369 PUSBDEVICE pDevices = pThis->getDevices();
370 pThis->updateDeviceList(pDevices);
371 }
372
373 pThis->serviceThreadTerm();
374 LogFlowFunc(("returns %Rrc\n", vrc));
375 return vrc;
376}
377
378
379/**
380 * First call made on the service thread, use it to do
381 * thread initialization.
382 *
383 * The default implementation in USBProxyBackend just a dummy stub.
384 */
385void USBProxyBackend::serviceThreadInit(void)
386{
387}
388
389
390/**
391 * Last call made on the service thread, use it to do
392 * thread termination.
393 */
394void USBProxyBackend::serviceThreadTerm(void)
395{
396}
397
398
399/**
400 * Wait for a change in the USB devices attached to the host.
401 *
402 * The default implementation in USBProxyBackend just a dummy stub.
403 *
404 * @returns VBox status code. VERR_INTERRUPTED and VERR_TIMEOUT are considered
405 * harmless, while all other error status are fatal.
406 * @param aMillies Number of milliseconds to wait.
407 */
408int USBProxyBackend::wait(RTMSINTERVAL aMillies)
409{
410 return RTThreadSleep(RT_MIN(aMillies, 250));
411}
412
413
414/**
415 * Interrupt any wait() call in progress.
416 *
417 * The default implementation in USBProxyBackend just a dummy stub.
418 *
419 * @returns VBox status code.
420 */
421int USBProxyBackend::interruptWait(void)
422{
423 return VERR_NOT_IMPLEMENTED;
424}
425
426
427/**
428 * Get a list of USB device currently attached to the host.
429 *
430 * The default implementation in USBProxyBackend just a dummy stub.
431 *
432 * @returns Pointer to a list of USB devices.
433 * The list nodes are freed individually by calling freeDevice().
434 */
435PUSBDEVICE USBProxyBackend::getDevices(void)
436{
437 return NULL;
438}
439
440
441/**
442 * Increments the reference counter.
443 *
444 * @returns New reference count value.
445 */
446uint32_t USBProxyBackend::incRef()
447{
448 Assert(isWriteLockOnCurrentThread());
449
450 return ++m_cRefs;
451}
452
453/**
454 * Decrements the reference counter.
455 *
456 * @returns New reference count value.
457 */
458uint32_t USBProxyBackend::decRef()
459{
460 Assert(isWriteLockOnCurrentThread());
461
462 return --m_cRefs;
463}
464
465
466/**
467 * Free all the members of a USB device returned by getDevice().
468 *
469 * @param pDevice Pointer to the device.
470 */
471/*static*/ void
472USBProxyBackend::freeDeviceMembers(PUSBDEVICE pDevice)
473{
474 RTStrFree((char *)pDevice->pszManufacturer);
475 pDevice->pszManufacturer = NULL;
476 RTStrFree((char *)pDevice->pszProduct);
477 pDevice->pszProduct = NULL;
478 RTStrFree((char *)pDevice->pszSerialNumber);
479 pDevice->pszSerialNumber = NULL;
480
481 RTStrFree((char *)pDevice->pszAddress);
482 pDevice->pszAddress = NULL;
483 RTStrFree((char *)pDevice->pszBackend);
484 pDevice->pszBackend = NULL;
485#ifdef RT_OS_WINDOWS
486 RTStrFree(pDevice->pszAltAddress);
487 pDevice->pszAltAddress = NULL;
488 RTStrFree(pDevice->pszHubName);
489 pDevice->pszHubName = NULL;
490#elif defined(RT_OS_SOLARIS)
491 RTStrFree(pDevice->pszDevicePath);
492 pDevice->pszDevicePath = NULL;
493#endif
494}
495
496
497/**
498 * Free one USB device returned by getDevice().
499 *
500 * @param pDevice Pointer to the device.
501 */
502/*static*/ void
503USBProxyBackend::freeDevice(PUSBDEVICE pDevice)
504{
505 freeDeviceMembers(pDevice);
506 RTMemFree(pDevice);
507}
508
509void USBProxyBackend::deviceAdded(ComObjPtr<HostUSBDevice> &aDevice, PUSBDEVICE pDev)
510{
511 /* Nothing to do. */
512 NOREF(aDevice);
513 NOREF(pDev);
514}
515
516/**
517 * Initializes a filter with the data from the specified device.
518 *
519 * @param aFilter The filter to fill.
520 * @param aDevice The device to fill it with.
521 */
522/*static*/ void
523USBProxyBackend::initFilterFromDevice(PUSBFILTER aFilter, HostUSBDevice *aDevice)
524{
525 PCUSBDEVICE pDev = aDevice->i_getUsbData();
526 int vrc;
527
528 vrc = USBFilterSetNumExact(aFilter, USBFILTERIDX_VENDOR_ID, pDev->idVendor, true); AssertRC(vrc);
529 vrc = USBFilterSetNumExact(aFilter, USBFILTERIDX_PRODUCT_ID, pDev->idProduct, true); AssertRC(vrc);
530 vrc = USBFilterSetNumExact(aFilter, USBFILTERIDX_DEVICE_REV, pDev->bcdDevice, true); AssertRC(vrc);
531 vrc = USBFilterSetNumExact(aFilter, USBFILTERIDX_DEVICE_CLASS, pDev->bDeviceClass, true); AssertRC(vrc);
532 vrc = USBFilterSetNumExact(aFilter, USBFILTERIDX_DEVICE_SUB_CLASS, pDev->bDeviceSubClass, true); AssertRC(vrc);
533 vrc = USBFilterSetNumExact(aFilter, USBFILTERIDX_DEVICE_PROTOCOL, pDev->bDeviceProtocol, true); AssertRC(vrc);
534 vrc = USBFilterSetNumExact(aFilter, USBFILTERIDX_PORT, pDev->bPort, false); AssertRC(vrc);
535 vrc = USBFilterSetNumExact(aFilter, USBFILTERIDX_BUS, pDev->bBus, false); AssertRC(vrc);
536 if (pDev->pszSerialNumber)
537 {
538 vrc = USBFilterSetStringExact(aFilter, USBFILTERIDX_SERIAL_NUMBER_STR, pDev->pszSerialNumber,
539 true /*fMustBePresent*/, true /*fPurge*/);
540 AssertRC(vrc);
541 }
542 if (pDev->pszProduct)
543 {
544 vrc = USBFilterSetStringExact(aFilter, USBFILTERIDX_PRODUCT_STR, pDev->pszProduct,
545 true /*fMustBePresent*/, true /*fPurge*/);
546 AssertRC(vrc);
547 }
548 if (pDev->pszManufacturer)
549 {
550 vrc = USBFilterSetStringExact(aFilter, USBFILTERIDX_MANUFACTURER_STR, pDev->pszManufacturer,
551 true /*fMustBePresent*/, true /*fPurge*/);
552 AssertRC(vrc);
553 }
554}
555
556HRESULT USBProxyBackend::getName(com::Utf8Str &aName)
557{
558 /* strId is constant during life time, no need to lock */
559 aName = m_strId;
560 return S_OK;
561}
562
563HRESULT USBProxyBackend::getType(com::Utf8Str &aType)
564{
565 aType = Utf8Str::Empty;
566 return S_OK;
567}
568
569/**
570 * Sort a list of USB devices.
571 *
572 * @returns Pointer to the head of the sorted doubly linked list.
573 * @param pDevices Head pointer (can be both singly and doubly linked list).
574 */
575static PUSBDEVICE sortDevices(PUSBDEVICE pDevices)
576{
577 PUSBDEVICE pHead = NULL;
578 PUSBDEVICE pTail = NULL;
579 while (pDevices)
580 {
581 /* unlink head */
582 PUSBDEVICE pDev = pDevices;
583 pDevices = pDev->pNext;
584 if (pDevices)
585 pDevices->pPrev = NULL;
586
587 /* find location. */
588 PUSBDEVICE pCur = pTail;
589 while ( pCur
590 && HostUSBDevice::i_compare(pCur, pDev) > 0)
591 pCur = pCur->pPrev;
592
593 /* insert (after pCur) */
594 pDev->pPrev = pCur;
595 if (pCur)
596 {
597 pDev->pNext = pCur->pNext;
598 pCur->pNext = pDev;
599 if (pDev->pNext)
600 pDev->pNext->pPrev = pDev;
601 else
602 pTail = pDev;
603 }
604 else
605 {
606 pDev->pNext = pHead;
607 if (pHead)
608 pHead->pPrev = pDev;
609 else
610 pTail = pDev;
611 pHead = pDev;
612 }
613 }
614
615 LogFlowFuncLeave();
616 return pHead;
617}
618
619
620/**
621 * Process any relevant changes in the attached USB devices.
622 *
623 * This is called from any available USB proxy backends service thread when they discover
624 * a change.
625 */
626void USBProxyBackend::updateDeviceList(PUSBDEVICE pDevices)
627{
628 LogFlowThisFunc(("\n"));
629
630 pDevices = sortDevices(pDevices);
631
632 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
633
634 /*
635 * Compare previous list with the new list of devices
636 * and merge in any changes while notifying Host.
637 */
638 HostUSBDeviceList::iterator it = this->m_llDevices.begin();
639 while ( it != m_llDevices.end()
640 || pDevices)
641 {
642 ComObjPtr<HostUSBDevice> pHostDevice;
643
644 if (it != m_llDevices.end())
645 pHostDevice = *it;
646
647 /*
648 * Assert that the object is still alive (we still reference it in
649 * the collection and we're the only one who calls uninit() on it.
650 */
651 AutoCaller devCaller(pHostDevice.isNull() ? NULL : pHostDevice);
652 AssertComRC(devCaller.hrc());
653
654 /*
655 * Lock the device object since we will read/write its
656 * properties. All Host callbacks also imply the object is locked.
657 */
658 AutoWriteLock devLock(pHostDevice.isNull() ? NULL : pHostDevice
659 COMMA_LOCKVAL_SRC_POS);
660
661 /* We should never get devices from other backends here. */
662 Assert(pHostDevice.isNull() || pHostDevice->i_getUsbProxyBackend() == this);
663
664 /*
665 * Compare.
666 */
667 int iDiff;
668 if (pHostDevice.isNull())
669 iDiff = 1;
670 else
671 {
672 if (!pDevices)
673 iDiff = -1;
674 else
675 iDiff = pHostDevice->i_compare(pDevices);
676 }
677 if (!iDiff)
678 {
679 /*
680 * The device still there, update the state and move on. The PUSBDEVICE
681 * structure is eaten by updateDeviceState / HostUSBDevice::updateState().
682 */
683 PUSBDEVICE pCur = pDevices;
684 pDevices = pDevices->pNext;
685 pCur->pPrev = pCur->pNext = NULL;
686
687 devLock.release();
688 alock.release();
689 m_pUsbProxyService->i_updateDeviceState(pHostDevice, pCur, isFakeUpdateRequired());
690 alock.acquire();
691 ++it;
692 }
693 else
694 {
695 if (iDiff > 0)
696 {
697 /*
698 * Head of pDevices was attached.
699 */
700 PUSBDEVICE pNew = pDevices;
701 pDevices = pDevices->pNext;
702 pNew->pPrev = pNew->pNext = NULL;
703
704 ComObjPtr<HostUSBDevice> NewObj;
705 NewObj.createObject();
706 NewObj->init(pNew, this);
707 LogFlowThisFunc(("attached %p {%s} %s / %p:{.idVendor=%#06x, .idProduct=%#06x, .pszProduct=\"%s\", .pszManufacturer=\"%s\"}\n",
708 (HostUSBDevice *)NewObj,
709 NewObj->i_getName().c_str(),
710 NewObj->i_getStateName(),
711 pNew,
712 pNew->idVendor,
713 pNew->idProduct,
714 pNew->pszProduct,
715 pNew->pszManufacturer));
716
717 m_llDevices.insert(it, NewObj);
718
719 devLock.release();
720 alock.release();
721 /* Do any backend specific work. */
722 deviceAdded(NewObj, pNew);
723 m_pUsbProxyService->i_deviceAdded(NewObj, pNew);
724 alock.acquire();
725 }
726 else
727 {
728 /*
729 * Check if the device was actually detached or logically detached
730 * as the result of a re-enumeration.
731 */
732 if (!pHostDevice->i_wasActuallyDetached())
733 ++it;
734 else
735 {
736 it = m_llDevices.erase(it);
737 devLock.release();
738 alock.release();
739 m_pUsbProxyService->i_deviceRemoved(pHostDevice);
740 LogFlowThisFunc(("detached %p {%s}\n",
741 (HostUSBDevice *)pHostDevice,
742 pHostDevice->i_getName().c_str()));
743
744 /* from now on, the object is no more valid,
745 * uninitialize to avoid abuse */
746 devCaller.release();
747 pHostDevice->uninit();
748 alock.acquire();
749 }
750 }
751 }
752 } /* while */
753
754 LogFlowThisFunc(("returns void\n"));
755}
756
757/* vi: set tabstop=4 shiftwidth=4 expandtab: */
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use