/*
* Copyright © 1998-2012 Apple Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
#import "BusProber.h"
@implementation BusProber
static void DeviceAdded(void *refCon, io_iterator_t iterator)
{
io_service_t ioDeviceObj = IO_OBJECT_NULL;
while( (ioDeviceObj = IOIteratorNext( iterator)) )
{
IOObjectRelease( ioDeviceObj );
}
[(BusProber *)refCon refreshData:false];
}
static void DeviceRemoved(void *refCon, io_iterator_t iterator)
{
io_service_t ioDeviceObj = IO_OBJECT_NULL;
while( (ioDeviceObj = IOIteratorNext( iterator)))
{
IOObjectRelease( ioDeviceObj );
}
[(BusProber *)refCon refreshData:false];
}
- initWithListener:(id)listener devicesArray:(NSMutableArray *)devices {
if (self = [super init]) {
_runLoopSource = NULL;
_listener = listener;
_devicesArray = [devices retain];
if (listener == nil) {
[NSException raise:@"BusProberBadListener" format:@"Listener must be non-nil"];
[self dealloc];
self = nil;
} else if (! [self registerForUSBNotifications]) {
NSLog(@"USB Prober was unable to register for USB notifications");
[self dealloc];
self = nil;
} else {
// no problems
[self refreshData:true];
}
}
return self;
}
- (void)dealloc {
[self unregisterForUSBNotifications];
[_devicesArray release];
[super dealloc];
}
- (BOOL)registerForUSBNotifications
{
kern_return_t kr;
mach_port_t masterPort;
IONotificationPortRef gNotifyPort;
io_iterator_t gAddedIter,gRemovedIter;
kr = IOMasterPort(MACH_PORT_NULL, &masterPort);
if (kr != KERN_SUCCESS) return NO;
gNotifyPort = IONotificationPortCreate(masterPort);
_runLoopSource = IONotificationPortGetRunLoopSource(gNotifyPort);
CFRunLoopAddSource([[NSRunLoop currentRunLoop] getCFRunLoop], _runLoopSource, kCFRunLoopDefaultMode);
kr = IOServiceAddMatchingNotification(gNotifyPort,
kIOFirstMatchNotification,
IOServiceMatching(kIOUSBDeviceClassName),
DeviceAdded,
self,
&gAddedIter);
if (kr != KERN_SUCCESS) return NO;
kr = IOServiceAddMatchingNotification(gNotifyPort,
kIOTerminatedNotification,
IOServiceMatching(kIOUSBDeviceClassName),
DeviceRemoved,
self,
&gRemovedIter);
if (kr != KERN_SUCCESS) return NO;
DeviceAdded(self, gAddedIter);
DeviceRemoved(self, gRemovedIter);
mach_port_deallocate(mach_task_self(), masterPort);
return YES;
}
- (void)unregisterForUSBNotifications {
if (_runLoopSource != NULL)
{
CFRunLoopSourceInvalidate(_runLoopSource);
CFRelease(_runLoopSource);
}
}
- (void)refreshData:(BOOL)shouldForce {
if (!shouldForce && [[NSUserDefaults standardUserDefaults] boolForKey:@"BusProbeAutoRefresh"] == NO) {
return;
}
[_devicesArray removeAllObjects];
CFDictionaryRef matchingDict = NULL;
mach_port_t mMasterDevicePort = MACH_PORT_NULL;
io_iterator_t devIter = IO_OBJECT_NULL;
io_service_t ioDeviceObj = IO_OBJECT_NULL;
IOReturn kr;
int deviceNumber = 0; //used to iterate through devices
kr = IOMasterPort(MACH_PORT_NULL, &mMasterDevicePort);
if (kr != kIOReturnSuccess) {
NSLog(@"USB Prober: error in -refresh at IOMasterPort()");
return;
}
matchingDict = IOServiceMatching(kIOUSBDeviceClassName);
if (matchingDict == NULL) {
NSLog(@"USB Prober: error in -refresh at IOServiceMatching() - dictionary was NULL");
mach_port_deallocate(mach_task_self(), mMasterDevicePort);
return;
}
kr = IOServiceGetMatchingServices(mMasterDevicePort, matchingDict /*reference consumed*/, &devIter);
if (kr != kIOReturnSuccess) {
NSLog(@"USB Prober: error in -refresh at IOServiceGetMatchingServices()");
mach_port_deallocate(mach_task_self(), mMasterDevicePort);
return;
}
while ((ioDeviceObj = IOIteratorNext(devIter))) {
IOCFPlugInInterface **ioPlugin;
IOUSBDeviceRef deviceIntf = NULL;
SInt32 score;
NSString * prodName = NULL;
kr = IOCreatePlugInInterfaceForService(ioDeviceObj, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &ioPlugin, &score);
if (kr != kIOReturnSuccess) {
IOObjectRelease(ioDeviceObj);
continue;
}
kr = (*ioPlugin)->QueryInterface(ioPlugin, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID *)&deviceIntf);
IODestroyPlugInInterface(ioPlugin);
ioPlugin = NULL;
if (kr != kIOReturnSuccess) {
IOObjectRelease(ioDeviceObj);
continue;
}
// Get the product name from the registry in case we can't get it from the device later on
prodName = GetUSBProductNameFromRegistry( ioDeviceObj);
if ( prodName == NULL )
{
io_name_t class;
IOReturn status;
status = IORegistryEntryGetNameInPlane(ioDeviceObj, kIOServicePlane, class);
if ( status == kIOReturnSuccess )
prodName = [[NSString alloc] initWithCString:class encoding:NSUTF8StringEncoding];
else
prodName = [[NSString alloc] initWithFormat:@"Unknown Device"];
}
[self processDevice:deviceIntf deviceNumber:deviceNumber usbName:prodName];
deviceNumber++;
[prodName release];
(*deviceIntf)->Release(deviceIntf);
IOObjectRelease(ioDeviceObj);
}
IOObjectRelease(devIter);
mach_port_deallocate(mach_task_self(), mMasterDevicePort);
[_listener busProberInformationDidChange:self];
}
- (void)processDevice:(IOUSBDeviceRef)deviceIntf deviceNumber:(int)deviceNumber usbName:(NSString *)usbName {
BusProbeDevice * thisDevice;
UInt32 locationID = 0;
uint32_t portInfo = 0;
UInt8 speed = 0;
USBDeviceAddress address = 0;
IOUSBDeviceDescriptor dev;
int len = 0;
int currConfig;
IOReturn error = kIOReturnSuccess, actErr = kIOReturnSuccess;
BOOL needToSuspend = FALSE;
thisDevice = [[BusProbeDevice alloc] init];
bzero(&dev, sizeof(dev));
[_devicesArray addObject:thisDevice];
// Get the locationID for this device
if (GetDeviceLocationID( deviceIntf, &locationID ) == 0) {
[thisDevice setLocationID:locationID];
}
// Get the connection speed for this device
if (GetDeviceSpeed( deviceIntf, &speed ) == 0) {
[thisDevice setSpeed:speed];
}
// Get the device address for this device
if (GetDeviceAddress( deviceIntf, &address ) == 0) {
[thisDevice setAddress:address];
}
// Set the name of the device (this is what will be shown in the UI)
[thisDevice setDeviceName:
[NSString stringWithFormat:@" (speed == kUSBDeviceSpeedSuper ? "Super" : (speed == kUSBDeviceSpeedHigh ? "High" :(speed == kUSBDeviceSpeedFull ? "Full" : "Low"))),
address,
locationID]];
// Get the Port Information
error = GetPortInformation(deviceIntf, &portInfo);
if (error == kIOReturnSuccess) {
[thisDevice setPortInfo:portInfo];
[self PrintPortInfo:portInfo forDevice:thisDevice];
}
else {
char buf[256];
sprintf((char *)buf, " [thisDevice addProperty:"Port Information:" withValue:buf atDepth:ROOT_LEVEL];
//[thisDevice addProperty:"Port Information:" withValue:(char *)[NSString stringWithFormat:@"Error NSLog(@"USB Prober: GetUSBDeviceInformation() for device @ error = kIOReturnSuccess;
}
// Log the number of endpoints for each configuration
[self GetAndPrintNumberOfEndpoints:deviceIntf forDevice:thisDevice portInfo:portInfo];
// If the device is suspended, then unsuspend it first
if ( portInfo & (1<<kUSBInformationDeviceIsSuspendedBit) ) {
if ([[NSUserDefaults standardUserDefaults] boolForKey:@"BusProbeSuspended"] == YES) {
// Set this flag so we re-suspend the device later on
needToSuspend = TRUE;
error = SuspendDevice(deviceIntf,false);
}
else {
error = kIOReturnNotResponding;
}
}
// If we are not suspended, talk to our device and get the Device Desriptor
if (error == kIOReturnSuccess) {
error = GetDescriptor(deviceIntf, kUSBDeviceDesc, 0, &dev, sizeof(dev), &actErr);
}
// OK, go get the descriptors and run with it
if ( error == kIOReturnSuccess )
{
int iconfig;
[DecodeDeviceDescriptor decodeBytes:&dev forDevice:thisDevice deviceInterface:deviceIntf wasSuspended:needToSuspend];
if (actErr != kIOReturnSuccess) {
[thisDevice setDeviceDescription: [NSString stringWithFormat:@" }
IOUSBBOSDescriptor bosDescriptor;
// If we have a SuperSpeed device, then go get and decode the BOS descriptors. We first read the root BOS descriptor, which tells us the length, and then we read
// the full descriptor, just like we do for config descriptors
if ( speed == kUSBDeviceSpeedSuper )
{
error = GetDescriptor(deviceIntf, kUSBBOSDescriptor, 0, &bosDescriptor, sizeof(bosDescriptor), &actErr);
if ( error == kIOReturnSuccess)
{
[DecodeBOSDescriptor decodeBytes:(IOUSBBOSDescriptor *)&bosDescriptor forDevice:thisDevice deviceInterface:deviceIntf];
}
}
// Check the current configuration
currConfig = GetCurrentConfiguration(deviceIntf);
if(currConfig < 1) // Almost everything has a current config of 1
{
char buf[256];
if(currConfig == -1)
{
// failed the request for some reason
sprintf((char *)buf, " }
else
{
sprintf((char *)buf, " }
[thisDevice addProperty:"Current configuration:" withValue:buf atDepth:ROOT_LEVEL];
}
// Go decode the Config Desriptor
for (iconfig = 0; iconfig < dev.bNumConfigurations; ++iconfig) {
IOUSBConfigurationDescHeader cfgHeader;
IOUSBConfigurationDescriptor config;
// Get the Configuration descriptor. We first get just the header and later we get the full
// descriptor
error = GetDescriptor(deviceIntf, kUSBConfDesc, iconfig, &cfgHeader, sizeof(cfgHeader), nil);
if (error != kIOReturnSuccess) {
// Set a flag to the decodeBytes descriptor indicating that we didn't get the header
//
cfgHeader.bDescriptorType = sizeof(cfgHeader);
cfgHeader.wTotalLength = 0;
[DecodeConfigurationDescriptor decodeBytes:(IOUSBConfigurationDescHeader *)&cfgHeader forDevice:thisDevice deviceInterface:deviceIntf configNumber:iconfig currentConfig:currConfig isOtherSpeedDesc:NO];
// Try to get the descriptor again, using the sizeof(IOUSBConfigurationDescriptor)
//
bzero(&config,sizeof(config)-1);
error = GetDescriptor(deviceIntf, kUSBConfDesc, iconfig, &config, sizeof(config)-1, nil);
if (error != kIOReturnSuccess) {
cfgHeader.bDescriptorType = sizeof(config)-1;
cfgHeader.wTotalLength = 0;
}
else {
cfgHeader.bLength = config.bLength;
cfgHeader.bDescriptorType = config.bDescriptorType;
cfgHeader.wTotalLength = config.wTotalLength;
}
}
[DecodeConfigurationDescriptor decodeBytes:(IOUSBConfigurationDescHeader *)&cfgHeader forDevice:thisDevice deviceInterface:deviceIntf configNumber:iconfig currentConfig:currConfig isOtherSpeedDesc:NO];
}
// If the device is a hub, then dump the Hub descriptor
//
if ( dev.bDeviceClass == kUSBHubClass ) {
if(dev.bDeviceProtocol == 3)
{
IOUSB3HubDescriptor cfg;
len = GetClassDescriptor(deviceIntf, kUSB3HUBDesc, 0, &cfg, sizeof(cfg));
if (len > 0) {
[DescriptorDecoder decodeBytes:(Byte *)&cfg forDevice:thisDevice deviceInterface:deviceIntf userInfo:NULL isOtherSpeedDesc:false isinCurrentConfig:false];
}
}
else
{
IOUSBHubDescriptor cfg;
len = GetClassDescriptor(deviceIntf, kUSBHUBDesc, 0, &cfg, sizeof(cfg));
if (len > 0) {
[DescriptorDecoder decodeBytes:(Byte *)&cfg forDevice:thisDevice deviceInterface:deviceIntf userInfo:NULL isOtherSpeedDesc:false isinCurrentConfig:false];
}
}
}
// Check to see if the device has the "Device Qualifier" descriptor
//
if ( dev.bcdUSB >= kUSBRel20 && speed == kUSBDeviceSpeedHigh) {
IOUSBDeviceQualifierDescriptor desc;
error = GetDescriptor(deviceIntf, kUSBDeviceQualifierDesc, 0, &desc, sizeof(desc), nil);
if (error == kIOReturnSuccess) {
[DescriptorDecoder decodeBytes:(Byte *)&desc forDevice:thisDevice deviceInterface:deviceIntf userInfo:NULL isOtherSpeedDesc:false isinCurrentConfig:false];
// Since we have a Device Qualifier Descriptor, we can get a "Other Speed Configuration Descriptor"
// (It's the same as a regular configuration descriptor)
//
int iconfig;
for (iconfig = 0; iconfig < desc.bNumConfigurations; ++iconfig) {
IOUSBConfigurationDescHeader cfgHeader;
IOUSBConfigurationDescriptor config;
// Get the Configuration descriptor. We first get just the header and later we get the full
// descriptor
error = GetDescriptor(deviceIntf, kUSBOtherSpeedConfDesc, iconfig, &cfgHeader, sizeof(cfgHeader), nil);
if (error != kIOReturnSuccess) {
// Set a flag to the decodeBytes descriptor indicating that we didn't get the header
//
cfgHeader.bDescriptorType = sizeof(cfgHeader);
cfgHeader.wTotalLength = 0;
// Note: currentConfig:-1 as this is never in the current config.
[DecodeConfigurationDescriptor decodeBytes:(IOUSBConfigurationDescHeader *)&cfgHeader forDevice:thisDevice deviceInterface:deviceIntf configNumber:iconfig currentConfig:-1 isOtherSpeedDesc:YES];
// Try to get the descriptor again, using the sizeof(IOUSBConfigurationDescriptor)
//
bzero(&config,sizeof(config)-1);
error = GetDescriptor(deviceIntf, kUSBOtherSpeedConfDesc, iconfig, &config, sizeof(config)-1, nil);
if (error != kIOReturnSuccess) {
cfgHeader.bDescriptorType = sizeof(config)-1;
cfgHeader.wTotalLength = 0;
}
else {
cfgHeader.bLength = config.bLength;
cfgHeader.bDescriptorType = config.bDescriptorType;
cfgHeader.wTotalLength = config.wTotalLength;
}
}
[DecodeConfigurationDescriptor decodeBytes:(IOUSBConfigurationDescHeader *)&cfgHeader forDevice:thisDevice deviceInterface:deviceIntf configNumber:iconfig currentConfig:-1 isOtherSpeedDesc:YES];
}
}
}
}
else {
if ( portInfo & (1<<kUSBInformationDeviceIsSuspendedBit) ) {
[thisDevice setDeviceDescription: [NSString stringWithFormat:@" }
else {
// This description will be shown in the UI, to the right of the device's name
[thisDevice setDeviceDescription: [NSString stringWithFormat:@" }
}
if ( needToSuspend )
SuspendDevice(deviceIntf,true);
[thisDevice release];
}
- (void)PrintPortInfo: (uint32_t)portInfo forDevice:(BusProbeDevice *)thisDevice {
char buf[256];
sprintf((char *)buf, "0x [thisDevice addProperty:"Port Information:" withValue:buf atDepth:ROOT_LEVEL];
if (portInfo & (1<<kUSBInformationRootHubisBuiltIn))
sprintf((char *)buf, " else
sprintf((char *)buf, "
if (portInfo & (1<<kUSBInformationDeviceIsRootHub))
{
strcat(buf,"Root Hub");
[thisDevice addProperty:"" withValue:buf atDepth:ROOT_LEVEL+1];
}
if (portInfo & (1<<kUSBInformationDeviceIsCaptiveBit))
[thisDevice addProperty:"" withValue:"Captive" atDepth:ROOT_LEVEL+1];
else
[thisDevice addProperty:"" withValue:"Not Captive" atDepth:ROOT_LEVEL+1];
if (portInfo & (1<<kUSBInformationDeviceIsAttachedToRootHubBit))
[thisDevice addProperty:"" withValue:"Attached to Root Hub" atDepth:ROOT_LEVEL+1];
if (portInfo & (1<<kUSBInformationDeviceIsInternalBit))
[thisDevice addProperty:"" withValue:"Internal Device" atDepth:ROOT_LEVEL+1];
else
[thisDevice addProperty:"" withValue:"External Device" atDepth:ROOT_LEVEL+1];
if (portInfo & (1<<kUSBInformationDeviceIsConnectedBit))
[thisDevice addProperty:"" withValue:"Connected" atDepth:ROOT_LEVEL+1];
else
[thisDevice addProperty:"" withValue:"Unplugged" atDepth:ROOT_LEVEL+1];
if (portInfo & (1<<kUSBInformationDeviceIsEnabledBit))
[thisDevice addProperty:"" withValue:"Enabled" atDepth:ROOT_LEVEL+1];
else
[thisDevice addProperty:"" withValue:"Disabled" atDepth:ROOT_LEVEL+1];
if (portInfo & (1<<kUSBInformationDeviceIsOnThunderboltBit))
[thisDevice addProperty:"" withValue:"On Thunderbolt" atDepth:ROOT_LEVEL+1];
if (portInfo & (1<<kUSBInformationDeviceIsSuspendedBit))
[thisDevice addProperty:"" withValue:"Suspended" atDepth:ROOT_LEVEL+1];
if (portInfo & (1<<kUSBInformationDeviceIsInResetBit))
[thisDevice addProperty:"" withValue:"Reset" atDepth:ROOT_LEVEL+1];
if (portInfo & (1<<kUSBInformationDeviceOvercurrentBit))
[thisDevice addProperty:"" withValue:"Overcurrent" atDepth:ROOT_LEVEL+1];
if (portInfo & (1<<kUSBInformationDevicePortIsInTestModeBit))
[thisDevice addProperty:"" withValue:"Test Mode" atDepth:ROOT_LEVEL+1];
if (portInfo & (1<<kUSBInformationDeviceIsRemote))
[thisDevice addProperty:"" withValue:"Remote" atDepth:ROOT_LEVEL+1];
if (portInfo & (1<<kUSBInformationDeviceIsAttachedToEnclosure))
[thisDevice addProperty:"" withValue:"Connected to External Port" atDepth:ROOT_LEVEL+1];
}
- (void)GetAndPrintNumberOfEndpoints:(IOUSBDeviceRef)deviceIntf forDevice:(BusProbeDevice *)thisDevice portInfo:(UInt32)portInfo {
IOUSBConfigurationDescriptorPtr desc = NULL;
IOUSBInterfaceDescriptor *intfDesc = NULL;
IOUSBInterfaceDescriptor * descOut = NULL;
uint8_t numberOfConfigurations = 1;
uint8_t currentConfig = 0;
int interfaceNumber = -1;
uint32_t totalEndpoints = 1;
uint8_t maxEndpoints = 0;
IOReturn err;
int i;
char buf[256];
char buf2[256];
boolean_t unconfigured = false;
[thisDevice addProperty:"Number Of Endpoints (includes EP0):" withValue:"" atDepth:ROOT_LEVEL];
// Get the number of configurations that this device has
err = GetNumberOfConfigurations(deviceIntf, &numberOfConfigurations);
if (err)
{
sprintf((char *)buf, "(0x [thisDevice addProperty:"Got error from GetNumberOfConfigurations" withValue:buf atDepth:ROOT_LEVEL+1];
return;
}
// Get the current configuration -- this will return an error if the device is suspended. Also, we then assume
// that the configuration value returned is 1-based and we subtract one to get it as an index into the list of configurations.
err = GetConfiguration(deviceIntf,¤tConfig);
if ( err != kIOReturnSuccess)
{
// fprintf(stderr, "\t got error, setting to currentConfig of 0\n");
currentConfig = 0;
}
else {
// fprintf(stderr, "\tcurrent config: if ( currentConfig == 0 )
{
unconfigured = true;
}
else
currentConfig--;
}
for ( i = 0; i < numberOfConfigurations; i++ )
{
totalEndpoints = 1;
intfDesc = NULL;
interfaceNumber = -1;
maxEndpoints = 0;
err = GetConfigurationDescriptor(deviceIntf, i, &desc);
if (err)
{
sprintf((char *)buf, "(0x sprintf((char *)buf2, "Got error from GetConfigurationDescriptor( [thisDevice addProperty:buf2 withValue:buf atDepth:ROOT_LEVEL+1];
break;
}
// fprintf(stderr, "Found Configuration: // dump((void *)desc, (desc->wTotalLength));
// Go through each interface, and count the number of endpoints. If we alternate settings, count the one with the highest number
// of endpoints
do
{
err = FindNextInterfaceDescriptor(desc, intfDesc, &descOut);
if ( descOut && err == kIOReturnSuccess)
{
if ( interfaceNumber != descOut->bInterfaceNumber)
{
// This is a new interface, so now add the maxEndpoints from the previous one to our running total (totalEndpoints)
interfaceNumber = descOut->bInterfaceNumber;
totalEndpoints += maxEndpoints;
maxEndpoints = 0;
}
if ( descOut->bAlternateSetting > 0 )
{
// This means that we have already seen this interface #, so only update the numEndpoints if greater than what we saved
if (descOut->bNumEndpoints > maxEndpoints )
maxEndpoints = descOut->bNumEndpoints;
}
else
{
maxEndpoints = descOut->bNumEndpoints;
}
// fprintf(stderr, "Found interface # // dump((void*) descOut, descOut->bLength);
// fprintf(stderr, "\tbInterfaceNumber:
// Start looking for the next interface descriptor after the current one
intfDesc = descOut;
}
// If we get to the end, we will get a kIOUSBInterfaceNotFound. We still need to take into accountthe maxEndpoints for the last interface we iterated
if ( err ==kIOUSBInterfaceNotFound )
totalEndpoints += maxEndpoints;
}
while ( err == kIOReturnSuccess && descOut != NULL);
sprintf((char *)buf, "Total Endpoints for Configuration sprintf((char *)buf2, "
[thisDevice addProperty:buf withValue:buf2 atDepth:ROOT_LEVEL+1];
}
}
@end