From c413afc9b18190d878fb3f5d9151b3037142d7e2 Mon Sep 17 00:00:00 2001
From: Hannes Weisbach <hannes_weisbach@gmx.net>
Date: Sat, 27 Apr 2013 21:49:27 +0000
Subject: [PATCH] Fixes bugs #38659 and #38831

Apparently, doing too large read/write I/O on the MPSSE fills up the RX Buffer
of the chip, resulting in STALL/NAK of additional transactions of an OUT request.
Since the OUT request is issued synchronously, an appropriate IN request cannot
be issued, resulting in a deadlock.
This problem is handled by submitting small enough OUT requests, that will not
fill up the RX buffer and interleaving them with IN requests.


git-svn-id: svn://svn.savannah.nongnu.org/avrdude/trunk/avrdude@1142 81a1dc3b-b13d-400b-aceb-764788c761c2
---
 ChangeLog |  4 +++
 avrftdi.c | 90 +++++++++++++++++++++++++++++++++++++------------------
 2 files changed, 65 insertions(+), 29 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index b0a47a50..c76d2752 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -7,6 +7,10 @@
 	error messages. avrftdi_print is changed so that a message is printed when
 	the verbosity level is greater or equal the message level, to have always-on
 	messages.
+	Fix a bug where the RX fifo of the FTDI chip is full, resulting in STALL/NAK
+	of the ongoing OUT request and subsequently timeout, because an IN request
+	cannot be issued due to the synchronous part of libftdi. This should fix
+	#38831 and #38659.
 	
 2013-04-25  Joerg Wunsch <j.gnu@uriah.heep.sax.de>
 
diff --git a/avrftdi.c b/avrftdi.c
index 609af985..3ae0cc81 100644
--- a/avrftdi.c
+++ b/avrftdi.c
@@ -95,6 +95,8 @@ typedef struct avrftdi_s {
 	uint16_t led_mask;
 	/* total number of pins supported by a programmer. varies with FTDI chips */
 	int pin_limit;
+	/* internal RX buffer of the device. needed for INOUT transfers */
+	int rx_buffer_size;
 } avrftdi_t;
 
 static int write_flush(avrftdi_t *);
@@ -513,44 +515,65 @@ static int set_led_vfy(struct programmer_t * pgm, int value)
 /* Send 'buf_size' bytes from 'cmd' to device and return data from device in
  * buffer 'data'.
  * Write is only performed when mode contains MPSSE_DO_WRITE.
- * Read is only performed when mode contains MPSSE_DO_READ.
+ * Read is only performed when mode contains MPSSE_DO_WRITE and MPSSE_DO_READ.
  */
-static int avrftdi_transmit(avrftdi_t* pdata, unsigned char mode, unsigned char *cmd,
+static int avrftdi_transmit(avrftdi_t* pdata, unsigned char mode, unsigned char *buf,
 			    unsigned char *data, int buf_size)
 {
-	int k = 0;
-	int n;
-	unsigned char buf[4 + buf_size];
+	size_t blocksize;
+	size_t bsize;
+	size_t remaining = buf_size;
+	size_t written = 0;
+	
+	unsigned char cmd[3];
+//	unsigned char si = SEND_IMMEDIATE;
 
-	if (mode & MPSSE_DO_WRITE) {
-		buf[0] = mode | MPSSE_WRITE_NEG;
-		buf[1] = ((buf_size - 1) & 0xff);
-		buf[2] = (((buf_size - 1) >> 8) & 0xff);
+	cmd[0] = mode | MPSSE_WRITE_NEG;
+	cmd[1] = ((buf_size - 1) & 0xff);
+	cmd[2] = (((buf_size - 1) >> 8) & 0xff);
 
-		memcpy(buf + 3, cmd, buf_size);
-		buf[buf_size + 3] = 0x87;
+	//if we are not reading back, we can just write the data out
+	if(!(mode & MPSSE_DO_READ))
+		blocksize = buf_size;
+	else
+		blocksize = pdata->rx_buffer_size;
 
 #ifndef DRYRUN
-		E(ftdi_write_data(pdata->ftdic, buf, buf_size + 4) != buf_size + 4, pdata->ftdic);
+	E(ftdi_write_data(pdata->ftdic, cmd, sizeof(cmd)) != sizeof(cmd), pdata->ftdic);
 #endif
-	}
 
-	if (mode & MPSSE_DO_READ) {
-		memset(buf, 0, sizeof(buf));
-		do {
+	while(remaining)
+	{
+		size_t transfer_size = (remaining > blocksize) ? blocksize : remaining;
+
 #ifndef DRYRUN
-			n = ftdi_read_data(pdata->ftdic, buf + k, buf_size - k);
-			E(n < 0, pdata->ftdic);
-#else
-			n = buf_size - k;
+		E(ftdi_write_data(pdata->ftdic, &buf[written], transfer_size) != transfer_size, pdata->ftdic);
+#endif
+#if 0
+		if(remaining < blocksize)
+			E(ftdi_write_data(pdata->ftdic, &si, sizeof(si)) != sizeof(si), pdata->ftdic);
 #endif
-			k += n;
-		} while (k < buf_size);
 
-		memcpy(data, buf, buf_size);
+		if (mode & MPSSE_DO_READ) {
+			int n;
+			int k = 0;
+			do {
+	#ifndef DRYRUN
+				n = ftdi_read_data(pdata->ftdic, &data[written + k], transfer_size - k);
+				E(n < 0, pdata->ftdic);
+	#else
+				n = transfer_size - k;
+	#endif
+				k += n;
+			} while (k < transfer_size);
+
+		}
+		
+		written += transfer_size;
+		remaining -= transfer_size;
 	}
-
-	return k;
+	
+	return written;
 }
 
 static int write_flush(avrftdi_t* pdata)
@@ -721,24 +744,33 @@ static int avrftdi_open(PROGRAMMER * pgm, char *port)
 
 	/* set pin limit depending on chip type */
 	switch(pdata->ftdic->type) {
-#if 0
-		//TODO: issue an error - no MPSSE. hint the user to syncbb?
 		case TYPE_AM:
 		case TYPE_BM:
 		case TYPE_R:
-#endif
+			avrftdi_print(0, "Found unsupported device type AM, BM or R. " \
+				"avrftdi cannot work with your chip. Try the 'synbb' programmer.\n");
 		case TYPE_2232C:
 			pdata->pin_limit = 11;
+			pdata->rx_buffer_size = 384;
 			break;
 		case TYPE_2232H:
+			pdata->pin_limit = 15;
+			pdata->rx_buffer_size = 4096;
+			break;
 		case TYPE_232H:
 			pdata->pin_limit = 15;
+			pdata->rx_buffer_size = 1024;
 			break;
 		case TYPE_4232H:
 			pdata->pin_limit = 7;
+			pdata->rx_buffer_size = 2048;
 			break;
 		default:
-		//TODO: error/unsupported device
+			avrftdi_print(0, "Found unkown device %x. " \
+				"I will do my best to work with it, but no guarantees ...\n",
+				pdata->ftdic->type);
+			pdata->pin_limit = 7;
+			pdata->rx_buffer_size = pdata->ftdic->max_packet_size;
 			break;
 	}