// // exSID_ftdiwrap.c // An FTDI access wrapper for exSID USB // // (C) 2016 Thibaut VARENE // License: GPLv2 - http://www.gnu.org/licenses/gpl-2.0.html // // Coding style is somewhat unorthodox ;P /** * @file * exSID USB FTDI access wrapper * @author Thibaut VARENE * @date 2016 * @note Primary target is libftdi (cleaner API), adaptations are made for others. * Sadly, libftdi's implementation of read() is unreliable (it doesn't seem * to honour the usb timeout value and doesn't properly block long enough). * This is why libftd2xx is prefered (tried first) for now. Unfortunately, * using libftd2xx comes with a significant performance penalty since * the code is tailored for libftdi. */ #include "exSID_defs.h" #include #ifdef HAVE_DLFCN_H #include #define TEXT(x) x #elif defined (_WIN32) #include #else #error dl not supported #endif #ifdef HAVE_FTD2XX #include #ifndef XSFW_SUPPORT #define XSFW_SUPPORT #endif #else #warning libftd2xx support disabled. #endif #ifdef HAVE_FTDI #include #ifndef XSFW_SUPPORT #define XSFW_SUPPORT #endif #else #warning libftdi support disabled. #endif #ifndef XSFW_SUPPORT #error No known method to access FTDI chip #endif #define XSFW_WRAPDECL #include "exSID_ftdiwrap.h" #define EXSID_INTERFACES "libftd2xx, libftdi" // XXX TODO Should be set by configure static unsigned int dummysize = 0; // DWORD in unsigned int #ifdef _WIN32 static HMODULE dlhandle = NULL; static char *_xSfw_dlerror() { DWORD dwError = GetLastError(); char* lpMsgBuf = NULL; FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_ALLOCATE_BUFFER, 0, dwError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&lpMsgBuf, 0, NULL); return lpMsgBuf; } #define _xSfw_dlopen(libName) LoadLibrary(libName) #define _xSfw_dlsym(hModule, lpProcName) GetProcAddress(hModule, lpProcName) #define _xSfw_dlclose(hModule) FreeLibrary(hModule) #define _xSfw_clear_dlerror() SetLastError(0) #define _xSfw_free_errstr(str) LocalFree(str) #else // ! _WIN32 static void * dlhandle = NULL; #define _xSfw_dlopen(filename) dlopen(filename, RTLD_NOW|RTLD_LOCAL) #define _xSfw_dlsym(handle, symbol) dlsym(handle, symbol) #define _xSfw_dlclose(handle) dlclose(handle) #define _xSfw_dlerror() dlerror() #define _xSfw_clear_dlerror() dlerror() #define _xSfw_free_errstr(str) /* nothing */ #endif // _WIN32 /** Flag to signal which of the supported libraries is in use */ typedef enum { XS_LIBNONE, XS_LIBFTDI, XS_LIBFTD2XX, } libtype_t; static libtype_t libtype = XS_LIBNONE; // private functions static int (* _xSfw_set_baudrate)(void * ftdi, int baudrate); static int (* _xSfw_set_line_property)(void * ftdi, int bits, int sbit, int parity); static int (* _xSfw_setflowctrl)(void * ftdi, int flowctrl); static int (* _xSfw_set_latency_timer)(void * ftdi, unsigned char latency); // callbacks for ftdi #ifdef HAVE_FTDI static int (* _ftdi_usb_open_desc)(void *, int, int, const char *, const char *); #endif // callbacks for FTD2XX #ifdef HAVE_FTD2XX static int (*_FT_Write)(void *, LPVOID, int, unsigned int *); static int (*_FT_Read)(void *, LPVOID, int, unsigned int *); static int (*_FT_OpenEx)(const char *, int, void **); static int (*_FT_SetBaudRate)(void *, int); static int (*_FT_SetDataCharacteristics)(void *, int, int, int); static int (*_FT_SetFlowControl)(void *, int, int, int); static int (*_FT_SetLatencyTimer)(void *, unsigned char); static int (*_FT_Purge)(void *, int); static int (*_FT_Close)(void *); #endif // wrappers for ftdi #ifdef HAVE_FTDI static int _xSfwftdi_usb_open_desc(void ** ftdi, int vid, int pid, const char * desc, const char * serial) { return _ftdi_usb_open_desc(*ftdi, vid, pid, desc, serial); } #endif // wrappers for FTD2XX #ifdef HAVE_FTD2XX static int _xSfwFT_write_data(void * restrict ftdi, const unsigned char * restrict buf, int size) { static int rval; if(unlikely(rval = _FT_Write(ftdi, (LPVOID)buf, size, &dummysize))) return -rval; else return dummysize; } static int _xSfwFT_read_data(void * restrict ftdi, unsigned char * restrict buf, int size) { static int rval; if (unlikely(rval = _FT_Read(ftdi, (LPVOID)buf, size, &dummysize))) return -rval; else return dummysize; } static int _xSfwFT_usb_open_desc(void ** ftdi, int vid, int pid, const char * desc, const char * serial) { return -_FT_OpenEx(desc, FT_OPEN_BY_DESCRIPTION, ftdi); } static int _xSfwFT_usb_purge_buffers(void * ftdi) { return -_FT_Purge(ftdi, FT_PURGE_RX | FT_PURGE_TX); } static int _xSfwFT_usb_close(void * ftdi) { return -_FT_Close(ftdi); } static char * _xSfwFT_get_error_string(void * ftdi) { return "FTD2XX error"; } #endif /** * Attempt to dlopen a known working library to access FTDI chip. * Will try libftd2xx first, then libftdi. * @return 0 on success, -1 on error. */ int xSfw_dlopen() { #define XSFW_DLSYM(a, b) \ *(void **)(&a) = _xSfw_dlsym(dlhandle, b); \ if (a == NULL) { \ dlerrorstr = _xSfw_dlerror(); \ goto dlfail; \ } char * dlerrorstr = NULL; #ifdef HAVE_FTD2XX #ifdef _WIN32 # define LIBFTD2XX "ftd2xx" #else # define LIBFTD2XX "libftd2xx" #endif // try libftd2xx first - XXX TODO version check if ((dlhandle = _xSfw_dlopen(TEXT(LIBFTD2XX SHLIBEXT)))) { _xSfw_clear_dlerror(); // clear dlerror xSfw_new = NULL; xSfw_free = NULL; XSFW_DLSYM(_FT_Write, "FT_Write"); xSfw_write_data = _xSfwFT_write_data; XSFW_DLSYM(_FT_Read, "FT_Read"); xSfw_read_data = _xSfwFT_read_data; XSFW_DLSYM(_FT_OpenEx, "FT_OpenEx"); xSfw_usb_open_desc = _xSfwFT_usb_open_desc; XSFW_DLSYM(_FT_SetBaudRate, "FT_SetBaudRate"); XSFW_DLSYM(_FT_SetDataCharacteristics, "FT_SetDataCharacteristics"); XSFW_DLSYM(_FT_SetFlowControl, "FT_SetFlowControl"); XSFW_DLSYM(_FT_SetLatencyTimer, "FT_SetLatencyTimer"); XSFW_DLSYM(_FT_Purge, "FT_Purge"); xSfw_usb_purge_buffers = _xSfwFT_usb_purge_buffers; XSFW_DLSYM(_FT_Close, "FT_Close"); xSfw_usb_close = _xSfwFT_usb_close; xSfw_get_error_string = _xSfwFT_get_error_string; libtype = XS_LIBFTD2XX; xsdbg("Using libftd2xx\n"); } else #endif #ifdef HAVE_FTDI // otherwise try libftdi1 - XXX TODO version check if ((dlhandle = _xSfw_dlopen(TEXT("libftdi1" SHLIBEXT)))) { _xSfw_clear_dlerror(); // clear dlerror XSFW_DLSYM(xSfw_new, "ftdi_new"); XSFW_DLSYM(xSfw_free, "ftdi_free"); XSFW_DLSYM(xSfw_write_data, "ftdi_write_data"); XSFW_DLSYM(xSfw_read_data, "ftdi_read_data"); XSFW_DLSYM(_ftdi_usb_open_desc, "ftdi_usb_open_desc"); xSfw_usb_open_desc = _xSfwftdi_usb_open_desc; XSFW_DLSYM(_xSfw_set_baudrate, "ftdi_set_baudrate"); XSFW_DLSYM(_xSfw_set_line_property, "ftdi_set_line_property"); XSFW_DLSYM(_xSfw_setflowctrl, "ftdi_setflowctrl"); XSFW_DLSYM(_xSfw_set_latency_timer, "ftdi_set_latency_timer"); XSFW_DLSYM(xSfw_usb_purge_buffers, "ftdi_usb_purge_buffers"); XSFW_DLSYM(xSfw_usb_close, "ftdi_usb_close"); XSFW_DLSYM(xSfw_get_error_string, "ftdi_get_error_string"); libtype = XS_LIBFTDI; xsdbg("Using libftdi\n"); } else #endif // if none worked, fail. { xserror("No method found to access FTDI interface.\n" "Are any of the following libraries installed?\n" "\t" EXSID_INTERFACES "\n"); return -1; } return 0; dlfail: xserror("dlsym error: %s\n", dlerrorstr); _xSfw_free_errstr(dlerrorstr); xSfw_dlclose(dlhandle); return -1; } /** * Setup FTDI chip to match exSID firmware. * Defaults to 8N1, no flow control. * @param ftdi ftdi handle * @param baudrate Target baudrate * @param latency Target latency * @return 0 on success, rval on error. */ int xSfw_usb_setup(void * ftdi, int baudrate, int latency) { int rval = 0; #ifdef HAVE_FTDI if (XS_LIBFTDI == libtype) { rval = _xSfw_set_baudrate(ftdi, baudrate); if (rval < 0) xserror("SBR error\n"); rval = _xSfw_set_line_property(ftdi, BITS_8 , STOP_BIT_1, NONE); if (rval < 0) xserror("SLP error\n"); rval = _xSfw_setflowctrl(ftdi, SIO_DISABLE_FLOW_CTRL); if (rval < 0) xserror("SFC error\n"); rval = _xSfw_set_latency_timer(ftdi, latency); if (rval < 0) xserror("SLT error\n"); } else #endif #ifdef HAVE_FTD2XX if (XS_LIBFTD2XX == libtype) { rval = -_FT_SetBaudRate(ftdi, baudrate); if (rval < 0) xserror("SBR error\n"); rval = -_FT_SetDataCharacteristics(ftdi, FT_BITS_8, FT_STOP_BITS_1, FT_PARITY_NONE); if (rval < 0) xserror("SLP error\n"); rval = -_FT_SetFlowControl(ftdi, FT_FLOW_NONE, 0, 0); if (rval < 0) xserror("SFC error\n"); rval = -_FT_SetLatencyTimer(ftdi, latency); if (rval < 0) xserror("SLT error\n"); } else #endif xserror("Unkown access method\n"); return rval; } /** * Release dlopen'd library. */ void xSfw_dlclose() { if (dlhandle != NULL) { _xSfw_dlclose(dlhandle); dlhandle = NULL; } }