Subject: More com driver changes; need testers
To: None <port-i386@NetBSD.ORG>
From: Charles M. Hannum <mycroft@gnu.ai.mit.edu>
List: port-i386
Date: 04/29/1997 21:57:35
I've made some more changes to the com driver, which should fix the
problems a few people have observed with characters being dropped.
However, I can't adequately test them here at the moment.  If you've
been having this problem, please try the patch below.

--

What I've done is to implement a full `back-pressure' mechanism.  The
driver will now detect when the tty buffer overflows; when this
happens, it will leave any remaining data in the second-level buffer
(the one in the driver itself), and stop scheduling soft receive
interrupts until the line discipline informs it, through the flow
control callback, that its buffer has dropped below the low water
mark.  A similar mechanism is implemented for turning off hard receive
interrupts entirely when the second-level buffer overflows.

What this means, in theory, is that if a slow program fails to read
the tty buffer fast enough, the data will simply back up in the
second-level buffer until the tty buffer empties a bit.  The driver
thus acts as an additional buffer between the chip and the tty layer,
and can be tweaked to buffer as little or as much data as may be
needed.

Note that the above applies *only* when hardware flow control is in
use; if it's not, excess data will be dropped on the floor as before
(but more efficiently B-)).  This is because there's currently no
mechanism for the tty layer to inform the driver when the low water
mark is reached if flow control is not enabled.  It might be worth
changing this at some point, but it's not clear to me how important it
is.

--

PLEASE NOTE that the copyright on com.c applies to the following diff.
That copyright is:

 * Copyright (c) 1993, 1994, 1995, 1996, 1997
 *	Charles M. Hannum.  All rights reserved.

Index: com.c
===================================================================
RCS file: /cvsroot/src/sys/dev/isa/com.c,v
retrieving revision 1.99
diff -c -2 -r1.99 com.c
*** com.c	1997/04/04 20:56:34	1.99
--- com.c	1997/04/30 01:42:31
***************
*** 125,129 ****
  
  void	com_loadchannelregs __P((struct com_softc *));
! void	com_hwiflow	__P((struct com_softc *, int));
  void	com_break	__P((struct com_softc *, int));
  void	com_modem	__P((struct com_softc *, int));
--- 125,129 ----
  
  void	com_loadchannelregs __P((struct com_softc *));
! void	com_hwiflow	__P((struct com_softc *));
  void	com_break	__P((struct com_softc *, int));
  void	com_modem	__P((struct com_softc *, int));
***************
*** 145,148 ****
--- 145,149 ----
  integrate void comtxint		__P((struct com_softc *, struct tty *));
  integrate void commsrint	__P((struct com_softc *, struct tty *));
+ integrate void com_schedrx	__P((struct com_softc *));
  
  struct cfdriver com_cd = {
***************
*** 224,228 ****
  	    sc->sc_tx_stopped ? "+" : "-");
  
! 	printf("%s: %s %scrtscts %scts %sts_ttstop  %srts %srx_blocked\n",
  	    sc->sc_dev.dv_xname, str,
  	    ISSET(tp->t_cflag, CRTSCTS) ? "+" : "-",
--- 225,229 ----
  	    sc->sc_tx_stopped ? "+" : "-");
  
! 	printf("%s: %s %scrtscts %scts %sts_ttstop  %srts %xrx_flags\n",
  	    sc->sc_dev.dv_xname, str,
  	    ISSET(tp->t_cflag, CRTSCTS) ? "+" : "-",
***************
*** 230,234 ****
  	    ISSET(tp->t_state, TS_TTSTOP) ? "+" : "-",
  	    ISSET(sc->sc_mcr, MCR_RTS) ? "+" : "-",
! 	    sc->sc_rx_blocked ? "+" : "-");
  }
  #endif
--- 231,235 ----
  	    ISSET(tp->t_state, TS_TTSTOP) ? "+" : "-",
  	    ISSET(sc->sc_mcr, MCR_RTS) ? "+" : "-",
! 	    sc->sc_rx_flags);
  }
  #endif
***************
*** 537,542 ****
  		sc->sc_rbavail = RXBUFSIZE;
  		com_iflush(sc);
! 		sc->sc_rx_blocked = 0;
! 		com_hwiflow(sc, 0);
  
  #ifdef COM_DEBUG
--- 538,543 ----
  		sc->sc_rbavail = RXBUFSIZE;
  		com_iflush(sc);
! 		CLR(sc->sc_rx_flags, RX_ANY_BLOCK);
! 		com_hwiflow(sc);
  
  #ifdef COM_DEBUG
***************
*** 595,600 ****
  
  	/* If we were asserting flow control, then deassert it. */
! 	sc->sc_rx_blocked = 1;
! 	com_hwiflow(sc, 1);
  	/* Clear any break condition set with TIOCSBRK. */
  	com_break(sc, 0);
--- 596,601 ----
  
  	/* If we were asserting flow control, then deassert it. */
! 	SET(sc->sc_rx_flags, RX_IBUF_BLOCKED);
! 	com_hwiflow(sc);
  	/* Clear any break condition set with TIOCSBRK. */
  	com_break(sc, 0);
***************
*** 715,718 ****
--- 716,741 ----
  }
  
+ integrate void
+ com_schedrx(sc)
+ 	struct com_softc *sc;
+ {
+ 
+ 	sc->sc_rx_ready = 1;
+ 
+ 	/* Wake up the poller. */
+ #ifdef __GENERIC_SOFT_INTERRUPTS
+ 	softintr_schedule(sc->sc_si);
+ #else
+ #ifndef alpha
+ 	setsoftserial();
+ #else
+ 	if (!com_softintr_scheduled) {
+ 		com_softintr_scheduled = 1;
+ 		timeout(comsoft, NULL, 1);
+ 	}
+ #endif
+ #endif
+ }
+ 
  void
  com_break(sc, onoff)
***************
*** 915,924 ****
  #endif
  
- 	/* XXXXX FIX ME */
  	/* Block or unblock as needed. */
  	if (!ISSET(t->c_cflag, CHWFLOW)) {
! 		if (sc->sc_rx_blocked) {
! 			sc->sc_rx_blocked = 0;
! 			com_hwiflow(sc, 0);
  		}
  		if (sc->sc_tx_stopped) {
--- 938,950 ----
  #endif
  
  	/* Block or unblock as needed. */
  	if (!ISSET(t->c_cflag, CHWFLOW)) {
! 		if (ISSET(sc->sc_rx_flags, RX_TTY_OVERFLOWED)) {
! 			CLR(sc->sc_rx_flags, RX_TTY_OVERFLOWED);
! 			com_schedrx(sc);
! 		}
! 		if (ISSET(sc->sc_rx_flags, RX_TTY_BLOCKED|RX_IBUF_BLOCKED)) {
! 			CLR(sc->sc_rx_flags, RX_TTY_BLOCKED|RX_IBUF_BLOCKED);
! 			com_hwiflow(sc);
  		}
  		if (sc->sc_tx_stopped) {
***************
*** 927,930 ****
--- 953,957 ----
  		}
  	} else {
+ 		/* XXXXX FIX ME */
  #if 0
  		commsrint(sc, tp);
***************
*** 982,1001 ****
  	s = splserial();
  	if (block) {
! 		/*
! 		 * The tty layer is asking us to block input.
! 		 * If we already did it, just return TRUE.
! 		 */
! 		if (sc->sc_rx_blocked)
! 			goto out;
! 		sc->sc_rx_blocked = 1;
  	} else {
! 		/*
! 		 * The tty layer is asking us to resume input.
! 		 * The input ring is always empty by now.
! 		 */
! 		sc->sc_rx_blocked = 0;
  	}
- 	com_hwiflow(sc, block);
- out:
  	splx(s);
  	return (1);
--- 1009,1026 ----
  	s = splserial();
  	if (block) {
! 		if (!ISSET(sc->sc_rx_flags, RX_TTY_BLOCKED)) {
! 			SET(sc->sc_rx_flags, RX_TTY_BLOCKED);
! 			com_hwiflow(sc);
! 		}
  	} else {
! 		if (ISSET(sc->sc_rx_flags, RX_TTY_OVERFLOWED)) {
! 			CLR(sc->sc_rx_flags, RX_TTY_OVERFLOWED);
! 			com_schedrx(sc);
! 		}
! 		if (ISSET(sc->sc_rx_flags, RX_TTY_BLOCKED)) {
! 			CLR(sc->sc_rx_flags, RX_TTY_BLOCKED);
! 			com_hwiflow(sc);
! 		}
  	}
  	splx(s);
  	return (1);
***************
*** 1006,1012 ****
   */
  void
! com_hwiflow(sc, block)
  	struct com_softc *sc;
- 	int block;
  {
  	bus_space_tag_t iot = sc->sc_iot;
--- 1031,1036 ----
   */
  void
! com_hwiflow(sc)
  	struct com_softc *sc;
  {
  	bus_space_tag_t iot = sc->sc_iot;
***************
*** 1016,1020 ****
  		return;
  
! 	if (block) {
  		CLR(sc->sc_mcr, sc->sc_mcr_rts);
  		CLR(sc->sc_mcr_active, sc->sc_mcr_rts);
--- 1040,1044 ----
  		return;
  
! 	if (ISSET(sc->sc_rx_flags, RX_ANY_BLOCK)) {
  		CLR(sc->sc_mcr, sc->sc_mcr_rts);
  		CLR(sc->sc_mcr_active, sc->sc_mcr_rts);
***************
*** 1173,1177 ****
  	}
  
! 	while (cc--) {
  		lsr = sc->sc_lbuf[get];
  		if (ISSET(lsr, LSR_BI)) {
--- 1197,1201 ----
  	}
  
! 	while (cc) {
  		lsr = sc->sc_lbuf[get];
  		if (ISSET(lsr, LSR_BI)) {
***************
*** 1188,1207 ****
  		code = sc->sc_rbuf[get] |
  		    lsrmap[(lsr & (LSR_BI|LSR_FE|LSR_PE)) >> 2];
! 		(*linesw[tp->t_line].l_rint)(code, tp);
  		get = (get + 1) & RXBUFMASK;
  	}
  
! 	sc->sc_rbget = get;
! 	s = splserial();
! 	sc->sc_rbavail += scc;
! 	/*
! 	 * Buffers should be ok again, release possible block, but only if the
! 	 * tty layer isn't blocking too.
! 	 */
! 	if (sc->sc_rx_blocked && !ISSET(tp->t_state, TS_TBLOCK)) {
! 		sc->sc_rx_blocked = 0;
! 		com_hwiflow(sc, 0);
  	}
- 	splx(s);
  }
  
--- 1212,1262 ----
  		code = sc->sc_rbuf[get] |
  		    lsrmap[(lsr & (LSR_BI|LSR_FE|LSR_PE)) >> 2];
! 		if ((*linesw[tp->t_line].l_rint)(code, tp) == -1) {
! 			/*
! 			 * The line discipline's buffer is out of space.
! 			 */
! 			if (!ISSET(sc->sc_rx_flags, RX_TTY_BLOCKED)) {
! 				/*
! 				 * We're either not using flow control, or the
! 				 * line discipline didn't tell us to block for
! 				 * some reason.  Either way, we have no way to
! 				 * know when there's more space available, so
! 				 * just drop the rest of the data.
! 				 */
! 				get = (get + cc) & RXBUFMASK;
! 				cc = 0;
! 			} else {
! 				/*
! 				 * Don't schedule any more receive processing
! 				 * until the line discipline tells us there's
! 				 * space available (through comhwiflow()).
! 				 * Leave the rest of the data in the input
! 				 * buffer.
! 				 */
! 				SET(sc->sc_rx_flags, RX_TTY_OVERFLOWED);
! 			}
! 			break;
! 		}
  		get = (get + 1) & RXBUFMASK;
+ 		cc--;
  	}
  
! 	if (cc != scc) {
! 		sc->sc_rbget = get;
! 		s = splserial();
! 		cc = sc->sc_rbavail += scc - cc;
! 		/* Buffers should be ok again, release possible block. */
! 		if (ISSET(sc->sc_rx_flags, RX_IBUF_OVERFLOWED)) {
! 			CLR(sc->sc_rx_flags, RX_IBUF_OVERFLOWED);
! 			SET(sc->sc_ier, IER_ERXRDY);
! 			bus_space_write_1(sc->sc_iot, sc->sc_ioh, com_ier, sc->sc_ier);
! 		}
! 		if (ISSET(sc->sc_rx_flags, RX_IBUF_BLOCKED) &&
! 		    cc >= sc->sc_r_hiwat) {
! 			CLR(sc->sc_rx_flags, RX_IBUF_BLOCKED);
! 			com_hwiflow(sc);
! 		}
! 		splx(s);
  	}
  }
  
***************
*** 1339,1343 ****
  
  		lsr = bus_space_read_1(iot, ioh, com_lsr);
! 		if (ISSET(lsr, LSR_RCV_MASK)) {
  			for (; ISSET(lsr, LSR_RCV_MASK) && cc > 0; cc--) {
  				sc->sc_rbuf[put] =
--- 1394,1399 ----
  
  		lsr = bus_space_read_1(iot, ioh, com_lsr);
! 		if (ISSET(lsr, LSR_RCV_MASK) &&
! 		    !ISSET(sc->sc_rx_flags, RX_IBUF_OVERFLOWED)) {
  			for (; ISSET(lsr, LSR_RCV_MASK) && cc > 0; cc--) {
  				sc->sc_rbuf[put] =
***************
*** 1355,1377 ****
  			sc->sc_rbput = put;
  			sc->sc_rbavail = cc;
! 			sc->sc_rx_ready = 1;
  			/*
  			 * See if we are in danger of overflowing a buffer. If
  			 * so, use hardware flow control to ease the pressure.
  			 */
! 			if (sc->sc_rx_blocked == 0 &&
  			    cc < sc->sc_r_hiwat) {
! 				sc->sc_rx_blocked = 1;
! 				com_hwiflow(sc, 1);
  			}
  			/*
! 			 * If we're out of space, throw away any further input.
  			 */
  			if (!cc) {
! 				while (ISSET(lsr, LSR_RCV_MASK)) {
! 					bus_space_read_1(iot, ioh, com_data);
! 					lsr = bus_space_read_1(iot, ioh,
! 								com_lsr);
! 				}
  			}
  		} else {
--- 1411,1433 ----
  			sc->sc_rbput = put;
  			sc->sc_rbavail = cc;
! 			if (!ISSET(sc->sc_rx_flags, RX_TTY_OVERFLOWED))
! 				sc->sc_rx_ready = 1;
  			/*
  			 * See if we are in danger of overflowing a buffer. If
  			 * so, use hardware flow control to ease the pressure.
  			 */
! 			if (!ISSET(sc->sc_rx_flags, RX_IBUF_BLOCKED) &&
  			    cc < sc->sc_r_hiwat) {
! 				SET(sc->sc_rx_flags, RX_IBUF_BLOCKED);
! 				com_hwiflow(sc);
  			}
  			/*
! 			 * If we're out of space, disable receive interrupts
! 			 * until the queue has drained a bit.
  			 */
  			if (!cc) {
! 				SET(sc->sc_rx_flags, RX_IBUF_OVERFLOWED);
! 				CLR(sc->sc_ier, IER_ERXRDY);
! 				bus_space_write_1(iot, ioh, com_ier, sc->sc_ier);
  			}
  		} else {
Index: comvar.h
===================================================================
RCS file: /cvsroot/src/sys/dev/isa/comvar.h,v
retrieving revision 1.8
diff -c -2 -r1.8 comvar.h
*** comvar.h	1997/04/04 20:56:40	1.8
--- comvar.h	1997/04/30 01:42:31
***************
*** 83,87 ****
  	    sc_heldtbc;
  
! 	volatile u_char sc_rx_blocked,
  			sc_tx_busy,
  			sc_tx_done,
--- 83,92 ----
  	    sc_heldtbc;
  
! 	volatile u_char sc_rx_flags,
! #define	RX_TTY_BLOCKED		0x01
! #define	RX_TTY_OVERFLOWED	0x02
! #define	RX_IBUF_BLOCKED		0x04
! #define	RX_IBUF_OVERFLOWED	0x08
! #define	RX_ANY_BLOCK		0x0f
  			sc_tx_busy,
  			sc_tx_done,