/* * 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); }