diff options
Diffstat (limited to 'src/VBox/HostDrivers/VBoxUSB/darwin')
-rw-r--r-- | src/VBox/HostDrivers/VBoxUSB/darwin/Info.plist | 89 | ||||
-rw-r--r-- | src/VBox/HostDrivers/VBoxUSB/darwin/Makefile.kmk | 87 | ||||
-rw-r--r-- | src/VBox/HostDrivers/VBoxUSB/darwin/USBLib-darwin.cpp | 201 | ||||
-rw-r--r-- | src/VBox/HostDrivers/VBoxUSB/darwin/VBoxUSB.cpp | 1890 | ||||
-rw-r--r-- | src/VBox/HostDrivers/VBoxUSB/darwin/VBoxUSBInterface.h | 65 | ||||
-rwxr-xr-x | src/VBox/HostDrivers/VBoxUSB/darwin/loadusb.sh | 123 | ||||
-rw-r--r-- | src/VBox/HostDrivers/VBoxUSB/darwin/testcase/Makefile.kup | 0 | ||||
-rw-r--r-- | src/VBox/HostDrivers/VBoxUSB/darwin/testcase/tstOpenUSBDev.cpp | 295 |
8 files changed, 2750 insertions, 0 deletions
diff --git a/src/VBox/HostDrivers/VBoxUSB/darwin/Info.plist b/src/VBox/HostDrivers/VBoxUSB/darwin/Info.plist new file mode 100644 index 00000000..16aac548 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/darwin/Info.plist @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> <string>English</string> + <key>CFBundleExecutable</key> <string>VBoxUSB</string> + <key>CFBundleIdentifier</key> <string>org.virtualbox.kext.VBoxUSB</string> + <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> + <key>CFBundleName</key> <string>VBoxUSB</string> + <key>CFBundlePackageType</key> <string>KEXT</string> + <key>CFBundleSignature</key> <string>????</string> + <key>CFBundleVersion</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + <key>CFBundleShortVersionString</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + <key>CFBundleGetInfoString</key> <string>@VBOX_PRODUCT@ @VBOX_VERSION_STRING@, © 2007-@VBOX_C_YEAR@ @VBOX_VENDOR@</string> + <key>IOKitPersonalities</key> + <dict> + <key>VBoxUSB</key> + <dict> + <key>CFBundleIdentifier</key> <string>org.virtualbox.kext.VBoxUSB</string> + <key>IOClass</key> <string>org_virtualbox_VBoxUSB</string> + <key>IOMatchCategory</key> <string>org_virtualbox_VBoxUSB</string> + <key>IOProviderClass</key> <string>IOResources</string> + <key>IOResourceMatch</key> <string>IOKit</string> + <key>IOUserClientClass</key> <string>org_virtualbox_VBoxUSBClient</string> + </dict> + <key>VBoxUSBDevice</key> + <dict> + <key>CFBundleIdentifier</key> <string>org.virtualbox.kext.VBoxUSB</string> + <key>IOClass</key> <string>org_virtualbox_VBoxUSBDevice</string> + <key>IOProviderClass</key> <string>IOUSBDevice</string> + <key>idVendor</key> <string>*</string> + <key>idProduct</key> <string>*</string> + <key>bcdDevice</key> <string>*</string> + <key>IOProbeScore</key> <integer>9942</integer> + <key>IOProviderMergeProperties</key> + <dict> + <key>IOCFPlugInTypes</key> + <dict> + <key>9dc7b780-9ec0-11d4-a54f-000a27052861</key> <string>IOUSBFamily.kext/Contents/PlugIns/IOUSBLib.bundle</string> + </dict> + <key>IOUserClientClass</key> <string>IOUSBDeviceUserClientV2</string> + </dict> + </dict> + <key>VBoxUSBInterface</key> + <dict> + <key>CFBundleIdentifier</key> <string>org.virtualbox.kext.VBoxUSB</string> + <key>IOClass</key> <string>org_virtualbox_VBoxUSBInterface</string> + <key>IOProviderClass</key> <string>IOUSBInterface</string> + <key>idVendor</key> <string>*</string> + <key>idProduct</key> <string>*</string> + <key>bcdDevice</key> <string>*</string> + <key>bConfigurationValue</key> <string>*</string> + <key>bInterfaceNumber</key> <string>*</string> + <key>IOProbeScore</key> <integer>9942</integer> + <key>IOProviderMergeProperties</key> + <dict> + <key>IOCFPlugInTypes</key> + <dict> + <key>2d9786c6-9ef3-11d4-ad51-000a27052861</key> <string>IOUSBFamily.kext/Contents/PlugIns/IOUSBLib.bundle</string> + </dict> + <key>IOUserClientClass</key> <string>IOUSBInterfaceUserClientV2</string> + </dict> + </dict> + </dict> + <key>OSBundleLibraries</key> + <dict> + <key>com.apple.iokit.IOUSBFamily</key> <string>3.0.0</string> + <key>com.apple.iokit.IOUSBUserClient</key> <string>3.0.0</string> + <key>com.apple.kpi.iokit</key> <string>9.0.0</string> + <key>com.apple.kpi.bsd</key> <string>9.0.0</string> + <key>com.apple.kpi.mach</key> <string>9.0.0</string> + <key>com.apple.kpi.libkern</key> <string>9.0.0</string> + <key>com.apple.kpi.unsupported</key> <string>9.0.0</string> + <key>org.virtualbox.kext.VBoxDrv</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + </dict> + <key>OSBundleLibraries_x86_64</key> + <dict> + <key>com.apple.iokit.IOUSBFamily</key> <string>3.5.0a25</string> + <key>com.apple.iokit.IOUSBUserClient</key> <string>3.5.0a25</string> + <key>com.apple.kpi.iokit</key> <string>10.0.0d3</string> + <key>com.apple.kpi.bsd</key> <string>10.0.0d3</string> + <key>com.apple.kpi.mach</key> <string>10.0.0d3</string> + <key>com.apple.kpi.libkern</key> <string>10.0.0d3</string> + <key>com.apple.kpi.unsupported</key> <string>10.0.0d3</string> + <key>org.virtualbox.kext.VBoxDrv</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + </dict> +</dict> +</plist> + diff --git a/src/VBox/HostDrivers/VBoxUSB/darwin/Makefile.kmk b/src/VBox/HostDrivers/VBoxUSB/darwin/Makefile.kmk new file mode 100644 index 00000000..435b89f4 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/darwin/Makefile.kmk @@ -0,0 +1,87 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Darwin VBoxUSB kernel extension. +# + +# +# 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. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# VBoxUSB.kext - The Darwin Kernel Extension. +# + +# Leopard (x86) and Snow Leopard (x86/amd64) +SYSMODS.darwin += VBoxUSB +VBoxUSB_TEMPLATE = VBOXR0DRV +VBoxUSB_INST = $(INST_VBOXUSB)Contents/MacOS/ +VBoxUSB_DEBUG_INST.darwin = $(patsubst %/,%,$(INST_VBOXUSB)) +VBoxUSB_INCS = \ + . \ + .. +#VBoxUSB_LDFLAGS = -v -Wl,-whyload -Wl,-v -Wl,-whatsloaded +VBoxUSB_SOURCES := \ + VBoxUSB.cpp \ + ../USBFilter.cpp \ + ../VBoxUSBFilterMgr.cpp + +INSTALLS += VBoxUSB.kext +VBoxUSB.kext_INST = $(INST_VBOXUSB)Contents/ +VBoxUSB.kext_SOURCES = $(VBoxUSB.kext_0_OUTDIR)/Contents/Info.plist +VBoxUSB.kext_CLEAN = $(VBoxUSB.kext_0_OUTDIR)/Contents/Info.plist +VBoxUSB.kext_BLDDIRS = $(VBoxUSB.kext_0_OUTDIR)/Contents/ + +$$(VBoxUSB.kext_0_OUTDIR)/Contents/Info.plist: $(PATH_SUB_CURRENT)/Info.plist $(VBOX_VERSION_MK) | $$(dir $$@) + $(call MSG_GENERATE,VBoxUSB,$@,$<) + $(QUIET)$(RM) -f $@ + $(QUIET)$(SED) \ + -e 's/@VBOX_VERSION_STRING@/$(VBOX_VERSION_STRING)/g' \ + -e 's/@VBOX_VERSION_MAJOR@/$(VBOX_VERSION_MAJOR)/g' \ + -e 's/@VBOX_VERSION_MINOR@/$(VBOX_VERSION_MINOR)/g' \ + -e 's/@VBOX_VERSION_BUILD@/$(VBOX_VERSION_BUILD)/g' \ + -e 's/@VBOX_VENDOR@/$(VBOX_VENDOR)/g' \ + -e 's/@VBOX_PRODUCT@/$(VBOX_PRODUCT)/g' \ + -e 's/@VBOX_C_YEAR@/$(VBOX_C_YEAR)/g' \ + --output $@ \ + $< + +$(evalcall2 VBOX_TEST_SIGN_KEXT,VBoxUSB) + +# Common manual loader script. +INSTALLS += ScriptsUSB +ScriptsUSB_INST = $(INST_DIST) +ScriptsUSB_EXEC_SOURCES = \ + loadusb.sh + +ifdef VBOX_WITH_TESTCASES +# +# Testcase for doing some manual driver testing... +# +PROGRAMS += tstOpenUSBDev +tstOpenUSBDev_TEMPLATE = VBOXR3TSTEXE +tstOpenUSBDev_SOURCES = testcase/tstOpenUSBDev.cpp +tstOpenUSBDev_LDFLAGS = -framework CoreFoundation -framework IOKit +endif + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/VBoxUSB/darwin/USBLib-darwin.cpp b/src/VBox/HostDrivers/VBoxUSB/darwin/USBLib-darwin.cpp new file mode 100644 index 00000000..30415310 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/darwin/USBLib-darwin.cpp @@ -0,0 +1,201 @@ +/** $Id: USBLib-darwin.cpp $ */ +/** @file + * USBLib - Library for wrapping up the VBoxUSB functionality, Darwin flavor. + */ + +/* + * Copyright (C) 2007-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 * +*********************************************************************************************************************************/ +#include <VBox/usblib.h> +#include <iprt/errcore.h> +#include <VBox/log.h> +#include "VBoxUSBInterface.h" + +#include <iprt/assert.h> +#include <iprt/asm.h> + +#include <mach/mach_port.h> +#include <IOKit/IOKitLib.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The IOClass key of the service (see VBoxUSB.cpp / Info.plist). */ +#define IOCLASS_NAME "org_virtualbox_VBoxUSB" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Reference counter. */ +static uint32_t volatile g_cUsers = 0; +/** The IOMasterPort. */ +static mach_port_t g_MasterPort = 0; +/** The current service connection. */ +static io_connect_t g_Connection = 0; + + + +USBLIB_DECL(int) USBLibInit(void) +{ + /* + * Already open? + * This isn't properly serialized, but we'll be fine with the current usage. + */ + if (g_cUsers) + { + ASMAtomicIncU32(&g_cUsers); + return VINF_SUCCESS; + } + + /* + * Finding the VBoxUSB service. + */ + mach_port_t MasterPort; + kern_return_t kr = IOMasterPort(MACH_PORT_NULL, &MasterPort); + if (kr != kIOReturnSuccess) + { + LogRel(("USBLib: IOMasterPort -> %#x\n", kr)); + return RTErrConvertFromDarwinKern(kr); + } + + CFDictionaryRef ClassToMatch = IOServiceMatching(IOCLASS_NAME); + if (!ClassToMatch) + { + LogRel(("USBLib: IOServiceMatching(\"%s\") failed.\n", IOCLASS_NAME)); + return VERR_GENERAL_FAILURE; + } + + /* Create an io_iterator_t for all instances of our drivers class that exist in the IORegistry. */ + io_iterator_t Iterator; + kr = IOServiceGetMatchingServices(g_MasterPort, ClassToMatch, &Iterator); + if (kr != kIOReturnSuccess) + { + LogRel(("USBLib: IOServiceGetMatchingServices returned %#x\n", kr)); + return RTErrConvertFromDarwinKern(kr); + } + + /* Get the first item in the iterator and release it. */ + io_service_t ServiceObject = IOIteratorNext(Iterator); + IOObjectRelease(Iterator); + if (!ServiceObject) + { + LogRel(("USBLib: Couldn't find any matches.\n")); + return VERR_GENERAL_FAILURE; + } + + /* + * Open the service. + * This will cause the user client class in VBoxUSB.cpp to be instantiated. + */ + kr = IOServiceOpen(ServiceObject, mach_task_self(), VBOXUSB_DARWIN_IOSERVICE_COOKIE, &g_Connection); + IOObjectRelease(ServiceObject); + if (kr != kIOReturnSuccess) + { + LogRel(("USBLib: IOServiceOpen returned %#x\n", kr)); + return RTErrConvertFromDarwinKern(kr); + } + + ASMAtomicIncU32(&g_cUsers); + return VINF_SUCCESS; +} + + +USBLIB_DECL(int) USBLibTerm(void) +{ + if (!g_cUsers) + return VERR_WRONG_ORDER; + if (ASMAtomicDecU32(&g_cUsers) != 0) + return VINF_SUCCESS; + + /* + * We're the last guy, close down the connection. + */ + kern_return_t kr = IOServiceClose(g_Connection); + if (kr != kIOReturnSuccess) + { + LogRel(("USBLib: Warning: IOServiceClose(%p) returned %#x\n", g_Connection, kr)); + AssertMsgFailed(("%#x\n", kr)); + } + g_Connection = 0; + + return VINF_SUCCESS; +} + + +USBLIB_DECL(void *) USBLibAddFilter(PCUSBFILTER pFilter) +{ + VBOXUSBADDFILTEROUT Out = { 0, VERR_WRONG_ORDER }; +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 + IOByteCount cbOut = sizeof(Out); + kern_return_t kr = IOConnectMethodStructureIStructureO(g_Connection, + VBOXUSBMETHOD_ADD_FILTER, + sizeof(*pFilter), + &cbOut, + (void *)pFilter, + &Out); +#else /* 10.5 and later */ + size_t cbOut = sizeof(Out); + kern_return_t kr = IOConnectCallStructMethod(g_Connection, + VBOXUSBMETHOD_ADD_FILTER, + (void *)pFilter, sizeof(*pFilter), + &Out, &cbOut); +#endif /* 10.5 and later */ + if ( kr == kIOReturnSuccess + && RT_SUCCESS(Out.rc)) + { + Assert(cbOut == sizeof(Out)); + Assert((void *)Out.uId != NULL); + return (void *)Out.uId; + } + + AssertMsgFailed(("kr=%#x Out.rc=%Rrc\n", kr, Out.rc)); + return NULL; +} + + +USBLIB_DECL(void) USBLibRemoveFilter(void *pvId) +{ + int rc = VERR_WRONG_ORDER; +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 + IOByteCount cbOut = sizeof(rc); + kern_return_t kr = IOConnectMethodStructureIStructureO(g_Connection, + VBOXUSBMETHOD_REMOVE_FILTER, + sizeof(pvId), + &cbOut, + &pvId, + &rc); +#else /* 10.5 and later */ + size_t cbOut = sizeof(rc); + kern_return_t kr = IOConnectCallStructMethod(g_Connection, + VBOXUSBMETHOD_REMOVE_FILTER, + (void *)&pvId, sizeof(pvId), + &rc, &cbOut); +#endif /* 10.5 and later */ + AssertMsg(kr == kIOReturnSuccess && RT_SUCCESS(rc), ("kr=%#x rc=%Rrc\n", kr, rc)); + NOREF(kr); +} + 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; +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/darwin/VBoxUSBInterface.h b/src/VBox/HostDrivers/VBoxUSB/darwin/VBoxUSBInterface.h new file mode 100644 index 00000000..362189b7 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/darwin/VBoxUSBInterface.h @@ -0,0 +1,65 @@ +/** $Id: VBoxUSBInterface.h $ */ +/** @file + * VirtualBox USB Driver User<->Kernel Interface. + */ + +/* + * Copyright (C) 2007-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. + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_darwin_VBoxUSBInterface_h +#define VBOX_INCLUDED_SRC_VBoxUSB_darwin_VBoxUSBInterface_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/usbfilter.h> + +/** + * org_virtualbox_VBoxUSBClient method indexes. + */ +typedef enum VBOXUSBMETHOD +{ + /** org_virtualbox_VBoxUSBClient::addFilter */ + VBOXUSBMETHOD_ADD_FILTER = 0, + /** org_virtualbox_VBoxUSBClient::removeFilter */ + VBOXUSBMETHOD_REMOVE_FILTER, + /** End/max. */ + VBOXUSBMETHOD_END +} VBOXUSBMETHOD; + +/** + * Output from a VBOXUSBMETHOD_ADD_FILTER call. + */ +typedef struct VBOXUSBADDFILTEROUT +{ + /** The ID. */ + uintptr_t uId; + /** The return code. */ + int rc; +} VBOXUSBADDFILTEROUT; +/** Pointer to a VBOXUSBADDFILTEROUT. */ +typedef VBOXUSBADDFILTEROUT *PVBOXUSBADDFILTEROUT; + +/** Cookie used to fend off some unwanted clients to the IOService. */ +#define VBOXUSB_DARWIN_IOSERVICE_COOKIE UINT32_C(0x62735556) /* 'VUsb' */ + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_darwin_VBoxUSBInterface_h */ + diff --git a/src/VBox/HostDrivers/VBoxUSB/darwin/loadusb.sh b/src/VBox/HostDrivers/VBoxUSB/darwin/loadusb.sh new file mode 100755 index 00000000..972ff6ad --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/darwin/loadusb.sh @@ -0,0 +1,123 @@ +#!/bin/bash +## @file +# For development. +# + +# +# 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. +# + +SCRIPT_NAME="loadusb" +XNU_VERSION=`LC_ALL=C uname -r | LC_ALL=C cut -d . -f 1` + +DRVNAME="VBoxUSB.kext" +BUNDLE="org.virtualbox.kext.VBoxUSB" + +DEP_DRVNAME="VBoxDrv.kext" +DEP_BUNDLE="org.virtualbox.kext.VBoxDrv" + + +DIR=`dirname "$0"` +DIR=`cd "$DIR" && pwd` +DEP_DIR="$DIR/$DEP_DRVNAME" +DIR="$DIR/$DRVNAME" +if [ ! -d "$DIR" ]; then + echo "Cannot find $DIR or it's not a directory..." + exit 1; +fi +if [ ! -d "$DEP_DIR" ]; then + echo "Cannot find $DEP_DIR or it's not a directory... (dependency)" + exit 1; +fi +if [ -n "$*" ]; then + OPTS="$*" +else + OPTS="-t" +fi + +trap "sudo chown -R `whoami` $DIR $DEP_DIR; exit 1" INT + +# Try unload any existing instance first. +LOADED=`kextstat -b $BUNDLE -l` +if test -n "$LOADED"; then + echo "${SCRIPT_NAME}.sh: Unloading $BUNDLE..." + sudo kextunload -v 6 -b $BUNDLE + LOADED=`kextstat -b $BUNDLE -l` + if test -n "$LOADED"; then + echo "${SCRIPT_NAME}.sh: failed to unload $BUNDLE, see above..." + exit 1; + fi + echo "${SCRIPT_NAME}.sh: Successfully unloaded $BUNDLE" +fi + +set -e + +# Copy the .kext to the symbols directory and tweak the kextload options. +if test -n "$VBOX_DARWIN_SYMS"; then + echo "${SCRIPT_NAME}.sh: copying the extension the symbol area..." + rm -Rf "$VBOX_DARWIN_SYMS/$DRVNAME" + mkdir -p "$VBOX_DARWIN_SYMS" + cp -R "$DIR" "$VBOX_DARWIN_SYMS/" + OPTS="$OPTS -s $VBOX_DARWIN_SYMS/ " + sync +fi + +# On smbfs, this might succeed just fine but make no actual changes, +# so we might have to temporarily copy the driver to a local directory. +if sudo chown -R root:wheel "$DIR" "$DEP_DIR"; then + OWNER=`/usr/bin/stat -f "%u" "$DIR"` +else + OWNER=1000 +fi +if test "$OWNER" -ne 0; then + TMP_DIR=/tmp/${SCRIPT_NAME}.tmp + echo "${SCRIPT_NAME}.sh: chown didn't work on $DIR, using temp location $TMP_DIR/$DRVNAME" + + # clean up first (no sudo rm) + if test -e "$TMP_DIR"; then + sudo chown -R `whoami` "$TMP_DIR" + rm -Rf "$TMP_DIR" + fi + + # make a copy and switch over DIR + mkdir -p "$TMP_DIR/" + sudo cp -Rp "$DIR" "$TMP_DIR/" + DIR="$TMP_DIR/$DRVNAME" + + # load.sh puts it here. + DEP_DIR="/tmp/loaddrv.tmp/$DEP_DRVNAME" + + # retry + sudo chown -R root:wheel "$DIR" "$DEP_DIR" +fi + +sudo chmod -R o-rwx "$DIR" +sync +if [ "$XNU_VERSION" -ge "10" ]; then + echo "${SCRIPT_NAME}.sh: loading $DIR... (kextutil $OPTS -d \"$DEP_DIR\" \"$DIR\")" + sudo kextutil $OPTS -d "$DEP_DIR" "$DIR" +else + echo "${SCRIPT_NAME}.sh: loading $DIR... (kextload $OPTS -d \"$DEP_DIR\" \"$DIR\")" + sudo kextload $OPTS -d "$DEP_DIR" "$DIR" +fi +sync +sudo chown -R `whoami` "$DIR" "$DEP_DIR" +kextstat | grep org.virtualbox.kext + diff --git a/src/VBox/HostDrivers/VBoxUSB/darwin/testcase/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/darwin/testcase/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/darwin/testcase/Makefile.kup diff --git a/src/VBox/HostDrivers/VBoxUSB/darwin/testcase/tstOpenUSBDev.cpp b/src/VBox/HostDrivers/VBoxUSB/darwin/testcase/tstOpenUSBDev.cpp new file mode 100644 index 00000000..3eec565b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/darwin/testcase/tstOpenUSBDev.cpp @@ -0,0 +1,295 @@ +/* $Id: tstOpenUSBDev.cpp $ */ +/** @file + * Testcase that attempts to locate and open the specified device. + */ + +/* + * 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 * +*********************************************************************************************************************************/ +#include <mach/mach.h> +#include <Carbon/Carbon.h> +#include <IOKit/IOKitLib.h> +#include <IOKit/storage/IOStorageDeviceCharacteristics.h> +#include <IOKit/scsi/SCSITaskLib.h> +#include <mach/mach_error.h> +#include <IOKit/usb/IOUSBLib.h> +#include <IOKit/IOCFPlugIn.h> + +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/process.h> +#include <iprt/assert.h> +#include <iprt/thread.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/stream.h> + + +/** + * Gets an unsigned 32-bit integer value. + * + * @returns Success indicator (true/false). + * @param DictRef The dictionary. + * @param KeyStrRef The key name. + * @param pu32 Where to store the key value. + */ +static bool tstDictGetU32(CFMutableDictionaryRef DictRef, CFStringRef KeyStrRef, uint32_t *pu32) +{ + CFTypeRef ValRef = CFDictionaryGetValue(DictRef, KeyStrRef); + if (ValRef) + { + if (CFNumberGetValue((CFNumberRef)ValRef, kCFNumberSInt32Type, pu32)) + return true; + } + *pu32 = 0; + return false; +} + + +/** + * Gets an unsigned 64-bit integer value. + * + * @returns Success indicator (true/false). + * @param DictRef The dictionary. + * @param KeyStrRef The key name. + * @param pu64 Where to store the key value. + */ +static bool tstDictGetU64(CFMutableDictionaryRef DictRef, CFStringRef KeyStrRef, uint64_t *pu64) +{ + CFTypeRef ValRef = CFDictionaryGetValue(DictRef, KeyStrRef); + if (ValRef) + { + if (CFNumberGetValue((CFNumberRef)ValRef, kCFNumberSInt64Type, pu64)) + return true; + } + *pu64 = 0; + return false; +} + + +static int tstDoWork(io_object_t USBDevice, const char *argv0) +{ + /* + * Create a plugin interface for the device and query its IOUSBDeviceInterface. + */ + int vrc = VINF_SUCCESS; + SInt32 Score = 0; + IOCFPlugInInterface **ppPlugInInterface = NULL; + IOReturn irc = IOCreatePlugInInterfaceForService(USBDevice, kIOUSBDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, &ppPlugInInterface, &Score); + if (irc == kIOReturnSuccess) + { + IOUSBDeviceInterface245 **ppDevI = NULL; + HRESULT hrc = (*ppPlugInInterface)->QueryInterface(ppPlugInInterface, + CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID245), + (LPVOID *)&ppDevI); + irc = IODestroyPlugInInterface(ppPlugInInterface); Assert(irc == kIOReturnSuccess); + ppPlugInInterface = NULL; + if (hrc == S_OK) + { + /* + * Try open the device for exclusive access. + */ + irc = (*ppDevI)->USBDeviceOpenSeize(ppDevI); + if (irc == kIOReturnExclusiveAccess) + { + RTThreadSleep(20); + irc = (*ppDevI)->USBDeviceOpenSeize(ppDevI); + } + if (irc == kIOReturnSuccess) + { +#if 0 + /* + * Re-enumerate the device and bail out. + */ + irc = (*ppDevI)->USBDeviceReEnumerate(ppDevI, 0); + if (irc != kIOReturnSuccess) + { + vrc = RTErrConvertFromDarwinIO(irc); + RTPrintf("%s: Failed to re-enumerate the device, irc=%#x (vrc=%Rrc).\n", argv0, irc, vrc); + } +#endif + + (*ppDevI)->USBDeviceClose(ppDevI); + } + else if (irc == kIOReturnExclusiveAccess) + { + vrc = VERR_SHARING_VIOLATION; + RTPrintf("%s: The device is being used by another process (irc=kIOReturnExclusiveAccess)\n", argv0); + } + else + { + vrc = VERR_OPEN_FAILED; + RTPrintf("%s: Failed to open the device, irc=%#x (vrc=%Rrc).\n", argv0, irc, vrc); + } + } + else + { + vrc = VERR_OPEN_FAILED; + RTPrintf("%s: Failed to create plugin interface for the device, hrc=%#x (vrc=%Rrc).\n", argv0, hrc, vrc); + } + + (*ppDevI)->Release(ppDevI); + } + else + { + vrc = RTErrConvertFromDarwinIO(irc); + RTPrintf("%s: Failed to open the device, plug-in creation failed with irc=%#x (vrc=%Rrc).\n", argv0, irc, vrc); + } + + return vrc; +} + + +static int tstSyntax(const char *argv0) +{ + RTPrintf("syntax: %s [criteria]\n" + "\n" + "Criteria:\n" + " -l <location>\n" + " -s <session>\n" + , argv0); + return 1; +} + + +int main(int argc, char **argv) +{ + RTR3InitExe(argc, &argv, 0); + + /* + * Show help if not arguments. + */ + if (argc <= 1) + return tstSyntax(argv[0]); + + /* + * Parse arguments. + */ + static const RTGETOPTDEF g_aOptions[] = + { + { "--location", 'l', RTGETOPT_REQ_UINT32 }, + { "--session", 's', RTGETOPT_REQ_UINT64 }, + }; + + kern_return_t krc; + uint64_t u64SessionId = 0; + uint32_t u32LocationId = 0; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, g_aOptions, RT_ELEMENTS(g_aOptions), 1, 0 /* fFlags */); + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) + { + case 'l': + u32LocationId = ValueUnion.u32; + break; + case 's': + u64SessionId = ValueUnion.u64; + break; + case 'h': + return tstSyntax(argv[0]); + case 'V': + RTPrintf("$Revision: 127855 $\n"); + return 0; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + /* + * Open the master port. + */ + mach_port_t MasterPort = MACH_PORT_NULL; + krc = IOMasterPort(MACH_PORT_NULL, &MasterPort); + if (krc != KERN_SUCCESS) + { + RTPrintf("%s: IOMasterPort -> %x\n", argv[0], krc); + return 1; + } + + /* + * Iterate the USB devices and find all that matches. + */ + CFMutableDictionaryRef RefMatchingDict = IOServiceMatching(kIOUSBDeviceClassName); + if (!RefMatchingDict) + { + RTPrintf("%s: IOServiceMatching failed\n", argv[0]); + return 1; + } + + io_iterator_t USBDevices = IO_OBJECT_NULL; + IOReturn irc = IOServiceGetMatchingServices(MasterPort, RefMatchingDict, &USBDevices); + if (irc != kIOReturnSuccess) + { + RTPrintf("%s: IOServiceGetMatchingServices -> %#x\n", argv[0], irc); + return 1; + } + RefMatchingDict = NULL; /* the reference is consumed by IOServiceGetMatchingServices. */ + + unsigned cDevices = 0; + unsigned cMatches = 0; + io_object_t USBDevice; + while ((USBDevice = IOIteratorNext(USBDevices))) + { + cDevices++; + CFMutableDictionaryRef PropsRef = 0; + krc = IORegistryEntryCreateCFProperties(USBDevice, &PropsRef, kCFAllocatorDefault, kNilOptions); + if (krc == KERN_SUCCESS) + { + uint64_t u64CurSessionId; + uint32_t u32CurLocationId; + if ( ( !u64SessionId + || ( tstDictGetU64(PropsRef, CFSTR("sessionID"), &u64CurSessionId) + && u64CurSessionId == u64SessionId)) + && ( !u32LocationId + || ( tstDictGetU32(PropsRef, CFSTR(kUSBDevicePropertyLocationID), &u32CurLocationId) + && u32CurLocationId == u32LocationId)) + ) + { + cMatches++; + CFRelease(PropsRef); + tstDoWork(USBDevice, argv[0]); + } + else + CFRelease(PropsRef); + } + IOObjectRelease(USBDevice); + } + IOObjectRelease(USBDevices); + + /* + * Bitch if we didn't find anything matching the criteria. + */ + if (!cMatches) + RTPrintf("%s: No matching devices found from a total of %d.\n", argv[0], cDevices); + return !cMatches; +} + |