/* * Copyright (c) 2000-2002 Apple Computer, Inc. All Rights Reserved. * The contents of this file constitute Original Code as defined in and are * subject to the Apple Public Source License Version 1.2 (the 'License'). * You may not use this file except in compliance with the License. Please * obtain a copy of the License at http://www.apple.com/publicsource and * read it before using this file. * * This 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. */ /****************************************************************** MUSCLE SmartCard Development ( http://www.linuxnet.com ) Title : hotplug_macosx.c Package: pcsc lite Author : Stephen M. Webb Date : 03 Dec 2002 License: Copyright (C) 2002 David Corcoran Purpose: This provides a search API for hot pluggble devices. ********************************************************************/ #include #include #include #include #include #include #include "config.h" #include "wintypes.h" #include "pcsclite.h" #include "debuglog.h" #include "hotplug.h" #include "readerfactory.h" #include "thread_generic.h" #define PCSCLITE_HP_DROPDIR "/usr/libexec/SmartCardServices/drivers/" #define PCSCLITE_HP_MANUKEY_NAME "ifdVendorID" #define PCSCLITE_HP_PRODKEY_NAME "ifdProductID" #define PCSCLITE_HP_NAMEKEY_NAME "ifdFriendlyName" #define PCSCLITE_HP_IFACECLASSKEY_NAME "ifdInterfaceClass" #define PCSCLITE_HP_IFACESUBCLASSKEY_NAME "ifdInterfaceSubClass" #define PCSCLITE_HP_IFACEPROTOCOLKEY_NAME "ifdInterfaceProtocol" #define PCSCLITE_HP_BASE_PORT 0x200000 /* * Defines the type of driver in the driver vector */ typedef enum { PCSCLITE_HP_Proprietary = 0, PCSCLITE_HP_InterfaceClass = 1, // * Could accomodate more types */ } HPDriverType; /* * An aggregation of useful information on a driver bundle in the * drop directory. */ typedef struct HPDriver { UInt8 m_NotEOV; /* set to 1 for any driver before the end */ UInt8 m_initialized; /* set to 1 on successful intialization */ HPDriverType m_type; /* type of the driver in this element */ UInt32 m_vendorId; /* unique vendor's manufacturer code */ UInt32 m_productId; /* manufacturer's unique product code */ UInt8 m_class; /* class of a non product specific driver */ UInt8 m_subClass; /* subClass of a non product specific driver */ UInt8 m_protocol; /* protocol of a non product specific driver */ char* m_friendlyName; /* bundle friendly name */ char* m_libPath; /* bundle's plugin library location */ } HPDriver, *HPDriverVector; /* * An aggregation on information on currently active reader drivers. */ typedef struct HPDevice { HPDriver* m_driver; /* driver bundle information */ UInt32 m_address; /* unique system address of device */ struct HPDevice* m_next; /* next device in list */ } HPDevice, *HPDeviceList; /* * Pointer to a list of (currently) known hotplug reader devices (and their * drivers). */ static HPDeviceList sDeviceList = NULL; static IONotificationPortRef sNotificationPort = NULL; static io_iterator_t sUSBAppearedIter = NULL; static io_iterator_t sUSBRemovedIter = NULL; static io_iterator_t sPCCardAppearedIter = NULL; static io_iterator_t sPCCardRemovedIter = NULL; /* * A callback to handle the asynchronous appearance of new devices that are * candidates for PCSC readers. */ static void HPDeviceAppeared(void* refCon, io_iterator_t iterator) { kern_return_t kret; io_service_t obj; while ((obj = IOIteratorNext(iterator))) { kret = IOObjectRelease(obj); } HPSearchHotPluggables(); } /* * A callback to handle the asynchronous disappearance of devices that are * possibly PCSC readers. */ static void HPDeviceDisappeared(void* refCon, io_iterator_t iterator) { kern_return_t kret; io_service_t obj; while ((obj = IOIteratorNext(iterator))) { kret = IOObjectRelease(obj); } HPSearchHotPluggables(); } /* * Creates a vector of driver bundle info structures from the hot-plug driver * directory. * * Returns NULL on error and a pointer to an allocated HPDriver vector on * success. The caller must free the HPDriver with a call to * HPDriversRelease(). */ static HPDriverVector HPDriversGetFromDirectory(const char* driverBundlePath) { HPDriverVector bundleVector = NULL; CFArrayRef bundleArray; CFStringRef driverBundlePathString; driverBundlePathString = CFStringCreateWithCString(kCFAllocatorDefault, driverBundlePath, kCFStringEncodingMacRoman); CFURLRef pluginUrl = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, driverBundlePathString, kCFURLPOSIXPathStyle, TRUE); CFRelease(driverBundlePathString); if (!pluginUrl) { DebugLogA("error getting plugin directory URL"); return bundleVector; } bundleArray = CFBundleCreateBundlesFromDirectory(kCFAllocatorDefault, pluginUrl, NULL); if (!bundleArray) { DebugLogA("error getting plugin directory bundles"); return bundleVector; } CFRelease(pluginUrl); size_t bundleArraySize = CFArrayGetCount(bundleArray); // bundleArraySize + 1 <- because the last vector element is // blank and is used to determine the length (m_NotEOV == 0) bundleVector = (HPDriver*)calloc(bundleArraySize + 1, sizeof(HPDriver)); if (!bundleVector) { DebugLogA("memory allocation failure"); return bundleVector; } int i = 0; for (; i < bundleArraySize; ++i) { HPDriver* driverBundle = bundleVector + i; // This is not the last driverBundle->m_NotEOV = 1; CFBundleRef currBundle = (CFBundleRef)CFArrayGetValueAtIndex(bundleArray, i); CFDictionaryRef dict = CFBundleGetInfoDictionary(currBundle); CFURLRef bundleUrl = CFBundleCopyBundleURL(currBundle); CFStringRef bundlePath = CFURLCopyPath(bundleUrl); driverBundle->m_libPath = strdup(CFStringGetCStringPtr(bundlePath, CFStringGetSystemEncoding())); if (driverBundle->m_libPath == NULL) { DebugLogA("memory allocation failure"); return bundleVector; } UInt32 vendorId = 0; UInt8 gotVendorId = 0; UInt32 productId = 0; UInt8 gotProductId = 0; CFStringRef strValue = (CFStringRef)CFDictionaryGetValue(dict, CFSTR(PCSCLITE_HP_MANUKEY_NAME)); if (strValue) { gotVendorId = 1; vendorId = strtoul(CFStringGetCStringPtr(strValue, CFStringGetSystemEncoding()), NULL, 16); strValue = (CFStringRef)CFDictionaryGetValue(dict, CFSTR(PCSCLITE_HP_PRODKEY_NAME)); if (strValue) { gotProductId = 1; productId = strtoul(CFStringGetCStringPtr(strValue, CFStringGetSystemEncoding()), NULL, 16); } } if (gotVendorId && gotProductId) { /* This is a product-specific driver */ driverBundle->m_productId = productId; driverBundle->m_vendorId = vendorId; driverBundle->m_type = PCSCLITE_HP_Proprietary; } else { /* If not a product-specific driver, it must be */ /* an interface class-specifc driver */ UInt8 class; UInt8 subClass; UInt8 protocol; strValue = (CFStringRef)CFDictionaryGetValue(dict, CFSTR(PCSCLITE_HP_IFACECLASSKEY_NAME)); if (strValue) { class = (UInt8) strtoul(CFStringGetCStringPtr(strValue, CFStringGetSystemEncoding()), NULL, 16); driverBundle->m_class = class; } else { DebugLogB("Malformed bundle (class absent) in driver folder: %s. Will be ignored", driverBundle->m_libPath); free(driverBundle->m_libPath); driverBundle->m_libPath = NULL; continue; } strValue = (CFStringRef)CFDictionaryGetValue(dict, CFSTR(PCSCLITE_HP_IFACESUBCLASSKEY_NAME)); if (strValue) { subClass = (UInt8) strtoul(CFStringGetCStringPtr(strValue, CFStringGetSystemEncoding()), NULL, 16); driverBundle->m_subClass = subClass; } else { DebugLogB("Malformed bundle (subClass absent) in driver folder: %s. Will be ignored", driverBundle->m_libPath); free(driverBundle->m_libPath); driverBundle->m_libPath = NULL; continue; } strValue = (CFStringRef)CFDictionaryGetValue(dict, CFSTR(PCSCLITE_HP_IFACEPROTOCOLKEY_NAME)); if (strValue) { protocol = (UInt8) strtoul(CFStringGetCStringPtr(strValue, CFStringGetSystemEncoding()), NULL, 16); driverBundle->m_protocol = protocol; } else { DebugLogB("Malformed bundle (protocol absent) in driver folder: %s. Will be ignored", driverBundle->m_libPath); free(driverBundle->m_libPath); driverBundle->m_libPath = NULL; continue; } driverBundle->m_type = PCSCLITE_HP_InterfaceClass; } strValue = (CFStringRef)CFDictionaryGetValue(dict, CFSTR(PCSCLITE_HP_NAMEKEY_NAME)); if (!strValue) { DebugLogB("Product friendly name absent in driver folder: %s.", driverBundle->m_libPath); driverBundle->m_friendlyName = strdup("unnamed device"); } else { const char* cstr = CFStringGetCStringPtr(strValue, CFStringGetSystemEncoding()); driverBundle->m_friendlyName = strdup(cstr); } driverBundle->m_initialized = 1; } CFRelease(bundleArray); return bundleVector; } /* * Copies a driver bundle instance. */ static HPDriver* HPDriverCopy(HPDriver* rhs) { if (!rhs) { return NULL; } HPDriver* newDriverBundle = (HPDriver*)calloc(1, sizeof(HPDriver)); if (!newDriverBundle) { return NULL; } newDriverBundle->m_initialized = rhs->m_initialized; newDriverBundle->m_type = rhs->m_type; newDriverBundle->m_vendorId = rhs->m_vendorId; newDriverBundle->m_productId = rhs->m_productId; newDriverBundle->m_class = rhs->m_class; newDriverBundle->m_subClass = rhs->m_subClass; newDriverBundle->m_friendlyName = strdup(rhs->m_friendlyName); newDriverBundle->m_libPath = strdup(rhs->m_libPath); if (newDriverBundle->m_friendlyName == NULL) { if (newDriverBundle->m_libPath != NULL) { free(newDriverBundle->m_libPath); } free(newDriverBundle); return NULL; } if (newDriverBundle->m_libPath == NULL) { if (newDriverBundle->m_friendlyName != NULL) { free(newDriverBundle->m_friendlyName); } free(newDriverBundle); return NULL; } return newDriverBundle; } /* * Releases resources allocated to a driver bundle vector. */ static void HPDriverRelease(HPDriver* driverBundle) { if (driverBundle) { free(driverBundle->m_friendlyName); free(driverBundle->m_libPath); } } /* * Releases resources allocated to a driver bundle vector. */ static void HPDriverVectorRelease(HPDriverVector driverBundleVector) { if (driverBundleVector) { HPDriver* b = driverBundleVector; for (; b->m_initialized; ++b) { HPDriverRelease(b); } free(driverBundleVector); } } /* * Inserts a new reader device in the list. */ static HPDeviceList HPDeviceListInsert(HPDeviceList list, HPDriver* bundle, UInt32 address) { HPDevice* newReader = (HPDevice*)calloc(1, sizeof(HPDevice)); if (!newReader) { DebugLogA("memory allocation failure"); return list; } newReader->m_driver = HPDriverCopy(bundle); newReader->m_address = address; newReader->m_next = list; return newReader; } /* * Frees resources allocated to a HPDeviceList. */ static void HPDeviceListRelease(HPDeviceList list) { HPDevice* p = list; for (; p; p = p->m_next) { HPDriverRelease(p->m_driver); } } /* * Compares two driver bundle instances for equality. */ static int HPDeviceEquals(HPDevice* a, HPDevice* b) { int res; if (a->m_driver->m_type == b->m_driver->m_type) { if (a->m_driver->m_type == PCSCLITE_HP_Proprietary) { // a and b have same vendor and product id res = (a->m_driver->m_vendorId == b->m_driver->m_vendorId) && (a->m_driver->m_productId == b->m_driver->m_productId); } else { // a and b have same class res = (a->m_driver->m_subClass == b->m_driver->m_subClass) && (a->m_driver->m_class == b->m_driver->m_class); } // AND have the same address res = res && (a->m_address == b->m_address); return res; } return 0; } /* * Finds USB devices currently registered in the system that match any of * the drivers detected in the driver bundle vector. */ static int HPDriversMatchUSBDevices(HPDriverVector driverBundle, HPDeviceList* readerList) { CFDictionaryRef usbMatch = IOServiceMatching("IOUSBDevice"); if (0 == usbMatch) { DebugLogA("error getting USB match from IOServiceMatching()"); return 1; } io_iterator_t usbIter; kern_return_t kret = IOServiceGetMatchingServices(kIOMasterPortDefault, usbMatch, &usbIter); if (kret != 0) { DebugLogA("error getting iterator from IOServiceGetMatchingServices()"); return 1; } io_object_t usbDevice = 0; while ((usbDevice = IOIteratorNext(usbIter))) { IOCFPlugInInterface** iodev; SInt32 score; kret = IOCreatePlugInInterfaceForService(usbDevice, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &iodev, &score); IOObjectRelease(usbDevice); if (kret != 0) { DebugLogA("error getting plugin interface from IOCreatePlugInInterfaceForService()"); continue; } IOUSBDeviceInterface245** usbdev; HRESULT hres = (*iodev)->QueryInterface(iodev, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID245), (LPVOID*)&usbdev); if (hres) { DebugLogA("error querying interface in QueryInterface()"); IODestroyPlugInInterface ( iodev ); continue; } else { UInt16 vendorId = 0; UInt16 productId = 0; UInt32 usbAddress = 0; kret = (*usbdev)->GetDeviceVendor(usbdev, &vendorId); kret = (*usbdev)->GetDeviceProduct(usbdev, &productId); kret = (*usbdev)->GetLocationID(usbdev, &usbAddress); HPDriver* driver = driverBundle; int match = 0; for (; driver->m_NotEOV; ++driver) { if (!driver->m_initialized) { // Malformed driver, skip continue; } if ( (driver->m_type == PCSCLITE_HP_Proprietary) && (driver->m_vendorId == vendorId) && (driver->m_productId == productId)) { *readerList = HPDeviceListInsert(*readerList, driver, usbAddress); match = 1; } } if (!match) { // Now try to locate Interfaces with supported classes // We create an interface iterator for each of the // classes supported by drivers of PCSCLITE_HP_InterfaceClass // type. // Using IOServiceMatching(kIOUSBInterfaceClassName) // does not seem feasible as there does not seem to be a // way to limit the search to the device we are currently // analysing // Another option would be to iterate on all interfaces // and get the class of each of them. This is probably // not interesting as the list of PCSCLITE_HP_InterfaceClass // type of readers should only have one element (CCID) // Restart scan at the begining of the array driver = driverBundle; // Iterate on PCSCLITE_HP_InterfaceClass driver types for (; driver->m_NotEOV; ++driver) { if (!driver->m_initialized) { // Malformed driver, skip continue; } if ( driver->m_type == PCSCLITE_HP_InterfaceClass) { // Iterate on interfaces of the current device IOUSBFindInterfaceRequest interfaceClassRequest; io_iterator_t interfaceIterator; io_service_t interface; interfaceClassRequest.bInterfaceClass = driver->m_class; interfaceClassRequest.bInterfaceSubClass = driver->m_subClass; interfaceClassRequest.bInterfaceProtocol = driver->m_protocol; interfaceClassRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare; hres = (*usbdev)->CreateInterfaceIterator(usbdev, &interfaceClassRequest, &interfaceIterator); if (hres) { // Continue to next driver class continue; } while ( (interface = IOIteratorNext(interfaceIterator)) ) { // Found a matching device *readerList = HPDeviceListInsert(*readerList, driver, usbAddress); match = 1; IOObjectRelease ( interface ); } IOObjectRelease ( interfaceIterator ); } } // Add another if (!match) for other driver types } (*usbdev)->Release(usbdev); IODestroyPlugInInterface ( iodev ); } } IOObjectRelease(usbIter); return 0; } /* * Finds PC Card devices currently registered in the system that match any of * the drivers detected in the driver bundle vector. */ static int HPDriversMatchPCCardDevices(HPDriver* driverBundle, HPDeviceList* readerList) { CFDictionaryRef pccMatch = IOServiceMatching("IOPCCard16Device"); if (0 == pccMatch) { DebugLogA("error getting PCCard match from IOServiceMatching()"); return 1; } io_iterator_t pccIter; kern_return_t kret = IOServiceGetMatchingServices(kIOMasterPortDefault, pccMatch, &pccIter); if (kret != 0) { DebugLogA("error getting iterator from IOServiceGetMatchingServices()"); return 1; } io_object_t pccDevice = 0; while ((pccDevice = IOIteratorNext(pccIter))) { UInt32 vendorId = 0; UInt32 productId = 0; UInt32 pccAddress = 0; CFTypeRef valueRef = IORegistryEntryCreateCFProperty(pccDevice, CFSTR("VendorID"), kCFAllocatorDefault, 0); if (!valueRef) { DebugLogA("error getting vendor"); } else { CFNumberGetValue((CFNumberRef)valueRef, kCFNumberSInt32Type, &vendorId); CFRelease ( valueRef ); } valueRef = IORegistryEntryCreateCFProperty(pccDevice, CFSTR("DeviceID"), kCFAllocatorDefault, 0); if (!valueRef) { DebugLogA("error getting device"); } else { CFNumberGetValue((CFNumberRef)valueRef, kCFNumberSInt32Type, &productId); CFRelease ( valueRef ); } valueRef = IORegistryEntryCreateCFProperty(pccDevice, CFSTR("SocketNumber"), kCFAllocatorDefault, 0); if (!valueRef) { DebugLogA("error getting PC Card socket"); } else { CFNumberGetValue((CFNumberRef)valueRef, kCFNumberSInt32Type, &pccAddress); CFRelease ( valueRef ); } HPDriver* driver = driverBundle; for (; driver->m_vendorId; ++driver) { if ((driver->m_vendorId == vendorId) && (driver->m_productId == productId)) { *readerList = HPDeviceListInsert(*readerList, driver, pccAddress); } } IOObjectRelease ( pccDevice ); } IOObjectRelease(pccIter); return 0; } static void HPEstablishUSBNotification() { CFMutableDictionaryRef matchingDictionary; IOReturn kret; if ( sNotificationPort == NULL ) sNotificationPort = IONotificationPortCreate(kIOMasterPortDefault); CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(sNotificationPort), kCFRunLoopDefaultMode); matchingDictionary = IOServiceMatching("IOUSBDevice"); if (!matchingDictionary) { DebugLogB("IOServiceMatching() failed", 0); } matchingDictionary = (CFMutableDictionaryRef)CFRetain(matchingDictionary); kret = IOServiceAddMatchingNotification(sNotificationPort, kIOMatchedNotification, matchingDictionary, HPDeviceAppeared, NULL, &sUSBAppearedIter); if (kret) { DebugLogB("IOServiceAddMatchingNotification()-1 failed with code %d", kret); } HPDeviceAppeared(NULL, sUSBAppearedIter); kret = IOServiceAddMatchingNotification(sNotificationPort, kIOTerminatedNotification, matchingDictionary, HPDeviceDisappeared, NULL, &sUSBRemovedIter); if (kret) { DebugLogB("IOServiceAddMatchingNotification()-2 failed with code %d", kret); } HPDeviceDisappeared(NULL, sUSBRemovedIter); } static void HPEstablishPCCardNotification() { CFMutableDictionaryRef matchingDictionary; IOReturn kret; if ( sNotificationPort == NULL ) sNotificationPort = IONotificationPortCreate(kIOMasterPortDefault); CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(sNotificationPort), kCFRunLoopDefaultMode); matchingDictionary = IOServiceMatching("IOPCCard16Device"); if (!matchingDictionary) { DebugLogB("IOServiceMatching() failed", 0); } matchingDictionary = (CFMutableDictionaryRef)CFRetain(matchingDictionary); kret = IOServiceAddMatchingNotification(sNotificationPort, kIOMatchedNotification, matchingDictionary, HPDeviceAppeared, NULL, &sPCCardAppearedIter); if (kret) { DebugLogB("IOServiceAddMatchingNotification()-1 failed with code %d", kret); } HPDeviceAppeared(NULL, sPCCardAppearedIter); kret = IOServiceAddMatchingNotification(sNotificationPort, kIOTerminatedNotification, matchingDictionary, HPDeviceDisappeared, NULL, &sPCCardRemovedIter); if (kret) { DebugLogB("IOServiceAddMatchingNotification()-2 failed with code %d", kret); } HPDeviceDisappeared(NULL, sPCCardRemovedIter); } /* * Thread runner (does not return). */ static void HPDeviceNotificationThread() { HPEstablishUSBNotification(); HPEstablishPCCardNotification(); CFRunLoopRun(); } /* * Scans the hotplug driver directory and looks in the system for matching devices. * Adds or removes matching readers as necessary. */ LONG HPSearchHotPluggables() { HPDriver* drivers = HPDriversGetFromDirectory(PCSCLITE_HP_DROPDIR); if (!drivers) return 1; HPDeviceList devices = NULL; int istat; istat = HPDriversMatchUSBDevices(drivers, &devices); if (istat) { return -1; } istat = HPDriversMatchPCCardDevices(drivers, &devices); if (istat) { return -1; } HPDevice* a = devices; for (; a; a = a->m_next) { int found = 0; HPDevice* b = sDeviceList; for (; b; b = b->m_next) { if (HPDeviceEquals(a, b)) { found = 1; break; } } if (!found) { RFAddReader(a->m_driver->m_friendlyName, PCSCLITE_HP_BASE_PORT + a->m_address, a->m_driver->m_libPath); } } a = sDeviceList; for (; a; a = a->m_next) { int found = 0; HPDevice* b = devices; for (; b; b = b->m_next) { if (HPDeviceEquals(a, b)) { found = 1; break; } } if (!found) { RFRemoveReader(a->m_driver->m_friendlyName, PCSCLITE_HP_BASE_PORT + a->m_address); } } HPDeviceListRelease(sDeviceList); sDeviceList = devices; HPDriverVectorRelease(drivers); return 0; } PCSCLITE_THREAD_T sHotplugWatcherThread; /* * Sets up callbacks for device hotplug events. */ LONG HPRegisterForHotplugEvents() { LONG sstat; sstat = SYS_ThreadCreate(&sHotplugWatcherThread, NULL, (LPVOID)HPDeviceNotificationThread, NULL); return 0; }