V8/usr/sys/chunix/chaos.c
/*
* UNIX device driver interface to the Chaos N.C.P.
*/
#include "../chunix/chsys.h"
#include "../chunix/chconf.h"
#include "../chaos/chaos.h"
#include "../chaos/user.h"
#include "../chaos/dev.h"
#include "../h/inode.h"
#include "../h/file.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/conf.h"
#include "../h/ioctl.h"
#include "../h/buf.h"
#include "../h/systm.h"
#include "../h/tty.h"
#ifdef VMUNIX
#include "cht.h"
#endif
static int initted; /* NCP initialization flag */
int Rfcwaiting; /* Someone waiting on unmatched RFC */
static char *chnames[][3] = {
/* reading writing read/write */
{ "uread", "ucreate", "ureadwrite", },
};
#define CHFCONN(fp) ((fp)->f_conn)
#define CHIOPRIO (PZERO+1) /* Just interruptible */
#ifndef VMUNIX
#define setjmp save
#endif
static struct packet *fillpacket();
/*
* Open a chaos channel.
* Special case devices are indicated if CHHOST(dev) == CHHSPEC.
* See <chaos/dev.h> for the definitions.
* Non-special device opens imply a request to create a new connection
* via an RFC to a specified host, with a specified contact name string.
* The host number is specified one of three ways (CHHOST(dev)):
* CHHREAD Read an ascii host number from the remainder
* of the pathname (after the component that
* matched this device).
* CHHUSE Use the component that matched itself
* as an ascii number.
* else Use the CHHOST(dev) as an index into an
* internal host table where frequently used
* host numbers should reside.
* The contact name can also be specified in three ways (CHNAME(dev)):
* CHNREAD Read the contact string from the rest of the
* path name (possibly after host number).
* CHNUSE Read the contact string from the path
* component that matched (possibly after host
* number).
* else Use the CHNAME(dev) as an index into an
* internal array of contact names, also
* indexed by the type of open (read, write, rw).
*/
chropen(dev, flag)
dev_t dev;
{
register struct connection *conn;
register struct packet *pkt;
int host, name, wstate;
ch_bufalloc();
/* initialize the NCP somewhere else? */
if (!initted) {
chrreset(); /* Reset drivers */
chtimeout(); /* Start clock "process" */
initted++;
}
conn = NOCONN;
name = CHNAME(dev);
host = CHHOST(dev);
wstate = 0;
if (host == CHHSPEC)
switch (name) {
case CHUNMATCHED:
if(Chrfcrcv == 0) {
Chrfcrcv++;
return;
}
break;
/*
* Listens create a connection waiting for RFC's containing
* the given contact name. The returned file descriptor
* corresponds to a connection that may not be open yet.
* Use ioctls (CHIOCGSTAT, CHIOCSWAIT), to check or
* hang on the state of the connection.
*/
case CHLISTEN:
if ((pkt = fillpacket("", 0)) != NOPKT)
conn = ch_listen(pkt);
break;
}
else {
register int len = DIRSIZ;
char *namp = u.u_dbuf;
switch(host) {
case CHHREAD:
host = uatoi((char **)0);
break;
case CHHUSE:
host = uatoi(&namp); /* stuffs namp */
break;
default:
host = chhosts[host];
}
if (u.u_error)
host = 0;
switch(name) {
case CHNREAD:
namp = "";
len = 0;
break;
case CHNUSE:
len = DIRSIZ - (namp - u.u_dbuf);
break;
default:
len = CHMAXRFC;
if ((namp = chnames[name][flag-1]) == NONAME)
host = 0;
}
if (host != 0 && (pkt = fillpacket(namp, len)) != NOPKT &&
(conn = ch_open(host, CHDRWSIZE, pkt)) != NOCONN &&
CHHANGDEV(dev))
wstate = CSRFCSENT; /* Wait until OPEN/CLOSED */
}
if (conn == NOCONN) {
u.u_error = ENXIO;
ch_buffree();
} else {
register struct file *fp;
fp = u.u_ofile[u.u_r.r_val1]; /* Yick. (see mx2.c) */
CHFCONN(fp) = (caddr_t)conn;
if (wstate) {
/*
* We should hang until the connection changes from
* its initial state.
* If interrupted, flush the connection.
*/
#ifdef VMUNIX
if (setjmp(u.u_qsav) == 0) {
#else
if (save(u.u_qsav) == 0) {
#endif
spl6();
while (conn->cn_state == wstate)
sleep((caddr_t)conn, CHIOPRIO);
spl0();
#ifdef DEBUG
if (ch_badaddr((char *)conn))
panic("chopen");
#endif DEBUG
}
/*
* If the connection is not open, the open failed.
* Unless is got an ANS back.
*/
if (conn->cn_state != CSOPEN &&
(conn->cn_state != CSCLOSED ||
(pkt = conn->cn_rhead) == NOPKT ||
pkt->pk_op != ANSOP)) {
rlsconn(conn);
u.u_error = ENXIO;
return;
}
}
conn->cn_sflags |= CHRAW;
conn->cn_mode = CHSTREAM;
}
}
/* ARGSUSED */
chrclose(dev, flag, cp)
dev_t dev;
struct chan *cp;
{
register struct connection *conn = (struct connection *)cp;
if (minor(dev) == CHURFCMIN) {
Chrfcrcv = 0;
freelist(Chrfclist);
Chrfclist = NOPKT;
return;
}
/*
* If this connection has been turned into a tty, then the
* tty owns it and we don't do anything.
*/
if (conn->cn_mode != CHTTY)
chclose(conn, flag);
}
chclose(conn, flag)
register struct connection *conn;
{
register struct packet *pkt;
switch (conn->cn_mode) {
case CHTTY:
panic("chclose on tty");
case CHSTREAM:
spl6();
if (setjmp(u.u_qsav)) {
pkt = pktstr(NOPKT, "User interrupted", 16);
if (pkt != NOPKT)
pkt->pk_op = CLSOP;
ch_close(conn, pkt, 0);
goto shut;
}
if (flag & FWRITE) {
/*
* If any input packets other than the RFC are around
* something is wrong and we just abort the connection
*/
while ((pkt = conn->cn_rhead) != NOPKT) {
ch_read(conn);
if (pkt->pk_op != RFCOP)
goto recclose;
}
/*
* We set this flag telling the interrupt time
* receiver to abort the connection if any new packets
* arrive.
*/
conn->cn_sflags |= CHCLOSING;
/*
* Closing a stream transmitter involves flushing
* the last packet, sending an EOF and waiting for
* it to be acknowledged. If the connection was
* bidirectional, the reader should have already
* read until EOF if everything is to be closed
* cleanly.
*/
checkfull:
while (chtfull(conn)) {
conn->cn_sflags |= CHOWAIT;
sleep((caddr_t)&conn->cn_thead, CHIOPRIO);
}
if (conn->cn_state == CSOPEN ||
conn->cn_state == CSRFCRCVD) {
if (conn->cn_toutput) {
ch_sflush(conn);
goto checkfull;
}
if (conn->cn_state == CSOPEN)
(void)ch_eof(conn);
}
while (!chtempty(conn)) {
conn->cn_sflags |= CHOWAIT;
sleep((caddr_t)&conn->cn_thead, CHIOPRIO);
}
} else if (conn->cn_state == CSOPEN) {
/*
* If we are only reading then we should read the EOF
* before closing and wiat for the other end to close.
*/
if (conn->cn_flags & CHEOFSEEN)
while (conn->cn_state == CSOPEN)
sleep((caddr_t)conn, CHIOPRIO);
}
recclose:
spl0();
/* Fall into... */
case CHRECORD: /* Record oriented close is just sending a CLOSE */
if (conn->cn_state == CSOPEN) {
pkt = pkalloc(0, 0);
if (pkt != NOPKT) {
pkt->pk_op = CLSOP;
pkt->pk_len = 0;
}
ch_close(conn, pkt, 0);
}
}
shut:
spl0();
ch_close(conn, NOPKT, 1);
ch_buffree();
}
/*
* Raw read routine.
*/
chrread(dev)
dev_t dev;
{
register struct connection *conn;
register struct packet *pkt;
register int count, n;
if(minor(dev) == CHURFCMIN) {
spl6();
while ((pkt = ch_rnext()) == NOPKT) {
Rfcwaiting++;
sleep((caddr_t)&Chrfclist, CHIOPRIO);
}
spl0();
if (u.u_count < pkt->pk_len)
u.u_error = EIO;
else
iomove((caddr_t)pkt->pk_cdata, pkt->pk_len, B_READ);
return;
}
conn = (struct connection *)CHFCONN(getf(u.u_arg[0]));
switch (conn->cn_mode) {
case CHTTY:
panic("chread on tty");
break;
case CHSTREAM:
for (count = u.u_count; u.u_count != 0; )
switch (n = ch_sread(conn, (char*)0, u.u_count)) {
case 0: /* No data to read */
if (count != u.u_count)
return;
spl6();
while (chrempty(conn)) {
conn->cn_sflags |= CHIWAIT;
sleep((char *)&conn->cn_rhead,
CHIOPRIO);
}
spl0();
break;
case CHEOF:
return;
default:
if (n < 0) {
u.u_error = EIO;
return;
}
}
break;
/*
* Record oriented mode gives a one byte packet opcode
* followed by the data in the packet. The buffer must
* be large enough to fit the data and the opcode, otherwise an
* i/o error results.
*/
case CHRECORD:
spl6();
while (chrempty(conn)) {
conn->cn_sflags |= CHIWAIT;
sleep((char *)&conn->cn_rhead,
CHIOPRIO);
}
spl0();
if ((pkt = conn->cn_rhead) == NOPKT ||
pkt->pk_len + 1 > u.u_count) /* + 1 for opcode */
u.u_error = EIO;
else {
iomove((caddr_t)&pkt->pk_op, 1, B_READ);
iomove((caddr_t)pkt->pk_cdata, pkt->pk_len, B_READ);
spl6();
ch_read(conn);
spl0();
}
}
}
/*
* Raw write routine
* Note that user programs can write to a connection
* that has been CHIOCANSWER"'d, implying transmission of an ANS packet
* rather than a normal packet. This is illegal for TTY mode connections,
* is handled in the system independent stream code for STREAM mode, and
* is handled here for RECORD mode.
*/
chrwrite(dev)
dev_t dev;
{
register struct connection *conn;
if(minor(dev) == CHURFCMIN) {
u.u_error = EIO;
return;
}
conn = (struct connection *)CHFCONN(getf(u.u_arg[0]));
chwrite(conn);
}
chwrite(conn)
register struct connection *conn;
{
register struct packet *pkt;
register int n;
if (conn->cn_rhead != NOPKT && conn->cn_rhead->pk_op == RFCOP) {
spl6();
ch_read(conn);
spl0();
}
switch (conn->cn_mode) {
case CHTTY:
/* Fall into (on RAW mode only) */
case CHSTREAM:
while (u.u_count != 0)
switch (n = ch_swrite(conn, (char *)0, u.u_count)) {
case 0:
spl6();
while (chtfull(conn)) {
conn->cn_sflags |= CHOWAIT;
sleep((caddr_t)&conn->cn_thead,
CHIOPRIO);
}
spl0();
break;
case CHTEMP:
sleep((caddr_t)&lbolt, CHIOPRIO);
break;
default:
if (n < 0) {
u.u_error = EIO;
return;
}
}
break;
case CHRECORD: /* one write call -> one packet */
if (u.u_count < 1 || u.u_count - 1 > CHMAXDATA) {
u.u_error = EIO;
return;
}
loop:
spl6();
while (chtfull(conn)) {
conn->cn_sflags |= CHOWAIT;
sleep((caddr_t)&conn->cn_thead, CHIOPRIO);
}
spl0();
if ((pkt = pkalloc((int)(u.u_count - 1), 0)) == NOPKT) {
sleep((caddr_t)&lbolt, CHIOPRIO);
goto loop;
}
iomove(&pkt->pk_op, 1, B_WRITE);
pkt->pk_len = u.u_count;
if (u.u_count)
iomove(pkt->pk_cdata, u.u_count, B_WRITE);
if (u.u_error == 0) {
spl6();
if (ch_write(conn, pkt))
u.u_error = EIO;
spl0();
}
}
}
/*
* This routine allocates a packet and fills it with data from pointer in
* user space until null or CHMAXDATA characters
*/
static struct packet *
fillpacket(str, len)
register char *str;
int len; /* assumed <= CHMAXDATA */
{
register char *ptr;
register struct packet *pkt;
int c;
extern uchar();
if ((pkt = pkalloc(CHMAXDATA, 0)) != NOPKT) {
ptr = pkt->pk_cdata;
for (pkt->pk_len = 0; *str != '\0' && pkt->pk_len < len; pkt->pk_len++)
*ptr++ = *str++;
while ((c = uchar()) > 0 && pkt->pk_len < CHMAXDATA) {
*ptr++ = c;
pkt->pk_len++;
}
if (c > 0) {
debug(DABNOR,
printf("Fillpacket too long: %s\n",
pkt->pk_cdata));
ch_free((char *)pkt);
} else
return(pkt);
}
u.u_error = ENXIO;
return(NOPKT);
}
/*
* This is atoi from users space terminates on / or null.
* If cpp is not NULL then get the number from *cpp (u.u_dbuf).
* *cpp is stuffed where the scan stopped, enabling further usage of the
* contents of u.u_dbuf.
*/
static
uatoi(cpp)
char **cpp;
{
register char *p = cpp ? *cpp - 1 : (char *)0;
register int i = 0, chr;
extern uchar();
for (;;) {
if (p)
if (++p >= &u.u_dbuf[DIRSIZ])
break;
else
chr = *p;
else if ((chr = uchar()) == '/')
break;
if ((chr -= '0') < 0 || chr > 9)
break;
i *= 10;
i += chr;
}
if (cpp)
*cpp = p;
return(i);
}
/*
* Raw ioctl routine - perform non-connection functions, otherwise call down
*/
chrioctl(dev, cmd, addr, flag)
register int cmd;
caddr_t addr;
{
register char *cp;
register int c;
switch (cmd) {
/*
* Skip the first unmatched RFC at the head of the queue
* and mark it so that ch_rnext will never pick it up again.
*/
case CHIOCRSKIP:
if (minor(dev) != CHURFCMIN)
break;
spl6();
ch_rskip();
spl0();
return;
/*
* Specify the chaosnet address of an interlan ethernet interface.
*/
case CHIOCILADDR:
if (minor(dev) != CHURFCMIN)
break;
#if NCHIL > 0
{
struct chiladdr ca;
if (addr == 0 || copyin(addr, (caddr_t)&ca, sizeof(ca)))
break;
if (chilseta(ca.cil_device, ca.cil_address))
break;
return;
}
#else
break;
#endif
case CHIOCNAME:
if (minor(dev) != CHURFCMIN)
break;
for (cp = Chmyname; c = fubyte(addr); *cp++ = c, addr++)
if (c < 0) {
cp = Chmyname;
u.u_error = EFAULT;
break;
} else if(cp >= &Chmyname[CHSTATNAME])
break;
while (cp < &Chmyname[CHSTATNAME])
*cp++ = '\0';
if (c >= 0)
return;
break;
/*
* Specify my own network number.
*/
case CHIOCADDR:
if (minor(dev) != CHURFCMIN)
break;
Chmyaddr = (int)addr;
return;
default:
if (minor(dev) == CHURFCMIN)
break;
chioctl(CHFCONN(getf(u.u_arg[0])), dev, cmd, addr, flag);
return;
}
u.u_error = ENXIO;
}
/* ARGSUSED */
chioctl(conn, dev, cmd, addr, flag)
register struct connection *conn;
dev_t dev;
caddr_t addr;
{
register struct packet *pkt;
struct chstatus chst;
switch(cmd) {
/*
* Read the first packet in the read queue for a connection.
* This call is primarily intended for those who want to read
* non-data packets (which are normally ignored) like RFC
* (for arguments in the contact string), CLS (for error string) etc.
* The reader's buffer is assumed to be CHMAXDATA long. When ioctl's
* change on the VAX to have data length arguments, this will be done
* right. An error results if there is no packet to read.
* No hanging is currently provided for.
* The normal mode of operation for reading such packets is to
* first do a CHIOCGSTAT call to find out whether there is a packet
* to read (and what kind) and then make this call - except for
* RFC's when you know it must be there.
*/
case CHIOCPREAD:
if ((pkt = conn->cn_rhead) == NULL)
break;
if (copyout((caddr_t)pkt->pk_cdata, addr, pkt->pk_len))
break;
spl6();
ch_read(conn);
spl0();
return;
/*
* Change the mode of the connection.
* The default mode is CHSTREAM.
*/
case CHIOCSMODE:
switch ((int)addr) {
case CHTTY:
#if NCHT > 0
if (conn->cn_state == CSOPEN &&
conn->cn_mode != CHTTY) {
register struct tty *tp;
extern struct tty cht_tty[];
extern int cht_cnt;
/*
* To turn a connection into a tty,
* we need to find a tty that is waiting to
* be opened and connect ourselves to it.
*/
for (tp = cht_tty; tp < &cht_tty[cht_cnt]; tp++)
if (tp->t_addr == 0 &&
tp->t_state & WOPEN) {
tp->t_addr = (caddr_t)conn;
tp->t_state |= CARR_ON;
conn->cn_ttyp = tp;
conn->cn_mode = CHTTY;
wakeup((caddr_t)&tp->t_rawq);
return;
}
}
#endif
break;
case CHSTREAM:
case CHRECORD:
if (conn->cn_mode == CHTTY)
break;
conn->cn_mode = (int)addr;
return;
}
break;
/*
* Flush the current output packet if there is one.
* This is only valid in stream mode.
* If the argument is non-zero an error is returned if the
* transmit window is full, otherwise we hang.
*/
case CHIOCFLUSH:
if (conn->cn_mode == CHSTREAM) {
spl6();
while ((flag = ch_sflush(conn)) == CHTEMP)
if (addr)
break;
else {
conn->cn_sflags |= CHOWAIT;
sleep((caddr_t)&conn->cn_thead,
CHIOPRIO);
}
if (flag)
u.u_error = EIO;
spl0();
return;
}
break;
/*
* Wait for all output to be acknowledged. If addr is non-zero
* an EOF packet is also sent before waiting.
* If in stream mode, output is flushed first.
*/
case CHIOCOWAIT:
if (conn->cn_mode == CHSTREAM) {
spl6();
while ((flag = ch_sflush(conn)) == CHTEMP) {
conn->cn_sflags |= CHOWAIT;
sleep((caddr_t)&conn->cn_thead, CHIOPRIO);
}
spl0();
if (flag) {
u.u_error = EIO;
return;
}
}
if (addr) {
spl6();
while (chtfull(conn)) {
conn->cn_sflags |= CHOWAIT;
sleep((caddr_t)&conn->cn_thead, CHIOPRIO);
}
flag = ch_eof(conn);
spl0();
if (flag) {
u.u_error = EIO;
return;
}
}
spl6();
while (!chtempty(conn)) {
conn->cn_sflags |= CHOWAIT;
sleep((caddr_t)&conn->cn_thead, CHIOPRIO);
}
spl0();
if (conn->cn_state != CSOPEN)
u.u_error = EIO;
return;
/*
* Return the status of the connection in a structure supplied
* by the user program.
*/
case CHIOCGSTAT:
chst.st_fhost = conn->cn_faddr;
chst.st_cnum = conn->cn_ltidx;
chst.st_rwsize = conn->cn_rwsize;
chst.st_twsize = conn->cn_twsize;
chst.st_state = conn->cn_state;
chst.st_cmode = conn->cn_mode;
chst.st_oroom = conn->cn_twsize - (conn->cn_tlast - conn->cn_tacked);
if ((pkt = conn->cn_rhead) != NOPKT) {
chst.st_ptype = pkt->pk_op;
chst.st_plength = pkt->pk_len;
} else {
chst.st_ptype = 0;
chst.st_plength = 0;
}
if (copyout((caddr_t)&chst, addr, sizeof(chst)))
break;
return;
/*
* Wait for the state of the connection to be different from
* the given state.
*/
case CHIOCSWAIT:
spl6();
while (conn->cn_state == (int)addr)
sleep((caddr_t)conn, CHIOPRIO);
spl0();
return;
/*
* Answer an RFC. Basically this call does nothing except
* setting a bit that says this connection should be of the
* datagram variety so that the connection automatically gets
* closed after the first write, whose data is immediately sent
* in an ANS packet.
*/
case CHIOCANSWER:
spl6();
if (conn->cn_state == CSRFCRCVD && conn->cn_mode != CHTTY)
conn->cn_flags |= CHANSWER;
else
u.u_error = EIO;
spl0();
return;
/*
* Reject a RFC, giving a string (null terminated), to put in the
* close packet. This call can also be used to shut down a connection
* prematurely giving an ascii close reason.
*/
case CHIOCREJECT:
spl6();
if (conn->cn_state == CSRFCRCVD || conn->cn_state == CSOPEN) {
u.u_dirp = addr; /* a kludge for fillpacket */
pkt = fillpacket("", 0);
pkt->pk_op = CLSOP;
ch_close(conn, pkt, 0);
} else
u.u_error = EIO;
spl0();
return;
/*
* Accept an RFC causing the OPEN packet to be sent
*/
case CHIOCACCEPT:
spl6();
if (conn->cn_state == CSRFCRCVD)
ch_accept(conn);
else
u.u_error = EIO;
spl0();
return;
/*
* Count how many bytes can be immediately read.
*/
case FIONREAD:
if (conn->cn_mode != CHTTY) {
off_t nread = 0;
for (pkt = conn->cn_rhead; pkt != NOPKT; pkt = pkt->pk_next)
if (ISDATOP(pkt))
nread += pkt->pk_len;
if (conn->cn_rhead != NOPKT)
nread -= conn->cn_roffset;
if (copyout((caddr_t)&nread, addr, sizeof(off_t)))
u.u_error = EFAULT;
return;
}
default:
break;
}
u.u_error = ENXIO;
}
/*
* Timeout routine that implements the chaosnet clock process.
*/
chtimeout()
{
register int s = spl6();
ch_clock();
timeout(chtimeout, 0, 1);
splx(s);
}