/* Hey EMACS -*- linux-c -*- */ /* $Id: link_usb.c 4331 2010-05-13 20:09:19Z debrouxl $ */ /* libticables2 - link cable library, a part of the TiLP project * Copyright (c) 1999-2006 Romain Lievin * Copyright (c) 2001 Julien Blache (original author) * Copyright (c) 2007 Romain Liévin (libusb-win32 support) * Copyright (c) 2007 Kevin Kofler (libusb-win32 slv_check support) * Copyright (c) 2011 Jon Sturm (libusb-1.0 support) * * Portions lifted from libusb (LGPL): * Copyright (C) 2007-2008 Daniel Drake * Copyright (c) 2001 Johannes Erdfelt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* TI-GRAPH LINK USB and direct USB cable support (libusb-1.0) */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #ifndef __WIN32__ # include # include # include # include # include #endif #include "../ticables.h" #include "../logging.h" #include "../error.h" #include "../gettext.h" #if defined(__WIN32__) # include "../win32/detect.h" #elif defined(__BSD__) # include "../bsd/detect.h" #else # include "detect.h" #endif #include "../timeout.h" /* Constants */ #define MAX_CABLES 4 #define VID_TI 0x0451 /* Texas Instruments, Inc. */ #define to (100 * h->timeout) // in ms /* Types */ // device infos typedef struct { uint16_t vid; uint16_t pid; const char* str; struct libusb_device *dev; } usb_infos; // list of known devices static usb_infos tigl_infos[] = { {VID_TI, PID_TIGLUSB, "TI-GRAPH LINK USB", NULL}, {VID_TI, PID_TI84P, "TI-84 Plus Hand-Held", NULL}, {VID_TI, PID_TI89TM, "TI-89 Titanium Hand-Held", NULL}, {VID_TI, PID_TI84P_SE, "TI-84 Plus Silver Hand-Held", NULL}, {VID_TI, PID_NSPIRE, "TI-Nspire Hand-Held", NULL}, {0, 0, NULL, NULL} }; // list of devices found static usb_infos tigl_devices[MAX_CABLES+1]; // internal structure for holding data typedef struct { struct libusb_device *device; struct libusb_device_handle *handle; int nBytesRead; uint8_t rBuf[64]; uint8_t* rBufPtr; int in_endpoint; int out_endpoint; int max_ps; int was_max_ps; } usb_struct; // convenient macros #define uDev (((usb_struct *)(h->priv2))->device) #define uHdl (((usb_struct *)(h->priv2))->handle) #define max_ps (((usb_struct *)(h->priv2))->max_ps) #define nBytesRead (((usb_struct *)(h->priv2))->nBytesRead) #define rBuf (((usb_struct *)(h->priv2))->rBuf) #define rBufPtr (((usb_struct *)(h->priv2))->rBufPtr) #define uInEnd (((usb_struct *)(h->priv2))->in_endpoint) #define uOutEnd (((usb_struct *)(h->priv2))->out_endpoint) /* * Taken from libusb git, will be included in later releases of * libusb-1.0 but as most distros will not be shipping that for a while * this will have to do. ~ Jon 8/2/2011 */ const char* tigl_strerror(enum libusb_error errcode) { switch (errcode) { case LIBUSB_SUCCESS: return "Success"; case LIBUSB_ERROR_IO: return "Input/output error"; case LIBUSB_ERROR_INVALID_PARAM: return "Invalid parameter"; case LIBUSB_ERROR_ACCESS: return "Access denied (insufficient permissions)"; case LIBUSB_ERROR_NO_DEVICE: return "No such device (it may have been disconnected)"; case LIBUSB_ERROR_NOT_FOUND: return "Entity not found"; case LIBUSB_ERROR_BUSY: return "Resource busy"; case LIBUSB_ERROR_TIMEOUT: return "Operation timed out"; case LIBUSB_ERROR_OVERFLOW: return "Overflow"; case LIBUSB_ERROR_PIPE: return "Pipe error"; case LIBUSB_ERROR_INTERRUPTED: return "System call interrupted (perhaps due to signal)"; case LIBUSB_ERROR_NO_MEM: return "Insufficient memory"; case LIBUSB_ERROR_NOT_SUPPORTED: return "Operation not supported or unimplemented on this platform"; case LIBUSB_ERROR_OTHER: return "Other error"; } return "Unknown error"; } static const char* tigl_get_product(struct libusb_device *dev) { libusb_device_handle *han; int ret; static unsigned char string[64]; struct libusb_device_descriptor desc; int r = libusb_get_device_descriptor(dev, &desc); if (r < 0) { ticables_error("failed to get device descriptor"); return ""; } if (desc.iProduct) { if (!libusb_open(dev, &han)) { ret = libusb_get_string_descriptor_ascii(han, desc.iProduct, string, sizeof(string)); libusb_close(han); if (ret > 0) return (const char *) string; else ticables_warning("libusb_get_string_descriptor_ascii (%s).\n", tigl_strerror(ret)); return ""; } else return ""; } return string; } static int tigl_find(void) { // discover devices libusb_device **list; ssize_t cnt = libusb_get_device_list(NULL, &list); ssize_t i = 0; int j = 0; int k; if (cnt <= 0) return 0; for (i = 0; i < cnt; i++) { libusb_device *device = list[i]; struct libusb_device_descriptor desc; int r = libusb_get_device_descriptor(device, &desc); if (r < 0) { fprintf(stderr, "failed to get device descriptor"); return r; } if ((desc.idVendor == VID_TI)) { for(k = 0; k < (int)(sizeof(tigl_infos) / sizeof(usb_infos)); k++) { if(desc.idProduct == tigl_infos[k].pid) { ticables_info(_(" found %s on #%i, version <%x.%02x>\n"), tigl_get_product(device), j+1, desc.bcdDevice >> 8, desc.bcdDevice & 0xff); memcpy(&tigl_devices[j], &tigl_infos[k], sizeof(usb_infos)); tigl_devices[j++].dev = device; } } } } return j; } static int tigl_enum(void) { int ret = 0; /* find all TI products on all ports */ ret = tigl_find(); if(ret == 0) { ticables_warning(_(" no devices found!\n")); return ERR_LIBUSB_OPEN; } return 0; } static int tigl_open(int id, libusb_device_handle ** udh) { int ret; tigl_enum(); if(tigl_devices[id].dev == NULL) return ERR_LIBUSB_OPEN; if (!libusb_open(tigl_devices[id].dev, udh)) { /* only one configuration: #1 */ ret = libusb_set_configuration(*udh, 1); if (ret) { ticables_warning("libusb_set_configuration (%s).\n", tigl_strerror(ret)); } /* configuration #1, interface #0 */ ret = libusb_claim_interface(*udh, 0); if (ret) { ticables_warning("libusb_claim_interface (%s).\n", tigl_strerror(ret)); return ERR_LIBUSB_CLAIM; } return 0; } else return ERR_LIBUSB_OPEN; return 0; } static int tigl_close(libusb_device_handle **udh) { libusb_release_interface(*udh, 0); libusb_close(*udh); *udh = NULL; return 0; } static int tigl_reset(CableHandle *h) { int ret; // Reset out pipe ret = libusb_clear_halt(uHdl, uOutEnd); if (ret) { ticables_warning("libusb_clear_halt (%s).\n", tigl_strerror(ret)); } // Reset in pipe ret = libusb_clear_halt(uHdl, uInEnd); if (ret) { ticables_warning("libusb_clear_halt (%s).\n", tigl_strerror(ret)); } return 0; } /* API */ static int slv_prepare(CableHandle *h) { char str[64]; #if defined(__WIN32__) TRYC(win32_check_libusb()); #elif defined(__BSD__) TRYC(bsd_check_libusb()); #else TRYC(linux_check_libusb()); #endif if(h->port >= MAX_CABLES) return ERR_ILLEGAL_ARG; h->address = h->port-1; sprintf(str, "TiglUsb #%i", h->port); h->device = strdup(str); h->priv2 = (usb_struct *)calloc(1, sizeof(usb_struct)); return 0; } static int slv_open(CableHandle *h) { int i; struct libusb_config_descriptor *config; const struct libusb_interface *interface_; const struct libusb_interface_descriptor *interface; const struct libusb_endpoint_descriptor *endpoint; // open device TRYC(tigl_open(h->address, &uHdl)); uDev = tigl_devices[h->address].dev; uInEnd = 0x81; uOutEnd = 0x02; // get max packet size libusb_get_active_config_descriptor(uDev, &config); interface_ = &(config->interface[0]); interface = &(interface_->altsetting[0]); endpoint = &(interface->endpoint[0]); max_ps = endpoint->wMaxPacketSize; // Enumerate endpoints. for (i = 0; i < interface->bNumEndpoints; i++) { endpoint = &(interface->endpoint[i]); if ((endpoint->bmAttributes & LIBUSB_TRANSFER_TYPE_BULK) == LIBUSB_TRANSFER_TYPE_BULK) { if (endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) { if (endpoint->bEndpointAddress != 0x83) // Some Nspire OS use that seemingly bogus endpoint. { uInEnd = endpoint->bEndpointAddress; ticables_info("found bulk in endpoint 0x%02X\n", uInEnd); } else { ticables_info("XXX: swallowing bulk in endpoint 0x83, advertised by Nspire (CAS and non-CAS) 1.x but seemingly not working\n"); } } else { uOutEnd = endpoint->bEndpointAddress; ticables_info("found bulk out endpoint 0x%02X\n", uOutEnd); } } } nBytesRead = 0; return 0; } static int slv_close(CableHandle *h) { if (uHdl != NULL) tigl_close(&uHdl); uDev = NULL; free(h->priv2); h->priv2 = NULL; return 0; } static int slv_reset(CableHandle *h) { int ret = 0; /* Reset both endpoints (send an URB_FUNCTION_RESET_PIPE) */ TRYC(tigl_reset(h)); /* Reset USB port (send an IOCTL_INTERNAL_USB_RESET_PORT) */ ret = libusb_reset_device(uHdl); if (ret != 0) { ticables_warning("libusb_device_reset (%s).\n", tigl_strerror(ret)); return ERR_LIBUSB_RESET; } else { // lib-usb doc: after calling usb_reset, the device will need to re-enumerate // and thusly, requires you to find the new device and open a new handle. The // handle used to call usb_reset will no longer work. #ifdef __WIN32__ Sleep(500); #else usleep(500000); #endif TRYC(slv_close(h)); h->priv2 = (usb_struct *)calloc(1, sizeof(usb_struct)); TRYC(slv_open(h)); } return 0; } // convenient function which send one or more bytes static int send_block(CableHandle *h, uint8_t *data, int length) { int ret, tmp, tmp2; ret = libusb_bulk_transfer(uHdl, uOutEnd, (unsigned char*)data, length, &tmp, to); if ((tigl_devices[h->port].pid == PID_NSPIRE) && (tmp % max_ps == 0) && !ret) { ticables_info("XXX triggering an extra bulk write for buggy Nspire OS versions"); ret = libusb_bulk_transfer(uHdl, uOutEnd, (unsigned char*)data, 0, &tmp2, to); } if (ret == LIBUSB_ERROR_TIMEOUT) { ticables_warning("libusb_bulk_transfer (%s).\n", tigl_strerror(ret)); return ERR_WRITE_TIMEOUT; } else if(ret != 0) { ticables_warning("libusb_bulk_transfer (%s).\n", tigl_strerror(ret)); return ERR_WRITE_ERROR; } return 0; } static int slv_put(CableHandle* h, uint8_t *data, uint32_t len) { return send_block(h, data, len); } static int slv_get_(CableHandle *h, uint8_t *data) { int ret = 0; int len = 0; int tmp; tiTIME clk; /* Read up to 32/64 bytes and store them in a buffer for subsequent accesses */ if (nBytesRead <= 0) { TO_START(clk); do { ret = libusb_bulk_transfer(uHdl, uInEnd, (unsigned char*)rBuf, max_ps, &len, 0); } while(!len && !ret); if (tigl_devices[h->port].pid == PID_NSPIRE && (len % max_ps) == 0 && !ret) { ticables_info("XXX triggering an extra bulk read for buggy Nspire OS versions"); ret = libusb_bulk_transfer(uHdl, uInEnd, (unsigned char*)data, 0, &tmp, to); } if(ret == LIBUSB_ERROR_TIMEOUT) { ticables_warning("libusb_bulk_transfer (%s).\n", tigl_strerror(ret)); nBytesRead = 0; return ERR_READ_TIMEOUT; } else if(ret != 0) { ticables_warning("libusb_bulk_transfer (%s).\n", tigl_strerror(ret)); nBytesRead = 0; return ERR_READ_ERROR; } /* I'm not sure what this stuff is for but the old driver had it soo... */ nBytesRead = len; rBufPtr = rBuf; } *data = *rBufPtr++; nBytesRead--; return 0; } static int slv_get(CableHandle* h, uint8_t *data, uint32_t len) { int i=0; /* we can't do that in any other way because slv_get_ can returns * 1, 2, ..., len bytes. * * But we know how much was actually recived can't we just try * again if its less than we expected rather than this mess, what * ever the point of it was? */ for(i = 0; i < (int)len; i++) TRYC(slv_get_(h, data+i)); return 0; } static int slv_probe(CableHandle *h) { int i; TRYC(tigl_enum()); for(i = 0; i < MAX_CABLES; i++) { if(tigl_devices[h->address].pid == PID_TIGLUSB) return 0; } return ERR_PROBE_FAILED; } static int raw_probe(CableHandle *h) { int i; TRYC(tigl_enum()); for(i = 0; i < MAX_CABLES; i++) { if(tigl_devices[h->address].pid == PID_TI89TM || tigl_devices[h->address].pid == PID_TI84P || tigl_devices[h->address].pid == PID_TI84P_SE || tigl_devices[h->address].pid == PID_NSPIRE) return 0; } return ERR_PROBE_FAILED; } static int slv_check(CableHandle *h, int *status) { // We are using the syncronous api for now so transfers finish right away. return 0; } static int slv_set_red_wire(CableHandle *h, int b) { return 0; } static int slv_set_white_wire(CableHandle *h, int b) { return 0; } static int slv_get_red_wire(CableHandle *h) { return 1; } static int slv_get_white_wire(CableHandle *h) { return 1; } const CableFncts cable_slv = { CABLE_SLV, "SLV", N_("SilverLink"), N_("SilverLink (TI-GRAPH LINK USB) cable"), 0, &slv_prepare, &slv_open, &slv_close, &slv_reset, &slv_probe, NULL, &slv_put, &slv_get, &slv_check, &slv_set_red_wire, &slv_set_white_wire, &slv_get_red_wire, &slv_get_white_wire, }; const CableFncts cable_raw = { CABLE_USB, "USB", N_("DirectLink"), N_("DirectLink (DIRECT USB) cable"), 0, &slv_prepare, &slv_open, &slv_close, &slv_reset, &raw_probe, NULL, &slv_put, &slv_get, &slv_check, &slv_set_red_wire, &slv_set_white_wire, &slv_get_red_wire, &slv_get_white_wire, }; //======================= TIEXPORT1 int TICALL usb_probe_devices1(int **list); TIEXPORT1 int TICALL usb_probe_devices(int **list) { return usb_probe_devices1(list); } // returns number of devices and list of PIDs (dynamically allocated) TIEXPORT1 int TICALL usb_probe_devices1(int **list) { int i; TRYC(tigl_enum()); *list = (int *)calloc(MAX_CABLES+1, sizeof(int)); for(i = 0; i < MAX_CABLES; i++) (*list)[i] = tigl_devices[i].pid; return 0; }