#include "ftdi.h"
+/* stuff needed for async write */
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <linux/usbdevice_fs.h>
+
#define ftdi_error_return(code, str) do { \
ftdi->error_str = str; \
return code; \
*/
int ftdi_init(struct ftdi_context *ftdi)
{
+ int i;
+
ftdi->usb_dev = NULL;
ftdi->usb_read_timeout = 5000;
ftdi->usb_write_timeout = 5000;
ftdi->error_str = NULL;
+ ftdi->async_usb_buffer_size=10;
+ if ((ftdi->async_usb_buffer=malloc(sizeof(struct usbdevfs_urb)*ftdi->async_usb_buffer_size)) == NULL)
+ ftdi_error_return(-1, "out of memory for async usb buffer");
+
+ /* initialize async usb buffer with unused-marker */
+ for (i=0; i < ftdi->async_usb_buffer_size; i++)
+ ((struct usbdevfs_urb*)ftdi->async_usb_buffer)[i].usercontext = FTDI_URB_USERCONTEXT_COOKIE;
+
/* All fine. Now allocate the readbuffer */
return ftdi_read_data_set_chunksize(ftdi, 4096);
}
*/
void ftdi_deinit(struct ftdi_context *ftdi)
{
+ if (ftdi->async_usb_buffer != NULL) {
+ free(ftdi->async_usb_buffer);
+ ftdi->async_usb_buffer = NULL;
+ }
+
if (ftdi->readbuffer != NULL) {
free(ftdi->readbuffer);
ftdi->readbuffer = NULL;
ftdi_error_return(-2, "usb_find_devices() failed");
curdev = devlist;
+ *curdev = NULL;
for (bus = usb_busses; bus; bus = bus->next) {
for (dev = bus->devices; dev; dev = dev->next) {
if (dev->descriptor.idVendor == vendor
*/
void ftdi_list_free(struct ftdi_device_list **devlist)
{
- struct ftdi_device_list **curdev;
- for (; *devlist == NULL; devlist = curdev) {
- curdev = &(*devlist)->next;
- free(*devlist);
+ struct ftdi_device_list *curdev, *next;
+
+ for (curdev = *devlist; curdev != NULL;) {
+ next = curdev->next;
+ free(curdev);
+ curdev = next;
}
- devlist = NULL;
+ *devlist = NULL;
+}
+
+/**
+ Return device ID strings from the usb device.
+
+ The parameters manufacturer, description and serial may be NULL
+ or pointer to buffers to store the fetched strings.
+
+ \note Use this function only in combination with ftdi_usb_find_all()
+ as it closes the internal "usb_dev" after use.
+
+ \param ftdi pointer to ftdi_context
+ \param dev libusb usb_dev to use
+ \param manufacturer Store manufacturer string here if not NULL
+ \param mnf_len Buffer size of manufacturer string
+ \param description Store product description string here if not NULL
+ \param desc_len Buffer size of product description string
+ \param serial Store serial string here if not NULL
+ \param serial_len Buffer size of serial string
+
+ \retval 0: all fine
+ \retval -1: wrong arguments
+ \retval -4: unable to open device
+ \retval -7: get product manufacturer failed
+ \retval -8: get product description failed
+ \retval -9: get serial number failed
+ \retval -10: unable to close device
+*/
+int ftdi_usb_get_strings(struct ftdi_context * ftdi, struct usb_device * dev,
+ char * manufacturer, int mnf_len, char * description, int desc_len, char * serial, int serial_len)
+{
+ if ((ftdi==NULL) || (dev==NULL))
+ return -1;
+
+ if (!(ftdi->usb_dev = usb_open(dev)))
+ ftdi_error_return(-4, usb_strerror());
+
+ if (manufacturer != NULL) {
+ if (usb_get_string_simple(ftdi->usb_dev, dev->descriptor.iManufacturer, manufacturer, mnf_len) <= 0) {
+ usb_close (ftdi->usb_dev);
+ ftdi_error_return(-7, usb_strerror());
+ }
+ }
+
+ if (description != NULL) {
+ if (usb_get_string_simple(ftdi->usb_dev, dev->descriptor.iProduct, description, desc_len) <= 0) {
+ usb_close (ftdi->usb_dev);
+ ftdi_error_return(-8, usb_strerror());
+ }
+ }
+
+ if (serial != NULL) {
+ if (usb_get_string_simple(ftdi->usb_dev, dev->descriptor.iSerialNumber, serial, serial_len) <= 0) {
+ usb_close (ftdi->usb_dev);
+ ftdi_error_return(-9, usb_strerror());
+ }
+ }
+
+ if (usb_close (ftdi->usb_dev) != 0)
+ ftdi_error_return(-10, usb_strerror());
+
+ return 0;
}
/**
ftdi->type = TYPE_2232C;
if (!ftdi->index)
ftdi->index = INTERFACE_A;
- }
+ } else if (dev->descriptor.bcdDevice == 0x600)
+ ftdi->type = TYPE_R;
ftdi_error_return(0, "all fine");
}
{
int rtn = 0;
+ /* try to release some kernel resources */
+ ftdi_async_complete(ftdi,1);
+
if (usb_release_interface(ftdi->usb_dev, ftdi->interface) != 0)
rtn = -1;
return total_written;
}
+/* this is strongly dependent on libusb using the same struct layout. If libusb
+ changes in some later version this may break horribly (this is for libusb 0.1.12) */
+struct usb_dev_handle {
+ int fd;
+ // some other stuff coming here we don't need
+};
+
+static int usb_get_async_urbs_pending(struct ftdi_context *ftdi)
+{
+ struct usbdevfs_urb *urb;
+ int pending=0;
+ int i;
+
+ for (i=0; i < ftdi->async_usb_buffer_size; i++) {
+ urb=&((struct usbdevfs_urb *)(ftdi->async_usb_buffer))[i];
+ if (urb->usercontext != FTDI_URB_USERCONTEXT_COOKIE)
+ pending++;
+ }
+
+ return pending;
+}
+
+static void usb_async_cleanup(struct ftdi_context *ftdi, int wait_for_more, int timeout_msec)
+{
+ struct timeval tv;
+ struct usbdevfs_urb *urb=NULL;
+ int ret;
+ fd_set writefds;
+ int keep_going=0;
+
+ FD_ZERO(&writefds);
+ FD_SET(ftdi->usb_dev->fd, &writefds);
+
+ /* init timeout only once, select writes time left after call */
+ tv.tv_sec = timeout_msec / 1000;
+ tv.tv_usec = (timeout_msec % 1000) * 1000;
+
+ do {
+ while (usb_get_async_urbs_pending(ftdi)
+ && (ret = ioctl(ftdi->usb_dev->fd, USBDEVFS_REAPURBNDELAY, &urb)) == -1
+ && errno == EAGAIN)
+ {
+ if (keep_going && !wait_for_more) {
+ /* don't wait if repeating only for keep_going */
+ keep_going=0;
+ break;
+ }
+
+ /* wait for timeout msec or something written ready */
+ select(ftdi->usb_dev->fd+1, NULL, &writefds, NULL, &tv);
+ }
+
+ if (ret == 0 && urb != NULL) {
+ /* got a free urb, mark it */
+ urb->usercontext = FTDI_URB_USERCONTEXT_COOKIE;
+
+ /* try to get more urbs that are ready now, but don't wait anymore */
+ urb=NULL;
+ keep_going=1;
+ } else {
+ /* no more urbs waiting */
+ keep_going=0;
+ }
+ } while (keep_going);
+}
+
+/**
+ Wait until at least one async write is complete
+
+ \param ftdi pointer to ftdi_context
+ \param wait_for_more if != 0 wait for more than one write to complete (until write timeout)
+*/
+void ftdi_async_complete(struct ftdi_context *ftdi, int wait_for_more)
+{
+ usb_async_cleanup(ftdi,wait_for_more,ftdi->usb_write_timeout);
+}
+
+/**
+ Stupid libusb does not offer async writes nor does it allow
+ access to its fd - so we need some hacks here.
+*/
+static int usb_bulk_write_async(struct ftdi_context *ftdi, int ep, char *bytes, int size)
+{
+ struct usbdevfs_urb *urb;
+ int bytesdone = 0, requested;
+ int ret, i;
+ int cleanup_count;
+
+ do {
+ /* find a free urb buffer we can use */
+ urb=NULL;
+ for (cleanup_count=0; urb==NULL && cleanup_count <= 1; cleanup_count++)
+ {
+ if (i==ftdi->async_usb_buffer_size) {
+ /* wait until some buffers are free */
+ usb_async_cleanup(ftdi,0,ftdi->usb_write_timeout);
+ }
+
+ for (i=0; i < ftdi->async_usb_buffer_size; i++) {
+ urb=&((struct usbdevfs_urb *)(ftdi->async_usb_buffer))[i];
+ if (urb->usercontext == FTDI_URB_USERCONTEXT_COOKIE)
+ break; /* found a free urb position */
+ urb=NULL;
+ }
+ }
+
+ /* no free urb position found */
+ if (urb==NULL)
+ return -1;
+
+ requested = size - bytesdone;
+ if (requested > 4096)
+ requested = 4096;
+
+ memset(urb,0,sizeof(urb));
+
+ urb->type = USBDEVFS_URB_TYPE_BULK;
+ urb->endpoint = ep;
+ urb->flags = 0;
+ urb->buffer = bytes + bytesdone;
+ urb->buffer_length = requested;
+ urb->signr = 0;
+ urb->actual_length = 0;
+ urb->number_of_packets = 0;
+ urb->usercontext = 0;
+
+ do {
+ ret = ioctl(ftdi->usb_dev->fd, USBDEVFS_SUBMITURB, urb);
+ } while (ret < 0 && errno == EINTR);
+ if (ret < 0)
+ return ret; /* the caller can read errno to get more info */
+
+ bytesdone += requested;
+ } while (bytesdone < size);
+ return bytesdone;
+}
+
+/**
+ Writes data in chunks (see ftdi_write_data_set_chunksize()) to the chip.
+ Does not wait for completion of the transfer nor does it make sure that
+ the transfer was successful.
+
+ This function could be extended to use signals and callbacks to inform the
+ caller of completion or error - but this is not done yet, volunteers welcome.
+
+ Works around libusb and directly accesses functions only available on Linux.
+
+ \param ftdi pointer to ftdi_context
+ \param buf Buffer with the data
+ \param size Size of the buffer
+
+ \retval <0: error code from usb_bulk_write()
+ \retval >0: number of bytes written
+*/
+int ftdi_write_data_async(struct ftdi_context *ftdi, unsigned char *buf, int size)
+{
+ int ret;
+ int offset = 0;
+ int total_written = 0;
+
+ while (offset < size) {
+ int write_size = ftdi->writebuffer_chunksize;
+
+ if (offset+write_size > size)
+ write_size = size-offset;
+
+ ret = usb_bulk_write_async(ftdi, ftdi->in_ep, buf+offset, write_size);
+ if (ret < 0)
+ ftdi_error_return(ret, "usb bulk write async failed");
+
+ total_written += ret;
+ offset += write_size;
+ }
+
+ return total_written;
+}
+
+
/**
Configure write buffer chunk size.
Default is 4096.
\param size Size of the buffer
\retval <0: error code from usb_bulk_read()
+ \retval 0: no data was available
\retval >0: number of bytes read
\remark This function is not useful in bitbang mode.
return 0;
}
+/*
+ ftdi_read_chipid_shift does the bitshift operation needed for the FTDIChip-ID
+ Function is only used internally
+ \internal
+*/
+static unsigned char ftdi_read_chipid_shift(unsigned char value)
+{
+ return ((value & 1) << 1) |
+ ((value & 2) << 5) |
+ ((value & 4) >> 2) |
+ ((value & 8) << 4) |
+ ((value & 16) >> 1) |
+ ((value & 32) >> 1) |
+ ((value & 64) >> 4) |
+ ((value & 128) >> 2);
+}
+
+/**
+ Read the FTDIChip-ID from R-type devices
+
+ \param ftdi pointer to ftdi_context
+ \param chipid Pointer to store FTDIChip-ID
+
+ \retval 0: all fine
+ \retval -1: read failed
+*/
+int ftdi_read_chipid(struct ftdi_context *ftdi, unsigned int *chipid)
+{
+ unsigned int a = 0, b = 0;
+
+ if (usb_control_msg(ftdi->usb_dev, 0xC0, 0x90, 0, 0x43, (char *)&a, 2, ftdi->usb_read_timeout) == 2)
+ {
+ a = a << 8 | a >> 8;
+ if (usb_control_msg(ftdi->usb_dev, 0xC0, 0x90, 0, 0x44, (char *)&b, 2, ftdi->usb_read_timeout) == 2)
+ {
+ b = b << 8 | b >> 8;
+ a = (a << 16) | b;
+ a = ftdi_read_chipid_shift(a) | ftdi_read_chipid_shift(a>>8)<<8
+ | ftdi_read_chipid_shift(a>>16)<<16 | ftdi_read_chipid_shift(a>>24)<<24;
+ *chipid = a ^ 0xa5f0f7d1;
+ return 0;
+ }
+ }
+
+ ftdi_error_return(-1, "read of FTDIChip-ID failed");
+}
+
/**
Write eeprom