/* |
File: USBPrivateDataSample.c |
|
Description: This sample demonstrates how to use IOKitLib and IOUSBLib to set up asynchronous |
callbacks when a USB device is attached to or removed from the system. |
It also shows how to associate arbitrary data with each device instance. |
|
Copyright: © Copyright 2001-2006 Apple Computer, Inc. All rights reserved. |
|
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. |
("Apple") in consideration of your agreement to the following terms, and your |
use, installation, modification or redistribution of this Apple software |
constitutes acceptance of these terms. If you do not agree with these terms, |
please do not use, install, modify or redistribute this Apple software. |
|
In consideration of your agreement to abide by the following terms, and subject |
to these terms, Apple grants you a personal, non-exclusive license, under AppleÕs |
copyrights in this original Apple software (the "Apple Software"), to use, |
reproduce, modify and redistribute the Apple Software, with or without |
modifications, in source and/or binary forms; provided that if you redistribute |
the Apple Software in its entirety and without modifications, you must retain |
this notice and the following text and disclaimers in all such redistributions of |
the Apple Software. Neither the name, trademarks, service marks or logos of |
Apple Computer, Inc. may be used to endorse or promote products derived from the |
Apple Software without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or implied, |
are granted by Apple herein, including but not limited to any patent rights that |
may be infringed by your derivative works or by other works in which the Apple |
Software may be incorporated. |
|
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO |
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED |
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
COMBINATION WITH YOUR PRODUCTS. |
|
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR |
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT |
(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN |
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
Change History (most recent first): |
|
1.2 10/04/2006 Updated to produce a universal binary. Now requires Xcode 2.2.1 or |
later to build. Modernized and incorporated bug fixes. |
|
1.1 04/24/2002 Added comments, release of interface object, use of USB location ID |
|
1.0 10/30/2001 New sample. |
|
*/ |
|
#include <CoreFoundation/CoreFoundation.h> |
|
#include <IOKit/IOKitLib.h> |
#include <IOKit/IOMessage.h> |
#include <IOKit/IOCFPlugIn.h> |
#include <IOKit/usb/IOUSBLib.h> |
|
// Change these two constants to match your device's idVendor and idProduct. |
// Or, just pass your idVendor and idProduct as command line arguments when running this sample. |
#define kMyVendorID 1351 |
#define kMyProductID 8193 |
|
typedef struct MyPrivateData { |
io_object_t notification; |
IOUSBDeviceInterface **deviceInterface; |
CFStringRef deviceName; |
UInt32 locationID; |
} MyPrivateData; |
|
static IONotificationPortRef gNotifyPort; |
static io_iterator_t gAddedIter; |
static CFRunLoopRef gRunLoop; |
|
//================================================================================================ |
// |
// DeviceNotification |
// |
// This routine will get called whenever any kIOGeneralInterest notification happens. We are |
// interested in the kIOMessageServiceIsTerminated message so that's what we look for. Other |
// messages are defined in IOMessage.h. |
// |
//================================================================================================ |
void DeviceNotification(void *refCon, io_service_t service, natural_t messageType, void *messageArgument) |
{ |
kern_return_t kr; |
MyPrivateData *privateDataRef = (MyPrivateData *) refCon; |
|
if (messageType == kIOMessageServiceIsTerminated) { |
fprintf(stderr, "Device removed.\n"); |
|
// Dump our private data to stderr just to see what it looks like. |
fprintf(stderr, "privateDataRef->deviceName: "); |
CFShow(privateDataRef->deviceName); |
fprintf(stderr, "privateDataRef->locationID: 0x%lx.\n\n", privateDataRef->locationID); |
|
// Free the data we're no longer using now that the device is going away |
CFRelease(privateDataRef->deviceName); |
|
if (privateDataRef->deviceInterface) { |
kr = (*privateDataRef->deviceInterface)->Release(privateDataRef->deviceInterface); |
} |
|
kr = IOObjectRelease(privateDataRef->notification); |
|
free(privateDataRef); |
} |
} |
|
//================================================================================================ |
// |
// DeviceAdded |
// |
// This routine is the callback for our IOServiceAddMatchingNotification. When we get called |
// we will look at all the devices that were added and we will: |
// |
// 1. Create some private data to relate to each device (in this case we use the service's name |
// and the location ID of the device |
// 2. Submit an IOServiceAddInterestNotification of type kIOGeneralInterest for this device, |
// using the refCon field to store a pointer to our private data. When we get called with |
// this interest notification, we can grab the refCon and access our private data. |
// |
//================================================================================================ |
void DeviceAdded(void *refCon, io_iterator_t iterator) |
{ |
kern_return_t kr; |
io_service_t usbDevice; |
IOCFPlugInInterface **plugInInterface = NULL; |
SInt32 score; |
HRESULT res; |
|
while ((usbDevice = IOIteratorNext(iterator))) { |
io_name_t deviceName; |
CFStringRef deviceNameAsCFString; |
MyPrivateData *privateDataRef = NULL; |
UInt32 locationID; |
|
printf("Device added.\n"); |
|
// Add some app-specific information about this device. |
// Create a buffer to hold the data. |
privateDataRef = malloc(sizeof(MyPrivateData)); |
bzero(privateDataRef, sizeof(MyPrivateData)); |
|
// Get the USB device's name. |
kr = IORegistryEntryGetName(usbDevice, deviceName); |
if (KERN_SUCCESS != kr) { |
deviceName[0] = '\0'; |
} |
|
deviceNameAsCFString = CFStringCreateWithCString(kCFAllocatorDefault, deviceName, |
kCFStringEncodingASCII); |
|
// Dump our data to stderr just to see what it looks like. |
fprintf(stderr, "deviceName: "); |
CFShow(deviceNameAsCFString); |
|
// Save the device's name to our private data. |
privateDataRef->deviceName = deviceNameAsCFString; |
|
// Now, get the locationID of this device. In order to do this, we need to create an IOUSBDeviceInterface |
// for our device. This will create the necessary connections between our userland application and the |
// kernel object for the USB Device. |
kr = IOCreatePlugInInterfaceForService(usbDevice, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, |
&plugInInterface, &score); |
|
if ((kIOReturnSuccess != kr) || !plugInInterface) { |
fprintf(stderr, "IOCreatePlugInInterfaceForService returned 0x%08x.\n", kr); |
continue; |
} |
|
// Use the plugin interface to retrieve the device interface. |
res = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), |
(LPVOID*) &privateDataRef->deviceInterface); |
|
// Now done with the plugin interface. |
(*plugInInterface)->Release(plugInInterface); |
|
if (res || privateDataRef->deviceInterface == NULL) { |
fprintf(stderr, "QueryInterface returned %d.\n", (int) res); |
continue; |
} |
|
// Now that we have the IOUSBDeviceInterface, we can call the routines in IOUSBLib.h. |
// In this case, fetch the locationID. The locationID uniquely identifies the device |
// and will remain the same, even across reboots, so long as the bus topology doesn't change. |
|
kr = (*privateDataRef->deviceInterface)->GetLocationID(privateDataRef->deviceInterface, &locationID); |
if (KERN_SUCCESS != kr) { |
fprintf(stderr, "GetLocationID returned 0x%08x.\n", kr); |
continue; |
} |
else { |
fprintf(stderr, "Location ID: 0x%lx\n\n", locationID); |
} |
|
privateDataRef->locationID = locationID; |
|
// Register for an interest notification of this device being removed. Use a reference to our |
// private data as the refCon which will be passed to the notification callback. |
kr = IOServiceAddInterestNotification(gNotifyPort, // notifyPort |
usbDevice, // service |
kIOGeneralInterest, // interestType |
DeviceNotification, // callback |
privateDataRef, // refCon |
&(privateDataRef->notification) // notification |
); |
|
if (KERN_SUCCESS != kr) { |
printf("IOServiceAddInterestNotification returned 0x%08x.\n", kr); |
} |
|
// Done with this USB device; release the reference added by IOIteratorNext |
kr = IOObjectRelease(usbDevice); |
} |
} |
|
//================================================================================================ |
// |
// SignalHandler |
// |
// This routine will get called when we interrupt the program (usually with a Ctrl-C from the |
// command line). |
// |
//================================================================================================ |
void SignalHandler(int sigraised) |
{ |
fprintf(stderr, "\nInterrupted.\n"); |
|
exit(0); |
} |
|
//================================================================================================ |
// main |
//================================================================================================ |
int main(int argc, const char *argv[]) |
{ |
CFMutableDictionaryRef matchingDict; |
CFRunLoopSourceRef runLoopSource; |
CFNumberRef numberRef; |
kern_return_t kr; |
long usbVendor = kMyVendorID; |
long usbProduct = kMyProductID; |
sig_t oldHandler; |
|
// pick up command line arguments |
if (argc > 1) { |
usbVendor = atoi(argv[1]); |
} |
if (argc > 2) { |
usbProduct = atoi(argv[2]); |
} |
|
// Set up a signal handler so we can clean up when we're interrupted from the command line |
// Otherwise we stay in our run loop forever. |
oldHandler = signal(SIGINT, SignalHandler); |
if (oldHandler == SIG_ERR) { |
fprintf(stderr, "Could not establish new signal handler."); |
} |
|
fprintf(stderr, "Looking for devices matching vendor ID=%ld and product ID=%ld.\n", usbVendor, usbProduct); |
|
// Set up the matching criteria for the devices we're interested in. The matching criteria needs to follow |
// the same rules as kernel drivers: mainly it needs to follow the USB Common Class Specification, pp. 6-7. |
// See also Technical Q&A QA1076 "Tips on USB driver matching on Mac OS X" |
// <http://developer.apple.com/qa/qa2001/qa1076.html>. |
// One exception is that you can use the matching dictionary "as is", i.e. without adding any matching |
// criteria to it and it will match every IOUSBDevice in the system. IOServiceAddMatchingNotification will |
// consume this dictionary reference, so there is no need to release it later on. |
|
matchingDict = IOServiceMatching(kIOUSBDeviceClassName); // Interested in instances of class |
// IOUSBDevice and its subclasses |
if (matchingDict == NULL) { |
fprintf(stderr, "IOServiceMatching returned NULL.\n"); |
return -1; |
} |
|
// We are interested in all USB devices (as opposed to USB interfaces). The Common Class Specification |
// tells us that we need to specify the idVendor, idProduct, and bcdDevice fields, or, if we're not interested |
// in particular bcdDevices, just the idVendor and idProduct. Note that if we were trying to match an |
// IOUSBInterface, we would need to set more values in the matching dictionary (e.g. idVendor, idProduct, |
// bInterfaceNumber and bConfigurationValue. |
|
// Create a CFNumber for the idVendor and set the value in the dictionary |
numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usbVendor); |
CFDictionarySetValue(matchingDict, |
CFSTR(kUSBVendorID), |
numberRef); |
CFRelease(numberRef); |
|
// Create a CFNumber for the idProduct and set the value in the dictionary |
numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usbProduct); |
CFDictionarySetValue(matchingDict, |
CFSTR(kUSBProductID), |
numberRef); |
CFRelease(numberRef); |
numberRef = NULL; |
|
// Create a notification port and add its run loop event source to our run loop |
// This is how async notifications get set up. |
|
gNotifyPort = IONotificationPortCreate(kIOMasterPortDefault); |
runLoopSource = IONotificationPortGetRunLoopSource(gNotifyPort); |
|
gRunLoop = CFRunLoopGetCurrent(); |
CFRunLoopAddSource(gRunLoop, runLoopSource, kCFRunLoopDefaultMode); |
|
// Now set up a notification to be called when a device is first matched by I/O Kit. |
kr = IOServiceAddMatchingNotification(gNotifyPort, // notifyPort |
kIOFirstMatchNotification, // notificationType |
matchingDict, // matching |
DeviceAdded, // callback |
NULL, // refCon |
&gAddedIter // notification |
); |
|
// Iterate once to get already-present devices and arm the notification |
DeviceAdded(NULL, gAddedIter); |
|
// Start the run loop. Now we'll receive notifications. |
fprintf(stderr, "Starting run loop.\n\n"); |
CFRunLoopRun(); |
|
// We should never get here |
fprintf(stderr, "Unexpectedly back from CFRunLoopRun()!\n"); |
return 0; |
} |