pcsc-lite  1.8.3
hotplug_libudev.c
Go to the documentation of this file.
00001 /*
00002  * MUSCLE SmartCard Development ( http://www.linuxnet.com )
00003  *
00004  * Copyright (C) 2011
00005  *  Ludovic Rousseau <ludovic.rousseau@free.fr>
00006  *
00007  * $Id: hotplug_libudev.c 6130 2011-12-05 14:44:09Z rousseau $
00008  */
00009 
00015 #include "config.h"
00016 #if defined(HAVE_LIBUDEV) && defined(USE_USB)
00017 
00018 #include <string.h>
00019 #include <stdio.h>
00020 #include <dirent.h>
00021 #include <stdlib.h>
00022 #include <pthread.h>
00023 #include <libudev.h>
00024 
00025 #include "debuglog.h"
00026 #include "parser.h"
00027 #include "readerfactory.h"
00028 #include "sys_generic.h"
00029 #include "hotplug.h"
00030 #include "utils.h"
00031 #include "strlcpycat.h"
00032 
00033 #undef DEBUG_HOTPLUG
00034 #define ADD_SERIAL_NUMBER
00035 #define ADD_INTERFACE_NAME
00036 
00037 #define FALSE           0
00038 #define TRUE            1
00039 
00040 pthread_mutex_t usbNotifierMutex;
00041 
00042 static pthread_t usbNotifyThread;
00043 static int driverSize = -1;
00044 static char AraKiriHotPlug = FALSE;
00045 
00049 static struct _driverTracker
00050 {
00051     unsigned int manuID;
00052     unsigned int productID;
00053 
00054     char *bundleName;
00055     char *libraryPath;
00056     char *readerName;
00057     char *CFBundleName;
00058 } *driverTracker = NULL;
00059 #define DRIVER_TRACKER_SIZE_STEP 10
00060 
00061 /* The CCID driver already supports 176 readers.
00062  * We start with a big array size to avoid reallocation. */
00063 #define DRIVER_TRACKER_INITIAL_SIZE 200
00064 
00065 typedef enum {
00066  READER_ABSENT,
00067  READER_PRESENT,
00068  READER_FAILED
00069 } readerState_t;
00070 
00074 static struct _readerTracker
00075 {
00076     readerState_t status;   
00077     char bInterfaceNumber;  
00078     char *devpath;  
00079     char *fullName; 
00080 } readerTracker[PCSCLITE_MAX_READERS_CONTEXTS];
00081 
00082 
00083 static LONG HPReadBundleValues(void)
00084 {
00085     LONG rv;
00086     DIR *hpDir;
00087     struct dirent *currFP = NULL;
00088     char fullPath[FILENAME_MAX];
00089     char fullLibPath[FILENAME_MAX];
00090     int listCount = 0;
00091 
00092     hpDir = opendir(PCSCLITE_HP_DROPDIR);
00093 
00094     if (NULL == hpDir)
00095     {
00096         Log1(PCSC_LOG_ERROR, "Cannot open PC/SC drivers directory: " PCSCLITE_HP_DROPDIR);
00097         Log1(PCSC_LOG_ERROR, "Disabling USB support for pcscd.");
00098         return -1;
00099     }
00100 
00101     /* allocate a first array */
00102     driverSize = DRIVER_TRACKER_INITIAL_SIZE;
00103     driverTracker = calloc(driverSize, sizeof(*driverTracker));
00104     if (NULL == driverTracker)
00105     {
00106         Log1(PCSC_LOG_CRITICAL, "Not enough memory");
00107         (void)closedir(hpDir);
00108         return -1;
00109     }
00110 
00111 #define GET_KEY(key, values) \
00112     rv = LTPBundleFindValueWithKey(&plist, key, values); \
00113     if (rv) \
00114     { \
00115         Log2(PCSC_LOG_ERROR, "Value/Key not defined for " key " in %s", \
00116             fullPath); \
00117         continue; \
00118     }
00119 
00120     while ((currFP = readdir(hpDir)) != 0)
00121     {
00122         if (strstr(currFP->d_name, ".bundle") != 0)
00123         {
00124             unsigned int alias;
00125             list_t plist, *values;
00126             list_t *manuIDs, *productIDs, *readerNames;
00127             char *CFBundleName;
00128             char *libraryPath;
00129 
00130             /*
00131              * The bundle exists - let's form a full path name and get the
00132              * vendor and product ID's for this particular bundle
00133              */
00134             (void)snprintf(fullPath, sizeof(fullPath), "%s/%s/Contents/Info.plist",
00135                 PCSCLITE_HP_DROPDIR, currFP->d_name);
00136             fullPath[sizeof(fullPath) - 1] = '\0';
00137 
00138             rv = bundleParse(fullPath, &plist);
00139             if (rv)
00140                 continue;
00141 
00142             /* get CFBundleExecutable */
00143             GET_KEY(PCSCLITE_HP_LIBRKEY_NAME, &values)
00144             libraryPath = list_get_at(values, 0);
00145             (void)snprintf(fullLibPath, sizeof(fullLibPath),
00146                 "%s/%s/Contents/%s/%s",
00147                 PCSCLITE_HP_DROPDIR, currFP->d_name, PCSC_ARCH,
00148                 libraryPath);
00149             fullLibPath[sizeof(fullLibPath) - 1] = '\0';
00150 
00151             GET_KEY(PCSCLITE_HP_MANUKEY_NAME, &manuIDs)
00152             GET_KEY(PCSCLITE_HP_PRODKEY_NAME, &productIDs)
00153             GET_KEY(PCSCLITE_HP_NAMEKEY_NAME, &readerNames)
00154 
00155             /* Get CFBundleName */
00156             rv = LTPBundleFindValueWithKey(&plist, PCSCLITE_HP_CFBUNDLE_NAME,
00157                 &values);
00158             if (rv)
00159                 CFBundleName = NULL;
00160             else
00161                 CFBundleName = strdup(list_get_at(values, 0));
00162 
00163             /* while we find a nth ifdVendorID in Info.plist */
00164             for (alias=0; alias<list_size(manuIDs); alias++)
00165             {
00166                 char *value;
00167 
00168                 /* variables entries */
00169                 value = list_get_at(manuIDs, alias);
00170                 driverTracker[listCount].manuID = strtol(value, NULL, 16);
00171 
00172                 value = list_get_at(productIDs, alias);
00173                 driverTracker[listCount].productID = strtol(value, NULL, 16);
00174 
00175                 driverTracker[listCount].readerName = strdup(list_get_at(readerNames, alias));
00176 
00177                 /* constant entries for a same driver */
00178                 driverTracker[listCount].bundleName = strdup(currFP->d_name);
00179                 driverTracker[listCount].libraryPath = strdup(fullLibPath);
00180                 driverTracker[listCount].CFBundleName = CFBundleName;
00181 
00182 #ifdef DEBUG_HOTPLUG
00183                 Log2(PCSC_LOG_INFO, "Found driver for: %s",
00184                     driverTracker[listCount].readerName);
00185 #endif
00186                 listCount++;
00187                 if (listCount >= driverSize)
00188                 {
00189                     int i;
00190 
00191                     /* increase the array size */
00192                     driverSize += DRIVER_TRACKER_SIZE_STEP;
00193 #ifdef DEBUG_HOTPLUG
00194                     Log2(PCSC_LOG_INFO,
00195                         "Increase driverTracker to %d entries", driverSize);
00196 #endif
00197                     driverTracker = realloc(driverTracker,
00198                         driverSize * sizeof(*driverTracker));
00199                     if (NULL == driverTracker)
00200                     {
00201                         Log1(PCSC_LOG_CRITICAL, "Not enough memory");
00202                         driverSize = -1;
00203                         (void)closedir(hpDir);
00204                         return -1;
00205                     }
00206 
00207                     /* clean the newly allocated entries */
00208                     for (i=driverSize-DRIVER_TRACKER_SIZE_STEP; i<driverSize; i++)
00209                     {
00210                         driverTracker[i].manuID = 0;
00211                         driverTracker[i].productID = 0;
00212                         driverTracker[i].bundleName = NULL;
00213                         driverTracker[i].libraryPath = NULL;
00214                         driverTracker[i].readerName = NULL;
00215                         driverTracker[i].CFBundleName = NULL;
00216                     }
00217                 }
00218             }
00219             bundleRelease(&plist);
00220         }
00221     }
00222 
00223     driverSize = listCount;
00224     (void)closedir(hpDir);
00225 
00226 #ifdef DEBUG_HOTPLUG
00227     Log2(PCSC_LOG_INFO, "Found drivers for %d readers", listCount);
00228 #endif
00229 
00230     return 0;
00231 } /* HPReadBundleValues */
00232 
00233 
00234 /*@null@*/ static struct _driverTracker *get_driver(struct udev_device *dev,
00235     const char *devpath, struct _driverTracker **classdriver)
00236 {
00237     int i;
00238     unsigned int idVendor, idProduct;
00239     static struct _driverTracker *driver;
00240     const char *str;
00241 
00242     str = udev_device_get_sysattr_value(dev, "idVendor");
00243     if (!str)
00244     {
00245         Log1(PCSC_LOG_ERROR, "udev_device_get_sysattr_value() failed");
00246         return NULL;
00247     }
00248     idVendor = strtol(str, NULL, 16);
00249 
00250     str = udev_device_get_sysattr_value(dev, "idProduct");
00251     if (!str)
00252     {
00253         Log1(PCSC_LOG_ERROR, "udev_device_get_sysattr_value() failed");
00254         return NULL;
00255     }
00256     idProduct = strtol(str, NULL, 16);
00257 
00258     Log4(PCSC_LOG_DEBUG,
00259         "Looking for a driver for VID: 0x%04X, PID: 0x%04X, path: %s",
00260         idVendor, idProduct, devpath);
00261 
00262     *classdriver = NULL;
00263     driver = NULL;
00264     /* check if the device is supported by one driver */
00265     for (i=0; i<driverSize; i++)
00266     {
00267         if (driverTracker[i].libraryPath != NULL &&
00268             idVendor == driverTracker[i].manuID &&
00269             idProduct == driverTracker[i].productID)
00270         {
00271             if ((driverTracker[i].CFBundleName != NULL)
00272                 && (0 == strcmp(driverTracker[i].CFBundleName, "CCIDCLASSDRIVER")))
00273                 *classdriver = &driverTracker[i];
00274             else
00275                 /* it is not a CCID Class driver */
00276                 driver = &driverTracker[i];
00277         }
00278     }
00279 
00280     /* if we found a specific driver */
00281     if (driver)
00282         return driver;
00283 
00284     /* else return the Class driver (if any) */
00285     return *classdriver;
00286 }
00287 
00288 
00289 static void HPAddDevice(struct udev_device *dev, struct udev_device *parent,
00290     const char *devpath)
00291 {
00292     int i;
00293     char deviceName[MAX_DEVICENAME];
00294     char fullname[MAX_READERNAME];
00295     struct _driverTracker *driver, *classdriver;
00296     const char *sSerialNumber = NULL, *sInterfaceName = NULL;
00297     LONG ret;
00298     int bInterfaceNumber;
00299 
00300     driver = get_driver(parent, devpath, &classdriver);
00301     if (NULL == driver)
00302     {
00303         /* not a smart card reader */
00304 #ifdef DEBUG_HOTPLUG
00305         Log2(PCSC_LOG_DEBUG, "%s is not a supported smart card reader",
00306             devpath);
00307 #endif
00308         return;
00309     }
00310 
00311     Log2(PCSC_LOG_INFO, "Adding USB device: %s", driver->readerName);
00312 
00313     bInterfaceNumber = atoi(udev_device_get_sysattr_value(dev,
00314         "bInterfaceNumber"));
00315     (void)snprintf(deviceName, sizeof(deviceName),
00316         "usb:%04x/%04x:libudev:%d:%s", driver->manuID, driver->productID,
00317         bInterfaceNumber, devpath);
00318     deviceName[sizeof(deviceName) -1] = '\0';
00319 
00320     (void)pthread_mutex_lock(&usbNotifierMutex);
00321 
00322     /* find a free entry */
00323     for (i=0; i<PCSCLITE_MAX_READERS_CONTEXTS; i++)
00324     {
00325         if (NULL == readerTracker[i].fullName)
00326             break;
00327     }
00328 
00329     if (PCSCLITE_MAX_READERS_CONTEXTS == i)
00330     {
00331         Log2(PCSC_LOG_ERROR,
00332             "Not enough reader entries. Already found %d readers", i);
00333         (void)pthread_mutex_unlock(&usbNotifierMutex);
00334         return;
00335     }
00336 
00337 #ifdef ADD_INTERFACE_NAME
00338     sInterfaceName = udev_device_get_sysattr_value(dev, "interface");
00339 #endif
00340 
00341 #ifdef ADD_SERIAL_NUMBER
00342     sSerialNumber = udev_device_get_sysattr_value(parent, "serial");
00343 #endif
00344 
00345     /* name from the Info.plist file */
00346     strlcpy(fullname, driver->readerName, sizeof(fullname));
00347 
00348     /* interface name from the device (if any) */
00349     if (sInterfaceName)
00350     {
00351         strlcat(fullname, " [", sizeof(fullname));
00352         strlcat(fullname, sInterfaceName, sizeof(fullname));
00353         strlcat(fullname, "]", sizeof(fullname));
00354     }
00355 
00356     /* serial number from the device (if any) */
00357     if (sSerialNumber)
00358     {
00359         /* only add the serial number if it is not already present in the
00360          * interface name */
00361         if (!sInterfaceName || NULL == strstr(sInterfaceName, sSerialNumber))
00362         {
00363             strlcat(fullname, " (", sizeof(fullname));
00364             strlcat(fullname, sSerialNumber, sizeof(fullname));
00365             strlcat(fullname, ")", sizeof(fullname));
00366         }
00367     }
00368 
00369     readerTracker[i].fullName = strdup(fullname);
00370     readerTracker[i].devpath = strdup(devpath);
00371     readerTracker[i].status = READER_PRESENT;
00372     readerTracker[i].bInterfaceNumber = bInterfaceNumber;
00373 
00374     ret = RFAddReader(fullname, PCSCLITE_HP_BASE_PORT + i,
00375         driver->libraryPath, deviceName);
00376     if ((SCARD_S_SUCCESS != ret) && (SCARD_E_UNKNOWN_READER != ret))
00377     {
00378         Log2(PCSC_LOG_ERROR, "Failed adding USB device: %s",
00379             driver->readerName);
00380 
00381         if (classdriver && driver != classdriver)
00382         {
00383             /* the reader can also be used by the a class driver */
00384             ret = RFAddReader(fullname, PCSCLITE_HP_BASE_PORT + i,
00385                 classdriver->libraryPath, deviceName);
00386             if ((SCARD_S_SUCCESS != ret) && (SCARD_E_UNKNOWN_READER != ret))
00387             {
00388                 Log2(PCSC_LOG_ERROR, "Failed adding USB device: %s",
00389                         driver->readerName);
00390 
00391                 readerTracker[i].status = READER_FAILED;
00392 
00393                 (void)CheckForOpenCT();
00394             }
00395         }
00396         else
00397         {
00398             readerTracker[i].status = READER_FAILED;
00399 
00400             (void)CheckForOpenCT();
00401         }
00402     }
00403 
00404     (void)pthread_mutex_unlock(&usbNotifierMutex);
00405 } /* HPAddDevice */
00406 
00407 
00408 static void HPRescanUsbBus(struct udev *udev)
00409 {
00410     int i, j;
00411     struct udev_enumerate *enumerate;
00412     struct udev_list_entry *devices, *dev_list_entry;
00413 
00414     /* all reader are marked absent */
00415     for (i=0; i < PCSCLITE_MAX_READERS_CONTEXTS; i++)
00416         readerTracker[i].status = READER_ABSENT;
00417 
00418     /* Create a list of the devices in the 'usb' subsystem. */
00419     enumerate = udev_enumerate_new(udev);
00420     udev_enumerate_add_match_subsystem(enumerate, "usb");
00421     udev_enumerate_scan_devices(enumerate);
00422     devices = udev_enumerate_get_list_entry(enumerate);
00423 
00424     /* For each item enumerated */
00425     udev_list_entry_foreach(dev_list_entry, devices)
00426     {
00427         const char *devpath;
00428         struct udev_device *dev, *parent;
00429         struct _driverTracker *driver, *classdriver;
00430         int newreader;
00431         int bInterfaceNumber;
00432         const char *interface;
00433 
00434         /* Get the filename of the /sys entry for the device
00435            and create a udev_device object (dev) representing it */
00436         devpath = udev_list_entry_get_name(dev_list_entry);
00437         dev = udev_device_new_from_syspath(udev, devpath);
00438 
00439         /* The device pointed to by dev contains information about
00440            the interface. In order to get information about the USB
00441            device, get the parent device with the subsystem/devtype pair
00442            of "usb"/"usb_device". This will be several levels up the
00443            tree, but the function will find it.*/
00444         parent = udev_device_get_parent_with_subsystem_devtype(dev, "usb",
00445             "usb_device");
00446         if (!parent)
00447             continue;
00448 
00449         devpath = udev_device_get_devnode(parent);
00450         if (!devpath)
00451         {
00452             /* the device disapeared? */
00453             Log1(PCSC_LOG_ERROR, "udev_device_get_devnode() failed");
00454             continue;
00455         }
00456 
00457         driver = get_driver(parent, devpath, &classdriver);
00458         if (NULL == driver)
00459             /* no driver known for this device */
00460             continue;
00461 
00462 #ifdef DEBUG_HOTPLUG
00463         Log2(PCSC_LOG_DEBUG, "Found matching USB device: %s", devpath);
00464 #endif
00465 
00466         newreader = TRUE;
00467         bInterfaceNumber = 0;
00468         interface = udev_device_get_sysattr_value(dev, "bInterfaceNumber");
00469         if (interface)
00470             bInterfaceNumber = atoi(interface);
00471 
00472         /* Check if the reader is a new one */
00473         for (j=0; j<PCSCLITE_MAX_READERS_CONTEXTS; j++)
00474         {
00475             if (readerTracker[j].devpath
00476                 && (strcmp(readerTracker[j].devpath, devpath) == 0)
00477                 && (bInterfaceNumber == readerTracker[j].bInterfaceNumber))
00478             {
00479                 /* The reader is already known */
00480                 readerTracker[j].status = READER_PRESENT;
00481                 newreader = FALSE;
00482 #ifdef DEBUG_HOTPLUG
00483                 Log2(PCSC_LOG_DEBUG, "Refresh USB device: %s", devpath);
00484 #endif
00485                 break;
00486             }
00487         }
00488 
00489         /* New reader found */
00490         if (newreader)
00491             HPAddDevice(dev, parent, devpath);
00492 
00493         /* free device */
00494         udev_device_unref(dev);
00495     }
00496 
00497     /* Free the enumerator object */
00498     udev_enumerate_unref(enumerate);
00499 
00500     pthread_mutex_lock(&usbNotifierMutex);
00501     /* check if all the previously found readers are still present */
00502     for (i=0; i<PCSCLITE_MAX_READERS_CONTEXTS; i++)
00503     {
00504         if ((READER_ABSENT == readerTracker[i].status)
00505             && (readerTracker[i].fullName != NULL))
00506         {
00507 
00508             Log3(PCSC_LOG_INFO, "Removing USB device[%d]: %s", i,
00509                 readerTracker[i].devpath);
00510 
00511             RFRemoveReader(readerTracker[i].fullName,
00512                 PCSCLITE_HP_BASE_PORT + i);
00513 
00514             readerTracker[i].status = READER_ABSENT;
00515             free(readerTracker[i].devpath);
00516             readerTracker[i].devpath = NULL;
00517             free(readerTracker[i].fullName);
00518             readerTracker[i].fullName = NULL;
00519 
00520         }
00521     }
00522     pthread_mutex_unlock(&usbNotifierMutex);
00523 }
00524 
00525 static void HPEstablishUSBNotifications(struct udev *udev)
00526 {
00527     struct udev_monitor *udev_monitor;
00528     int r, i;
00529     int fd;
00530     fd_set fds;
00531 
00532     udev_monitor = udev_monitor_new_from_netlink(udev, "udev");
00533 
00534     /* filter only the interfaces */
00535     r = udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, "usb",
00536         "usb_interface");
00537     if (r)
00538     {
00539         Log2(PCSC_LOG_ERROR, "udev_monitor_filter_add_match_subsystem_devtype() error: %d\n", r);
00540         return;
00541     }
00542 
00543     r = udev_monitor_enable_receiving(udev_monitor);
00544     if (r)
00545     {
00546         Log2(PCSC_LOG_ERROR, "udev_monitor_enable_receiving() error: %d\n", r);
00547         return;
00548     }
00549 
00550     /* udev monitor file descriptor */
00551     fd = udev_monitor_get_fd(udev_monitor);
00552 
00553     while (!AraKiriHotPlug)
00554     {
00555         struct udev_device *dev, *parent;
00556         const char *action, *devpath;
00557 
00558 #ifdef DEBUG_HOTPLUG
00559         Log0(PCSC_LOG_INFO);
00560 #endif
00561 
00562         FD_ZERO(&fds);
00563         FD_SET(fd, &fds);
00564 
00565         /* wait for a udev event */
00566         r = select(fd+1, &fds, NULL, NULL, NULL);
00567         if (r < 0)
00568         {
00569             Log2(PCSC_LOG_ERROR, "select(): %s", strerror(errno));
00570             return;
00571         }
00572 
00573         dev = udev_monitor_receive_device(udev_monitor);
00574         if (!dev)
00575         {
00576             Log1(PCSC_LOG_ERROR, "udev_monitor_receive_device() error\n");
00577             return;
00578         }
00579 
00580         action = udev_device_get_action(dev);
00581         if (0 == strcmp("remove", action))
00582         {
00583             Log1(PCSC_LOG_INFO, "Device removed");
00584             HPRescanUsbBus(udev);
00585             continue;
00586         }
00587 
00588         if (strcmp("add", action))
00589             continue;
00590 
00591         parent = udev_device_get_parent_with_subsystem_devtype(dev, "usb",
00592             "usb_device");
00593         devpath = udev_device_get_devnode(parent);
00594         if (!devpath)
00595         {
00596             /* the device disapeared? */
00597             Log1(PCSC_LOG_ERROR, "udev_device_get_devnode() failed");
00598             continue;
00599         }
00600 
00601         HPAddDevice(dev, parent, devpath);
00602 
00603         /* free device */
00604         udev_device_unref(dev);
00605 
00606     }
00607 
00608     for (i=0; i<driverSize; i++)
00609     {
00610         /* free strings allocated by strdup() */
00611         free(driverTracker[i].bundleName);
00612         free(driverTracker[i].libraryPath);
00613         free(driverTracker[i].readerName);
00614     }
00615     free(driverTracker);
00616 
00617     Log1(PCSC_LOG_INFO, "Hotplug stopped");
00618 } /* HPEstablishUSBNotifications */
00619 
00620 
00621 /***
00622  * Start a thread waiting for hotplug events
00623  */
00624 LONG HPSearchHotPluggables(void)
00625 {
00626     int i;
00627 
00628     for (i=0; i<PCSCLITE_MAX_READERS_CONTEXTS; i++)
00629     {
00630         readerTracker[i].status = READER_ABSENT;
00631         readerTracker[i].bInterfaceNumber = 0;
00632         readerTracker[i].devpath = NULL;
00633         readerTracker[i].fullName = NULL;
00634     }
00635 
00636     return HPReadBundleValues();
00637 } /* HPSearchHotPluggables */
00638 
00639 
00643 LONG HPStopHotPluggables(void)
00644 {
00645     AraKiriHotPlug = TRUE;
00646 
00647     return 0;
00648 } /* HPStopHotPluggables */
00649 
00650 
00654 ULONG HPRegisterForHotplugEvents(void)
00655 {
00656     struct udev *udev;
00657 
00658     (void)pthread_mutex_init(&usbNotifierMutex, NULL);
00659 
00660     if (driverSize <= 0)
00661     {
00662         Log1(PCSC_LOG_INFO, "No bundle files in pcsc drivers directory: "
00663             PCSCLITE_HP_DROPDIR);
00664         Log1(PCSC_LOG_INFO, "Disabling USB support for pcscd");
00665         return 0;
00666     }
00667 
00668     /* Create the udev object */
00669     udev = udev_new();
00670     if (!udev)
00671     {
00672         Log1(PCSC_LOG_ERROR, "udev_new() failed");
00673         return 0;
00674     }
00675 
00676     HPRescanUsbBus(udev);
00677 
00678     (void)ThreadCreate(&usbNotifyThread, THREAD_ATTR_DETACHED,
00679         (PCSCLITE_THREAD_FUNCTION( )) HPEstablishUSBNotifications, udev);
00680 
00681     return 0;
00682 } /* HPRegisterForHotplugEvents */
00683 
00684 
00685 void HPReCheckSerialReaders(void)
00686 {
00687     /* nothing to do here */
00688 #ifdef DEBUG_HOTPLUG
00689     Log0(PCSC_LOG_ERROR);
00690 #endif
00691 } /* HPReCheckSerialReaders */
00692 
00693 #endif
00694