# /* * Pseudo-teletype Driver * (Actually two drivers, requiring two entries in 'cdevsw') */ #include "../h/param.h" #include "../h/conf.h" #include "../h/inode.h" #include "../h/inodex.h" #include "../h/user.h" #include "../h/userx.h" #include "../h/tty.h" #include "../h/ioctl.h" #define NPTY 4 /* Number of pseudo-teletypes */ #define NODELAY 0377 /* * A pseudo-teletype is a special device which is like a pipe. * It is used to communicate between two processes. However, it allows * one to simulate a teletype, including mode setting, interrupt, and * multiple end of files (all not possible on a pipe). There are two * drivers: the slave side which is opened, read, written by the * side which thinks it is communicating with the standard tty; and * the controller side which runs in lieu of the actual tty. The slave * side routines are prefixed pts and the controller routines ptc. * To type on the simulated keyboard of the PTY, * one does a 'write' to the controlling device. To get the * simulated printout from the PTY, one does a 'read' on the controlling * device. Normally, the controlling device is called '/dev/ptyx' and the * slave device is called '/dev/ttyx' (to make programs like 'who' happy). * * Two entries were added to the tty struct for ptys: * t_procp and t_ptmode. T_procp contains the process * table ptr for a process which has opened to the controller * side of the pty and has enabled SIGTINT when its slave side * does an stty, a ttymode, a read (thus removing chars from * its canq) or a write (thus adding chars to its outq). T_ptmode * indicates which actions the controller wants to be signaled * about. Ioctl calls by the controller enable and disable * signaling. * * Modified by Dan Franklin: * 9/11/78 made ptcopen turn on virtual carrier. Otherwise the sequence * ptsopen - ptsopen - ptcclose - ptcopen leaves pty open with carrier off, * which causes slave reads to return EOF, which causes getty to go away * as soon as init forks it -- repeatedly! * 1/4/78 BBN(dan) to fix stty bug: an stty on slave after * controller closed called ttstart when t_addr == 0. Changed to not * clear t_addr in ptcclose. Also changed ptsstart to check if controller * closed and flushtty if so -- this assures that after controller closed, * sleeping ttwrites will still complete. * 1/16/78 BBN(dan) to fix stty bug: stty on controller could lead to deadlock * waiting for output to drain if the process doing the stty is the one * that would drain the output. Fixed by imitating stty code in ttystty * without wflushtty call. Also changed ptccap to return half of what it * what it was before -- this allows for every char input being followed * by \377 in ttyinput. * 3/22/79 BBN(dan) feature: ctrller can now tell whether slave open * by examining speeds. Nonzero speed means open, zero speed means closed. * 5/3/79 BBN(dan): Made ptcopen set SSTART bit (it already set t_addr). * Otherwise stty on ctrller with slave closed can crash system. * Also made ptsopen set t_addr (it already set SSTART). */ /* t_ptmode bits */ #define MODEINT 01 /* SIGTINT t_procp when pts mode change */ #define OUTQINT 02 /* SIGTINT t_procp when chars added to outq */ #define CANQINT 04 /* SIGTINT t_procp when chars removed from canq */ struct pty { struct tty _XXXX; int t_ptmode; struct proc *t_procp; } pty[NPTY]; /* TTY headers for PTYs */ int npty NPTY; ptsopen(dev,flag) { register struct pty *tp; register struct proc *pp; extern ptsstart(); if (dev.d_minor >= NPTY) /* Verify minor device number */ { u.u_error = ENXIO; /* No such device */ return; } tp = &pty[dev.d_minor]; /* Setup pointer to PTY header */ if ((tp->t_state & ISOPEN) == 0) /* Not already open? */ { /* Mark as open and with special start routine */ tp->t_state =| SSTART; tp->t_addr = &ptsstart; tp->t_flags = 0; /* tp->t_flags = XTABS|ECHO|CRMOD; */ } ttyopen(dev, tp); tp->t_flag2 |= XHIWAT; while((tp->t_state & CARR_ON) == 0) sleep(&tp->t_addr, TTIPRI); tp->t_speeds = (07<<8) | 07; /* Set to 300 baud */ } ptsclose(dev) { register struct pty *tp; tp = &pty[dev.d_minor]; /* Setup pointer to PTY header */ if (tp->t_state & CARR_ON) /* Other side open? */ wflushtty(tp); /* Yes, wait for it */ else flushtty(tp); /* Otherwise, just blast it */ tp->t_state = ((tp->t_state & CARR_ON)|SSTART); /* Mark as closed */ tp->t_speeds = 0; /* So ctrller can find out */ } /* ----------------------------- P T S R E A D ---------------------- */ ptsread(dev) /* Read from PTY, i.e. from data written by controlling device */ { register struct pty *tp; tp = &pty[dev.d_minor]; /* Setup pointer to PTY header */ ttread(tp); if ( (tp->t_ptmode & CANQINT) && (TTYHOG - tp->t_canq.c_cc) ) psignal(tp->t_procp, SIGTINT); } /* ----------------------------- P T S W R I T E -------------------- */ /* * Write on PTY, chars will be read from controlling device * Ptsstart will signal controller half if OUTQINT is enabled. */ ptswrite(dev) { register struct pty *tp; tp = &pty[dev.d_minor]; /* Setup pointer to PTY header */ ttwrite(tp); } /* -------------------------- P T C O P E N -------------------- */ ptcopen(dev,flag) /* Open for PTY Controller */ { register struct pty *tp; extern int ptsstart(); /* Routine to start write from slave */ if (dev.d_minor >= NPTY) /* Verify minor device number */ { u.u_error = ENXIO; /* No such device */ return; } tp = &pty[dev.d_minor]; /* Setup pointer to PTY header */ if (tp->t_state & CARR_ON) /* Already open? */ { u.u_error = EIO; /* I/O error (Is there something better?) */ return; } tp->t_state =| SSTART; /* Set "special start" flag */ tp->t_addr = &ptsstart; /* Set address of start routine */ tp->t_state =| CARR_ON; /* Turn on virtual carrier */ tp->t_flag2 &= (~(FORCECHOM|FECHO)); tp->t_ptmode = 0; tp->t_procp = 0; wakeup(&tp->t_addr); /* Wake up slave's openers */ } /* -------------------------- P T C C L O S E ------------------- */ /* Close PTY controller * ptccls1 called on every pty controller close call * ptcclose called on last close ala closef idiocy */ ptccls1(dev) int dev; { register struct pty *tp; tp = &pty[dev.d_minor]; /* Setup pointer to PTY header */ if (tp->t_procp == u.u_procp) { tp->t_ptmode &= (~(MODEINT|OUTQINT|CANQINT)); tp->t_procp = 0; } } ptcclose(dev) { register struct pty *tp; register int temp; tp = &pty[dev.d_minor]; if (tp->t_state & ISOPEN) /* Is it slave open? */ signal(tp->t_pgrp,SIGHUP); /* Yes, send a HANGUP */ flushtty(tp); /* Clean things up */ tp->t_state = ((tp->t_state & ISOPEN)|SSTART); /* Virtual carrier is gone */ } /* -------------------------- P T C R E A D ---------------------- */ ptcread(dev) /* Read from PTY's output buffer */ { register struct pty *tp; register int temp; tp = &pty[dev.d_minor]; /* Setup pointer to PTY header */ while (tp->t_outq.c_cc==0) /* Wait for something to arrive */ sleep(&tp->t_outq.c_cf,TTIPRI); /* (Woken by ptsstart) */ temp = tp->t_outq.c_cc; /* Remember it */ /* Copy in what's there, or all that will fit */ while (tp->t_outq.c_cc && passc(getc(&tp->t_outq))>=0); /* Now, decide whether to wake up process waiting on input queue */ if (tp->t_outq.c_cc==0 || (temp>TTLOWAT && tp->t_outq.c_cc<=TTLOWAT)) { wakeup(&tp->t_outq); } } /* -------------------------- P T S S T A R T ------------------- */ ptsstart(tp) /* Called by 'ttstart' to output a character. */ /* Merely wakes up controlling half, which does actual work */ register struct pty *tp; { if ((tp->t_state & CARR_ON) == 0) /* Nobody there! */ flushtty(tp); /* To get rid of chars */ if ((tp->t_ptmode & OUTQINT) && (tp->t_outq.c_cc)) psignal(tp->t_procp, SIGTINT); wakeup(&tp->t_outq.c_cf); } /* -------------------------- P T C W R I T E ------------------- */ ptcwrite(dev) /* Stuff characters into PTY's input buffer */ { register struct pty *tp; register char c; tp = &pty[dev.d_minor]; /* Setup pointer to PTY header */ /* For each character... */ while ((c = cpass()) >= 0) { ttyinput(c,tp); } } /* * Slave sgtty routine. Just call ttystty. * Stty when v == 0. */ ptssgtty(dev,v) int *v; { register struct pty *tp; tp = &pty[dev.d_minor]; ttystty(tp, v); if ( (v == 0) && (tp->t_ptmode & MODEINT) ) psignal(tp->t_procp, SIGTINT); } /* -------------------------- P T C S G T T Y ------------------- */ /* * Controller sgtty. Differs from slave only in that stty calls * do not do a wflushtty. */ ptcsgtty(dev, v) int *v; { register struct pty *tp; tp = &pty[dev.d_minor]; /* Setup pointer to PTY header */ if (v) /* gtty? */ { ttystty(tp, v); /* Normal */ } else { v = u.u_arg; tp->t_speeds = *v++; tp->t_erase = v->lobyte; tp->t_kill = v->hibyte; tp->t_flags = v[1]; } } /* * Slave ttymode routine. Just call ttmode. */ ptsttmd(dev, acp) int *acp; { register struct pty *tp; tp = &pty[dev.d_minor]; ttmode(tp, acp); if (tp->t_ptmode & MODEINT) psignal(tp->t_procp, SIGTINT); } /* * Controller ttymode routine. Just call ttmode. Unlike sgtty, * ttmode only does a wflushtty if explicitly called for. Therefore, * we will assume that users of the controller side of a PTY know what * they are doing if they request flushing. */ ptcttmd(dev, acp) int *acp; { register struct pty *tp; tp = &pty[dev.d_minor]; ttmode(tp, acp); } ptcioctl(dev, ip, cmd, cmdarg) int dev; char *ip; int cmd; char *cmdarg; { register struct pty *tp; short temp; tp = &pty[dev.d_minor]; if ((tp->t_state & CARR_ON) == 0) { u.u_error = EBADF; return; } switch(cmd) { case FIONREAD: temp = tp->t_outq.c_cc; if (copyout(&temp, cmdarg, 2)) return(ttu_err(EFAULT)); break; case FIONWRITE: temp = TTYHOG - tp->t_canq.c_cc; if (copyout(&temp, cmdarg, 2)) return(ttu_err(EFAULT)); break; /* For options which change ptmode, make sure that mode is only changed if 1) no process has procp slot, or 2) the process which has procp slot is doing the mode change. Note that interrupt bits in t_ptmode should be set after t_procp has been filled; otherwise disaster will happen when procp = 0 is sent to psignal, when an interrupt occurs between t_ptmode interrupt bits being set and t_procp being filled. */ case PTIOCSETM: if (copyin(cmdarg, &temp, 2)) return(ttu_err(EFAULT)); if ((tp->t_procp) && (tp->t_procp != u.u_procp)) return(ttu_err(EACCES)); if (temp & (MODEINT|CANQINT|OUTQINT)) tp->t_procp = u.u_procp; else tp->t_procp = 0; tp->t_ptmode = temp; break; case PTIOCGETM: temp = tp->t_ptmode; if (copyout(&temp, cmdarg, 2)) return(ttu_err(EFAULT)); break; case PTIOCSETMB: if (copyin(cmdarg, &temp, 2)) return(ttu_err(EFAULT)); if ( (tp->t_procp) && (tp->t_procp != u.u_procp) ) return(ttu_err(EACCES)); if ((tp->t_ptmode|temp) & (MODEINT|CANQINT|OUTQINT)) tp->t_procp = u.u_procp; else tp->t_procp = 0; tp->t_ptmode |= temp; break; case PTIOCCLRMB: if (copyin(cmdarg, &temp, 2)) return(ttu_err(EFAULT)); if ( (tp->t_procp) && (tp->t_procp != u.u_procp) ) return(ttu_err(EACCES)); tp->t_ptmode &= (~temp); if (tp->t_ptmode & (MODEINT|CANQINT|OUTQINT)) tp->t_procp = u.u_procp; else tp->t_procp = 0; break; case TIOCSETF2: if (copyin(cmdarg, &temp, 2)) return(ttu_err(EFAULT)); tp->t_flag2 = temp; break; case TIOCGETF2: temp = tp->t_flag2; if (copyout(&temp, cmdarg, 2)) return(ttu_err(EFAULT)); break; case TIOCSETF2B: if (copyin(cmdarg, &temp, 2)) return(ttu_err(EFAULT)); tp->t_flag2 |= temp; break; case TIOCCLRF2B: if (copyin(cmdarg, &temp, 2)) return(ttu_err(EFAULT)); tp->t_flag2 &= (~temp); break; default: u.u_error = EINVAL; break; } } ptsioctl(dev, ip, cmd, cmdarg) int dev; char *ip; int cmd; char *cmdarg; { register struct pty *tp; tp = &pty[dev.d_minor]; ttioctl(tp, cmd, cmdarg); }