Index: serial.c
===================================================================
RCS file: /var/cvs/devel/hydra-system/uclinux-3.2.0/linux-2.4.x/drivers/char/serial.c,v
retrieving revision 1.6
retrieving revision 1.14
diff -u -b -r1.6 -r1.14
--- serial.c	19 May 2005 22:55:40 -0000	1.6
+++ serial.c	19 Jun 2007 14:21:28 -0000	1.14
@@ -270,6 +270,7 @@
 static int serial_refcount;
 
 static struct timer_list serial_timer;
+static struct timer_list rtsxmit_timer;
 
 /* serial subtype definitions */
 #ifndef SERIAL_TYPE_NORMAL
@@ -861,7 +862,8 @@
 #endif
 }
 
-static _INLINE_ void transmit_chars(struct async_struct *info, int *intr_done)
+/* returns number of chars sent */
+static _INLINE_ int transmit_chars(struct async_struct *info, int *intr_done)
 {
 	int count;
 
@@ -876,18 +878,23 @@
 		info->x_char = 0;
 		if (intr_done)
 			*intr_done = 0;
-		return;
+        return 0;
 	}
 	if (info->xmit.head == info->xmit.tail
 	    || info->tty->stopped
 	    || info->tty->hw_stopped) {
 		info->IER &= ~UART_IER_THRI;
 		serial_out(info, UART_IER, info->IER);
-		return;
+#ifdef SERIAL_DEBUG_INTR
+        printk("stopped (mt=%d), DI..", info->xmit.head == info->xmit.tail);
+#endif
+        return 0;
 	}
 
 	count = info->xmit_fifo_size;
-
+#ifdef SERIAL_DEBUG_INTR
+    printk("Send %d?", count);
+#endif
 	do {
 		serial_out(info, UART_TX, info->xmit.buf[info->xmit.tail]);
 		info->xmit.tail = (info->xmit.tail + 1) & (SERIAL_XMIT_SIZE-1);
@@ -902,15 +909,19 @@
 		rs_sched_event(info, RS_EVENT_WRITE_WAKEUP);
 
 #ifdef SERIAL_DEBUG_INTR
-	printk("THRE...");
+    printk("sent.%d.", info->xmit_fifo_size-count);
 #endif
 	if (intr_done)
 		*intr_done = 0;
 
 	if (info->xmit.head == info->xmit.tail) {
+#ifdef SERIAL_DEBUG_INTR
+        printk("mt, DI..");
+#endif
 		info->IER &= ~UART_IER_THRI;
 		serial_out(info, UART_IER, info->IER);
 	}
+    return info->xmit_fifo_size-count;
 }
 
 static _INLINE_ void check_modem_status(struct async_struct *info)
@@ -1072,10 +1083,12 @@
 
 /*
  * This is the serial driver's interrupt routine for a single port
+ * returns timer delay if necessary
  */
 static void rs_interrupt_single(int irq, void *dev_id, struct pt_regs * regs)
 {
-	int status, iir;
+    int status, iir, mask, delay;
+    int chars_sent = 0;
 	int pass_counter = 0;
 	struct async_struct * info;
 #ifdef CONFIG_SERIAL_MULTIPORT
@@ -1084,7 +1097,7 @@
 #endif
 
 #ifdef SERIAL_DEBUG_INTR
-	printk("rs_interrupt_single(%d)...", irq);
+    printk("rs_interrupt_single(%d@%lu)...", irq, jiffies);
 #endif
 
 	info = IRQ_ports[irq];
@@ -1098,10 +1111,13 @@
 #endif
 
 	iir = serial_in(info, UART_IIR);
+#ifdef SERIAL_DEBUG_INTR
+    printk("IIR = %x...IER=%x ", iir, serial_in(info, UART_IER));
+#endif
 	do {
 		status = serial_inp(info, UART_LSR);
 #ifdef SERIAL_DEBUG_INTR
-		printk("status = %x...", status);
+        printk("stat=%x.", status);
 #endif
 		if (status & UART_LSR_DR)
 			receive_chars(info, &status, regs);
@@ -1112,12 +1128,22 @@
 		    ((iir & UART_IIR_ID) == UART_IIR_THRI))
 			transmit_chars(info, 0);
 #else
-		if (status & UART_LSR_THRE)
-			transmit_chars(info, 0);
+        if (info->flags & ASYNC_CTS_FLOW) {
+//        if (info->tty->hw_stopped) {
+            mask = UART_LSR_TEMT;
+        } else {
+            mask = UART_LSR_THRE;
+        }
+#ifdef SERIAL_DEBUG_INTR
+        printk("im=%x...", mask);
+#endif
+        if (status & mask)
+            chars_sent = transmit_chars(info, 0);
 #endif
+
 		if (pass_counter++ > RS_ISR_PASS_LIMIT) {
-#if SERIAL_DEBUG_INTR
-			printk("rs_single loop break.\n");
+#ifdef SERIAL_DEBUG_INTR
+            printk("pass break...");
 #endif
 			break;
 		}
@@ -1136,9 +1162,21 @@
 		       info->state->irq, first_multi,
 		       inb(multi->port_monitor));
 #endif
+    if ((info->flags & ASYNC_CTS_FLOW) && (chars_sent > 0 )) {
+        delay = chars_sent * 10 * 1000 / tty_get_baud_rate( info->tty ) / HZ;
+        if (delay<10) {
+            delay=10;
+        }
+#ifdef SERIAL_DEBUG_INTR
+        printk("delay=%d..", delay);
+#endif        
+        mod_timer(&rtsxmit_timer, jiffies + delay);
+    }
+    
 #ifdef SERIAL_DEBUG_INTR
 	printk("end.\n");
 #endif
+    return;
 }
 
 #if defined(CONFIG_IXP425_DSR0) || defined(CONFIG_IXP425_DSR1)
@@ -1437,6 +1476,46 @@
 }
 
 /*
+ * This subroutine is called when the rtsxmit goes off.  It is used
+ * by the serial driver to re-invoke the transmit routine.
+ * Added because the XScale (IXP425 UART is broken wrt flow control).
+ */
+static void rs_rtsxmit_timer(unsigned long dummy)
+{
+    struct async_struct *info;
+    unsigned int    i;
+    unsigned long flags;
+
+#ifdef SERIAL_DEBUG_INTR
+    printk("rs_rtsxmit_timer (@%lu)...", jiffies);
+#endif
+    for (i=0; i < NR_IRQS; i++) {
+        info = IRQ_ports[i];
+        if (!info)
+            continue;
+        save_flags(flags); cli();
+#ifdef CONFIG_SERIAL_SHARE_IRQ
+        if (info->next_port) {
+            do {
+                serial_out(info, UART_IER, 0);
+                info->IER |= UART_IER_THRI;
+                serial_out(info, UART_IER, info->IER);
+                info = info->next_port;
+            } while (info);
+#ifdef CONFIG_SERIAL_MULTIPORT
+            if (rs_multiport[i].port1)
+                rs_interrupt_multi(i, NULL, NULL);
+            else
+#endif
+                rs_interrupt(i, NULL, NULL);
+        } else
+#endif /* CONFIG_SERIAL_SHARE_IRQ */
+            rs_interrupt_single(i, NULL, NULL);
+        restore_flags(flags);
+    }
+}
+
+/*
  * ---------------------------------------------------------------
  * Low level utility subroutines for the serial driver:  routines to
  * figure out the appropriate timeout for an interrupt chain, routines
@@ -5885,6 +5963,8 @@
 	init_timer(&serial_timer);
 	serial_timer.function = rs_timer;
 	mod_timer(&serial_timer, jiffies + RS_STROBE_TIME);
+    init_timer(&rtsxmit_timer);
+    rtsxmit_timer.function = rs_rtsxmit_timer;
 
 	for (i = 0; i < NR_IRQS; i++) {
 		IRQ_ports[i] = 0;
@@ -6234,6 +6314,7 @@
 
 	/* printk("Unloading %s: version %s\n", serial_name, serial_version); */
 	del_timer_sync(&serial_timer);
+    del_timer_sync(&rtsxmit_timer);
 	save_flags(flags); cli();
         remove_bh(SERIAL_BH);
 	if ((e1 = tty_unregister_driver(&serial_driver)))
