#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;
{
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.
*/
int ftdi_read_chipid(struct ftdi_context *ftdi, unsigned int *chipid)
{
- unsigned int a = 0, b = 0, result = -1;
+ 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)
{
{
b = b << 8 | b >> 8;
a = (a << 16) | b;
- a = ftdi_read_chipid_shift(a) | ftdi_read_chipid_shift(a>>8)<<8;
- a |= ftdi_read_chipid_shift(a>>16)<<16 | ftdi_read_chipid_shift(a>>24)<<24;
+ 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;
- result = 0;
+ return 0;
}
}
- if (result != 0)
- ftdi_error_return(result, "read of FTDIChip-ID failed");
-
- return 0;
+ ftdi_error_return(-1, "read of FTDIChip-ID failed");
}
/**