[mod.sources] dial-out mods for 4.3BSD

sources-request@panda.UUCP (12/26/85)

Mod.sources:  Volume 3, Issue 71
Submitted by: Chris Torek <seismo!umcp-cs!chris>

[ I don't have BSD 4.3, so have not been able to check this out -  moderator ]

: Run this shell script with "sh" not "csh"
PATH=/bin:/usr/bin:/usr/ucb:/etc:$PATH
export PATH
all=FALSE
if [ x$1 = x-a ]; then
	all=TRUE
fi
echo 'Extracting README'
sed 's/^X//' <<'//go.sysin dd *' >README
			4.3BSD DIALOUT MODS
			-------------------

	N.B.:  These changes should work for 4.2BSD as well; but
	the `original' code shown in the diff listings will not
	quite match.  You will have to install the changes by
	hand---`patch' will not work.

Installation instructions for the dial devices:

1.  Save copies of the files you will edit.  These include vax/conf.c
    and at least one of the device drivers in vaxuba (vaxuba/dh.c,
    vaxuba/dz.c, etc.)

2.  Apply the changes shown to the device driver(s).  For other
    devices you will need to extrapolate the changes.  Look at dh.c
    and dz.c to see what needs to be done, and read the `dial
    device' document.
    
    N.B.:  For dmf32s the changes can be rather tricky.  I have a
    dmf32 driver that does dial out, but it is not the standard
    4.3BSD version, so I cannot make diff listings.

3.  Add the device(s) to the `cdevsw' table in vax/conf.c.  The
    diff listing included here is heavily edited and should be used
    only as an example.  You will need to assign major device
    numbers yourself.  We used 41 and 42 for the dz and dh
    respectively; but the first number free in the distributed
    kernel is much lower---near 25, as I recall.

4.  Compile a new kernel, and reboot.

5.  Make `/dev' entries for the dial devices.  (See the `dial device'
    document.)

6.  Test.  Included is a C program that can talk to dial lines.
    The program may need slight changes for 4.2BSD, though I think
    I have removed most system dependencies already.  (You will,
    however, need `getopt'.)


		May the stars shine upon your kernel for ever,
						Chris Torek
						University of Maryland
						22 December 1983
//go.sysin dd *
if [ `wc -c < README` != 1700 ]; then
	made=FALSE
	echo 'error transmitting "README" --'
	echo 'length should be 1700, not' `wc -c < README`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 README
	echo -n '	'; ls -ld README
fi
echo 'Extracting c.1'
sed 's/^X//' <<'//go.sysin dd *' >c.1
X.TH C 1
X.UC 4
X.SH NAME
c \- communications program
X.SH SYNOPSIS
X.B "c -l"
X.I link
[
X.B -s
X.I speed
] [
X.B -p
X.I parity
] [
X.B -e
X.I escape
]
X.SH DESCRIPTION
X.I C
is a small communications program for tty lines.  It has almost
no features, unlike
X.IR tip ,
so it starts and runs quickly and is very small.  It is also useful
for talking to balky modems when all other methods fail.
X.PP
The
X.I link
option is required, and should be the name of the tty line, e.g.,
`/dev/dial05' or `/dev/ttya'.
X.PP
The
X.I speed
option selects the baud rate.  It should be a numeric value from
the set {300, 600, 1200, 2400, 4800, 9600}.  The default is 1200
baud.
X.PP
The
X.I parity
switch selects parity, which may be any of the following:
X.TP
X.B e
Even parity
X.TP
X.B o
Odd parity
X.TP
X.B 1
One parity
X.TP
X.B 0
Zero parity
X.TP
X.B n
No parity (the data path is eight bits wide\(emthis is the default).
X.PP
The
X.I esc
parameter sets the escape character (default ESC).  This
character must be doubled to be sent through the link.  Also
available are the following `escape commands':
X.TP
X.B b
Generate a break.
X.TP
X.B h, ?
Help (list these options).
X.TP
X.B o
Set options.  The program will prompt for an option line
on which any of the flags except
X.B -l
may be changed.  Options are given in the same way
as when the program is initially run.
X.TP
X.B q
Quit (exit the program).  The program will quit by itself
if carrier is lost, though this depends on the kernel configuration.
X.TP
X.B z
Suspend.
X.PP
Other characters cause the bell to ring.
X.SH SEE ALSO
tip(1), cu(1)
X.SH AUTHOR
Chris Torek (seismo!umcp-cs!chris, chris@mimsy.umd.edu)
X.SH BUGS
Parity only affects outgoing characters.  Incoming data is
always turned into zero parity.
//go.sysin dd *
if [ `wc -c < c.1` != 1715 ]; then
	made=FALSE
	echo 'error transmitting "c.1" --'
	echo 'length should be 1715, not' `wc -c < c.1`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 c.1
	echo -n '	'; ls -ld c.1
fi
echo 'Extracting c.c'
sed 's/^X//' <<'//go.sysin dd *' >c.c
#ifndef lint
static char rcsid[] = "$Header$";
#endif

#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sgtty.h>
#ifdef lint
#include <sys/time.h>
#endif

X/*
 * Usage: c -l link [ -s speed ] [ -p e|o|0|1|n ] [ -e escapechar ]
 *
 * Comm program.
 *
 * Compilation:
 % cc -O -R -o c c.c
 *
 * N.B.:  Should be setuid root, so that it can run at high priority
 * (helps avoid losing input at high baud rates).
 */

#ifndef lint
extern	int	errno;
extern	char	*optarg;
extern	int	optind;
#else
int	errno;
char	*optarg;
int	optind;
X/* VARARGS3 ARGSUSED */
error(quit, e, fmt) int quit, e; char *fmt; { ; }
#endif

char	*_argv0;
char	*linkname;
int	linkfd;
int	speed;
int	escchar;
int	parbis;
int	parbic;
char	*partab;
char	ttybuf[BUFSIZ];
int	ttycount;
char	ttyobuf[BUFSIZ];
int	ttyocount;
char	linebuf[BUFSIZ];
int	linecount;
char	lineobuf[BUFSIZ];
int	lineocount;

X/*
 * 4.3 and V8 style file descriptor mask macros
 * Should be in <sys/types.h> but aren't in 4.2.
 */
#ifndef FD_SET
typedef struct fd_set {
	int	fds_bits;
} fd_set;
#define	FD_SET(n, p)	((p)->fds_bits[0] |= (1 << (n)))
#define	FD_CLR(n, p)	((p)->fds_bits[0] &= ~(1 << (n)))
#define	FD_ISSET(n, p)	((p)->fds_bits[0] & (1 << (n)))
#define	FD_ZERO(p)	((p)->fds_bits = 0)
#endif

char	evenpar[128] = {
	0x00, 0x81, 0x82, 0x03, 0x84, 0x05, 0x06, 0x87,
	0x88, 0x09, 0x0a, 0x8b, 0x0c, 0x8d, 0x8e, 0x0f,
	0x90, 0x11, 0x12, 0x93, 0x14, 0x95, 0x96, 0x17,
	0x18, 0x99, 0x9a, 0x1b, 0x9c, 0x1d, 0x1e, 0x9f,
	0xa0, 0x21, 0x22, 0xa3, 0x24, 0xa5, 0xa6, 0x27,
	0x28, 0xa9, 0xaa, 0x2b, 0xac, 0x2d, 0x2e, 0xaf,
	0x30, 0xb1, 0xb2, 0x33, 0xb4, 0x35, 0x36, 0xb7,
	0xb8, 0x39, 0x3a, 0xbb, 0x3c, 0xbd, 0xbe, 0x3f,
	0xc0, 0x41, 0x42, 0xc3, 0x44, 0xc5, 0xc6, 0x47,
	0x48, 0xc9, 0xca, 0x4b, 0xcc, 0x4d, 0x4e, 0xcf,
	0x50, 0xd1, 0xd2, 0x53, 0xd4, 0x55, 0x56, 0xd7,
	0xd8, 0x59, 0x5a, 0xdb, 0x5c, 0xdd, 0xde, 0x5f,
	0x60, 0xe1, 0xe2, 0x63, 0xe4, 0x65, 0x66, 0xe7,
	0xe8, 0x69, 0x6a, 0xeb, 0x6c, 0xed, 0xee, 0x6f,
	0xf0, 0x71, 0x72, 0xf3, 0x74, 0xf5, 0xf6, 0x77,
	0x78, 0xf9, 0xfa, 0x7b, 0xfc, 0x7d, 0x7e, 0xff,
};
char	oddpar[128] = {
	0x80, 0x01, 0x02, 0x83, 0x04, 0x85, 0x86, 0x07,
	0x08, 0x89, 0x8a, 0x0b, 0x8c, 0x0d, 0x0e, 0x8f,
	0x10, 0x91, 0x92, 0x13, 0x94, 0x15, 0x16, 0x97,
	0x98, 0x19, 0x1a, 0x9b, 0x1c, 0x9d, 0x9e, 0x1f,
	0x20, 0xa1, 0xa2, 0x23, 0xa4, 0x25, 0x26, 0xa7,
	0xa8, 0x29, 0x2a, 0xab, 0x2c, 0xad, 0xae, 0x2f,
	0xb0, 0x31, 0x32, 0xb3, 0x34, 0xb5, 0xb6, 0x37,
	0x38, 0xb9, 0xba, 0x3b, 0xbc, 0x3d, 0x3e, 0xbf,
	0x40, 0xc1, 0xc2, 0x43, 0xc4, 0x45, 0x46, 0xc7,
	0xc8, 0x49, 0x4a, 0xcb, 0x4c, 0xcd, 0xce, 0x4f,
	0xd0, 0x51, 0x52, 0xd3, 0x54, 0xd5, 0xd6, 0x57,
	0x58, 0xd9, 0xda, 0x5b, 0xdc, 0x5d, 0x5e, 0xdf,
	0xe0, 0x61, 0x62, 0xe3, 0x64, 0xe5, 0xe6, 0x67,
	0x68, 0xe9, 0xea, 0x6b, 0xec, 0x6d, 0x6e, 0xef,
	0x70, 0xf1, 0xf2, 0x73, 0xf4, 0x75, 0x76, 0xf7,
	0xf8, 0x79, 0x7a, 0xfb, 0x7c, 0xfd, 0xfe, 0x7f,
};

struct	sgttyb sgbuf;

main(argc, argv)
	int argc;
	char **argv;
{
	register int fd, c, tx, lx;
	static int state;

	_argv0 = argv[0];

	speed = B1200;
	escchar = 27;
	argue(argc, argv, 1);

	(void) nice(-10);
	(void) setuid(getuid());

	fd = open(linkname, O_RDWR, 0);
	if (fd < 0)
		error(1, errno, "can't open %s", linkname);

	linkfd = fd;
	fixlink();
	rawtty();
	printf("open\r\n");
	lx = 0;
	tx = 0;

	for (;;) {
		if (lx >= linecount && tx >= ttycount) {
			fd_set inbits;

			if (ttyocount)
				flusho(ttyobuf, &ttyocount, 1);
			if (lineocount)
				flusho(lineobuf, &lineocount, fd);

			/* read from tty and/or line */
			FD_ZERO(&inbits);
			FD_SET(1, &inbits);
			FD_SET(fd, &inbits);
			c = select(fd + 1, &inbits, (fd_set *) 0, (fd_set *) 0,
				   (struct timeval *) 0);
			if (c == 0) {
				error(0, errno, "select");
				cleanup(1);
			}
			if (FD_ISSET(1, &inbits)) {
				ttycount = read(0, ttybuf, BUFSIZ);
				if (ttycount < 0) {
					error(0, errno, "read(0)");
					cleanup(1);
				}
				if (ttycount == 0) {
					error(0, 0, "eof(0)?");
					cleanup(1);
				}
				tx = 0;
			}
			if (FD_ISSET(fd, &inbits)) {
				linecount = read(fd, linebuf, BUFSIZ);
				if (linecount < 0) {
					error(0, errno, "read(%d)", fd);
					cleanup(1);
				}
				if (linecount == 0) {
					error(0, 0, "eof(%d)?", fd);
					cleanup(1);
				}
				lx = 0;
			}
		}
		/* handle tty input */
		while (tx < ttycount) {
			c = ttybuf[tx++] & 0x7f;
			if (state) {
				state = 0;
				if (c == escchar)
					goto put;
				switch (c) {
				case 'b':
					genbreak();
					break;

				case 'q':
					cleanup(0);
					/*NOTREACHED*/

				case 'z':
					printf("\r\nsuspend\r\n");
					susp();
					printf("resumed\r\n");
					break;

				case 'h':
				case 'H':
				case '?':
					printf("\r\n\
ESC subcommands:\r\n\
    b - generate a break\r\n\
    o - set options\r\n\
    q - quit\r\n\
    z - suspend\r\n");
					break;

				case 'o':
				case 'O':
					options();
					break;

				default:
beep:
					ttyobuf[ttyocount++] = 7;
					if (ttyocount == BUFSIZ)
						flusho(ttyobuf, &ttyocount, 1);
					break;
				}
			} else if (c == escchar) {
				state = 1;
				goto beep;
			} else {
put:
				if (partab)
					c = partab[c];
				else	/* restore parity */
					c = ttybuf[tx - 1];
				c |= parbis;
				c &= ~parbic;
				lineobuf[lineocount++] = c;
				if (lineocount == BUFSIZ)
					flusho(lineobuf, &lineocount, fd);
			}
		}
		/* handle line input */
		while (lx < linecount) {
			c = linebuf[lx++];
			/* handle parity here someday */
			ttyobuf[ttyocount++] = c & 0x7f;
			if (ttyocount == BUFSIZ)
				flusho(ttyobuf, &ttyocount, fd);
		}
	}
}

flusho(buf, p, fd)
	char *buf;
	register int *p;
	int fd;
{

	if (write(fd, buf, *p) != *p) {
		error(0, errno, "write(%d)", fd);
		cleanup(1);
	}
	*p = 0;
}

cleanup(code)
	int code;
{

	printf("\r\nclosed\r\n");
	(void) ioctl(0, TIOCSETP, &sgbuf);
	exit(code);
}

rawtty()
{
	struct sgttyb raw;

	if (ioctl(0, TIOCGETP, &sgbuf))
		error(1, errno, "ioctl TIOCGETP");
	raw = sgbuf;
	raw.sg_flags = RAW;
	(void) ioctl(0, TIOCSETP, &raw);
}

susp()
{

	(void) ioctl(0, TIOCSETP, &sgbuf);
	kill(0, SIGTSTP);
	rawtty();
}

struct speeds {
	int sp;
	int sp_b;
};

findspeed(sp)
{
	register struct speeds *spp;
	static struct speeds speeds[] = {
		300, B300, 600, B600, 1200, B1200, 2400, B2400,
		4800, B4800, 9600, B9600, -1, -1
	};

	spp = speeds;
	while (spp->sp >= 0) {
		if (spp->sp == sp)
			return (spp->sp_b);
		spp++;
	}
	return (-1);
}

genbreak()
{

	(void) ioctl(linkfd, TIOCSBRK, 0);
	sleep(1);
	(void) ioctl(linkfd, TIOCCBRK, 0);
}

char **
makeargv(avp, s)
	register char **avp, *s;
{

	for (;;) {
		while (*s == ' ' || *s == '\t' || *s == '\n')
			s++;
		if (*s == 0) {
			*avp = 0;
			return avp;
		}
		*avp++ = s;
		while (*s && *s != ' ' && *s != '\t' && *s != '\n')
			s++;
		if (*s == 0) {
			*avp = 0;
			return avp;
		}
		*s++ = 0;
	}
}

options()
{
	char buf[200];
	char *argv[100];
	int argc;

	(void) ioctl(0, TIOCSETP, &sgbuf);
	printf("options: ");
	if (fgets(buf, sizeof buf, stdin) == NULL)
		exit(1);
	argv[0] = _argv0;
	argc = makeargv(argv + 1, buf) - argv;
	argue(argc, argv, 0);
	rawtty();
}

argue(argc, argv, toplevel)
	int argc;
	char **argv;
	int toplevel;
{
	register int c;

	optind = 1;
	while ((c = getopt(argc, argv, "s:p:l:e:")) != EOF) {
		switch (c) {
		case '?':
usage:
			if (toplevel) {
				error(1, 0, "\
usage: c -l link [-s speed] [-p par] [-e esc]");
				/* NOTREACHED */
			}
			error(0, 0, "options: [-s speed] [-p par] [-e esc]");
			break;

		case 'l':
			if (!toplevel) {
				error(0, 0, "can't change link in midstream");
				break;
			}
			if (linkname)
				error(1, 0, "use only one -l option");
			linkname = optarg;
			break;

		case 's':
			if ((speed = findspeed(atoi(optarg))) < 0)
				error(toplevel, 0, "invalid speed %s", optarg);
			if (!toplevel)
				fixlink();
			break;

		case 'p':
			if (*optarg == 'e') {
				partab = evenpar;
				parbis = parbic = 0;
			} else if (*optarg == 'o') {
				partab = oddpar;
				parbis = parbic = 0;
			} else if (*optarg == '1') {
				parbis = 0x80;
				parbic = 0;
			} else if (*optarg == '0') {
				parbis = 0;
				parbic = 0x80;
			} else if (*optarg == 'n') {
				parbis = parbic = 0;
				partab = 0;
			} else
				error(toplevel, 0, "\
for -p, use one of [e, o, 1, 0, n]");
			break;

		case 'e':
			if (optarg[1] == 0)
				escchar = *optarg;
			else if (optarg[0] == '^')
				escchar = optarg[1] == '?' ? 0x7f :
					  *optarg & 0x1f;
			else
				escchar = atoi(optarg);
			printf("escape set to %s%c.\n",
			       escchar >= 0x20 && escchar < 0x7f ? "" : "^",
			escchar >= 0x20 ? (escchar == 0x7f ? '?' : escchar) :
			       escchar + '@');
			break;
		default:
			error(1, 0, "internal error: argue switch");
		}
	}
	if (linkname == 0)
		goto usage;
}

fixlink()
{
	struct sgttyb l;

	l.sg_ispeed = l.sg_ospeed = speed;
	l.sg_flags = RAW;
	(void) ioctl(linkfd, TIOCSETN, &l);
}

#ifndef lint
X/*
 * Error routine from the local C library
 * N.B.: This code is system dependent.
 */

X/*
 * error - University of Maryland specific (sigh)
 *
 * Useful for printing error messages.  Will print the program name
 * and (optionally) the system error associated with the values in
 * <errno.h>.
 *
 * Note that the type (and even the existence!) of ``arg'' is undefined.
 */
error(quit, e, fmt, arg)
	int quit;
	register int e;
	char *fmt;
{
	extern char *sys_errlist[];
	extern int sys_nerr;
	register char *p = _argv0;

	if (p != NULL)
		(void) fprintf(stderr, "%s: ", p);
	_doprnt(fmt, &arg, stderr);	/* magic */
	if (e > 0) {
		if (e < sys_nerr)
			(void) fprintf(stderr, ": %s", sys_errlist[e]);
		else
			(void) fprintf(stderr, ": unknown error number %d", e);
	}
	(void) putc('\n', stderr);
	(void) fflush(stderr);
	if (quit)
		exit(quit);
}
#endif
//go.sysin dd *
if [ `wc -c < c.c` != 9712 ]; then
	made=FALSE
	echo 'error transmitting "c.c" --'
	echo 'length should be 9712, not' `wc -c < c.c`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 c.c
	echo -n '	'; ls -ld c.c
fi
echo 'Extracting dial.tex'
sed 's/^X//' <<'//go.sysin dd *' >dial.tex
%
% dial.tex - some notes on the implementation of simultaneous dial in/out
% on modem lines.
%

% 11 pt
\magnification=\magstephalf

% Fonts.  If you are using TeX78, or the new Computer Modern fonts, change
% this to cmcsc10.
\font\tencsc=amcsc10

% Macros
\newif\iftm	% true if we have mentioned the trademark
\def\Unix{{\tencsc Unix}\iftm\else\footnote{$\dag$}{{\tencsc Unix} is a
footnote of AT\&T Bell Laboratories}\global\tmtrue\fi}
\def\bsd{{\tencsc 4.3bsd} \Unix}
% \tencsc does not look very good on these, so we will use regular fonts.
%\def\dec{{\tencsc dec}}
%\def\dz{{\tencsc dz11}}	% official version
%\def\dzz{{\tencsc dz}}		% short version, purely for variety
%\def\dh{{\tencsc dh11}}
\def\dec{{\rm DEC}}
\def\dz{{\rm DZ11}}
\def\dzz{{\rm DZ}}
\def\dh{{\rm DH11}}
\chardef\us=`\_

% Layout
\parskip=.5\baselineskip plus1pt
\footline={\ifnum\pageno=1\hfil\else{\tenrm\hfil Dial Devices for
\bsd\hfil\llap{\folio}}\fi}

% Here we go...

% Header, plus page 1 (save paper)
% How about THIS title!  Catchy, no?
\null
\vskip1cm
\centerline{Some Notes on the Implementation of Dialout Capabilites}
\centerline{for Modem Lines under \bsd}
\vskip.5cm
\centerline{Chris Torek}
\vskip.5cm
\centerline{Department of Computer Science}
\centerline{University of Maryland}
\centerline{College Park, MD 20742}

\vskip 2cm

% Ok.  Now what?
% How about this:

Modern modems may be used in both `originate' and `answer' modes---that
is, they can both call out and answer incoming calls.  Traditional
\Unix\ systems, however, cannot handle this well; modems have
typically been dedicated to one purpose or the other.  At the
University of Maryland, we have devised a set of kernel modifications
that allow modem lines to be used bidirectionally.  These present
a simple user interface and require none of the error-prone locking
protocols that have been used in the past to implement similar
functionality.  Moreover, using our scheme, site administrators
can if they so choose implement accounting for all outgoing calls.
Previous systems rendered this difficult at best.

% Enough extolling of virtues.  Now for some details:

As far as user programs are concerned, the only change to the system
is the addition of a new set of {\it dial} devices.  These devices
have a one-to-one correspondence with the {\it tty} lines; they
are distinguished by having a different major device number.  For
example, on one machine we have two Racal-Vadic
VA3400\footnote{$\ddag$}{Vadic and VA3400 are no doubt trademarks
of someone or another} modems.  These appear as {\tt tty04} and {\tt
tty05}, and also as {\tt dial04} and {\tt dial05}:

{\tt\vskip\parskip\parskip=0pt\obeylines\obeyspaces%
crw--w--w-  1 root       1,   4 Dec  3 00:06 /dev/tty04
crw--w--w-  1 root       1,   5 Dec  2 00:27 /dev/tty05
crw-rw-rw-  1 root      41,   4 Dec  2 23:13 /dev/dial04
crw-rw-rw-  1 root      41,   5 Dec  2 23:16 /dev/dial05

} % what, me, work late?		^^^

Here major device 1 corresponds to the \dec\ \dz.\footnote{*}{\dec,
\dh, and \dz\ are trademarks of Digital Equipment Corporation}
Major device 41 is also the \dz, but in `dial out' or `outgoing'
mode.  When a program opens the normal or `incoming' line, it
behaves as such programs always have:  If there is a carrier on
the line, the open completes normally; if not, the open
`hangs'---suspends---waiting for a carrier.  When a program opens
the `outgoing' line, however, something new occurs.  If there is
a carrier on the line, that indicates that the device is already
in use; and the open is rejected with a `Device busy' error, {\tt
EBUSY}.  (In previous versions of \Unix\ this was called a `Mount
device busy' error.)  The open is likewise rejected if the line is
already in use outgoing.  Indeed, in such cases there is always a
carrier, so no additional code is required for this:  Only if there
is no carrier does the open complete---and it will hang up
automatically once a carrier has appeared and then vanished.

What happens, then, when a line is in the process of being opened
for incoming use when the outgoing call is made?  Why, nothing, of
course:  the incoming open remains suspended until the outgoing
call has been completed.  Anything else would break existing code.
As a bonus, no locking is required; outgoing opens succeed if and
only if the line is not in use, and incoming opens remain hung
during outgoing calls.

% Finally, the nitty-gritty; the details; the real guts of the game.

To illustrate how this is accomplished, I shall refer again to the
\dz\ driver.  We have added two new arrays, and made use of an
existing third array:  There is now a {\tt dz\us inout} and a {\tt
dz\us flags}, both of size {\tt NDZ} and type {\tt char}.  The first
holds the `outgoing mode' flag for each \dzz\ line.  Bit $b$ is set
in {\tt dz\us inout[$d\/$]} whenever line $b$ of \dzz\ $d$ has an outgoing
open in progress.  {\tt dz\us flags} holds a permanent copy of the
`flags' parameter from the kernel configuration file.  In this case
these are the software carrier flags.  In the original kernel these
were put into the array {\tt dz\us softCAR}---also of size {\tt
NDZ}---and never touched.  We must save them somewhere as outgoing
opens work by temporarily asserting the software carrier.

We modified the device open code to return {\tt EBUSY} not only on
attempts to open a line in `exclusive use' mode, but also on attempts
to open a line in outgoing mode whenever the line is already open
in any mode.  As usual, the super-user is exempt from this restriction.
We also modified the open code to:

\item {1)} assert the software carrier on outgoing opens;

\item {2)} hang until there is a carrier (or software carrier);

\item {3)} hang until the line is not in use outgoing, unless this is
the outgoing call itself; and

\item {4)} reassert DTR every time the state of the line changes.

\noindent
Incoming and outgoing opens are distinguished by the `flag' argument
to the device open routine.  This is always a small nonnegative
integer for normal opens; but the outgoing open routine calls the
regular open routine with a {\tt flag} of {\tt -1}.

The fourth point is important because of a change in the close
routine.  To ensure that a modem hangs up, it is necessary to drop
DTR for at least $1\over2$ second.  But modems will generally not
answer the phone unless DTR is asserted.  So the close routine
first drops DTR and maintains it that way for one second, then, if
it is an outgoing close, turns off the outgoing mode flag, restores
the software carrier, and awakens any hung incoming opens so that
they may reassert DTR if necessary.  As with the open routine, an
outgoing close is distinguished by its {\tt flag} argument.

We made one more change to the driver.  The modem interrupt code---or
in the case of the \dz, the modem poll code, for there are no modem
interrupts---now checks both the real carrier and the software
carrier.  Either will allow the open to complete; but if the real
carrier is asserted when the line is open in outgoing mode, the
software carrier for that line is cleared.  This means that once
an outgoing call completes, the line is usable only until the
connection is closed:  When the carrier goes away, the line will
be hung up, just as for a normal incoming open.  This allows one
to perform call accounting, under the condition that the modem
never allows calls to be placed without having first lost carrier;
and this is true of most modems.  The dial-out program would be
set-group-ID to a group that can open the `dial' devices.  This
program would open the device, dial the number, wait for the call
to complete, and then log the call in a file.  As long as the
program were careful not to let the user set {\tt NOHUP} mode on
the line, no further calls could be made, as the file descriptor
would be invalidated and the program would be sent a {\tt SIGHUP}
signal as soon as the connection were broken.

(We actually made another change to the \dzz\ driver.  {\tt MDMBUF}
mode, wherein carrier transitions act as flow control, now works
on \dz s as well as \dh s.  This is, however, of limited usefulness,
as the \dzz\ modem status is only polled once every second; at 1200
baud, up to 120 characters may be sent after a carrier drop before
transmission is suspended, and at 9600 baud, up to 960 characters
could conceivably be sent.  The lack of a modem interrupt is one of
many problems with \dz s, and cannot truly be corrected in software.)

% Ok.  Summary:

These kernel changes have allowed us to make greater use, with more
ease, of our existing dialer resources.  The automatic locking has
eliminated all protocol errors that have plagued other systems in
the past.  And we even log outgoing calls, though we do not charge
for them or otherwise restrict them.

% clean up

\vskip.5cm
\line{\it Acknowledgements\hfil}

Credit is due Joe Pallas, Bob Kirby, and Fred Blonder, who worked
with me on our original implementation for V6 and 4.1{\tencsc bsd}
\Unix.  This work was no doubt indirectly supported by several
government grants and other misdirected research funds.  All the
errors in this document, however, should be credited only to myself.

\bye
//go.sysin dd *
if [ `wc -c < dial.tex` != 9184 ]; then
	made=FALSE
	echo 'error transmitting "dial.tex" --'
	echo 'length should be 9184, not' `wc -c < dial.tex`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 dial.tex
	echo -n '	'; ls -ld dial.tex
fi
echo 'Extracting diff.vax.conf.c'
sed 's/^X//' <<'//go.sysin dd *' >diff.vax.conf.c
RCS file: RCS/conf.c,v
retrieving revision 1.1.1.2
retrieving revision 1.5
diff -c2 -r1.1.1.2 -r1.5  [edited]
*** /tmp/,RCSt1000435	Mon Dec  2 23:47:17 1985
--- /tmp/,RCSt2000435	Mon Dec  2 23:47:19 1985
***************
*** 268,277 ****
--- 301,3?? ----
  #define	dhstop	nodev
  #define	dhreset	nulldev
+ #define	dhoopen	nodev
+ #define	dhoclose nodev
  #define	dh11	0
  #else
  int	dhopen(),dhclose(),dhread(),dhwrite(),dhioctl(),dhstop(),dhreset();
+ int	dhoopen(),dhoclose();
  struct	tty dh11[];
  #endif
***************
*** 316,322 ****
--- 375,384 ----
  #define	dzstop	nodev
  #define	dzreset	nulldev
+ #define	dzoopen	nodev
+ #define	dzoclose nodev
  #define	dz_tty	0
  #else
  int	dzopen(),dzclose(),dzread(),dzwrite(),dzioctl(),dzstop(),dzreset();
+ int	dzoopen(),dzoclose();
  struct	tty dz_tty[];
  #endif
***************
*** ???,??? ****
--- ???,??? ----
+ 	dzoopen,	dzoclose,	dzread,		dzwrite,	/*??*/
+ 	dzioctl,	dzstop,		nulldev,	dz_tty,
+ 	ttselect,	nodev,
+ 	dhoopen,	dhoclose,	dhread,		dhwrite,	/*??*/
+ 	dhioctl,	dhstop,		nulldev,	dh11,
+ 	ttselect,	nodev,
//go.sysin dd *
if [ `wc -c < diff.vax.conf.c` != 1076 ]; then
	made=FALSE
	echo 'error transmitting "diff.vax.conf.c" --'
	echo 'length should be 1076, not' `wc -c < diff.vax.conf.c`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 diff.vax.conf.c
	echo -n '	'; ls -ld diff.vax.conf.c
fi
echo 'Extracting diff.vaxuba.dh.c'
sed 's/^X//' <<'//go.sysin dd *' >diff.vaxuba.dh.c
RCS file: RCS/dh.c,v
retrieving revision 1.1.1.3
retrieving revision 1.4
diff -c2 -r1.1.1.3 -r1.4
*** /tmp/,RCSt1000424	Mon Dec  2 23:46:43 1985
--- /tmp/,RCSt2000424	Mon Dec  2 23:46:45 1985
***************
*** 70,73 ****
--- 70,74 ----
  short	dhsar[NDH];			/* software copy of last bar */
  short	dhsoftCAR[NDH];
+ short	dh_inout[NDH];
  
  struct	tty dh11[NDH*16];
***************
*** 80,84 ****
  int	dhlowrate = 75;			/* silo off if dhrate < dhlowrate */
  static short timerstarted;
! int	dhstart(), ttrstrt();
  
  /*
--- 81,85 ----
  int	dhlowrate = 75;			/* silo off if dhrate < dhlowrate */
  static short timerstarted;
! int	dhstart(), ttrstrt(), wakeup();
  
  /*
***************
*** 169,173 ****
   * the first use of it.  Also do a dmopen to wait for carrier.
   */
- /*ARGSUSED*/
  dhopen(dev, flag)
  	dev_t dev;
--- 170,173 ----
***************
*** 184,188 ****
  		return (ENXIO);
  	tp = &dh11[unit];
! 	if (tp->t_state&TS_XCLUDE && u.u_uid!=0)
  		return (EBUSY);
  	addr = (struct dhdevice *)ui->ui_addr;
--- 184,189 ----
  		return (ENXIO);
  	tp = &dh11[unit];
! 	if (u.u_uid != 0 && (tp->t_state&TS_XCLUDE ||
! 	    flag < 0 && tp->t_state&TS_ISOPEN))
  		return (EBUSY);
  	addr = (struct dhdevice *)ui->ui_addr;
***************
*** 231,235 ****
  	 * Wait for carrier, then process line discipline specific open.
  	 */
! 	dmopen(dev);
  	return ((*linesw[tp->t_line].l_open)(dev, tp));
  }
--- 232,236 ----
  	 * Wait for carrier, then process line discipline specific open.
  	 */
! 	dmopen(dev, flag);
  	return ((*linesw[tp->t_line].l_open)(dev, tp));
  }
***************
*** 236,242 ****
  
  /*
!  * Close a DH11 line, turning off the DM11.
   */
  /*ARGSUSED*/
  dhclose(dev, flag)
  	dev_t dev;
--- 237,254 ----
  
  /*
!  * Outgoing mode open: fake the carrier.
   */
  /*ARGSUSED*/
+ dhoopen(dev, flag)
+ 	dev_t dev;
+ 	int flag;
+ {
+ 
+ 	return (dhopen(dev, -1));
+ }
+ 
+ /*
+  * Close a DH11 line, turning off the DM11.
+  */
  dhclose(dev, flag)
  	dev_t dev;
***************
*** 250,258 ****
  	(*linesw[tp->t_line].l_close)(tp);
  	((struct dhdevice *)(tp->t_addr))->dhbreak &= ~(1<<(unit&017));
! 	if (tp->t_state&TS_HUPCLS || (tp->t_state&TS_ISOPEN)==0)
  		dmctl(unit, DML_OFF, DMSET);
  	ttyclose(tp);
  }
  
  dhread(dev, uio)
  	dev_t dev;
--- 262,290 ----
  	(*linesw[tp->t_line].l_close)(tp);
  	((struct dhdevice *)(tp->t_addr))->dhbreak &= ~(1<<(unit&017));
! 	if (tp->t_state&TS_HUPCLS || (tp->t_state&TS_ISOPEN)==0 || flag < 0) {
  		dmctl(unit, DML_OFF, DMSET);
+ 		/* hold DTR low long enough for it to be detected */
+ 		timeout(wakeup, (caddr_t)&tp->t_state, hz);
+ 		sleep((caddr_t)&tp->t_state, TTOPRI);
+ 	}
  	ttyclose(tp);
+ 	if (flag < 0) {		/* clear outgoing mode */
+ 		dh_inout[unit >> 4] &= ~(1 << (unit & 0xf));
+ 		wakeup((caddr_t)&tp->t_rawq);
+ 	}
  }
  
+ /*
+  * Close an outgoing DH line
+  */
+ /*ARGSUSED*/
+ dhoclose(dev, flag)
+ 	dev_t dev;
+ 	int flag;
+ {
+ 
+ 	dhclose(dev, -1);
+ }
+ 
  dhread(dev, uio)
  	dev_t dev;
***************
*** 403,406 ****
--- 435,439 ----
  		tp->t_state |= TS_HUPCLS;
  		dmctl(unit, DML_OFF, DMSET);
+ 		splx(s);
  		return;
  	}
***************
*** 671,676 ****
   * Turn on the line associated with dh dev.
   */
! dmopen(dev)
  	dev_t dev;
  {
  	register struct tty *tp;
--- 704,710 ----
   * Turn on the line associated with dh dev.
   */
! dmopen(dev, flag)
  	dev_t dev;
+ 	int flag;
  {
  	register struct tty *tp;
***************
*** 685,689 ****
  	tp = &dh11[unit];
  	unit &= 0xf;
! 	if (dm >= NDH || (ui = dminfo[dm]) == 0 || ui->ui_alive == 0) {
  		tp->t_state |= TS_CARR_ON;
  		return;
--- 719,724 ----
  	tp = &dh11[unit];
  	unit &= 0xf;
! 	if (dm >= NDH || (ui = dminfo[dm]) == 0 || ui->ui_alive == 0 ||
! 	    (dhsoftCAR[dm]&(1<<unit))) {
  		tp->t_state |= TS_CARR_ON;
  		return;
***************
*** 691,694 ****
--- 726,754 ----
  	addr = (struct dmdevice *)ui->ui_addr;
  	s = spl5();
+ 	if (flag < 0) {
+ 		dh_inout[dm] |= 1 << unit;
+ 		dmassert(addr, dm, unit, tp);
+ 		tp->t_state |= TS_CARR_ON;
+ 	}
+ 	else {
+ 		do {
+ 			dmassert(addr, dm, unit, tp);
+ 			if (tp->t_state&TS_CARR_ON &&
+ 			    (dh_inout[dm]&(1<<unit)) == 0)
+ 				break;
+ 			tp->t_state |= TS_WOPEN;
+ 			sleep((caddr_t)&tp->t_rawq, TTIPRI);
+ 		} while ((tp->t_state&TS_CARR_ON) == 0 ||
+ 		    (dh_inout[dm]&(1<<unit)));
+ 	}
+ 	splx(s);
+ }
+ 
+ dmassert(addr, dm, unit, tp)
+ 	register struct dmdevice *addr;
+ 	int dm, unit;
+ 	struct tty *tp;
+ {
+ 
  	addr->dmcsr &= ~DM_SE;
  	while (addr->dmcsr & DM_BUSY)
***************
*** 696,705 ****
  	addr->dmcsr = unit;
  	addr->dmlstat = DML_ON;
! 	if ((addr->dmlstat&DML_CAR) || (dhsoftCAR[dm]&(1<<unit)))
  		tp->t_state |= TS_CARR_ON;
  	addr->dmcsr = DM_IE|DM_SE;
- 	while ((tp->t_state&TS_CARR_ON)==0)
- 		sleep((caddr_t)&tp->t_rawq, TTIPRI);
- 	splx(s);
  }
  
--- 756,762 ----
  	addr->dmcsr = unit;
  	addr->dmlstat = DML_ON;
! 	if (addr->dmlstat & DML_CAR || dhsoftCAR[dm] & (1 << unit))
  		tp->t_state |= TS_CARR_ON;
  	addr->dmcsr = DM_IE|DM_SE;
  }
  
//go.sysin dd *
if [ `wc -c < diff.vaxuba.dh.c` != 5035 ]; then
	made=FALSE
	echo 'error transmitting "diff.vaxuba.dh.c" --'
	echo 'length should be 5035, not' `wc -c < diff.vaxuba.dh.c`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 diff.vaxuba.dh.c
	echo -n '	'; ls -ld diff.vaxuba.dh.c
fi
echo 'Extracting diff.vaxuba.dz.c'
sed 's/^X//' <<'//go.sysin dd *' >diff.vaxuba.dz.c
RCS file: RCS/dz.c,v
retrieving revision 1.1.1.3
retrieving revision 1.4
diff -c2 -r1.1.1.3 -r1.4
*** /tmp/,RCSt1000449	Mon Dec  2 23:49:49 1985
--- /tmp/,RCSt2000449	Mon Dec  2 23:49:52 1985
***************
*** 52,56 ****
  
  int	dzstart(), dzxint(), dzdma();
! int	ttrstrt();
  struct	tty dz_tty[NDZLINE];
  int	dz_cnt = { NDZLINE };
--- 52,56 ----
  
  int	dzstart(), dzxint(), dzdma();
! int	ttrstrt(), wakeup();
  struct	tty dz_tty[NDZLINE];
  int	dz_cnt = { NDZLINE };
***************
*** 70,74 ****
  char	dz_brk[NDZ];
  char	dzsoftCAR[NDZ];
! char	dz_lnen[NDZ];	/* saved line enable bits for DZ32 */
  
  /*
--- 70,76 ----
  char	dz_brk[NDZ];
  char	dzsoftCAR[NDZ];
! char	dz_inout[NDZ];		/* outgoing mode flags */
! char	dz_flags[NDZ];		/* permanent copy of flags */
! char	dz_lnen[NDZ];		/* saved line enable bits for DZ32 */
  
  /*
***************
*** 130,134 ****
  		pdp++, tp++;
  	}
! 	dzsoftCAR[ui->ui_unit] = ui->ui_flags;
  	if (dz_timer == 0) {
  		dz_timer++;
--- 132,136 ----
  		pdp++, tp++;
  	}
! 	dzsoftCAR[ui->ui_unit] = dz_flags[ui->ui_unit] = ui->ui_flags;
  	if (dz_timer == 0) {
  		dz_timer++;
***************
*** 138,142 ****
  }
  
- /*ARGSUSED*/
  dzopen(dev, flag)
  	dev_t dev;
--- 140,143 ----
***************
*** 144,147 ****
--- 145,149 ----
  	register struct tty *tp;
  	register int unit;
+ 	register int dz, bit;
   
  	unit = minor(dev);
***************
*** 157,165 ****
  		/* tp->t_state |= TS_HUPCLS; */
  		dzparam(unit);
! 	} else if (tp->t_state&TS_XCLUDE && u.u_uid != 0)
  		return (EBUSY);
  	(void) dzmctl(dev, DZ_ON, DMSET);
  	(void) spl5();
! 	while ((tp->t_state & TS_CARR_ON) == 0) {
  		tp->t_state |= TS_WOPEN;
  		sleep((caddr_t)&tp->t_rawq, TTIPRI);
--- 159,175 ----
  		/* tp->t_state |= TS_HUPCLS; */
  		dzparam(unit);
! 	} else if ((tp->t_state&TS_XCLUDE || flag < 0) && u.u_uid != 0)
  		return (EBUSY);
  	(void) dzmctl(dev, DZ_ON, DMSET);
  	(void) spl5();
! 	dz = unit >> 3;
! 	bit = 1 << (unit & 7);
! 	if (flag < 0) {
! 		dz_inout[dz] |= bit;
! 		dzsoftCAR[dz] |= bit;
! 	}
! 	while ((tp->t_state & TS_CARR_ON) == 0 ||
! 	    dz_inout[dz] & bit && flag >= 0) {
! 		(void) dzmctl(dev, DZ_ON, DMSET);
  		tp->t_state |= TS_WOPEN;
  		sleep((caddr_t)&tp->t_rawq, TTIPRI);
***************
*** 168,173 ****
  	return ((*linesw[tp->t_line].l_open)(dev, tp));
  }
!  
  /*ARGSUSED*/
  dzclose(dev, flag)
  	dev_t dev;
--- 178,190 ----
  	return ((*linesw[tp->t_line].l_open)(dev, tp));
  }
! 
  /*ARGSUSED*/
+ dzoopen(dev, flag)
+ 	dev_t dev;
+ {
+ 
+ 	return (dzopen(dev, -1));
+ }
+ 
  dzclose(dev, flag)
  	dev_t dev;
***************
*** 187,195 ****
  	else
  		dzaddr->dzbrk = (dz_brk[dz] &= ~(1 << (unit&07)));
! 	if ((tp->t_state&(TS_HUPCLS|TS_WOPEN)) || (tp->t_state&TS_ISOPEN) == 0)
  		(void) dzmctl(dev, DZ_OFF, DMSET);
  	ttyclose(tp);
  }
   
  dzread(dev, uio)
  	dev_t dev;
--- 204,233 ----
  	else
  		dzaddr->dzbrk = (dz_brk[dz] &= ~(1 << (unit&07)));
! 	if (tp->t_state&TS_HUPCLS || (tp->t_state&TS_ISOPEN)==0 || flag < 0) {
  		(void) dzmctl(dev, DZ_OFF, DMSET);
+ 		timeout(wakeup, (caddr_t)&tp->t_state, hz);
+ 		sleep((caddr_t)&tp->t_state, TTOPRI);
+ 	}
  	ttyclose(tp);
+ 	if (flag < 0) {
+ 		register int bit = 1 << (unit & 7);
+ 
+ 		dz_inout[dz] &= ~bit;
+ 		if (dz_flags[dz] & bit)
+ 			dzsoftCAR[dz] |= bit;
+ 		else
+ 			dzsoftCAR[dz] &= ~bit;
+ 		wakeup((caddr_t)&tp->t_rawq);
+ 	}
  }
   
+ /*ARGSUSED*/
+ dzoclose(dev, flag)
+ 	dev_t dev;
+ {
+ 
+ 	dzclose(dev, -1);
+ }
+ 
  dzread(dev, uio)
  	dev_t dev;
***************
*** 575,581 ****
  	register struct tty *tp;
  	register car;
! 	int olddzsilos = dzsilos;
  	int dztimer();
!  
  	for (i = 0; i < dz_cnt ; i++) {
  		dzaddr = dzpdma[i].p_addr;
--- 613,619 ----
  	register struct tty *tp;
  	register car;
! 	int olddzsilos = dzsilos, realcar;
  	int dztimer();
! 
  	for (i = 0; i < dz_cnt ; i++) {
  		dzaddr = dzpdma[i].p_addr;
***************
*** 584,598 ****
  		tp = &dz_tty[i];
  		bit = 1<<(i&07);
! 		car = 0;
! 		if (dzsoftCAR[i>>3]&bit)
! 			car = 1;
! 		else if (dzaddr->dzcsr & DZ_32) {
  			dzaddr->dzlcs = i&07;
  			dzwait(dzaddr);
! 			car = dzaddr->dzlcs & DZ_CD;
  		} else
! 			car = dzaddr->dzmsr&bit;
  		if (car) {
  			/* carrier present */
  			if ((tp->t_state & TS_CARR_ON) == 0) {
  				wakeup((caddr_t)&tp->t_rawq);
--- 622,638 ----
  		tp = &dz_tty[i];
  		bit = 1<<(i&07);
! 		if (dzaddr->dzcsr & DZ_32) {
  			dzaddr->dzlcs = i&07;
  			dzwait(dzaddr);
! 			realcar = dzaddr->dzlcs & DZ_CD;
  		} else
! 			realcar = dzaddr->dzmsr&bit;
! 		car = 0;
! 		if (realcar || dzsoftCAR[i>>3] & bit)
! 			car++;
  		if (car) {
  			/* carrier present */
+ 			if (realcar && dz_inout[i>>3] & bit)
+ 				dzsoftCAR[i >> 3] &= ~bit;
  			if ((tp->t_state & TS_CARR_ON) == 0) {
  				wakeup((caddr_t)&tp->t_rawq);
***************
*** 599,604 ****
  				tp->t_state |= TS_CARR_ON;
  			}
  		} else {
! 			if ((tp->t_state&TS_CARR_ON) &&
  			    (tp->t_flags&NOHANG) == 0) {
  				/* carrier lost */
--- 639,651 ----
  				tp->t_state |= TS_CARR_ON;
  			}
+ 			else if (tp->t_state&TS_TTSTOP && tp->t_flags&MDMBUF) {
+ 				tp->t_state &= ~TS_TTSTOP;
+ 				ttstart(tp);
+ 			}
  		} else {
! 			if (tp->t_flags&MDMBUF) {
! 				tp->t_state |= TS_TTSTOP;
! 				dzstop(tp, 0);
! 			} else if ((tp->t_state&TS_CARR_ON) &&
  			    (tp->t_flags&NOHANG) == 0) {
  				/* carrier lost */
//go.sysin dd *
if [ `wc -c < diff.vaxuba.dz.c` != 5358 ]; then
	made=FALSE
	echo 'error transmitting "diff.vaxuba.dz.c" --'
	echo 'length should be 5358, not' `wc -c < diff.vaxuba.dz.c`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 diff.vaxuba.dz.c
	echo -n '	'; ls -ld diff.vaxuba.dz.c
fi