/* ccid_usb.c: USB access routines using the libusb library Copyright (C) 2003-2010 Ludovic Rousseau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #define __CCID_USB__ #include #include # ifdef S_SPLINT_S # include # endif #include #include #include #include #include #include #include "misc.h" #include "ccid.h" #include "debug.h" #include "defs.h" #include "utils.h" #include "parser.h" #include "ccid_ifdhandler.h" /* write timeout * we don't have to wait a long time since the card was doing nothing */ #define USB_WRITE_TIMEOUT (5 * 1000) /* 5 seconds timeout */ /* * Proprietary USB Class (0xFF) are (or are not) accepted * A proprietary class is used for devices released before the final CCID * specifications were ready. * We should not have problems with non CCID devices because the * Manufacturer and Product ID are also used to identify the device */ #define ALLOW_PROPRIETARY_CLASS #define BUS_DEVICE_STRSIZE 32 /* Using the default libusb context */ /* does not work for libusb <= 1.0.8 */ /* #define ctx NULL */ libusb_context *ctx = NULL; #define CCID_INTERRUPT_SIZE 8 struct usbDevice_MultiSlot_Extension { int reader_index; /* The multi-threaded polling part */ int terminated; int status; unsigned char buffer[CCID_INTERRUPT_SIZE]; pthread_t thread_proc; pthread_mutex_t mutex; pthread_cond_t condition; struct libusb_transfer *transfer; }; typedef struct { libusb_device_handle *dev_handle; uint8_t bus_number; uint8_t device_address; int interface; /* * Endpoints */ int bulk_in; int bulk_out; int interrupt; /* Number of slots using the same device */ int real_nb_opened_slots; int *nb_opened_slots; /* * CCID infos common to USB and serial */ _ccid_descriptor ccid; /* libusb transfer for the polling (or NULL) */ struct libusb_transfer *polling_transfer; /* pointer to the multislot extension (if any) */ struct usbDevice_MultiSlot_Extension *multislot_extension; } _usbDevice; /* The _usbDevice structure must be defined before including ccid_usb.h */ #include "ccid_usb.h" /* Specific hooks for multislot readers */ static int Multi_InterruptRead(int reader_index, int timeout /* in ms */); static void Multi_InterruptStop(int reader_index); static struct usbDevice_MultiSlot_Extension *Multi_CreateFirstSlot(int reader_index); static struct usbDevice_MultiSlot_Extension *Multi_CreateNextSlot(int physical_reader_index); static void Multi_PollingTerminate(struct usbDevice_MultiSlot_Extension *msExt); static int get_end_points(struct libusb_config_descriptor *desc, _usbDevice *usbdevice, int num); int ccid_check_firmware(struct libusb_device_descriptor *desc); static unsigned int *get_data_rates(unsigned int reader_index, struct libusb_config_descriptor *desc, int num); /* ne need to initialize to 0 since it is static */ static _usbDevice usbDevice[CCID_DRIVER_MAX_READERS]; #define PCSCLITE_MANUKEY_NAME "ifdVendorID" #define PCSCLITE_PRODKEY_NAME "ifdProductID" #define PCSCLITE_NAMEKEY_NAME "ifdFriendlyName" struct _bogus_firmware { int vendor; /* idVendor */ int product; /* idProduct */ int firmware; /* bcdDevice: previous firmwares have bugs */ }; static struct _bogus_firmware Bogus_firmwares[] = { { 0x04e6, 0xe001, 0x0516 }, /* SCR 331 */ { 0x04e6, 0x5111, 0x0620 }, /* SCR 331-DI */ { 0x04e6, 0xe003, 0x0510 }, /* SPR 532 */ { 0x0D46, 0x3001, 0x0037 }, /* KAAN Base */ { 0x0D46, 0x3002, 0x0037 }, /* KAAN Advanced */ { 0x09C3, 0x0008, 0x0203 }, /* ActivCard V2 */ { 0x0DC3, 0x1004, 0x0502 }, /* ASE IIIe USBv2 */ { 0x0DC3, 0x1102, 0x0607 }, /* ASE IIIe KB USB */ { 0x058F, 0x9520, 0x0102 }, /* Alcor AU9520-G */ { 0x072F, 0x2200, 0x0206 }, /* ACS ACR122U-WB-R */ { 0x08C3, 0x0402, 0x5000 }, /* Precise Biometrics Precise 200 MC */ { 0x08C3, 0x0401, 0x5000 }, /* Precise Biometrics Precise 250 MC */ { 0x0B0C, 0x0050, 0x0101 }, /* Todos Argos Mini II */ { 0x0DC3, 0x0900, 0x0200 }, /* Athena IDProtect Key v2 */ { 0x03F0, 0x0036, 0x0124 }, /* HP USB CCID Smartcard Keyboard */ { 0x062D, 0x0001, 0x0102 }, /* THRC Smart Card Reader */ { 0x04E6, 0x5291, 0x0112 }, /* SCM SCL010 Contactless Reader */ /* the firmware version is not correct since I do not have received a * working reader yet */ #ifndef O2MICRO_OZ776_PATCH { 0x0b97, 0x7762, 0x0111 }, /* Oz776S */ #endif }; /* data rates supported by the secondary slots on the GemCore Pos Pro & SIM Pro */ unsigned int SerialCustomDataRates[] = { GEMPLUS_CUSTOM_DATA_RATES, 0 }; /***************************************************************************** * * close_libusb_if_needed * ****************************************************************************/ static void close_libusb_if_needed(void) { int i, to_exit = TRUE; if (NULL == ctx) return; /* if at least 1 reader is still in use we do not exit libusb */ for (i=0; ialtsetting->bInterfaceNumber; if (interface_number >= 0 && interface != interface_number) { /* an interface was specified and it is not the * current one */ DEBUG_INFO3("Found interface %d but expecting %d", interface, interface_number); DEBUG_INFO3("Wrong interface for USB device %d/%d." " Checking next one.", bus_number, device_address); /* check for another CCID interface on the same device */ num++; goto again; } r = libusb_claim_interface(dev_handle, interface); if (r < 0) { (void)libusb_close(dev_handle); DEBUG_CRITICAL4("Can't claim interface %d/%d: %s", bus_number, device_address, libusb_error_name(r)); claim_failed = TRUE; interface_number = -1; continue; } DEBUG_INFO4("Found Vendor/Product: %04X/%04X (%s)", desc.idVendor, desc.idProduct, friendlyName); DEBUG_INFO3("Using USB bus/device: %d/%d", bus_number, device_address); /* check for firmware bugs */ if (ccid_check_firmware(&desc)) { (void)libusb_close(dev_handle); return_value = STATUS_UNSUCCESSFUL; goto end2; } #ifdef USE_COMPOSITE_AS_MULTISLOT if ((GEMALTOPROXDU == readerID) || (GEMALTOPROXSU == readerID) || (HID_OMNIKEY_5422 == readerID) || (FEITIANR502DUAL == readerID)) { /* use the next interface for the next "slot" */ static_interface = interface + 1; /* reset for a next reader */ /* max interface number for all 3 readers is 2 */ if (static_interface > max_interface_number) static_interface = -1; } #endif /* Get Endpoints values*/ (void)get_end_points(config_desc, &usbDevice[reader_index], num); /* store device information */ usbDevice[reader_index].dev_handle = dev_handle; usbDevice[reader_index].bus_number = bus_number; usbDevice[reader_index].device_address = device_address; usbDevice[reader_index].interface = interface; usbDevice[reader_index].real_nb_opened_slots = 1; usbDevice[reader_index].nb_opened_slots = &usbDevice[reader_index].real_nb_opened_slots; usbDevice[reader_index].polling_transfer = NULL; /* CCID common informations */ usbDevice[reader_index].ccid.real_bSeq = 0; usbDevice[reader_index].ccid.pbSeq = &usbDevice[reader_index].ccid.real_bSeq; usbDevice[reader_index].ccid.readerID = (desc.idVendor << 16) + desc.idProduct; usbDevice[reader_index].ccid.dwFeatures = dw2i(device_descriptor, 40); usbDevice[reader_index].ccid.wLcdLayout = (device_descriptor[51] << 8) + device_descriptor[50]; usbDevice[reader_index].ccid.bPINSupport = device_descriptor[52]; usbDevice[reader_index].ccid.dwMaxCCIDMessageLength = dw2i(device_descriptor, 44); usbDevice[reader_index].ccid.dwMaxIFSD = dw2i(device_descriptor, 28); usbDevice[reader_index].ccid.dwDefaultClock = dw2i(device_descriptor, 10); usbDevice[reader_index].ccid.dwMaxDataRate = dw2i(device_descriptor, 23); usbDevice[reader_index].ccid.bMaxSlotIndex = device_descriptor[4]; usbDevice[reader_index].ccid.bCurrentSlotIndex = 0; usbDevice[reader_index].ccid.readTimeout = DEFAULT_COM_READ_TIMEOUT; if (device_descriptor[27]) usbDevice[reader_index].ccid.arrayOfSupportedDataRates = get_data_rates(reader_index, config_desc, num); else { usbDevice[reader_index].ccid.arrayOfSupportedDataRates = NULL; DEBUG_INFO1("bNumDataRatesSupported is 0"); } usbDevice[reader_index].ccid.bInterfaceProtocol = usb_interface->altsetting->bInterfaceProtocol; usbDevice[reader_index].ccid.bNumEndpoints = usb_interface->altsetting->bNumEndpoints; usbDevice[reader_index].ccid.dwSlotStatus = IFD_ICC_PRESENT; usbDevice[reader_index].ccid.bVoltageSupport = device_descriptor[5]; usbDevice[reader_index].ccid.sIFD_serial_number = NULL; usbDevice[reader_index].ccid.gemalto_firmware_features = NULL; #ifdef ENABLE_ZLP usbDevice[reader_index].ccid.zlp = FALSE; #endif if (desc.iSerialNumber) { unsigned char serial[128]; int ret; ret = libusb_get_string_descriptor_ascii(dev_handle, desc.iSerialNumber, serial, sizeof(serial)); if (ret > 0) usbDevice[reader_index].ccid.sIFD_serial_number = strdup((char *)serial); } usbDevice[reader_index].ccid.sIFD_iManufacturer = NULL; if (desc.iManufacturer) { unsigned char iManufacturer[128]; int ret; ret = libusb_get_string_descriptor_ascii(dev_handle, desc.iManufacturer, iManufacturer, sizeof(iManufacturer)); if (ret > 0) usbDevice[reader_index].ccid.sIFD_iManufacturer = strdup((char *)iManufacturer); } usbDevice[reader_index].ccid.IFD_bcdDevice = desc.bcdDevice; /* If this is a multislot reader, init the multislot stuff */ if (usbDevice[reader_index].ccid.bMaxSlotIndex) usbDevice[reader_index].multislot_extension = Multi_CreateFirstSlot(reader_index); else usbDevice[reader_index].multislot_extension = NULL; goto end; } } } end: if (usbDevice[reader_index].dev_handle == NULL) { /* free the libusb allocated list & devices */ libusb_free_device_list(devs, 1); #ifdef __APPLE__ /* give some time to libusb to detect the new USB devices on Mac OS X */ if (count_libusb > 0) { count_libusb--; DEBUG_INFO2("Wait after libusb: %d", count_libusb); nanosleep(&sleep_time, NULL); goto again_libusb; } #endif /* failed */ close_libusb_if_needed(); if (claim_failed) return STATUS_COMM_ERROR; DEBUG_INFO1("Device not found?"); return STATUS_NO_SUCH_DEVICE; } /* memorise the current reader_index so we can detect * a new OpenUSBByName on a multi slot reader */ previous_reader_index = reader_index; end2: /* free the libusb allocated list & devices */ libusb_free_device_list(devs, 1); end1: /* free bundle list */ bundleRelease(&plist); if (return_value != STATUS_SUCCESS) close_libusb_if_needed(); return return_value; } /* OpenUSBByName */ /***************************************************************************** * * WriteUSB * ****************************************************************************/ status_t WriteUSB(unsigned int reader_index, unsigned int length, unsigned char *buffer) { int rv; int actual_length; char debug_header[] = "-> 121234 "; (void)snprintf(debug_header, sizeof(debug_header), "-> %06X ", (int)reader_index); #ifdef ENABLE_ZLP if (usbDevice[reader_index].ccid.zlp) { /* Zero Length Packet */ int dummy_length; /* try to read a ZLP so transfer length = 0 * timeout of 10 ms */ (void)libusb_bulk_transfer(usbDevice[reader_index].dev_handle, usbDevice[reader_index].bulk_in, NULL, 0, &dummy_length, 10); } #endif DEBUG_XXD(debug_header, buffer, length); rv = libusb_bulk_transfer(usbDevice[reader_index].dev_handle, usbDevice[reader_index].bulk_out, buffer, length, &actual_length, USB_WRITE_TIMEOUT); if (rv < 0) { DEBUG_CRITICAL5("write failed (%d/%d): %d %s", usbDevice[reader_index].bus_number, usbDevice[reader_index].device_address, rv, libusb_error_name(rv)); if (LIBUSB_ERROR_NO_DEVICE == rv) return STATUS_NO_SUCH_DEVICE; return STATUS_UNSUCCESSFUL; } return STATUS_SUCCESS; } /* WriteUSB */ /***************************************************************************** * * ReadUSB * ****************************************************************************/ status_t ReadUSB(unsigned int reader_index, unsigned int * length, unsigned char *buffer) { int rv; int actual_length; char debug_header[] = "<- 121234 "; _ccid_descriptor *ccid_descriptor = get_ccid_descriptor(reader_index); int duplicate_frame = 0; read_again: (void)snprintf(debug_header, sizeof(debug_header), "<- %06X ", (int)reader_index); rv = libusb_bulk_transfer(usbDevice[reader_index].dev_handle, usbDevice[reader_index].bulk_in, buffer, *length, &actual_length, usbDevice[reader_index].ccid.readTimeout); if (rv < 0) { *length = 0; DEBUG_CRITICAL5("read failed (%d/%d): %d %s", usbDevice[reader_index].bus_number, usbDevice[reader_index].device_address, rv, libusb_error_name(rv)); if (LIBUSB_ERROR_NO_DEVICE == rv) return STATUS_NO_SUCH_DEVICE; return STATUS_UNSUCCESSFUL; } *length = actual_length; DEBUG_XXD(debug_header, buffer, *length); #define BSEQ_OFFSET 6 if ((*length >= BSEQ_OFFSET) && (buffer[BSEQ_OFFSET] < *ccid_descriptor->pbSeq -1)) { duplicate_frame++; if (duplicate_frame > 10) { DEBUG_CRITICAL("Too many duplicate frame detected"); return STATUS_UNSUCCESSFUL; } DEBUG_INFO1("Duplicate frame detected"); goto read_again; } return STATUS_SUCCESS; } /* ReadUSB */ /***************************************************************************** * * CloseUSB * ****************************************************************************/ status_t CloseUSB(unsigned int reader_index) { /* device not opened */ if (usbDevice[reader_index].dev_handle == NULL) return STATUS_UNSUCCESSFUL; DEBUG_COMM3("Closing USB device: %d/%d", usbDevice[reader_index].bus_number, usbDevice[reader_index].device_address); /* one slot closed */ (*usbDevice[reader_index].nb_opened_slots)--; /* release the allocated ressources for the last slot only */ if (0 == *usbDevice[reader_index].nb_opened_slots) { struct usbDevice_MultiSlot_Extension *msExt; DEBUG_COMM("Last slot closed. Release resources"); msExt = usbDevice[reader_index].multislot_extension; /* If this is a multislot reader, close using the multislot stuff */ if (msExt) { /* terminate the interrupt waiter thread */ Multi_PollingTerminate(msExt); /* wait for the thread to actually terminate */ pthread_join(msExt->thread_proc, NULL); /* release the shared objects */ pthread_cond_destroy(&msExt->condition); pthread_mutex_destroy(&msExt->mutex); /* Deallocate the extension itself */ free(msExt); /* Stop the slot */ usbDevice[reader_index].multislot_extension = NULL; } if (usbDevice[reader_index].ccid.gemalto_firmware_features) free(usbDevice[reader_index].ccid.gemalto_firmware_features); if (usbDevice[reader_index].ccid.sIFD_serial_number) free(usbDevice[reader_index].ccid.sIFD_serial_number); if (usbDevice[reader_index].ccid.sIFD_iManufacturer) free(usbDevice[reader_index].ccid.sIFD_iManufacturer); if (usbDevice[reader_index].ccid.arrayOfSupportedDataRates) free(usbDevice[reader_index].ccid.arrayOfSupportedDataRates); (void)libusb_release_interface(usbDevice[reader_index].dev_handle, usbDevice[reader_index].interface); (void)libusb_close(usbDevice[reader_index].dev_handle); } /* mark the resource unused */ usbDevice[reader_index].dev_handle = NULL; usbDevice[reader_index].interface = 0; close_libusb_if_needed(); return STATUS_SUCCESS; } /* CloseUSB */ /***************************************************************************** * * get_ccid_descriptor * ****************************************************************************/ _ccid_descriptor *get_ccid_descriptor(unsigned int reader_index) { return &usbDevice[reader_index].ccid; } /* get_ccid_descriptor */ /***************************************************************************** * * get_ccid_device_descriptor * ****************************************************************************/ const unsigned char *get_ccid_device_descriptor(const struct libusb_interface *usb_interface) { #ifdef O2MICRO_OZ776_PATCH uint8_t last_endpoint; #endif if (54 == usb_interface->altsetting->extra_length) return usb_interface->altsetting->extra; if (0 != usb_interface->altsetting->extra_length) { /* If extra_length is zero, the descriptor might be at * the end, but if it's not zero, we have a * problem. */ DEBUG_CRITICAL2("Extra field has a wrong length: %d", usb_interface->altsetting->extra_length); return NULL; } #ifdef O2MICRO_OZ776_PATCH /* Some devices, such as the Oz776, Reiner SCT and bludrive II * report the device descriptor at the end of the endpoint * descriptors; to support those, look for it at the end as well. */ last_endpoint = usb_interface->altsetting->bNumEndpoints-1; if (usb_interface->altsetting->endpoint && usb_interface->altsetting->endpoint[last_endpoint].extra_length == 54) return usb_interface->altsetting->endpoint[last_endpoint].extra; #else DEBUG_CRITICAL2("Extra field has a wrong length: %d", usb_interface->altsetting->extra_length); #endif return NULL; } /* get_ccid_device_descriptor */ /***************************************************************************** * * get_end_points * ****************************************************************************/ static int get_end_points(struct libusb_config_descriptor *desc, _usbDevice *usbdevice, int num) { int i; int bEndpointAddress; const struct libusb_interface *usb_interface; usb_interface = get_ccid_usb_interface(desc, &num); /* * 3 Endpoints maximum: Interrupt In, Bulk In, Bulk Out */ for (i=0; ialtsetting->bNumEndpoints; i++) { /* interrupt end point (if available) */ if (usb_interface->altsetting->endpoint[i].bmAttributes == LIBUSB_TRANSFER_TYPE_INTERRUPT) { usbdevice->interrupt = usb_interface->altsetting->endpoint[i].bEndpointAddress; continue; } if (usb_interface->altsetting->endpoint[i].bmAttributes != LIBUSB_TRANSFER_TYPE_BULK) continue; bEndpointAddress = usb_interface->altsetting->endpoint[i].bEndpointAddress; if ((bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN) usbdevice->bulk_in = bEndpointAddress; if ((bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_OUT) usbdevice->bulk_out = bEndpointAddress; } return 0; } /* get_end_points */ /***************************************************************************** * * get_ccid_usb_interface * ****************************************************************************/ /*@null@*/ EXTERNAL const struct libusb_interface * get_ccid_usb_interface( struct libusb_config_descriptor *desc, int *num) { const struct libusb_interface *usb_interface = NULL; int i; /* if multiple interfaces use the first one with CCID class type */ for (i = *num; i < desc->bNumInterfaces; i++) { /* CCID Class? */ if (desc->interface[i].altsetting->bInterfaceClass == 0xb #ifdef ALLOW_PROPRIETARY_CLASS || (desc->interface[i].altsetting->bInterfaceClass == 0xff && 54 == desc->interface[i].altsetting->extra_length) #endif ) { usb_interface = &desc->interface[i]; /* store the interface number for further reference */ *num = i; break; } } return usb_interface; } /* get_ccid_usb_interface */ /***************************************************************************** * * ccid_check_firmware * ****************************************************************************/ int ccid_check_firmware(struct libusb_device_descriptor *desc) { unsigned int i; for (i=0; iidVendor != Bogus_firmwares[i].vendor) continue; if (desc->idProduct != Bogus_firmwares[i].product) continue; /* firmware too old and buggy */ if (desc->bcdDevice < Bogus_firmwares[i].firmware) { if (DriverOptions & DRIVER_OPTION_USE_BOGUS_FIRMWARE) { DEBUG_INFO3("Firmware (%X.%02X) is bogus! but you choosed to use it", desc->bcdDevice >> 8, desc->bcdDevice & 0xFF); return FALSE; } else { DEBUG_CRITICAL3("Firmware (%X.%02X) is bogus! Upgrade the reader firmware or get a new reader.", desc->bcdDevice >> 8, desc->bcdDevice & 0xFF); return TRUE; } } } /* by default the firmware is not bogus */ return FALSE; } /* ccid_check_firmware */ /***************************************************************************** * * get_data_rates * ****************************************************************************/ static unsigned int *get_data_rates(unsigned int reader_index, struct libusb_config_descriptor *desc, int num) { int n, i, len; unsigned char buffer[256*sizeof(int)]; /* maximum is 256 records */ unsigned int *uint_array; /* See CCID 3.7.3 page 25 */ n = ControlUSB(reader_index, 0xA1, /* request type */ 0x03, /* GET_DATA_RATES */ 0x00, /* value */ buffer, sizeof(buffer)); /* we got an error? */ if (n <= 0) { DEBUG_INFO2("IFD does not support GET_DATA_RATES request: %d", n); return NULL; } /* we got a strange value */ if (n % 4) { DEBUG_INFO2("Wrong GET DATA RATES size: %d", n); return NULL; } /* allocate the buffer (including the end marker) */ n /= sizeof(int); /* we do not get the expected number of data rates */ len = get_ccid_device_descriptor(get_ccid_usb_interface(desc, &num))[27]; /* bNumDataRatesSupported */ if ((n != len) && len) { DEBUG_INFO3("Got %d data rates but was expecting %d", n, len); /* we got more data than expected */ if (n > len) n = len; } uint_array = calloc(n+1, sizeof(uint_array[0])); if (NULL == uint_array) { DEBUG_CRITICAL("Memory allocation failed"); return NULL; } /* convert in correct endianess */ for (i=0; iuser_data; *completed = 1; /* caller interprets results and frees transfer */ } /***************************************************************************** * * InterruptRead * ****************************************************************************/ int InterruptRead(int reader_index, int timeout /* in ms */) { int ret, actual_length; int return_value = IFD_SUCCESS; unsigned char buffer[8]; struct libusb_transfer *transfer; int completed = 0; /* Multislot reader: redirect to Multi_InterrupRead */ if (usbDevice[reader_index].multislot_extension != NULL) return Multi_InterruptRead(reader_index, timeout); DEBUG_PERIODIC2("before (%d)", reader_index); transfer = libusb_alloc_transfer(0); if (NULL == transfer) return LIBUSB_ERROR_NO_MEM; libusb_fill_bulk_transfer(transfer, usbDevice[reader_index].dev_handle, usbDevice[reader_index].interrupt, buffer, sizeof(buffer), bulk_transfer_cb, &completed, timeout); transfer->type = LIBUSB_TRANSFER_TYPE_INTERRUPT; ret = libusb_submit_transfer(transfer); if (ret < 0) { libusb_free_transfer(transfer); DEBUG_CRITICAL2("libusb_submit_transfer failed: %s", libusb_error_name(ret)); return IFD_COMMUNICATION_ERROR; } usbDevice[reader_index].polling_transfer = transfer; while (!completed) { ret = libusb_handle_events_completed(ctx, &completed); if (ret < 0) { if (ret == LIBUSB_ERROR_INTERRUPTED) continue; libusb_cancel_transfer(transfer); while (!completed) if (libusb_handle_events_completed(ctx, &completed) < 0) break; libusb_free_transfer(transfer); DEBUG_CRITICAL2("libusb_handle_events failed: %s", libusb_error_name(ret)); return IFD_COMMUNICATION_ERROR; } } actual_length = transfer->actual_length; ret = transfer->status; usbDevice[reader_index].polling_transfer = NULL; libusb_free_transfer(transfer); DEBUG_PERIODIC3("after (%d) (%d)", reader_index, ret); switch (ret) { case LIBUSB_TRANSFER_COMPLETED: DEBUG_XXD("NotifySlotChange: ", buffer, actual_length); break; case LIBUSB_TRANSFER_TIMED_OUT: break; default: /* if libusb_interrupt_transfer() times out we get EILSEQ or EAGAIN */ DEBUG_COMM4("InterruptRead (%d/%d): %s", usbDevice[reader_index].bus_number, usbDevice[reader_index].device_address, libusb_error_name(ret)); return_value = IFD_COMMUNICATION_ERROR; } return return_value; } /* InterruptRead */ /***************************************************************************** * * Stop the async loop * ****************************************************************************/ void InterruptStop(int reader_index) { struct libusb_transfer *transfer; /* Multislot reader: redirect to Multi_InterrupStop */ if (usbDevice[reader_index].multislot_extension != NULL) { Multi_InterruptStop(reader_index); return; } transfer = usbDevice[reader_index].polling_transfer; usbDevice[reader_index].polling_transfer = NULL; if (transfer) { int ret; ret = libusb_cancel_transfer(transfer); if (ret < 0) DEBUG_CRITICAL2("libusb_cancel_transfer failed: %d", ret); } } /* InterruptStop */ /***************************************************************************** * * Multi_PollingProc * ****************************************************************************/ static void *Multi_PollingProc(void *p_ext) { struct usbDevice_MultiSlot_Extension *msExt = p_ext; int rv, status, actual_length; unsigned char buffer[CCID_INTERRUPT_SIZE]; struct libusb_transfer *transfer; int completed; DEBUG_COMM3("Multi_PollingProc (%d/%d): thread starting", usbDevice[msExt->reader_index].bus_number, usbDevice[msExt->reader_index].device_address); rv = 0; while (!msExt->terminated) { DEBUG_COMM3("Multi_PollingProc (%d/%d): waiting", usbDevice[msExt->reader_index].bus_number, usbDevice[msExt->reader_index].device_address); transfer = libusb_alloc_transfer(0); if (NULL == transfer) { rv = LIBUSB_ERROR_NO_MEM; DEBUG_COMM2("libusb_alloc_transfer err %d", rv); break; } libusb_fill_bulk_transfer(transfer, usbDevice[msExt->reader_index].dev_handle, usbDevice[msExt->reader_index].interrupt, buffer, CCID_INTERRUPT_SIZE, bulk_transfer_cb, &completed, 0); /* No timeout ! */ transfer->type = LIBUSB_TRANSFER_TYPE_INTERRUPT; rv = libusb_submit_transfer(transfer); if (rv) { libusb_free_transfer(transfer); DEBUG_COMM2("libusb_submit_transfer err %d", rv); break; } usbDevice[msExt->reader_index].polling_transfer = transfer; completed = 0; while (!completed && !msExt->terminated) { rv = libusb_handle_events_completed(ctx, &completed); if (rv < 0) { DEBUG_COMM2("libusb_handle_events err %d", rv); if (rv == LIBUSB_ERROR_INTERRUPTED) continue; libusb_cancel_transfer(transfer); while (!completed && !msExt->terminated) { if (libusb_handle_events_completed(ctx, &completed) < 0) break; } break; } } usbDevice[msExt->reader_index].polling_transfer = NULL; if (rv < 0) libusb_free_transfer(transfer); else { int b, slot; actual_length = transfer->actual_length; status = transfer->status; libusb_free_transfer(transfer); switch (status) { case LIBUSB_TRANSFER_COMPLETED: DEBUG_COMM3("Multi_PollingProc (%d/%d): OK", usbDevice[msExt->reader_index].bus_number, usbDevice[msExt->reader_index].device_address); DEBUG_XXD("NotifySlotChange: ", buffer, actual_length); /* log the RDR_to_PC_NotifySlotChange data */ slot = 0; for (b=0; b> (s*2)) & 3); const char *present, *change; present = (slot_status & 1) ? "present" : "absent"; change = (slot_status & 2) ? "status changed" : "no change"; DEBUG_COMM3("slot %d status: %d", s + b*4, slot_status); DEBUG_COMM3("ICC %s, %s", present, change); } slot += 4; } break; case LIBUSB_TRANSFER_TIMED_OUT: DEBUG_COMM3("Multi_PollingProc (%d/%d): Timeout", usbDevice[msExt->reader_index].bus_number, usbDevice[msExt->reader_index].device_address); break; default: /* if libusb_interrupt_transfer() times out * we get EILSEQ or EAGAIN */ DEBUG_COMM4("Multi_PollingProc (%d/%d): %d", usbDevice[msExt->reader_index].bus_number, usbDevice[msExt->reader_index].device_address, status); } /* Tell other slots that there's a new interrupt buffer */ DEBUG_COMM3("Multi_PollingProc (%d/%d): Broadcast to slot(s)", usbDevice[msExt->reader_index].bus_number, usbDevice[msExt->reader_index].device_address); /* Lock the mutex */ pthread_mutex_lock(&msExt->mutex); /* Set the status and the interrupt buffer */ msExt->status = status; memset(msExt->buffer, 0, sizeof msExt->buffer); memcpy(msExt->buffer, buffer, actual_length); /* Broadcast the condition and unlock */ pthread_cond_broadcast(&msExt->condition); pthread_mutex_unlock(&msExt->mutex); } } msExt->terminated = TRUE; if (rv < 0) { DEBUG_CRITICAL4("Multi_PollingProc (%d/%d): error %d", usbDevice[msExt->reader_index].bus_number, usbDevice[msExt->reader_index].device_address, rv); } /* Wake up the slot threads so they will exit as well */ /* Lock the mutex */ pthread_mutex_lock(&msExt->mutex); /* Set the status and fill-in the interrupt buffer */ msExt->status = 0; memset(msExt->buffer, 0xFF, sizeof msExt->buffer); /* Broadcast the condition */ pthread_cond_broadcast(&msExt->condition); /* Unlock */ pthread_mutex_unlock(&msExt->mutex); /* Now exit */ DEBUG_COMM3("Multi_PollingProc (%d/%d): Thread terminated", usbDevice[msExt->reader_index].bus_number, usbDevice[msExt->reader_index].device_address); pthread_exit(NULL); return NULL; } /* Multi_PollingProc */ /***************************************************************************** * * Multi_PollingTerminate * ****************************************************************************/ static void Multi_PollingTerminate(struct usbDevice_MultiSlot_Extension *msExt) { struct libusb_transfer *transfer; if (msExt && !msExt->terminated) { msExt->terminated = TRUE; transfer = usbDevice[msExt->reader_index].polling_transfer; if (transfer) { int ret; ret = libusb_cancel_transfer(transfer); if (ret < 0) DEBUG_CRITICAL2("libusb_cancel_transfer failed: %d", ret); } } } /* Multi_PollingTerminate */ /***************************************************************************** * * Multi_InterruptRead * ****************************************************************************/ static int Multi_InterruptRead(int reader_index, int timeout /* in ms */) { struct usbDevice_MultiSlot_Extension *msExt; unsigned char buffer[CCID_INTERRUPT_SIZE]; struct timespec cond_wait_until; struct timeval local_time; int rv, status, interrupt_byte, interrupt_mask; msExt = usbDevice[reader_index].multislot_extension; /* When stopped, return 0 so IFDHPolling will return IFD_NO_SUCH_DEVICE */ if ((msExt == NULL) || msExt->terminated) return 0; DEBUG_PERIODIC3("Multi_InterruptRead (%d), timeout: %d ms", reader_index, timeout); /* Select the relevant bit in the interrupt buffer */ interrupt_byte = (usbDevice[reader_index].ccid.bCurrentSlotIndex / 4) + 1; interrupt_mask = 0x02 << (2 * (usbDevice[reader_index].ccid.bCurrentSlotIndex % 4)); /* Wait until the condition is signaled or a timeout occurs */ gettimeofday(&local_time, NULL); cond_wait_until.tv_sec = local_time.tv_sec; cond_wait_until.tv_nsec = local_time.tv_usec * 1000; cond_wait_until.tv_sec += timeout / 1000; cond_wait_until.tv_nsec += 1000000 * (timeout % 1000); again: pthread_mutex_lock(&msExt->mutex); rv = pthread_cond_timedwait(&msExt->condition, &msExt->mutex, &cond_wait_until); if (0 == rv) { /* Retrieve interrupt buffer and request result */ memcpy(buffer, msExt->buffer, sizeof buffer); status = msExt->status; } else if (rv == ETIMEDOUT) status = LIBUSB_TRANSFER_TIMED_OUT; else status = -1; /* Don't forget to unlock the mutex */ pthread_mutex_unlock(&msExt->mutex); /* When stopped, return 0 so IFDHPolling will return IFD_NO_SUCH_DEVICE */ if (msExt->terminated) return 0; /* Not stopped */ if (status == LIBUSB_TRANSFER_COMPLETED) { if (0 == (buffer[interrupt_byte] & interrupt_mask)) { DEBUG_PERIODIC2("Multi_InterruptRead (%d) -- skipped", reader_index); goto again; } DEBUG_PERIODIC2("Multi_InterruptRead (%d), got an interrupt", reader_index); } else { DEBUG_PERIODIC3("Multi_InterruptRead (%d), status=%d", reader_index, status); } return status; } /* Multi_InterruptRead */ /***************************************************************************** * * Multi_InterruptStop * ****************************************************************************/ static void Multi_InterruptStop(int reader_index) { struct usbDevice_MultiSlot_Extension *msExt; int interrupt_byte, interrupt_mask; msExt = usbDevice[reader_index].multislot_extension; /* Already stopped ? */ if ((NULL == msExt) || msExt->terminated) return; DEBUG_PERIODIC2("Stop (%d)", reader_index); interrupt_byte = (usbDevice[reader_index].ccid.bCurrentSlotIndex / 4) + 1; interrupt_mask = 0x02 << (2 * (usbDevice[reader_index].ccid.bCurrentSlotIndex % 4)); pthread_mutex_lock(&msExt->mutex); /* Broacast an interrupt to wake-up the slot's thread */ msExt->buffer[interrupt_byte] |= interrupt_mask; pthread_cond_broadcast(&msExt->condition); pthread_mutex_unlock(&msExt->mutex); } /* Multi_InterruptStop */ /***************************************************************************** * * Multi_CreateFirstSlot * ****************************************************************************/ static struct usbDevice_MultiSlot_Extension *Multi_CreateFirstSlot(int reader_index) { struct usbDevice_MultiSlot_Extension *msExt; /* Allocate a new extension buffer */ msExt = malloc(sizeof(struct usbDevice_MultiSlot_Extension)); if (NULL == msExt) return NULL; /* Remember the index */ msExt->reader_index = reader_index; msExt->terminated = FALSE; msExt->status = 0; msExt->transfer = NULL; /* Create mutex and condition object for the interrupt polling */ pthread_mutex_init(&msExt->mutex, NULL); pthread_cond_init(&msExt->condition, NULL); /* create the thread in charge of the interrupt polling */ pthread_create(&msExt->thread_proc, NULL, Multi_PollingProc, msExt); return msExt; } /* Multi_CreateFirstSlot */ /***************************************************************************** * * Multi_CreateNextSlot * ****************************************************************************/ static struct usbDevice_MultiSlot_Extension *Multi_CreateNextSlot(int physical_reader_index) { /* Take the extension buffer from the main slot */ return usbDevice[physical_reader_index].multislot_extension; } /* Multi_CreateNextSlot */