commit 72d8b6898dd8f069157d31a5d63b73c21338dd97
Author: David Mosberger-Tang <davidm@egauge.net>
Date:   Thu May 4 15:51:17 2017 -0600

    Eliminate separate reader thread by tracking how many bytes are queued
    in the FTDI chip's RX FIFO and reading those bytes when it fills up
    (since in synchronous bitbang mode, the chip won't send any more bytes
    until it has space in the RX FIFO).  This reduces TPI programming time
    by another 33%.
    
    Since write data is now queued as much as possible, we need flush this
    queued data (a) before reading and (b) before sleeping.  For the
    latter case, a new helper function ft245r_usleep() is introduced.
    
    ---
    $ avrdude -Cavrdude.conf -pt9 -ctc2030 -Uflash:w:out.hex -b375000
    
    avrdude: AVR device initialized and ready to accept instructions
    
    Reading | ################################################## | 100% 0.00s
    
    avrdude: Device signature = 0x1e9008 (probably t9)
    avrdude: NOTE: "flash" memory has been specified, an erase cycle will be performed
             To disable this feature, specify the -D option.
    	 avrdude: erasing chip
    	 avrdude: reading input file "out.hex"
    	 avrdude: input file out.hex auto detected as Intel Hex
    	 avrdude: writing flash (994 bytes):
    
    Writing | ################################################## | 100% 1.99s
    
    avrdude: 994 bytes of flash written
    avrdude: verifying flash memory against out.hex:
    avrdude: load data flash data from input file out.hex:
    avrdude: input file out.hex auto detected as Intel Hex
    avrdude: input file out.hex contains 994 bytes
    avrdude: reading on-chip flash data:
    
    Reading | ################################################## | 100% 0.67s
    
    avrdude: verifying ...
    avrdude: 994 bytes of flash verified
    
    avrdude done.  Thank you.
    ---

diff --git a/ft245r.c b/ft245r.c
index 097cdaa..79a4b0c 100644
--- a/ft245r.c
+++ b/ft245r.c
@@ -91,25 +91,7 @@
 #define DO_NOT_BUILD_FT245R
 #endif
 
-#ifndef HAVE_PTHREAD_H
-
-static int ft245r_nopthread_open (struct programmer_t *pgm, char * name) {
-    avrdude_message(MSG_INFO, "%s: error: no pthread support. Please compile again with pthread installed."
-#if defined(_WIN32)
-            " See http://sourceware.org/pthreads-win32/."
-#endif
-            "\n",
-            progname);
-
-    return -1;
-}
-
-void ft245r_initpgm(PROGRAMMER * pgm) {
-    strcpy(pgm->type, "ftdi_syncbb");
-    pgm->open = ft245r_nopthread_open;
-}
-
-#elif defined(DO_NOT_BUILD_FT245R)
+#if defined(DO_NOT_BUILD_FT245R)
 
 static int ft245r_noftdi_open (struct programmer_t *pgm, char * name) {
     avrdude_message(MSG_INFO, "%s: error: no libftdi or libusb support. Install libftdi1/libusb-1.0 or libftdi/libusb and run configure/make again.\n",
@@ -125,21 +107,6 @@ void ft245r_initpgm(PROGRAMMER * pgm) {
 
 #else
 
-#include <pthread.h>
-
-#ifdef __APPLE__
-/* Mac OS X defines sem_init but actually does not implement them */
-#include <dispatch/dispatch.h>
-
-typedef dispatch_semaphore_t	sem_t;
-
-#define sem_init(psem,x,val)	*psem = dispatch_semaphore_create(val)
-#define sem_post(psem)		dispatch_semaphore_signal(*psem)
-#define sem_wait(psem)		dispatch_semaphore_wait(*psem, DISPATCH_TIME_FOREVER)
-#else
-#include <semaphore.h>
-#endif
-
 #define FT245R_CYCLES	2
 #define FT245R_FRAGMENT_SIZE  512
 #define REQ_OUTSTANDINGS	10
@@ -152,86 +119,144 @@ static struct ftdi_context *handle;
 static unsigned char ft245r_ddr;
 static unsigned char ft245r_out;
 
-#define BUFSIZE 0x2000
+#define FT245R_BUFSIZE		0x2000	// receive buffer size
+#define FT245R_MIN_FIFO_SIZE	128	// min of FTDI RX/TX FIFO size
 
-// libftdi / libftd2xx compatibility functions.
+static struct {
+    int len;				// # of bytes in transmit buffer
+    uint8_t buf[FT245R_MIN_FIFO_SIZE];	// transmit buffer
+} tx;
 
-static pthread_t readerthread;
-static sem_t buf_data, buf_space;
-static unsigned char buffer[BUFSIZE];
-static int head, tail;
 static struct {
     int discard;	// # of bytes to discard during read
+    int pending;	// # of bytes that have been written since last read
+    int wr;		// write pointer
+    int rd;		// read pointer
+    uint8_t buf[FT245R_BUFSIZE];	// receive ring buffer
 } rx;
 
-static void add_to_buf (unsigned char c) {
-    int nh;
+// Discard all data from the receive buffer.
+static void ft245r_rx_buf_purge(PROGRAMMER * pgm) {
+    rx.rd = rx.wr = 0;
+}
 
-    sem_wait (&buf_space);
-    if (head == (BUFSIZE -1)) nh = 0;
-    else                      nh = head + 1;
+static void ft245r_rx_buf_put(PROGRAMMER * pgm, uint8_t byte) {
+    rx.buf[rx.wr++] = byte;
+    if (rx.wr >= sizeof(rx.buf))
+	rx.wr = 0;
+}
 
-    if (nh == tail) {
-        avrdude_message(MSG_INFO, "buffer overflow. Cannot happen!\n");
-    }
-    buffer[head] = c;
-    head = nh;
-    sem_post (&buf_data);
+static uint8_t ft245r_rx_buf_get(PROGRAMMER * pgm) {
+    uint8_t byte = rx.buf[rx.rd++];
+    if (rx.rd >= sizeof(rx.buf))
+	rx.rd = 0;
+    return byte;
+}
+
+/* Fill receive buffer with data from the FTDI receive FIFO.  */
+static int ft245r_fill(PROGRAMMER * pgm) {
+    uint8_t raw[FT245R_MIN_FIFO_SIZE];
+    int i, nread;
+
+    nread = ftdi_read_data(handle, raw, rx.pending);
+    if (nread < 0)
+	return -1;
+    rx.pending -= nread;
+#if FT245R_DEBUG
+    avrdude_message(MSG_INFO, "%s: read %d bytes (pending=%d)\n",
+		    __func__, nread, rx.pending);
+#endif
+    for (i = 0; i < nread; ++i)
+	ft245r_rx_buf_put(pgm, raw[i]);
+    return nread;
 }
 
-static void *reader (void *arg) {
-    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
-    struct ftdi_context *handle = (struct ftdi_context *)(arg);
-    unsigned char buf[0x1000];
-    int br, i;
-
-    while (1) {
-        pthread_testcancel();
-        br = ftdi_read_data (handle, buf, sizeof(buf));
-        for (i=0; i<br; i++)
-            add_to_buf (buf[i]);
+/* Flush pending TX data to the FTDI send FIFO.  */
+static int ft245r_flush(PROGRAMMER * pgm) {
+    int rv, len = tx.len, avail;
+    uint8_t *src = tx.buf;
+
+    if (!len)
+	return 0;
+
+    while (len > 0) {
+	avail = FT245R_MIN_FIFO_SIZE - rx.pending;
+	if (avail <= 0) {
+	    avail = ft245r_fill(pgm);
+	    if (avail < 0) {
+		avrdude_message(MSG_INFO,
+				"%s: fill returned %d: %s\n",
+				__func__, avail, ftdi_get_error_string(handle));
+		return -1;
+	    }
+	}
+	if (avail > len)
+	    avail = len;
+
+#if FT245R_DEBUG
+	avrdude_message(MSG_INFO, "%s: writing %d bytes\n", __func__, avail);
+#endif
+	rv = ftdi_write_data(handle, src, avail);
+	if (rv != avail) {
+	    avrdude_message(MSG_INFO,
+			    "%s: write returned %d (expected %d): %s\n",
+			    __func__, rv, avail, ftdi_get_error_string(handle));
+	    return -1;
+	}
+	src += avail;
+	len -= avail;
+	rx.pending += avail;
     }
-    return NULL;
+    tx.len = 0;
+    return 0;
 }
 
-static int ft245r_send(PROGRAMMER * pgm, unsigned char * buf, size_t len) {
-    int rv;
+static int ft245r_send2(PROGRAMMER * pgm, unsigned char * buf, size_t len,
+			bool discard_rx_data) {
+    int i;
 
-    rv = ftdi_write_data(handle, buf, len);
-    if (len != rv) return -1;
+    for (i = 0; i < len; ++i) {
+	if (discard_rx_data)
+	    ++rx.discard;
+	tx.buf[tx.len++] = buf[i];
+	if (tx.len >= FT245R_MIN_FIFO_SIZE)
+	    ft245r_flush(pgm);
+    }
     return 0;
 }
 
+static int ft245r_send(PROGRAMMER * pgm, unsigned char * buf, size_t len) {
+    return ft245r_send2(pgm, buf, len, false);
+}
+
 static int ft245r_send_and_discard(PROGRAMMER * pgm, unsigned char * buf,
 				   size_t len) {
-    rx.discard += len;
-    return ft245r_send(pgm, buf, len);
+    return ft245r_send2(pgm, buf, len, true);
 }
 
 static int ft245r_recv(PROGRAMMER * pgm, unsigned char * buf, size_t len) {
-    int i = 0;
-
-    // Copy over data from the circular buffer..
-    // XXX This should timeout, and return error if there isn't enough
-    // data.
-    while (i < len) {
-        sem_wait (&buf_data);
-	if (rx.discard > 0)
-	    --rx.discard;
-	else
-	    buf[i++] = buffer[tail];
-        if (tail == (BUFSIZE -1)) tail = 0;
-        else                      tail++;
-        sem_post (&buf_space);
+    int i;
+
+    ft245r_flush(pgm);
+    ft245r_fill(pgm);
+
+#if FT245R_DEBUG
+    avrdude_message(MSG_INFO, "%s: discarding %d, consuming %zu bytes\n",
+		    __func__, rx.discard, len);
+#endif
+    while (rx.discard > 0) {
+	ft245r_rx_buf_get(pgm);
+	--rx.discard;
     }
 
+    for (i = 0; i < len; ++i)
+	buf[i] = ft245r_rx_buf_get(pgm);
     return 0;
 }
 
 
 static int ft245r_drain(PROGRAMMER * pgm, int display) {
     int r;
-    unsigned char t;
 
     // flush the buffer in the chip by changing the mode.....
     r = ftdi_set_bitmode(handle, 0, BITMODE_RESET); 	// reset
@@ -240,13 +265,18 @@ static int ft245r_drain(PROGRAMMER * pgm, int display) {
     if (r) return -1;
 
     // drain our buffer.
-    while (head != tail) {
-        ft245r_recv (pgm, &t, 1);
-    }
+    ft245r_rx_buf_purge(pgm);
     return 0;
 }
 
 
+/* Ensure any pending writes are sent to the FTDI chip before sleeping.  */
+static void ft245r_usleep(PROGRAMMER * pgm, useconds_t usec) {
+    ft245r_flush(pgm);
+    usleep(usec);
+}
+
+
 static int ft245r_chip_erase(PROGRAMMER * pgm, AVRPART * p) {
     unsigned char cmd[4] = {0,0,0,0};
     unsigned char res[4];
@@ -262,7 +292,7 @@ static int ft245r_chip_erase(PROGRAMMER * pgm, AVRPART * p) {
 
     avr_set_bits(p->op[AVR_OP_CHIP_ERASE], cmd);
     pgm->cmd(pgm, cmd, res);
-    usleep(p->chip_erase_delay);
+    ft245r_usleep(pgm, p->chip_erase_delay);
     return pgm->initialize(pgm, p);
 }
 
@@ -296,6 +326,8 @@ static int ft245r_set_bitclock(PROGRAMMER * pgm) {
 static int get_pin(PROGRAMMER *pgm, int pinname) {
   uint8_t byte;
 
+  ft245r_flush(pgm);
+
   if (ftdi_read_pins(handle, &byte) != 0)
     return -1;
   if (FT245R_DEBUG)
@@ -359,7 +391,7 @@ static int set_led_vfy(struct programmer_t * pgm, int value) {
 static void ft245r_powerup(PROGRAMMER * pgm)
 {
     set_vcc(pgm, ON); /* power up */
-    usleep(100);
+    ft245r_usleep(pgm, 100);
 }
 
 
@@ -389,7 +421,7 @@ static void ft245r_enable(PROGRAMMER * pgm) {
    * and not via the buffer chip.
    */
     set_reset(pgm, OFF);
-    usleep(1);
+    ft245r_usleep(pgm, 1);
     set_buff(pgm, ON);
 }
 
@@ -428,12 +460,12 @@ static int ft245r_program_enable(PROGRAMMER * pgm, AVRPART * p) {
             fflush(stderr);
         }
         set_pin(pgm, PIN_AVR_RESET, ON);
-        usleep(20);
+        ft245r_usleep(pgm, 20);
         set_pin(pgm, PIN_AVR_RESET, OFF);
 
         if (i == 3) {
             ft245r_drain(pgm, 0);
-            tail = head;
+	    ft245r_rx_buf_purge(pgm);
         }
     }
 
@@ -458,15 +490,15 @@ static int ft245r_initialize(PROGRAMMER * pgm, AVRPART * p) {
     ft245r_powerup(pgm);
 
     set_reset(pgm, OFF);
-    usleep(5000); // 5ms
+    ft245r_usleep(pgm, 5000); // 5ms
     set_reset(pgm, ON);
-    usleep(5000); // 5ms
+    ft245r_usleep(pgm, 5000); // 5ms
     set_reset(pgm, OFF);
 
     /* Wait for at least 20 ms and enable serial programming by sending the Programming
      * Enable serial instruction to pin MOSI.
      */
-    usleep(20000); // 20ms
+    ft245r_usleep(pgm, 20000); // 20ms
 
     if (p->flags & AVRPART_HAS_TPI) {
 	bool io_link_ok = true;
@@ -825,15 +857,6 @@ static int ft245r_open(PROGRAMMER * pgm, char * port) {
         goto cleanup;
     }
 
-    /* We start a new thread to read the output from the FTDI. This is
-     * necessary because otherwise we'll deadlock. We cannot finish
-     * writing because the ftdi cannot send the results because we
-     * haven't provided a read buffer yet. */
-
-    sem_init (&buf_data, 0, 0);
-    sem_init (&buf_space, 0, BUFSIZE);
-    pthread_create (&readerthread, NULL, reader, handle);
-
     /*
      * drain any extraneous input
      */
@@ -860,8 +883,6 @@ static void ft245r_close(PROGRAMMER * pgm) {
         ftdi_set_bitmode(handle, 0, BITMODE_RESET); // disable Synchronous BitBang
         ftdi_usb_close(handle);
         ftdi_deinit (handle);
-        pthread_cancel(readerthread);
-        pthread_join(readerthread, NULL);
         free(handle);
         handle = NULL;
     }
@@ -1028,7 +1049,7 @@ static int ft245r_paged_write_flash(PROGRAMMER * pgm, AVRPART * p, AVRMEM * m,
 #if defined(USE_INLINE_WRITE_PAGE)
             while (do_request(pgm, m))
                 ;
-            usleep(m->max_write_delay);
+            ft245r_usleep(pgm, m->max_write_delay);
 #else
             int addr_wk = addr_save - (addr_save % m->page_size);
             int rc;
