--- linux/drivers/usb/acm.c~45-acm-tty.patch	2006-06-07 11:21:21.648422000 +0200
+++ linux/drivers/usb/acm.c	2006-06-09 17:20:51.735793750 +0200
@@ -139,6 +139,8 @@
  * Internal driver structures.
  */
 
+#define TD_SIZE 16384
+
 struct acm {
 	struct usb_device *dev;				/* the coresponding usb device */
 	struct usb_interface *iface;			/* the interfaces - +0 control +1 data */
@@ -153,6 +155,11 @@
 	unsigned int minor;				/* acm minor number */
 	unsigned char throttle;				/* throttled by tty layer */
 	unsigned char clocal;				/* termios CLOCAL */
+	unsigned long throttle_start;
+	unsigned char resubmit_to_unthrottle;		/* Leftover data from last operation */
+	unsigned char *throttle_data;
+	int td_len;
+	int td_busy;
 };
 
 /* global params controlling max sizes for read, write, control */
@@ -166,6 +173,96 @@
 
 #define ACM_READY(acm)	(acm && acm->dev && acm->used)
 
+
+/*
+ * Helper functions to optimize throttleing
+ */
+static int
+acm_fill_tty(struct urb *urb, struct tty_struct *tty, unsigned char *data, int length)
+{
+	struct acm *acm = urb->context;
+	int n = 0;
+	/*printk("acm_fill_tty: %d bytes\n", length);*/
+	if (!urb->status && !acm->throttle)  {
+		for (n = 0; n < length && !acm->throttle; n++) {
+			/* if we insert more than TTY_FLIPBUF_SIZE characters,
+			 * we drop them. */
+			if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
+				tty_flip_buffer_push(tty);
+			}
+			tty_insert_flip_char(tty, data[n], 0);
+		}
+		tty_flip_buffer_push(tty);
+	}
+	/*printk("copied %d bytes.\n", n);*/
+	return n;
+}
+
+static int
+acm_shift_if_throttle(unsigned char *data, int *length, int shift_by)
+{
+	if (shift_by < *length) {
+		dbg("need to shift uncopied %d bytes to front.", *length - shift_by);
+		memmove(data, data + shift_by, *length - shift_by);
+		*length -= shift_by;
+		return 1;
+	}
+	return 0;
+}
+
+static int
+acm_buffer_if_thottle(struct acm *acm, unsigned char *data, int start, int *length)
+{
+	int copied = *length;
+	if (start < *length) {
+		int space = TD_SIZE - acm->td_len;
+		int needed = *length - start;
+		copied = (space < needed)? space: needed;
+		dbg("need to push %d to throttle buffer, can copy %d.",
+				needed, copied);
+		memcpy(acm->throttle_data + acm->td_len, data, copied);
+		acm->td_len += copied;
+		*length -= copied;
+	}
+	return copied;
+}
+
+static int
+acm_empty_throttle(struct urb *urb, struct tty_struct *tty)
+{
+	unsigned long flags;
+	struct acm *acm = urb->context;
+
+	save_flags(flags);
+	cli();
+
+	if (acm->td_busy) {
+		restore_flags(flags);
+		return 0;
+	}
+	acm->td_busy = 1;
+	restore_flags(flags);
+
+	if (acm->td_len > 0) {
+
+		dbg("acm_empty_throttle: trying to empty throttle buffer: %d bytes.",
+		  acm->td_len);
+
+		/* if there has been something left from previous operations
+		 * we try to complete this before looking at the urb */
+		int copied = acm_fill_tty(urb, tty, acm->throttle_data, acm->td_len);
+		if (acm_shift_if_throttle(acm->throttle_data, &acm->td_len, copied)) {
+			/* we were unable to empty the throttle data, so we can't
+			 * copy anything more now */
+			acm->td_busy = 0;
+			return 0;
+		}
+		acm->td_len = 0;
+	}
+	acm->td_busy = 0;
+	return 1;
+}
+
 /*
  * Functions for ACM control messages.
  */
@@ -238,36 +335,40 @@
 	struct acm *acm = urb->context;
 	struct tty_struct *tty = acm->tty;
 	unsigned char *data = urb->transfer_buffer;
-	int i = 0;
+	int copied = 0;
+	int buffered = 0;
 
 	if (!ACM_READY(acm)) return;
 
-	if (urb->status)
+	if (urb->status) {
 		dbg("nonzero read bulk status received: %d", urb->status);
+	}
 
-	if (!urb->status && !acm->throttle)  {
-		for (i = 0; i < urb->actual_length && !acm->throttle; i++) {
-			/* if we insert more than TTY_FLIPBUF_SIZE characters,
-			 * we drop them. */
-			if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
-				tty_flip_buffer_push(tty);
-			}
-			tty_insert_flip_char(tty, data[i], 0);
-		}
-		tty_flip_buffer_push(tty);
+	dbg("acm_read_bulk, calling acm_empty_throttle()");
+	if (!acm_empty_throttle(urb, tty)) {
+		dbg("could not empty throttle buffer, entering throttle state, acm->td_busy: %d.", acm->td_busy);
 	}
 
+	/* got here, either there was nothing in the throttle data or it could
+	 * all be copied without throttleing again */
+	copied = acm_fill_tty(urb, tty, data, urb->actual_length);
 	if (acm->throttle) {
-		memmove(data, data + i, urb->actual_length - i);
-		urb->actual_length -= i;
-		return;
+		int length = urb->actual_length;
+		buffered = acm_buffer_if_thottle(acm, data, copied, &urb->actual_length);
+		if (buffered < length - copied
+				&& acm_shift_if_throttle(data, &urb->actual_length, copied + buffered)) {
+			printk("need to resubmit to unthrottle\n");
+			acm->resubmit_to_unthrottle = 1;
+			return;
+		}
 	}
 
 	urb->actual_length = 0;
 	urb->dev = acm->dev;
 
-	if (usb_submit_urb(urb))
+	if (usb_submit_urb(urb)) {
 		dbg("failed resubmitting read urb");
+	}
 }
 
 static void acm_write_bulk(struct urb *urb)
@@ -330,7 +431,12 @@
 
 	acm_set_control(acm, acm->ctrlout = ACM_CTRL_DTR | ACM_CTRL_RTS);
 
-	/* force low_latency on so that our tty_push actually forces the data through, 
+	acm->resubmit_to_unthrottle = 0;
+	acm->td_len = 0;
+	acm->td_busy = 0;
+	acm->throttle_data = kmalloc(TD_SIZE * sizeof (*acm->throttle_data), GFP_KERNEL);
+
+	/* force low_latency on so that our tty_push actually forces the data through,
 	   otherwise it is scheduled, and with high data rates data can get lost. */
 	tty->low_latency = 1;
 
@@ -352,6 +458,7 @@
 		} else {
 			tty_unregister_devfs(&acm_tty_driver, acm->minor);
 			acm_table[acm->minor] = NULL;
+			kfree(acm->throttle_data);
 			kfree(acm);
 		}
 	}
@@ -363,8 +470,16 @@
 	struct acm *acm = tty->driver_data;
 
 	if (!ACM_READY(acm)) return -EINVAL;
-	if (acm->writeurb.status == -EINPROGRESS) return 0;
-	if (!count) return 0;
+
+	if (acm->writeurb.status == -EINPROGRESS) {
+		dbg("tty_write in progress");
+		return 0;
+	}
+
+	if (!count) {
+		dbg("tty_write: nothing to write");
+		return 0;
+	}
 
 	count = (count > acm->writesize) ? acm->writesize : count;
 
@@ -401,16 +516,32 @@
 {
 	struct acm *acm = tty->driver_data;
 	if (!ACM_READY(acm)) return;
+	dbg("acm_tty_throttle ON %ld ---> %ld", jiffies-acm->throttle_start, jiffies);
 	acm->throttle = 1;
+	acm->throttle_start = jiffies;
 }
 
 static void acm_tty_unthrottle(struct tty_struct *tty)
 {
 	struct acm *acm = tty->driver_data;
 	if (!ACM_READY(acm)) return;
+	dbg("acm_tty_throttle OFF %ld ---> %ld", jiffies, jiffies-acm->throttle_start);
 	acm->throttle = 0;
-	if (acm->readurb.status != -EINPROGRESS)
+
+	dbg("acm_tty_unthrottle, calling acm_empty_throttle()");
+	if (!acm_empty_throttle(&acm->readurb, tty)) {
+		if (acm->td_busy) {
+			printk("***** pending acm_empty_throttle!\n");
+		} else {
+			dbg("throttle not emptied.\n");
+		}
+	}
+
+	if (acm->resubmit_to_unthrottle != 0) {
+		dbg("resubmit_to_unthrottle: acm_read_bulk");
+		acm->resubmit_to_unthrottle = 0;
 		acm_read_bulk(&acm->readurb);
+	}
 }
 
 static void acm_tty_break_ctl(struct tty_struct *tty, int state)