Subject: Changes to com.c for bidirectional use
To: None <current-users@sun-lamp.cs.berkeley.edu>
From: Bakul Shah <bakul@netcom.com>
List: current-users
Date: 08/20/1994 23:28:40
Changes to com.c (from Aug 1) for bidirectional use are appended
at the end of this message.  Here is a quick sketch of why, what
and how.  One caveat: the code has been tested only on one
machine.  Use at your own risk etc.  If you find any bugs please
let me know.

--Bakul Shah

WHY:
The goal is to allow the use of a serial line for outgoing calls
(for cu, kermit etc.) as well as for incoming calls (i.e. for logging
in).  In such a use typically the init blocks in open() of an incoming
device.  When an incoming call raises the carrier, open() completes
and getty is execed.  We would like to callout over the same line
if there is no incoming call.  While our outgoing call is active,
we do not want getty to get activated and while getty/login/shell
are active we do not want an outgoing call to succeed.  The
bidirectional use changes make  this possible.  The advantage of a
kernel based solution is that the window in which incoming calls
are not recognized is much smaller; in fact someone is *always*
`tending' the line.  The disadvantage is that every serial driver
has to be changed to affect this functionality.  This and some other
bits from many serial drivers are good candidates for factoring out.

WHAT:
Outgoing lines are named /dev/cua00, /dev/cua01...  Incoming lines
are named /dev/tty00, /dev/tty01...  If minor number of /dev/ttyXX
is M, minor number of the correspoding /dev/cuaXX is M+128.

First, note that the new behavior can be completely disregarded if
you don't plan to use /dev/cuaXX.  In fact, *while a cuaXX line is
not in use*, behavior of the new driver is identical to that of the
old driver.

New behavior:
1. ttyXX and cuaXX are mutually exclusive devices.
2. cuaXX may also be opened while a ttyXX open() call is blocked,
   waiting for a carrier detect.
3. When cuaXX is opened, the CLOCAL flag is set.  This prevents a
   carrier drop from sending the process a SIGUP signal.
4. While cuaXX is active, any ttyXX open() remains blocked.
5. ttyXX open() goes back to waiting on carrier detect once the last
   cuaXX close is done.
6. The last close on either cuaXX or ttyXX drops DTR if the SOFTCARR
   flag is not set.
7. The last close on cuaXX raises DTR after a second if there is a
   blocked ttyXX open.  Dropping DTR for a second allows a connected
   modem to hangup and reset to defaults and then go back to answering
   calls.  The effect is as if the intervening use of cuaXX had
   never happened.
8. Any number of concurrent open()s may succeed on either ttyXX (as
   before) or cuaXX provided the TS_XCLUDE flag is not set and
   subject to # 1 above.

HOW:
*** com.c-save	Mon Aug  1 13:13:56 1994
--- com.c	Sat Aug 20 16:52:52 1994
***************
*** 77,82 ****
--- 77,85 ----
  #define	COM_SW_CLOCAL	0x02
  #define	COM_SW_CRTSCTS	0x04
  #define	COM_SW_MDMBUF	0x08
+ #define	COM_SW_OUTGOING	0x10
+ #define	COM_SW_INCOMING	0x20
+ #define	COM_SW_WAIT	0x40
  	u_char sc_msr, sc_mcr;
  };
  /* XXXX should be in com_softc, but not ready for that yet */
***************
*** 110,116 ****
  extern int kgdb_debug_init;
  #endif
  
! #define	COMUNIT(x)	(minor(x))
  
  #define	bis(c, b)	do { const register u_short com_ad = (c); \
  			     outb(com_ad, inb(com_ad) | (b)); } while(0)
--- 113,122 ----
  extern int kgdb_debug_init;
  #endif
  
! #define	COMUNIT(x)	(minor(x) & ~0x80)
! #define	OUTGOING(x)	((x) & 0x80)
! #define	IS_OUTGOING(sc)	((sc)->sc_swflags & COM_SW_OUTGOING)
! #define	IS_INCOMING(sc)	((sc)->sc_swflags & COM_SW_INCOMING)
  
  #define	bis(c, b)	do { const register u_short com_ad = (c); \
  			     outb(com_ad, inb(com_ad) | (b)); } while(0)
***************
*** 320,339 ****
  		return EBUSY;
  	}
  
! 	/* wait for carrier if necessary */
! 	if ((flag & O_NONBLOCK) == 0)
! 		while ((tp->t_cflag & CLOCAL) == 0 &&
! 		    (tp->t_state & TS_CARR_ON) == 0) {
! 			tp->t_state |= TS_WOPEN;
! 			error = ttysleep(tp, (caddr_t)&tp->t_rawq, 
! 			    TTIPRI | PCATCH, ttopen, 0);
! 			if (error) {
! 				/* XXX should turn off chip if we're the
! 				   only waiter */
! 				splx(s);
! 				return error;
! 			}
  		}
  	splx(s);
  
  	return (*linesw[tp->t_line].l_open)(dev, tp);
--- 326,373 ----
  		return EBUSY;
  	}
  
! 	if (OUTGOING(dev)) {
! 		/* outgoing open may succeed only if incoming open has not */
! 		if (IS_INCOMING(sc)) {
! 			splx(s);
! 			return EBUSY;
! 		}
! 		tp->t_cflag |= CLOCAL;
! 		sc->sc_swflags |= COM_SW_OUTGOING;
! 
! 		/* wakeup the blocked incoming open to go sleep elsewhere */
! 		if (sc->sc_swflags & COM_SW_WAIT && tp->t_state & TS_WOPEN)
! 			wakeup((caddr_t)&tp->t_rawq);
! 	} else {
! 		/* incoming open may succeed only if outgoing open has not */
! 		if (IS_OUTGOING(sc)) {
! 			splx(s);
! 			return EBUSY;
  		}
+ 
+ 		/* wait for carrier if necessary */
+ 		if ((flag & O_NONBLOCK) == 0)
+ 			while ((tp->t_cflag & CLOCAL) == 0 &&
+ 			       (tp->t_state & TS_CARR_ON) == 0) {
+ 				tp->t_state |= TS_WOPEN;
+ 				sc->sc_swflags |= COM_SW_WAIT;
+ 				error = ttysleep(tp, (caddr_t)&tp->t_rawq, 
+ 						TTIPRI | PCATCH, ttopen, 0);
+ 				if (error) {
+ 					/* XXX should turn off chip if we're the
+ 					   only waiter */
+ 					splx(s);
+ 					return error;
+ 				}
+ 				/* wait elsewhere if outgoing open is active */
+ 				while (IS_OUTGOING(sc)) {
+ 					(void)ttysleep(tp,
+ 						(caddr_t)&sc->sc_swflags,
+ 						TTIPRI | PCATCH, ttopen, 0);
+ 				}
+ 			}
+ 		sc->sc_swflags |= COM_SW_INCOMING;
+ 	}
  	splx(s);
  
  	return (*linesw[tp->t_line].l_open)(dev, tp);
***************
*** 358,367 ****
  	{
  		bic(iobase + com_cfcr, CFCR_SBREAK);
  		outb(iobase + com_ier, 0);
  		if (tp->t_cflag & HUPCL &&
! 		    (sc->sc_swflags & COM_SW_SOFTCAR) == 0)
  			/* XXX perhaps only clear DTR */
  			outb(iobase + com_mcr, 0);
  	}
  	ttyclose(tp);
  #ifdef notyet /* XXXX */
--- 392,420 ----
  	{
  		bic(iobase + com_cfcr, CFCR_SBREAK);
  		outb(iobase + com_ier, 0);
+ 
  		if (tp->t_cflag & HUPCL &&
! 			   (sc->sc_swflags & COM_SW_SOFTCAR) == 0)
  			/* XXX perhaps only clear DTR */
  			outb(iobase + com_mcr, 0);
+ 
+ 		if (OUTGOING(dev)) {
+ 			/* drop DTR regardless of other flags */
+ 			outb(iobase + com_mcr, sc->sc_mcr &~ MCR_DTR);
+ 			sc->sc_swflags &= ~COM_SW_OUTGOING;
+ 			if (sc->sc_swflags & COM_SW_WAIT) {
+ 				/* wait a while before allowing incoming open */
+ 				(void)tsleep((caddr_t)&sc->sc_swflags+1, 
+ 				    TTIPRI | PCATCH, "comclose", hz);
+ 				/* setup for the waiting incoming open...*/
+ 				tp->t_cflag &= ~(MDMBUF|CLOCAL);
+ 				tp->t_state &= ~TS_ISOPEN;
+ 				sc->sc_mcr = MCR_DTR | MCR_RTS;
+ 				outb(iobase + com_mcr, sc->sc_mcr);
+ 				return 0;
+ 			}
+ 		} else
+ 			sc->sc_swflags &= ~COM_SW_INCOMING;
  	}
  	ttyclose(tp);
  #ifdef notyet /* XXXX */

------------------------------------------------------------------------------