diff options
Diffstat (limited to 'src/VBox/HostDrivers/VBoxUSB/darwin/VBoxUSB.cpp')
-rw-r--r-- | src/VBox/HostDrivers/VBoxUSB/darwin/VBoxUSB.cpp | 1890 |
1 files changed, 1890 insertions, 0 deletions
diff --git a/src/VBox/HostDrivers/VBoxUSB/darwin/VBoxUSB.cpp b/src/VBox/HostDrivers/VBoxUSB/darwin/VBoxUSB.cpp new file mode 100644 index 00000000..4b7868f1 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/darwin/VBoxUSB.cpp @@ -0,0 +1,1890 @@ +/* $Id: VBoxUSB.cpp $ */ +/** @file + * VirtualBox USB driver for Darwin. + * + * This driver is responsible for hijacking USB devices when any of the + * VBoxSVC daemons requests it. It is also responsible for arbitrating + * access to hijacked USB devices. + */ + +/* + * Copyright (C) 2006-2019 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. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_USB_DRV +/* Deal with conflicts first. + * (This is mess inherited from BSD. The *BSDs has clean this up long ago.) */ +#include <sys/param.h> +#undef PVM +#include <IOKit/IOLib.h> /* Assert as function */ + +#include "VBoxUSBInterface.h" +#include "VBoxUSBFilterMgr.h" +#include <VBox/version.h> +#include <VBox/usblib-darwin.h> +#include <VBox/log.h> +#include <iprt/types.h> +#include <iprt/initterm.h> +#include <iprt/assert.h> +#include <iprt/semaphore.h> +#include <iprt/process.h> +#include <iprt/alloc.h> +#include <iprt/errcore.h> +#include <iprt/asm.h> + +#include <mach/kmod.h> +#include <miscfs/devfs/devfs.h> +#include <sys/conf.h> +#include <sys/errno.h> +#include <sys/ioccom.h> +#include <sys/malloc.h> +#include <sys/proc.h> +#include <kern/task.h> +#include <IOKit/IOService.h> +#include <IOKit/IOUserClient.h> +#include <IOKit/IOKitKeys.h> +#include <IOKit/usb/USB.h> +#include <IOKit/usb/IOUSBDevice.h> +#include <IOKit/usb/IOUSBInterface.h> +#include <IOKit/usb/IOUSBUserClient.h> + +/* private: */ +RT_C_DECLS_BEGIN +extern void *get_bsdtask_info(task_t); +RT_C_DECLS_END + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Locks the lists. */ +#define VBOXUSB_LOCK() do { int rc = RTSemFastMutexRequest(g_Mtx); AssertRC(rc); } while (0) +/** Unlocks the lists. */ +#define VBOXUSB_UNLOCK() do { int rc = RTSemFastMutexRelease(g_Mtx); AssertRC(rc); } while (0) + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +RT_C_DECLS_BEGIN +static kern_return_t VBoxUSBStart(struct kmod_info *pKModInfo, void *pvData); +static kern_return_t VBoxUSBStop(struct kmod_info *pKModInfo, void *pvData); +RT_C_DECLS_END + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * The service class. + * + * This is the management service that VBoxSVC and the VMs speak to. + * + * @remark The method prototypes are ordered somewhat after their order of + * invocation, while the implementation is ordered by pair. + */ +class org_virtualbox_VBoxUSB : public IOService +{ + OSDeclareDefaultStructors(org_virtualbox_VBoxUSB); + +public: + RTR0MEMEF_NEW_AND_DELETE_OPERATORS_IOKIT(); + + /** @name IOService + * @{ */ + virtual bool init(OSDictionary *pDictionary = 0); + virtual bool start(IOService *pProvider); + virtual bool open(IOService *pForClient, IOOptionBits fOptions = 0, void *pvArg = 0); + virtual bool terminate(IOOptionBits fOptions); + virtual void close(IOService *pForClient, IOOptionBits fOptions = 0); + virtual void stop(IOService *pProvider); + virtual void free(); + /** @} */ + +private: + /** Guard against the parent class growing and us using outdated headers. */ + uint8_t m_abSafetyPadding[256]; +}; +OSDefineMetaClassAndStructors(org_virtualbox_VBoxUSB, IOService); + + +/** + * The user client class that pairs up with org_virtualbox_VBoxUSB. + */ +class org_virtualbox_VBoxUSBClient : public IOUserClient +{ + OSDeclareDefaultStructors(org_virtualbox_VBoxUSBClient); + +public: + RTR0MEMEF_NEW_AND_DELETE_OPERATORS_IOKIT(); + + /** @name IOService & IOUserClient + * @{ */ + virtual bool initWithTask(task_t OwningTask, void *pvSecurityId, UInt32 u32Type); + virtual bool start(IOService *pProvider); + virtual IOReturn clientClose(void); + virtual IOReturn clientDied(void); + virtual bool terminate(IOOptionBits fOptions = 0); + virtual bool finalize(IOOptionBits fOptions); + virtual void stop(IOService *pProvider); + virtual void free(); + virtual IOExternalMethod *getTargetAndMethodForIndex(IOService **ppService, UInt32 iMethod); + /** @} */ + + /** @name User client methods + * @{ */ + IOReturn addFilter(PUSBFILTER pFilter, PVBOXUSBADDFILTEROUT pOut, IOByteCount cbFilter, IOByteCount *pcbOut); + IOReturn removeFilter(uintptr_t *puId, int *prc, IOByteCount cbIn, IOByteCount *pcbOut); + /** @} */ + + static bool isClientTask(task_t ClientTask); + +private: + /** Guard against the parent class growing and us using outdated headers. */ + uint8_t m_abSafetyPadding[256]; + /** The service provider. */ + org_virtualbox_VBoxUSB *m_pProvider; + /** The client task. */ + task_t m_Task; + /** The client process. */ + RTPROCESS m_Process; + /** Pointer to the next user client. */ + org_virtualbox_VBoxUSBClient * volatile m_pNext; + /** List of user clients. Protected by g_Mtx. */ + static org_virtualbox_VBoxUSBClient * volatile s_pHead; +}; +OSDefineMetaClassAndStructors(org_virtualbox_VBoxUSBClient, IOUserClient); + + +/** + * The IOUSBDevice driver class. + * + * The main purpose of this is hijack devices matching current filters. + * + * @remarks This is derived from IOUSBUserClientInit instead of IOService because we must make + * sure IOUSBUserClientInit::start() gets invoked for this provider. The problem is that + * there is some kind of magic that prevents this from happening if we boost the probe + * score to high. With the result that we don't have the required plugin entry for + * user land and consequently cannot open it. + * + * So, to avoid having to write a lot of code we just inherit from IOUSBUserClientInit + * and make some possibly bold assumptions about it not changing. This just means + * we'll have to keep an eye on the source apple releases or only call + * IOUSBUserClientInit::start() and hand the rest of the super calls to IOService. For + * now we're doing it by the C++ book. + */ +class org_virtualbox_VBoxUSBDevice : public IOUSBUserClientInit +{ + OSDeclareDefaultStructors(org_virtualbox_VBoxUSBDevice); + +public: + RTR0MEMEF_NEW_AND_DELETE_OPERATORS_IOKIT(); + + /** @name IOService + * @{ */ + virtual bool init(OSDictionary *pDictionary = 0); + virtual IOService *probe(IOService *pProvider, SInt32 *pi32Score); + virtual bool start(IOService *pProvider); + virtual bool terminate(IOOptionBits fOptions = 0); + virtual void stop(IOService *pProvider); + virtual void free(); + virtual IOReturn message(UInt32 enmMsg, IOService *pProvider, void *pvArg = 0); + /** @} */ + + static void scheduleReleaseByOwner(RTPROCESS Owner); +private: + /** Padding to guard against parent class expanding (see class remarks). */ + uint8_t m_abSafetyPadding[256]; + /** The interface we're driving (aka. the provider). */ + IOUSBDevice *m_pDevice; + /** The owner process, meaning the VBoxSVC process. */ + RTPROCESS volatile m_Owner; + /** The client process, meaning the VM process. */ + RTPROCESS volatile m_Client; + /** The ID of the matching filter. */ + uintptr_t m_uId; + /** Have we opened the device or not? */ + bool volatile m_fOpen; + /** Should be open the device on the next close notification message? */ + bool volatile m_fOpenOnWasClosed; + /** Whether to re-enumerate this device when the client closes it. + * This is something we'll do when the filter owner dies. */ + bool volatile m_fReleaseOnClose; + /** Whether we're being unloaded or not. + * Only valid in stop(). */ + bool m_fBeingUnloaded; + /** Pointer to the next device in the list. */ + org_virtualbox_VBoxUSBDevice * volatile m_pNext; + /** Pointer to the list head. Protected by g_Mtx. */ + static org_virtualbox_VBoxUSBDevice * volatile s_pHead; + +#ifdef DEBUG + /** The interest notifier. */ + IONotifier *m_pNotifier; + + static IOReturn MyInterestHandler(void *pvTarget, void *pvRefCon, UInt32 enmMsgType, + IOService *pProvider, void * pvMsgArg, vm_size_t cbMsgArg); +#endif +}; +OSDefineMetaClassAndStructors(org_virtualbox_VBoxUSBDevice, IOUSBUserClientInit); + + +/** + * The IOUSBInterface driver class. + * + * The main purpose of this is hijack interfaces which device is driven + * by org_virtualbox_VBoxUSBDevice. + * + * @remarks See org_virtualbox_VBoxUSBDevice for why we use IOUSBUserClientInit. + */ +class org_virtualbox_VBoxUSBInterface : public IOUSBUserClientInit +{ + OSDeclareDefaultStructors(org_virtualbox_VBoxUSBInterface); + +public: + RTR0MEMEF_NEW_AND_DELETE_OPERATORS_IOKIT(); + + /** @name IOService + * @{ */ + virtual bool init(OSDictionary *pDictionary = 0); + virtual IOService *probe(IOService *pProvider, SInt32 *pi32Score); + virtual bool start(IOService *pProvider); + virtual bool terminate(IOOptionBits fOptions = 0); + virtual void stop(IOService *pProvider); + virtual void free(); + virtual IOReturn message(UInt32 enmMsg, IOService *pProvider, void *pvArg = 0); + /** @} */ + +private: + /** Padding to guard against parent class expanding (see class remarks). */ + uint8_t m_abSafetyPadding[256]; + /** The interface we're driving (aka. the provider). */ + IOUSBInterface *m_pInterface; + /** Have we opened the device or not? */ + bool volatile m_fOpen; + /** Should be open the device on the next close notification message? */ + bool volatile m_fOpenOnWasClosed; +}; +OSDefineMetaClassAndStructors(org_virtualbox_VBoxUSBInterface, IOUSBUserClientInit); + + + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/* + * Declare the module stuff. + */ +RT_C_DECLS_BEGIN +extern kern_return_t _start(struct kmod_info *pKModInfo, void *pvData); +extern kern_return_t _stop(struct kmod_info *pKModInfo, void *pvData); + +KMOD_EXPLICIT_DECL(VBoxDrv, VBOX_VERSION_STRING, _start, _stop) +DECLHIDDEN(kmod_start_func_t *) _realmain = VBoxUSBStart; +DECLHIDDEN(kmod_stop_func_t *) _antimain = VBoxUSBStop; +DECLHIDDEN(int) _kext_apple_cc = __APPLE_CC__; +RT_C_DECLS_END + +/** Mutex protecting the lists. */ +static RTSEMFASTMUTEX g_Mtx = NIL_RTSEMFASTMUTEX; +org_virtualbox_VBoxUSBClient * volatile org_virtualbox_VBoxUSBClient::s_pHead = NULL; +org_virtualbox_VBoxUSBDevice * volatile org_virtualbox_VBoxUSBDevice::s_pHead = NULL; + +/** Global instance count - just for checking proving that everything is destroyed correctly. */ +static volatile uint32_t g_cInstances = 0; + + +/** + * Start the kernel module. + */ +static kern_return_t VBoxUSBStart(struct kmod_info *pKModInfo, void *pvData) +{ + RT_NOREF(pKModInfo, pvData); + int rc; + Log(("VBoxUSBStart\n")); + + /* + * Initialize IPRT. + */ + rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + /* + * Create the spinlock. + */ + rc = RTSemFastMutexCreate(&g_Mtx); + if (RT_SUCCESS(rc)) + { + rc = VBoxUSBFilterInit(); + if (RT_SUCCESS(rc)) + { +#if 0 /* testing */ + USBFILTER Flt; + USBFilterInit(&Flt, USBFILTERTYPE_CAPTURE); + USBFilterSetNumExact(&Flt, USBFILTERIDX_VENDOR_ID, 0x096e, true); + uintptr_t uId; + rc = VBoxUSBFilterAdd(&Flt, 1, &uId); + printf("VBoxUSB: VBoxUSBFilterAdd #1 -> %d + %p\n", rc, uId); + + USBFilterInit(&Flt, USBFILTERTYPE_CAPTURE); + USBFilterSetStringPattern(&Flt, USBFILTERIDX_PRODUCT_STR, "*DISK*", true); + rc = VBoxUSBFilterAdd(&Flt, 2, &uId); + printf("VBoxUSB: VBoxUSBFilterAdd #2 -> %d + %p\n", rc, uId); +#endif + return KMOD_RETURN_SUCCESS; + } + printf("VBoxUSB: VBoxUSBFilterInit failed (rc=%d)\n", rc); + RTSemFastMutexDestroy(g_Mtx); + g_Mtx = NIL_RTSEMFASTMUTEX; + } + else + printf("VBoxUSB: RTSemFastMutexCreate failed (rc=%d)\n", rc); + RTR0Term(); + } + else + printf("VBoxUSB: failed to initialize IPRT (rc=%d)\n", rc); + + return KMOD_RETURN_FAILURE; +} + + +/** + * Stop the kernel module. + */ +static kern_return_t VBoxUSBStop(struct kmod_info *pKModInfo, void *pvData) +{ + RT_NOREF(pKModInfo, pvData); + Log(("VBoxUSBStop: g_cInstances=%d\n", g_cInstances)); + + /** @todo Fix problem with crashing when unloading a driver that's in use. */ + + /* + * Undo the work done during start (in reverse order). + */ + VBoxUSBFilterTerm(); + + int rc = RTSemFastMutexDestroy(g_Mtx); + AssertRC(rc); + g_Mtx = NIL_RTSEMFASTMUTEX; + + RTR0Term(); + + Log(("VBoxUSBStop - done\n")); + return KMOD_RETURN_SUCCESS; +} + + + + + +#ifdef LOG_ENABLED +/** + * Gets the name of a IOKit message. + * + * @returns Message name (read only). + * @param enmMsg The message. + */ +DECLINLINE(const char *) DbgGetIOKitMessageName(UInt32 enmMsg) +{ + switch (enmMsg) + { +# define MY_CASE(enm) case enm: return #enm; break + MY_CASE(kIOMessageServiceIsTerminated); + MY_CASE(kIOMessageServiceIsSuspended); + MY_CASE(kIOMessageServiceIsResumed); + MY_CASE(kIOMessageServiceIsRequestingClose); + MY_CASE(kIOMessageServiceIsAttemptingOpen); + MY_CASE(kIOMessageServiceWasClosed); + MY_CASE(kIOMessageServiceBusyStateChange); + MY_CASE(kIOMessageServicePropertyChange); + MY_CASE(kIOMessageCanDevicePowerOff); + MY_CASE(kIOMessageDeviceWillPowerOff); + MY_CASE(kIOMessageDeviceWillNotPowerOff); + MY_CASE(kIOMessageDeviceHasPoweredOn); + MY_CASE(kIOMessageCanSystemPowerOff); + MY_CASE(kIOMessageSystemWillPowerOff); + MY_CASE(kIOMessageSystemWillNotPowerOff); + MY_CASE(kIOMessageCanSystemSleep); + MY_CASE(kIOMessageSystemWillSleep); + MY_CASE(kIOMessageSystemWillNotSleep); + MY_CASE(kIOMessageSystemHasPoweredOn); + MY_CASE(kIOMessageSystemWillRestart); + MY_CASE(kIOMessageSystemWillPowerOn); + MY_CASE(kIOUSBMessageHubResetPort); + MY_CASE(kIOUSBMessageHubSuspendPort); + MY_CASE(kIOUSBMessageHubResumePort); + MY_CASE(kIOUSBMessageHubIsDeviceConnected); + MY_CASE(kIOUSBMessageHubIsPortEnabled); + MY_CASE(kIOUSBMessageHubReEnumeratePort); + MY_CASE(kIOUSBMessagePortHasBeenReset); + MY_CASE(kIOUSBMessagePortHasBeenResumed); + MY_CASE(kIOUSBMessageHubPortClearTT); + MY_CASE(kIOUSBMessagePortHasBeenSuspended); + MY_CASE(kIOUSBMessageFromThirdParty); + MY_CASE(kIOUSBMessagePortWasNotSuspended); + MY_CASE(kIOUSBMessageExpressCardCantWake); +// MY_CASE(kIOUSBMessageCompositeDriverReconfigured); +# undef MY_CASE + } + return "unknown"; +} +#endif /* LOG_ENABLED */ + + + + + +/* + * + * org_virtualbox_VBoxUSB + * + */ + + +/** + * Initialize the object. + * @remark Only for logging. + */ +bool +org_virtualbox_VBoxUSB::init(OSDictionary *pDictionary) +{ + uint32_t cInstances = ASMAtomicIncU32(&g_cInstances); + Log(("VBoxUSB::init([%p], %p) new g_cInstances=%d\n", this, pDictionary, cInstances)); + RT_NOREF_PV(cInstances); + + if (IOService::init(pDictionary)) + { + /* init members. */ + return true; + } + ASMAtomicDecU32(&g_cInstances); + return false; +} + + +/** + * Free the object. + * @remark Only for logging. + */ +void +org_virtualbox_VBoxUSB::free() +{ + uint32_t cInstances = ASMAtomicDecU32(&g_cInstances); NOREF(cInstances); + Log(("VBoxUSB::free([%p]) new g_cInstances=%d\n", this, cInstances)); + IOService::free(); +} + + +/** + * Start this service. + */ +bool +org_virtualbox_VBoxUSB::start(IOService *pProvider) +{ + Log(("VBoxUSB::start([%p], %p {%s})\n", this, pProvider, pProvider->getName())); + + if (IOService::start(pProvider)) + { + /* register the service. */ + registerService(); + return true; + } + return false; +} + + +/** + * Stop this service. + * @remark Only for logging. + */ +void +org_virtualbox_VBoxUSB::stop(IOService *pProvider) +{ + Log(("VBoxUSB::stop([%p], %p (%s))\n", this, pProvider, pProvider->getName())); + IOService::stop(pProvider); +} + + +/** + * Stop this service. + * @remark Only for logging. + */ +bool +org_virtualbox_VBoxUSB::open(IOService *pForClient, IOOptionBits fOptions/* = 0*/, void *pvArg/* = 0*/) +{ + Log(("VBoxUSB::open([%p], %p, %#x, %p)\n", this, pForClient, fOptions, pvArg)); + bool fRc = IOService::open(pForClient, fOptions, pvArg); + Log(("VBoxUSB::open([%p], %p, %#x, %p) -> %d\n", this, pForClient, fOptions, pvArg, fRc)); + return fRc; +} + + +/** + * Stop this service. + * @remark Only for logging. + */ +void +org_virtualbox_VBoxUSB::close(IOService *pForClient, IOOptionBits fOptions/* = 0*/) +{ + Log(("VBoxUSB::close([%p], %p, %#x)\n", this, pForClient, fOptions)); + IOService::close(pForClient, fOptions); +} + + +/** + * Terminate request. + * @remark Only for logging. + */ +bool +org_virtualbox_VBoxUSB::terminate(IOOptionBits fOptions) +{ + Log(("VBoxUSB::terminate([%p], %#x): g_cInstances=%d\n", this, fOptions, g_cInstances)); + bool fRc = IOService::terminate(fOptions); + Log(("VBoxUSB::terminate([%p], %#x): returns %d\n", this, fOptions, fRc)); + return fRc; +} + + + + + + + + + + + +/* + * + * org_virtualbox_VBoxUSBClient + * + */ + + +/** + * Initializer called when the client opens the service. + */ +bool +org_virtualbox_VBoxUSBClient::initWithTask(task_t OwningTask, void *pvSecurityId, UInt32 u32Type) +{ + if (!OwningTask) + { + Log(("VBoxUSBClient::initWithTask([%p], %p, %p, %#x) -> false (no task)\n", this, OwningTask, pvSecurityId, u32Type)); + return false; + } + if (u32Type != VBOXUSB_DARWIN_IOSERVICE_COOKIE) + { + Log(("VBoxUSBClient::initWithTask: Bade cookie %#x\n", u32Type)); + return false; + } + + proc_t pProc = (proc_t)get_bsdtask_info(OwningTask); /* we need the pid */ + Log(("VBoxUSBClient::initWithTask([%p], %p(->%p:{.pid=%d}, %p, %#x)\n", + this, OwningTask, pProc, pProc ? proc_pid(pProc) : -1, pvSecurityId, u32Type)); + + if (IOUserClient::initWithTask(OwningTask, pvSecurityId , u32Type)) + { + /* + * In theory we have to call task_reference() to make sure that the task is + * valid during the lifetime of this object. The pointer is only used to check + * for the context this object is called in though and never dereferenced + * or passed to anything which might, so we just skip this step. + */ + m_pProvider = NULL; + m_Task = OwningTask; + m_Process = pProc ? proc_pid(pProc) : NIL_RTPROCESS; + m_pNext = NULL; + + uint32_t cInstances = ASMAtomicIncU32(&g_cInstances); + Log(("VBoxUSBClient::initWithTask([%p], %p(->%p:{.pid=%d}, %p, %#x) -> true; new g_cInstances=%d\n", + this, OwningTask, pProc, pProc ? proc_pid(pProc) : -1, pvSecurityId, u32Type, cInstances)); + RT_NOREF_PV(cInstances); + return true; + } + + Log(("VBoxUSBClient::initWithTask([%p], %p(->%p:{.pid=%d}, %p, %#x) -> false\n", + this, OwningTask, pProc, pProc ? proc_pid(pProc) : -1, pvSecurityId, u32Type)); + return false; +} + + +/** + * Free the object. + * @remark Only for logging. + */ +void +org_virtualbox_VBoxUSBClient::free() +{ + uint32_t cInstances = ASMAtomicDecU32(&g_cInstances); NOREF(cInstances); + Log(("VBoxUSBClient::free([%p]) new g_cInstances=%d\n", this, cInstances)); + IOUserClient::free(); +} + + +/** + * Start the client service. + */ +bool +org_virtualbox_VBoxUSBClient::start(IOService *pProvider) +{ + Log(("VBoxUSBClient::start([%p], %p)\n", this, pProvider)); + if (IOUserClient::start(pProvider)) + { + m_pProvider = OSDynamicCast(org_virtualbox_VBoxUSB, pProvider); + if (m_pProvider) + { + /* + * Add ourselves to the list of user clients. + */ + VBOXUSB_LOCK(); + + m_pNext = s_pHead; + s_pHead = this; + + VBOXUSB_UNLOCK(); + + return true; + } + Log(("VBoxUSBClient::start: %p isn't org_virtualbox_VBoxUSB\n", pProvider)); + } + return false; +} + + +/** + * Client exits normally. + */ +IOReturn +org_virtualbox_VBoxUSBClient::clientClose(void) +{ + Log(("VBoxUSBClient::clientClose([%p:{.m_Process=%d}])\n", this, (int)m_Process)); + + /* + * Remove this process from the client list. + */ + VBOXUSB_LOCK(); + + org_virtualbox_VBoxUSBClient *pPrev = NULL; + for (org_virtualbox_VBoxUSBClient *pCur = s_pHead; pCur; pCur = pCur->m_pNext) + { + if (pCur == this) + { + if (pPrev) + pPrev->m_pNext = m_pNext; + else + s_pHead = m_pNext; + m_pNext = NULL; + break; + } + pPrev = pCur; + } + + VBOXUSB_UNLOCK(); + + /* + * Drop all filters owned by this client. + */ + if (m_Process != NIL_RTPROCESS) + VBoxUSBFilterRemoveOwner(m_Process); + + /* + * Schedule all devices owned (filtered) by this process for + * immediate release or release upon close. + */ + if (m_Process != NIL_RTPROCESS) + org_virtualbox_VBoxUSBDevice::scheduleReleaseByOwner(m_Process); + + /* + * Initiate termination. + */ + m_pProvider = NULL; + terminate(); + + return kIOReturnSuccess; +} + + +/** + * The client exits abnormally / forgets to do cleanups. + * @remark Only for logging. + */ +IOReturn +org_virtualbox_VBoxUSBClient::clientDied(void) +{ + Log(("VBoxUSBClient::clientDied([%p]) m_Task=%p R0Process=%p Process=%d\n", + this, m_Task, RTR0ProcHandleSelf(), RTProcSelf())); + + /* IOUserClient::clientDied() calls clientClose... */ + return IOUserClient::clientDied(); +} + + +/** + * Terminate the service (initiate the destruction). + * @remark Only for logging. + */ +bool +org_virtualbox_VBoxUSBClient::terminate(IOOptionBits fOptions) +{ + /* kIOServiceRecursing, kIOServiceRequired, kIOServiceTerminate, kIOServiceSynchronous - interesting option bits */ + Log(("VBoxUSBClient::terminate([%p], %#x)\n", this, fOptions)); + return IOUserClient::terminate(fOptions); +} + + +/** + * The final stage of the client service destruction. + * @remark Only for logging. + */ +bool +org_virtualbox_VBoxUSBClient::finalize(IOOptionBits fOptions) +{ + Log(("VBoxUSBClient::finalize([%p], %#x)\n", this, fOptions)); + return IOUserClient::finalize(fOptions); +} + + +/** + * Stop the client service. + */ +void +org_virtualbox_VBoxUSBClient::stop(IOService *pProvider) +{ + Log(("VBoxUSBClient::stop([%p])\n", this)); + IOUserClient::stop(pProvider); + + /* + * Paranoia. + */ + VBOXUSB_LOCK(); + + org_virtualbox_VBoxUSBClient *pPrev = NULL; + for (org_virtualbox_VBoxUSBClient *pCur = s_pHead; pCur; pCur = pCur->m_pNext) + { + if (pCur == this) + { + if (pPrev) + pPrev->m_pNext = m_pNext; + else + s_pHead = m_pNext; + m_pNext = NULL; + break; + } + pPrev = pCur; + } + + VBOXUSB_UNLOCK(); +} + + +/** + * Translate a user method index into a service object and an external method structure. + * + * @returns Pointer to external method structure descripting the method. + * NULL if the index isn't valid. + * @param ppService Where to store the service object on success. + * @param iMethod The method index. + */ +IOExternalMethod * +org_virtualbox_VBoxUSBClient::getTargetAndMethodForIndex(IOService **ppService, UInt32 iMethod) +{ + static IOExternalMethod s_aMethods[VBOXUSBMETHOD_END] = + { + /*[VBOXUSBMETHOD_ADD_FILTER] = */ + { + (IOService *)0, /* object */ + (IOMethod)&org_virtualbox_VBoxUSBClient::addFilter, /* func */ + kIOUCStructIStructO, /* flags - struct input (count0) and struct output (count1) */ + sizeof(USBFILTER), /* count0 - size of the input struct. */ + sizeof(VBOXUSBADDFILTEROUT) /* count1 - size of the return struct. */ + }, + /* [VBOXUSBMETHOD_FILTER_REMOVE] = */ + { + (IOService *)0, /* object */ + (IOMethod)&org_virtualbox_VBoxUSBClient::removeFilter, /* func */ + kIOUCStructIStructO, /* flags - struct input (count0) and struct output (count1) */ + sizeof(uintptr_t), /* count0 - size of the input (id) */ + sizeof(int) /* count1 - size of the output (rc) */ + }, + }; + + if (RT_UNLIKELY(iMethod >= RT_ELEMENTS(s_aMethods))) + return NULL; + + *ppService = this; + return &s_aMethods[iMethod]; +} + + +/** + * Add filter user request. + * + * @returns IOKit status code. + * @param pFilter The filter to add. + * @param pOut Pointer to the output structure. + * @param cbFilter Size of the filter structure. + * @param pcbOut In/Out - sizeof(*pOut). + */ +IOReturn +org_virtualbox_VBoxUSBClient::addFilter(PUSBFILTER pFilter, PVBOXUSBADDFILTEROUT pOut, IOByteCount cbFilter, IOByteCount *pcbOut) +{ + Log(("VBoxUSBClient::addFilter: [%p:{.m_Process=%d}] pFilter=%p pOut=%p\n", this, (int)m_Process, pFilter, pOut)); + + /* + * Validate input. + */ + if (RT_UNLIKELY( cbFilter != sizeof(*pFilter) + || *pcbOut != sizeof(*pOut))) + { + printf("VBoxUSBClient::addFilter: cbFilter=%#x expected %#x; *pcbOut=%#x expected %#x\n", + (int)cbFilter, (int)sizeof(*pFilter), (int)*pcbOut, (int)sizeof(*pOut)); + return kIOReturnBadArgument; + } + + /* + * Log the filter details. + */ +#ifdef DEBUG + Log2(("VBoxUSBClient::addFilter: idVendor=%#x idProduct=%#x bcdDevice=%#x bDeviceClass=%#x bDeviceSubClass=%#x bDeviceProtocol=%#x bBus=%#x bPort=%#x\n", + USBFilterGetNum(pFilter, USBFILTERIDX_VENDOR_ID), + USBFilterGetNum(pFilter, USBFILTERIDX_PRODUCT_ID), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_REV), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_CLASS), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_SUB_CLASS), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_PROTOCOL), + USBFilterGetNum(pFilter, USBFILTERIDX_BUS), + USBFilterGetNum(pFilter, USBFILTERIDX_PORT))); + Log2(("VBoxUSBClient::addFilter: 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>")); +#endif + + /* + * Since we cannot query the bus number, make sure the filter + * isn't requiring that field to be present. + */ + int rc = USBFilterSetMustBePresent(pFilter, USBFILTERIDX_BUS, false /* fMustBePresent */); AssertRC(rc); + + /* + * Add the filter. + */ + pOut->uId = 0; + pOut->rc = VBoxUSBFilterAdd(pFilter, m_Process, &pOut->uId); + + Log(("VBoxUSBClient::addFilter: returns *pOut={.rc=%d, .uId=%p}\n", pOut->rc, (void *)pOut->uId)); + return kIOReturnSuccess; +} + + +/** + * Removes filter user request. + * + * @returns IOKit status code. + * @param puId Where to get the filter ID. + * @param prc Where to store the return code. + * @param cbIn sizeof(*puId). + * @param pcbOut In/Out - sizeof(*prc). + */ +IOReturn +org_virtualbox_VBoxUSBClient::removeFilter(uintptr_t *puId, int *prc, IOByteCount cbIn, IOByteCount *pcbOut) +{ + Log(("VBoxUSBClient::removeFilter: [%p:{.m_Process=%d}] *puId=%p m_Proc\n", this, (int)m_Process, *puId)); + + /* + * Validate input. + */ + if (RT_UNLIKELY( cbIn != sizeof(*puId) + || *pcbOut != sizeof(*prc))) + { + printf("VBoxUSBClient::removeFilter: cbIn=%#x expected %#x; *pcbOut=%#x expected %#x\n", + (int)cbIn, (int)sizeof(*puId), (int)*pcbOut, (int)sizeof(*prc)); + return kIOReturnBadArgument; + } + + /* + * Remove the filter. + */ + *prc = VBoxUSBFilterRemove(m_Process, *puId); + + Log(("VBoxUSBClient::removeFilter: returns *prc=%d\n", *prc)); + return kIOReturnSuccess; +} + + +/** + * Checks whether the specified task is a VBoxUSB client task or not. + * + * This is used to validate clients trying to open any of the device + * or interfaces that we've hijacked. + * + * @returns true / false. + * @param ClientTask The task. + * + * @remark This protecting against other user clients is not currently implemented + * as it turned out to be more bothersome than first imagined. + */ +/* static*/ bool +org_virtualbox_VBoxUSBClient::isClientTask(task_t ClientTask) +{ + VBOXUSB_LOCK(); + + for (org_virtualbox_VBoxUSBClient *pCur = s_pHead; pCur; pCur = pCur->m_pNext) + if (pCur->m_Task == ClientTask) + { + VBOXUSB_UNLOCK(); + return true; + } + + VBOXUSB_UNLOCK(); + return false; +} + + + + + + + + + + + + + + +/* + * + * org_virtualbox_VBoxUSBDevice + * + */ + +/** + * Initialize instance data. + * + * @returns Success indicator. + * @param pDictionary The dictionary that will become the registry entry's + * property table, or NULL. Hand it up to our parents. + */ +bool +org_virtualbox_VBoxUSBDevice::init(OSDictionary *pDictionary) +{ + uint32_t cInstances = ASMAtomicIncU32(&g_cInstances); + Log(("VBoxUSBDevice::init([%p], %p) new g_cInstances=%d\n", this, pDictionary, cInstances)); + RT_NOREF_PV(cInstances); + + m_pDevice = NULL; + m_Owner = NIL_RTPROCESS; + m_Client = NIL_RTPROCESS; + m_uId = ~(uintptr_t)0; + m_fOpen = false; + m_fOpenOnWasClosed = false; + m_fReleaseOnClose = false; + m_fBeingUnloaded = false; + m_pNext = NULL; +#ifdef DEBUG + m_pNotifier = NULL; +#endif + + return IOUSBUserClientInit::init(pDictionary); +} + +/** + * Free the object. + * @remark Only for logging. + */ +void +org_virtualbox_VBoxUSBDevice::free() +{ + uint32_t cInstances = ASMAtomicDecU32(&g_cInstances); NOREF(cInstances); + Log(("VBoxUSBDevice::free([%p]) new g_cInstances=%d\n", this, cInstances)); + IOUSBUserClientInit::free(); +} + + +/** + * The device/driver probing. + * + * I/O Kit will iterate all device drivers suitable for this kind of device + * (this is something it figures out from the property file) and call their + * probe() method in order to try determine which is the best match for the + * device. We will match the device against the registered filters and set + * a ridiculously high score if we find it, thus making it extremely likely + * that we'll be the first driver to be started. We'll also set a couple of + * attributes so that it's not necessary to do a rematch in init to find + * the appropriate filter (might not be necessary..., see todo). + * + * @returns Service instance to be started and *pi32Score if matching. + * NULL if not a device suitable for this driver. + * + * @param pProvider The provider instance. + * @param pi32Score Where to store the probe score. + */ +IOService * +org_virtualbox_VBoxUSBDevice::probe(IOService *pProvider, SInt32 *pi32Score) +{ + Log(("VBoxUSBDevice::probe([%p], %p {%s}, %p={%d})\n", this, + pProvider, pProvider->getName(), pi32Score, pi32Score ? *pi32Score : 0)); + + /* + * Check against filters. + */ + USBFILTER Device; + USBFilterInit(&Device, USBFILTERTYPE_CAPTURE); + + static const struct + { + const char *pszName; + USBFILTERIDX enmIdx; + bool fNumeric; + } s_aProps[] = + { + { kUSBVendorID, USBFILTERIDX_VENDOR_ID, true }, + { kUSBProductID, USBFILTERIDX_PRODUCT_ID, true }, + { kUSBDeviceReleaseNumber, USBFILTERIDX_DEVICE_REV, true }, + { kUSBDeviceClass, USBFILTERIDX_DEVICE_CLASS, true }, + { kUSBDeviceSubClass, USBFILTERIDX_DEVICE_SUB_CLASS, true }, + { kUSBDeviceProtocol, USBFILTERIDX_DEVICE_PROTOCOL, true }, + { "PortNum", USBFILTERIDX_PORT, true }, + /// @todo { , USBFILTERIDX_BUS, true }, - must be derived :-/ + /// Seems to be the upper byte of locationID and our "grand parent" has a USBBusNumber prop. + { "USB Vendor Name", USBFILTERIDX_MANUFACTURER_STR, false }, + { "USB Product Name", USBFILTERIDX_PRODUCT_STR, false }, + { "USB Serial Number", USBFILTERIDX_SERIAL_NUMBER_STR, false }, + }; + for (unsigned i = 0; i < RT_ELEMENTS(s_aProps); i++) + { + OSObject *pObj = pProvider->getProperty(s_aProps[i].pszName); + if (!pObj) + continue; + if (s_aProps[i].fNumeric) + { + OSNumber *pNum = OSDynamicCast(OSNumber, pObj); + if (pNum) + { + uint16_t u16 = pNum->unsigned16BitValue(); + Log2(("VBoxUSBDevice::probe: %d/%s - %#x (32bit=%#x)\n", i, s_aProps[i].pszName, u16, pNum->unsigned32BitValue())); + int vrc = USBFilterSetNumExact(&Device, s_aProps[i].enmIdx, u16, true); + if (RT_FAILURE(vrc)) + Log(("VBoxUSBDevice::probe: pObj=%p pNum=%p - %d/%s - rc=%d!\n", pObj, pNum, i, s_aProps[i].pszName, vrc)); + } + else + Log(("VBoxUSBDevice::probe: pObj=%p pNum=%p - %d/%s!\n", pObj, pNum, i, s_aProps[i].pszName)); + } + else + { + OSString *pStr = OSDynamicCast(OSString, pObj); + if (pStr) + { + Log2(("VBoxUSBDevice::probe: %d/%s - %s\n", i, s_aProps[i].pszName, pStr->getCStringNoCopy())); + int vrc = USBFilterSetStringExact(&Device, s_aProps[i].enmIdx, pStr->getCStringNoCopy(), + true /*fMustBePresent*/, true /*fPurge*/); + if (RT_FAILURE(vrc)) + Log(("VBoxUSBDevice::probe: pObj=%p pStr=%p - %d/%s - rc=%d!\n", pObj, pStr, i, s_aProps[i].pszName, vrc)); + } + else + Log(("VBoxUSBDevice::probe: pObj=%p pStr=%p - %d/%s\n", pObj, pStr, i, s_aProps[i].pszName)); + } + } + /** @todo try figure the blasted bus number */ + + /* + * Run filters on it. + */ + uintptr_t uId = 0; + RTPROCESS Owner = VBoxUSBFilterMatch(&Device, &uId); + USBFilterDelete(&Device); + if (Owner == NIL_RTPROCESS) + { + Log(("VBoxUSBDevice::probe: returns NULL uId=%d\n", uId)); + return NULL; + } + + /* + * It matched. Save the owner in the provider registry (hope that works). + */ + /*IOService *pRet = IOUSBUserClientInit::probe(pProvider, pi32Score); - call always returns NULL on 10.11+ */ + /*AssertMsg(pRet == this, ("pRet=%p this=%p *pi32Score=%d \n", pRet, this, pi32Score ? *pi32Score : 0)); - call always returns NULL on 10.11+ */ + IOService *pRet = this; + m_Owner = Owner; + m_uId = uId; + Log(("%p: m_Owner=%d m_uId=%d\n", this, (int)m_Owner, (int)m_uId)); + *pi32Score = _1G; + Log(("VBoxUSBDevice::probe: returns %p and *pi32Score=%d\n", pRet, *pi32Score)); + return pRet; +} + + +/** + * Try start the device driver. + * + * We will do device linking, copy the filter and owner properties from the provider, + * set the client property, retain the device, and try open (seize) the device. + * + * @returns Success indicator. + * @param pProvider The provider instance. + */ +bool +org_virtualbox_VBoxUSBDevice::start(IOService *pProvider) +{ + Log(("VBoxUSBDevice::start([%p:{.m_Owner=%d, .m_uId=%p}], %p {%s})\n", + this, m_Owner, m_uId, pProvider, pProvider->getName())); + + m_pDevice = OSDynamicCast(IOUSBDevice, pProvider); + if (!m_pDevice) + { + printf("VBoxUSBDevice::start([%p], %p {%s}): failed!\n", this, pProvider, pProvider->getName()); + return false; + } + +#ifdef DEBUG + /* for some extra log messages */ + m_pNotifier = pProvider->registerInterest(gIOGeneralInterest, + &org_virtualbox_VBoxUSBDevice::MyInterestHandler, + this, /* pvTarget */ + NULL); /* pvRefCon */ +#endif + + /* + * Exploit IOUSBUserClientInit to process IOProviderMergeProperties. + */ + IOUSBUserClientInit::start(pProvider); /* returns false */ + + /* + * Link ourselves into the list of hijacked device. + */ + VBOXUSB_LOCK(); + + m_pNext = s_pHead; + s_pHead = this; + + VBOXUSB_UNLOCK(); + + /* + * Set the VBoxUSB properties. + */ + if (!setProperty(VBOXUSB_OWNER_KEY, (unsigned long long)m_Owner, sizeof(m_Owner) * 8 /* bits */)) + Log(("VBoxUSBDevice::start: failed to set the '" VBOXUSB_OWNER_KEY "' property!\n")); + if (!setProperty(VBOXUSB_CLIENT_KEY, (unsigned long long)m_Client, sizeof(m_Client) * 8 /* bits */)) + Log(("VBoxUSBDevice::start: failed to set the '" VBOXUSB_CLIENT_KEY "' property!\n")); + if (!setProperty(VBOXUSB_FILTER_KEY, (unsigned long long)m_uId, sizeof(m_uId) * 8 /* bits */)) + Log(("VBoxUSBDevice::start: failed to set the '" VBOXUSB_FILTER_KEY "' property!\n")); + + /* + * Retain and open the device. + */ + m_pDevice->retain(); + m_fOpen = m_pDevice->open(this, kIOServiceSeize, 0); + if (!m_fOpen) + Log(("VBoxUSBDevice::start: failed to open the device!\n")); + m_fOpenOnWasClosed = !m_fOpen; + + Log(("VBoxUSBDevice::start: returns %d\n", true)); + return true; +} + + +/** + * Stop the device driver. + * + * We'll unlink the device, start device re-enumeration and close it. And call + * the parent stop method of course. + * + * @param pProvider The provider instance. + */ +void +org_virtualbox_VBoxUSBDevice::stop(IOService *pProvider) +{ + Log(("VBoxUSBDevice::stop([%p], %p {%s})\n", this, pProvider, pProvider->getName())); + + /* + * Remove ourselves from the list of device. + */ + VBOXUSB_LOCK(); + + org_virtualbox_VBoxUSBDevice *pPrev = NULL; + for (org_virtualbox_VBoxUSBDevice *pCur = s_pHead; pCur; pCur = pCur->m_pNext) + { + if (pCur == this) + { + if (pPrev) + pPrev->m_pNext = m_pNext; + else + s_pHead = m_pNext; + m_pNext = NULL; + break; + } + pPrev = pCur; + } + + VBOXUSB_UNLOCK(); + + /* + * Should we release the device? + */ + if (m_fBeingUnloaded) + { + if (m_pDevice) + { + IOReturn irc = m_pDevice->ReEnumerateDevice(0); NOREF(irc); + Log(("VBoxUSBDevice::stop([%p], %p {%s}): m_pDevice=%p unload & ReEnumerateDevice -> %#x\n", + this, pProvider, pProvider->getName(), m_pDevice, irc)); + } + else + { + IOUSBDevice *pDevice = OSDynamicCast(IOUSBDevice, pProvider); + if (pDevice) + { + IOReturn irc = pDevice->ReEnumerateDevice(0); NOREF(irc); + Log(("VBoxUSBDevice::stop([%p], %p {%s}): pDevice=%p unload & ReEnumerateDevice -> %#x\n", + this, pProvider, pProvider->getName(), pDevice, irc)); + } + else + Log(("VBoxUSBDevice::stop([%p], %p {%s}): failed to cast provider to IOUSBDevice\n", + this, pProvider, pProvider->getName())); + } + } + else if (m_fReleaseOnClose) + { + ASMAtomicWriteBool(&m_fReleaseOnClose, false); + if (m_pDevice) + { + IOReturn irc = m_pDevice->ReEnumerateDevice(0); NOREF(irc); + Log(("VBoxUSBDevice::stop([%p], %p {%s}): m_pDevice=%p close & ReEnumerateDevice -> %#x\n", + this, pProvider, pProvider->getName(), m_pDevice, irc)); + } + } + + /* + * Close and release the IOUSBDevice if didn't do that already in message(). + */ + if (m_pDevice) + { + /* close it */ + if (m_fOpen) + { + m_fOpenOnWasClosed = false; + m_fOpen = false; + m_pDevice->close(this, 0); + } + + /* release it (see start()) */ + m_pDevice->release(); + m_pDevice = NULL; + } + +#ifdef DEBUG + /* avoid crashing on unload. */ + if (m_pNotifier) + { + m_pNotifier->release(); + m_pNotifier = NULL; + } +#endif + + IOUSBUserClientInit::stop(pProvider); + Log(("VBoxUSBDevice::stop: returns void\n")); +} + + +/** + * Terminate the service (initiate the destruction). + * @remark Only for logging. + */ +bool +org_virtualbox_VBoxUSBDevice::terminate(IOOptionBits fOptions) +{ + /* kIOServiceRecursing, kIOServiceRequired, kIOServiceTerminate, kIOServiceSynchronous - interesting option bits */ + Log(("VBoxUSBDevice::terminate([%p], %#x)\n", this, fOptions)); + + /* + * There aren't too many reasons why we gets terminated. + * The most common one is that the device is being unplugged. Another is + * that we've triggered reenumeration. In both cases we'll get a + * kIOMessageServiceIsTerminated message before we're stopped. + * + * But, when we're unloaded the provider service isn't terminated, and + * for some funny reason we're frequently causing kernel panics when the + * device is detached (after we're unloaded). So, for now, let's try + * re-enumerate it in stop. + * + * To avoid creating unnecessary trouble we'll try guess if we're being + * unloaded from the option bit mask. (kIOServiceRecursing is private btw.) + */ + /** @todo would be nice if there was a documented way of doing the unload detection this, or + * figure out what exactly we're doing wrong in the unload scenario. */ + if ((fOptions & 0xffff) == (kIOServiceRequired | kIOServiceSynchronous)) + m_fBeingUnloaded = true; + + return IOUSBUserClientInit::terminate(fOptions); +} + + +/** + * Intercept open requests and only let Mr. Right (the VM process) open the device. + * This is where it all gets a bit complicated... + * + * @return Status code. + * + * @param enmMsg The message number. + * @param pProvider Pointer to the provider instance. + * @param pvArg Message argument. + */ +IOReturn +org_virtualbox_VBoxUSBDevice::message(UInt32 enmMsg, IOService *pProvider, void *pvArg) +{ + Log(("VBoxUSBDevice::message([%p], %#x {%s}, %p {%s}, %p) - pid=%d\n", + this, enmMsg, DbgGetIOKitMessageName(enmMsg), pProvider, pProvider->getName(), pvArg, RTProcSelf())); + + IOReturn irc; + switch (enmMsg) + { + /* + * This message is send to the current IOService client from IOService::handleOpen(), + * expecting it to call pProvider->close() if it agrees to the other party seizing + * the service. It is also called in IOService::didTerminate() and perhaps some other + * odd places. The way to find out is to examin the pvArg, which would be including + * kIOServiceSeize if it's the handleOpen case. + * + * How to validate that the other end is actually our VM process? Well, IOKit doesn't + * provide any clue about the new client really. But fortunately, it seems like the + * calling task/process context when the VM tries to open the device is the VM process. + * We'll ASSUME this'll remain like this for now... + */ + case kIOMessageServiceIsRequestingClose: + irc = kIOReturnExclusiveAccess; + /* If it's not a seize request, assume it's didTerminate and pray that it isn't a rouge driver. + ... weird, doesn't seem to match for the post has-terminated messages. */ + if (!((uintptr_t)pvArg & kIOServiceSeize)) + { + Log(("VBoxUSBDevice::message([%p],%p {%s}, %p) - pid=%d: not seize - closing...\n", + this, pProvider, pProvider->getName(), pvArg, RTProcSelf())); + m_fOpen = false; + m_fOpenOnWasClosed = false; + if (m_pDevice) + m_pDevice->close(this, 0); + m_Client = NIL_RTPROCESS; + irc = kIOReturnSuccess; + } + else + { + if (org_virtualbox_VBoxUSBClient::isClientTask(current_task())) + { + Log(("VBoxUSBDevice::message([%p],%p {%s}, %p) - pid=%d task=%p: client process, closing.\n", + this, pProvider, pProvider->getName(), pvArg, RTProcSelf(), current_task())); + m_fOpen = false; + m_fOpenOnWasClosed = false; + if (m_pDevice) + m_pDevice->close(this, 0); + m_fOpenOnWasClosed = true; + m_Client = RTProcSelf(); + irc = kIOReturnSuccess; + } + else + Log(("VBoxUSBDevice::message([%p],%p {%s}, %p) - pid=%d task=%p: not client process!\n", + this, pProvider, pProvider->getName(), pvArg, RTProcSelf(), current_task())); + } + if (!setProperty(VBOXUSB_CLIENT_KEY, (unsigned long long)m_Client, sizeof(m_Client) * 8 /* bits */)) + Log(("VBoxUSBDevice::message: failed to set the '" VBOXUSB_CLIENT_KEY "' property!\n")); + break; + + /* + * The service was closed by the current client. + * Update the client property, check for scheduled re-enumeration and re-open. + * + * Note that we will not be called if we're doing the closing. (Even if we was + * called in that case, the code should be able to handle it.) + */ + case kIOMessageServiceWasClosed: + /* + * Update the client property value. + */ + if (m_Client != NIL_RTPROCESS) + { + m_Client = NIL_RTPROCESS; + if (!setProperty(VBOXUSB_CLIENT_KEY, (unsigned long long)m_Client, sizeof(m_Client) * 8 /* bits */)) + Log(("VBoxUSBDevice::message: failed to set the '" VBOXUSB_CLIENT_KEY "' property!\n")); + } + + if (m_pDevice) + { + /* + * Should we release the device? + */ + if (ASMAtomicXchgBool(&m_fReleaseOnClose, false)) + { + m_fOpenOnWasClosed = false; + irc = m_pDevice->ReEnumerateDevice(0); + Log(("VBoxUSBDevice::message([%p], %p {%s}) - ReEnumerateDevice() -> %#x\n", + this, pProvider, pProvider->getName(), irc)); + } + /* + * Should we attempt to re-open the device? + */ + else if (m_fOpenOnWasClosed) + { + Log(("VBoxUSBDevice::message: attempting to re-open the device...\n")); + m_fOpenOnWasClosed = false; + m_fOpen = m_pDevice->open(this, kIOServiceSeize, 0); + if (!m_fOpen) + Log(("VBoxUSBDevice::message: failed to open the device!\n")); + m_fOpenOnWasClosed = !m_fOpen; + } + } + + irc = IOUSBUserClientInit::message(enmMsg, pProvider, pvArg); + break; + + /* + * The IOUSBDevice is shutting down, so close it if we've opened it. + */ + case kIOMessageServiceIsTerminated: + m_fBeingUnloaded = false; + ASMAtomicWriteBool(&m_fReleaseOnClose, false); + if (m_pDevice) + { + /* close it */ + if (m_fOpen) + { + m_fOpen = false; + m_fOpenOnWasClosed = false; + Log(("VBoxUSBDevice::message: closing the device (%p)...\n", m_pDevice)); + m_pDevice->close(this, 0); + } + + /* release it (see start()) */ + Log(("VBoxUSBDevice::message: releasing the device (%p)...\n", m_pDevice)); + m_pDevice->release(); + m_pDevice = NULL; + } + + irc = IOUSBUserClientInit::message(enmMsg, pProvider, pvArg); + break; + + default: + irc = IOUSBUserClientInit::message(enmMsg, pProvider, pvArg); + break; + } + + Log(("VBoxUSBDevice::message([%p], %#x {%s}, %p {%s}, %p) -> %#x\n", + this, enmMsg, DbgGetIOKitMessageName(enmMsg), pProvider, pProvider->getName(), pvArg, irc)); + return irc; +} + + +/** + * Schedule all devices belonging to the specified process for release. + * + * Devices that aren't currently in use will be released immediately. + * + * @param Owner The owner process. + */ +/* static */ void +org_virtualbox_VBoxUSBDevice::scheduleReleaseByOwner(RTPROCESS Owner) +{ + Log2(("VBoxUSBDevice::scheduleReleaseByOwner: Owner=%d\n", Owner)); + AssertReturnVoid(Owner && Owner != NIL_RTPROCESS); + + /* + * Walk the list of devices looking for device belonging to this process. + * + * If we release a device, we have to lave the spinlock and will therefore + * have to restart the search. + */ + VBOXUSB_LOCK(); + + org_virtualbox_VBoxUSBDevice *pCur; + do + { + for (pCur = s_pHead; pCur; pCur = pCur->m_pNext) + { + Log2(("VBoxUSBDevice::scheduleReleaseByOwner: pCur=%p m_Owner=%d (%s) m_fReleaseOnClose=%d\n", + pCur, pCur->m_Owner, pCur->m_Owner == Owner ? "match" : "mismatch", pCur->m_fReleaseOnClose)); + if (pCur->m_Owner == Owner) + { + /* make sure we won't hit it again. */ + pCur->m_Owner = NIL_RTPROCESS; + IOUSBDevice *pDevice = pCur->m_pDevice; + if ( pDevice + && !pCur->m_fReleaseOnClose) + { + pCur->m_fOpenOnWasClosed = false; + if (pCur->m_Client != NIL_RTPROCESS) + { + /* It's currently open, so just schedule it for re-enumeration on close. */ + ASMAtomicWriteBool(&pCur->m_fReleaseOnClose, true); + Log(("VBoxUSBDevice::scheduleReleaseByOwner: %p {%s} - used by %d\n", + pDevice, pDevice->getName(), pCur->m_Client)); + } + else + { + /* + * Get the USBDevice object and do the re-enumeration now. + * Retain the device so we don't run into any trouble. + */ + pDevice->retain(); + VBOXUSB_UNLOCK(); + + IOReturn irc = pDevice->ReEnumerateDevice(0); NOREF(irc); + Log(("VBoxUSBDevice::scheduleReleaseByOwner: %p {%s} - ReEnumerateDevice -> %#x\n", + pDevice, pDevice->getName(), irc)); + + pDevice->release(); + VBOXUSB_LOCK(); + break; + } + } + } + } + } while (pCur); + + VBOXUSB_UNLOCK(); +} + + +#ifdef DEBUG +/*static*/ IOReturn +org_virtualbox_VBoxUSBDevice::MyInterestHandler(void *pvTarget, void *pvRefCon, UInt32 enmMsgType, + IOService *pProvider, void * pvMsgArg, vm_size_t cbMsgArg) +{ + org_virtualbox_VBoxUSBDevice *pThis = (org_virtualbox_VBoxUSBDevice *)pvTarget; + if (!pThis) + return kIOReturnError; + + switch (enmMsgType) + { + case kIOMessageServiceIsAttemptingOpen: + /* pvMsgArg == the open() fOptions, so we could check for kIOServiceSeize if we care. + We'll also get a kIIOServiceRequestingClose message() for that... */ + Log(("VBoxUSBDevice::MyInterestHandler: kIOMessageServiceIsAttemptingOpen - pvRefCon=%p pProvider=%p pvMsgArg=%p cbMsgArg=%d\n", + pvRefCon, pProvider, pvMsgArg, cbMsgArg)); + break; + + case kIOMessageServiceWasClosed: + Log(("VBoxUSBDevice::MyInterestHandler: kIOMessageServiceWasClosed - pvRefCon=%p pProvider=%p pvMsgArg=%p cbMsgArg=%d\n", + pvRefCon, pProvider, pvMsgArg, cbMsgArg)); + break; + + case kIOMessageServiceIsTerminated: + Log(("VBoxUSBDevice::MyInterestHandler: kIOMessageServiceIsTerminated - pvRefCon=%p pProvider=%p pvMsgArg=%p cbMsgArg=%d\n", + pvRefCon, pProvider, pvMsgArg, cbMsgArg)); + break; + + case kIOUSBMessagePortHasBeenReset: + Log(("VBoxUSBDevice::MyInterestHandler: kIOUSBMessagePortHasBeenReset - pvRefCon=%p pProvider=%p pvMsgArg=%p cbMsgArg=%d\n", + pvRefCon, pProvider, pvMsgArg, cbMsgArg)); + break; + + default: + Log(("VBoxUSBDevice::MyInterestHandler: %#x (%s) - pvRefCon=%p pProvider=%p pvMsgArg=%p cbMsgArg=%d\n", + enmMsgType, DbgGetIOKitMessageName(enmMsgType), pvRefCon, pProvider, pvMsgArg, cbMsgArg)); + break; + } + + return kIOReturnSuccess; +} +#endif /* DEBUG */ + + + + + + + + + + + + + + +/* + * + * org_virtualbox_VBoxUSBInterface + * + */ + +/** + * Initialize our data members. + */ +bool +org_virtualbox_VBoxUSBInterface::init(OSDictionary *pDictionary) +{ + uint32_t cInstances = ASMAtomicIncU32(&g_cInstances); + Log(("VBoxUSBInterface::init([%p], %p) new g_cInstances=%d\n", this, pDictionary, cInstances)); + RT_NOREF_PV(cInstances); + + m_pInterface = NULL; + m_fOpen = false; + m_fOpenOnWasClosed = false; + + return IOUSBUserClientInit::init(pDictionary); +} + + +/** + * Free the object. + * @remark Only for logging. + */ +void +org_virtualbox_VBoxUSBInterface::free() +{ + uint32_t cInstances = ASMAtomicDecU32(&g_cInstances); NOREF(cInstances); + Log(("VBoxUSBInterfaces::free([%p]) new g_cInstances=%d\n", this, cInstances)); + IOUSBUserClientInit::free(); +} + + +/** + * Probe the interface to see if we're the right driver for it. + * + * We implement this similarly to org_virtualbox_VBoxUSBDevice, except that + * we don't bother matching filters but instead just check if the parent is + * handled by org_virtualbox_VBoxUSBDevice or not. + */ +IOService * +org_virtualbox_VBoxUSBInterface::probe(IOService *pProvider, SInt32 *pi32Score) +{ + Log(("VBoxUSBInterface::probe([%p], %p {%s}, %p={%d})\n", this, + pProvider, pProvider->getName(), pi32Score, pi32Score ? *pi32Score : 0)); + + /* + * Check if VBoxUSBDevice is the parent's driver. + */ + bool fHijackIt = false; + const IORegistryPlane *pServicePlane = getPlane(kIOServicePlane); + IORegistryEntry *pParent = pProvider->getParentEntry(pServicePlane); + if (pParent) + { + Log(("VBoxUSBInterface::probe: pParent=%p {%s}\n", pParent, pParent->getName())); + + OSIterator *pSiblings = pParent->getChildIterator(pServicePlane); + if (pSiblings) + { + IORegistryEntry *pSibling; + while ( (pSibling = OSDynamicCast(IORegistryEntry, pSiblings->getNextObject())) ) + { + const OSMetaClass *pMetaClass = pSibling->getMetaClass(); + Log2(("sibling: %p - %s - %s\n", pMetaClass, pSibling->getName(), pMetaClass->getClassName())); + if (pMetaClass == &org_virtualbox_VBoxUSBDevice::gMetaClass) + { + fHijackIt = true; + break; + } + } + pSiblings->release(); + } + } + if (!fHijackIt) + { + Log(("VBoxUSBInterface::probe: returns NULL\n")); + return NULL; + } + + /* IOService *pRet = IOUSBUserClientInit::probe(pProvider, pi32Score); - call always returns NULL on 10.11+ */ + IOService *pRet = this; + *pi32Score = _1G; + Log(("VBoxUSBInterface::probe: returns %p and *pi32Score=%d - hijack it.\n", pRet, *pi32Score)); + return pRet; +} + + +/** + * Start the driver (this), retain and open the USB interface object (pProvider). + */ +bool +org_virtualbox_VBoxUSBInterface::start(IOService *pProvider) +{ + Log(("VBoxUSBInterface::start([%p], %p {%s})\n", this, pProvider, pProvider->getName())); + + /* + * Exploit IOUSBUserClientInit to process IOProviderMergeProperties. + */ + IOUSBUserClientInit::start(pProvider); /* returns false */ + + /* + * Retain the and open the interface (stop() or message() cleans up). + */ + bool fRc = true; + m_pInterface = OSDynamicCast(IOUSBInterface, pProvider); + if (m_pInterface) + { + m_pInterface->retain(); + m_fOpen = m_pInterface->open(this, kIOServiceSeize, 0); + if (!m_fOpen) + Log(("VBoxUSBInterface::start: failed to open the interface!\n")); + m_fOpenOnWasClosed = !m_fOpen; + } + else + { + printf("VBoxUSBInterface::start([%p], %p {%s}): failed!\n", this, pProvider, pProvider->getName()); + fRc = false; + } + + Log(("VBoxUSBInterface::start: returns %d\n", fRc)); + return fRc; +} + + +/** + * Close and release the USB interface object (pProvider) and stop the driver (this). + */ +void +org_virtualbox_VBoxUSBInterface::stop(IOService *pProvider) +{ + Log(("org_virtualbox_VBoxUSBInterface::stop([%p], %p {%s})\n", this, pProvider, pProvider->getName())); + + /* + * Close and release the IOUSBInterface if didn't do that already in message(). + */ + if (m_pInterface) + { + /* close it */ + if (m_fOpen) + { + m_fOpenOnWasClosed = false; + m_fOpen = false; + m_pInterface->close(this, 0); + } + + /* release it (see start()) */ + m_pInterface->release(); + m_pInterface = NULL; + } + + IOUSBUserClientInit::stop(pProvider); + Log(("VBoxUSBInterface::stop: returns void\n")); +} + + +/** + * Terminate the service (initiate the destruction). + * @remark Only for logging. + */ +bool +org_virtualbox_VBoxUSBInterface::terminate(IOOptionBits fOptions) +{ + /* kIOServiceRecursing, kIOServiceRequired, kIOServiceTerminate, kIOServiceSynchronous - interesting option bits */ + Log(("VBoxUSBInterface::terminate([%p], %#x)\n", this, fOptions)); + return IOUSBUserClientInit::terminate(fOptions); +} + + +/** + * @copydoc org_virtualbox_VBoxUSBDevice::message + */ +IOReturn +org_virtualbox_VBoxUSBInterface::message(UInt32 enmMsg, IOService *pProvider, void *pvArg) +{ + Log(("VBoxUSBInterface::message([%p], %#x {%s}, %p {%s}, %p)\n", + this, enmMsg, DbgGetIOKitMessageName(enmMsg), pProvider, pProvider->getName(), pvArg)); + + IOReturn irc; + switch (enmMsg) + { + /* + * See explanation in org_virtualbox_VBoxUSBDevice::message. + */ + case kIOMessageServiceIsRequestingClose: + irc = kIOReturnExclusiveAccess; + if (!((uintptr_t)pvArg & kIOServiceSeize)) + { + Log(("VBoxUSBInterface::message([%p],%p {%s}, %p) - pid=%d: not seize - closing...\n", + this, pProvider, pProvider->getName(), pvArg, RTProcSelf())); + m_fOpen = false; + m_fOpenOnWasClosed = false; + if (m_pInterface) + m_pInterface->close(this, 0); + irc = kIOReturnSuccess; + } + else + { + if (org_virtualbox_VBoxUSBClient::isClientTask(current_task())) + { + Log(("VBoxUSBInterface::message([%p],%p {%s}, %p) - pid=%d task=%p: client process, closing.\n", + this, pProvider, pProvider->getName(), pvArg, RTProcSelf(), current_task())); + m_fOpen = false; + m_fOpenOnWasClosed = false; + if (m_pInterface) + m_pInterface->close(this, 0); + m_fOpenOnWasClosed = true; + irc = kIOReturnSuccess; + } + else + Log(("VBoxUSBInterface::message([%p],%p {%s}, %p) - pid=%d task=%p: not client process!\n", + this, pProvider, pProvider->getName(), pvArg, RTProcSelf(), current_task())); + } + break; + + /* + * The service was closed by the current client, check for re-open. + */ + case kIOMessageServiceWasClosed: + if (m_pInterface && m_fOpenOnWasClosed) + { + Log(("VBoxUSBInterface::message: attempting to re-open the interface...\n")); + m_fOpenOnWasClosed = false; + m_fOpen = m_pInterface->open(this, kIOServiceSeize, 0); + if (!m_fOpen) + Log(("VBoxUSBInterface::message: failed to open the interface!\n")); + m_fOpenOnWasClosed = !m_fOpen; + } + + irc = IOUSBUserClientInit::message(enmMsg, pProvider, pvArg); + break; + + /* + * The IOUSBInterface/Device is shutting down, so close and release. + */ + case kIOMessageServiceIsTerminated: + if (m_pInterface) + { + /* close it */ + if (m_fOpen) + { + m_fOpen = false; + m_fOpenOnWasClosed = false; + m_pInterface->close(this, 0); + } + + /* release it (see start()) */ + m_pInterface->release(); + m_pInterface = NULL; + } + + irc = IOUSBUserClientInit::message(enmMsg, pProvider, pvArg); + break; + + default: + irc = IOUSBUserClientInit::message(enmMsg, pProvider, pvArg); + break; + } + + Log(("VBoxUSBInterface::message([%p], %#x {%s}, %p {%s}, %p) -> %#x\n", + this, enmMsg, DbgGetIOKitMessageName(enmMsg), pProvider, pProvider->getName(), pvArg, irc)); + return irc; +} + |