/* @(#)tt0.c 1.4 */ /* * Line discipline 0 * Includes Terminal Handling */ #include "sys/param.h" #include "sys/types.h" #include "sys/systm.h" #include "sys/conf.h" #include "sys/dir.h" #include "sys/signal.h" #include "sys/user.h" #include "sys/errno.h" #include "sys/proc.h" #include "sys/file.h" #include "sys/tty.h" #include "sys/termio.h" #include "sys/crtctl.h" #include "sys/sysinfo.h" #include "sys/var.h" extern char partab[]; char colsave, rowsave; /* temp save for high queue restore */ struct clist tempq; /* temp for echo during high queue */ /* * routine called on first teletype open. * establishes a process group for distribution * of quits and interrupts from the tty. */ ttopen(tp) register struct tty *tp; { register struct proc *pp; pp = u.u_procp; if ((pp->p_pid == pp->p_pgrp) && (u.u_ttyp == NULL) && (tp->t_pgrp == 0)) { u.u_ttyp = &tp->t_pgrp; tp->t_pgrp = pp->p_pgrp; } ttioctl(tp, LDOPEN, 0, 0); tp->t_state &= ~WOPEN; tp->t_state |= ISOPEN; } ttclose(tp) register struct tty *tp; { if ((tp->t_state&ISOPEN) == 0) return; tp->t_state &= ~ISOPEN; tp->t_pgrp = 0; ttioctl(tp, LDCLOSE, 0, 0); } /* * Called from device's read routine after it has * calculated the tty-structure given as argument. */ ttread(tp) register struct tty *tp; { register struct user *up; register struct clist *tq; up = &u; tq = &tp->t_canq; if (tq->c_cc == 0) canon(tp); while (up->u_count!=0 && up->u_error==0) { if (up->u_count >= CLSIZE) { register n; register struct cblock *cp; if ((cp = getcb(tq)) == NULL) break; n = min(up->u_count, (unsigned)(cp->c_last - cp->c_first)); if (copyout((caddr_t)&cp->c_data[cp->c_first], (caddr_t)up->u_base, n)) up->u_error = EFAULT; putcf((struct cblock *)cp); up->u_base += n; up->u_count -= n; } else { register c; if ((c = getc(tq)) < 0) break; if (subyte(up->u_base++, c)) up->u_error = EFAULT; up->u_count--; } } if (tp->t_state&TBLOCK) { if (tp->t_rawq.c_cct_proc)(tp, T_UNBLOCK); } } } /* * Called from device's write routine after it has * calculated the tty-structure given as argument. */ ttwrite(tp) register struct tty *tp; { register struct user *up; int hqflag; int col, row; up = &u; qwait: spltty(); while(tp->t_tmflag & QLOCKB) { tp->t_state |= OASLP; (void) sleep((caddr_t)&tp->t_outq, TTOPRI); } SPL0(); if (!(tp->t_state&CARR_ON)) return; hqflag = 0; while (up->u_count) { if (tp->t_outq.c_cc > tthiwat[tp->t_cflag&CBAUD]) { if (hqflag) { col = tp->t_col; row = tp->t_row; hqrelse(tp); } spltty(); (*tp->t_proc)(tp, T_OUTPUT); while (tp->t_outq.c_cc > tthiwat[tp->t_cflag&CBAUD]) { tp->t_state |= OASLP; if (sleep((caddr_t)&tp->t_outq, hqflag ? PZERO : (TTOPRI|PCATCH))) { SPL0(); goto out; } } SPL0(); if (hqflag) { tp->t_tmflag |= QLOCKI; colsave = tp->t_col; rowsave = tp->t_row; ttyctl(LCA, tp, col, row); continue; } if (tp->t_tmflag & QLOCKB) goto qwait; } if (up->u_count >= (CLSIZE/2) && tp->t_term == 0) { register n; register struct cblock *cp; if ((cp = getcf()) == NULL) break; n = min(up->u_count, (unsigned)cp->c_last); if (copyin((caddr_t)up->u_base, (caddr_t)cp->c_data, n)) { up->u_error = EFAULT; putcf((struct cblock *)cp); break; } up->u_base += n; up->u_count -= n; cp->c_last = n; ttxput(tp, cp, n); } else { register c; c = fubyte(up->u_base++); if (c<0) { up->u_error = EFAULT; break; } up->u_count--; if (c == ESC && tp->t_term) { switch (c = cpass()) { int col; case -1: continue; case ESC: goto norm; case HIQ: if (hqflag++) continue; tp->t_tmflag |= QLOCKB|QLOCKI; tp->t_hqcnt++; colsave = tp->t_col; rowsave = tp->t_row; continue; case LCA: case SVID: case DVID: case CVID: col = cpass(); default: ttyctl(c, tp, col, c==LCA ? cpass() : 0); } } else { norm: ttxput(tp, c, 0); } } } if (hqflag) { hqrelse(tp); (void) putc(QESC, &tp->t_outq); (void) putc(HQEND, &tp->t_outq); spltty(); if (tp->t_state & OASLP) { tp->t_state &= ~OASLP; wakeup((caddr_t)&tp->t_outq); } SPL0(); } out: tp->t_tmflag &= ~(QLOCKB|QLOCKI); spltty(); (*tp->t_proc)(tp, T_OUTPUT); SPL0(); } /* * Place a character on raw TTY input queue, putting in delimiters * and waking up top half as needed. * Also echo if required. */ #define LCLESC 0400 ttin(tp) register struct tty *tp; { register c; register flg; register char *cp; ushort nchar, nc; nchar = tp->t_rbuf.c_size - tp->t_rbuf.c_count; /* reinit rx control block */ tp->t_rbuf.c_count = tp->t_rbuf.c_size; if (nchar==0) return; flg = tp->t_iflag; /* KMC does all but IXOFF */ if (tp->t_state&EXTPROC) flg &= IXOFF; nc = nchar; cp = tp->t_rbuf.c_ptr; if (nc < cfreelist.c_size || (flg & (INLCR|IGNCR|ICRNL|IUCLC)) || tp->t_term) { /* must do per character processing */ for ( ;nc--; cp++) { c = *cp; if (tp->t_term) { c &= 0177; if ((c = (*termsw[tp->t_term].t_input) (c,tp)) == -1) continue; if (c & CPRES) { (void) putc(ESC, &tp->t_rawq); (void) putc(c, &tp->t_rawq); continue; } } if (c == '\n' && flg&INLCR) *cp = c = '\r'; else if (c == '\r') if (flg&IGNCR) continue; else if (flg&ICRNL) *cp = c = '\n'; if (flg&IUCLC && 'A' <= c && c <= 'Z') c += 'a' - 'A'; if (putc(c, &tp->t_rawq)) continue; sysinfo.rawch++; } cp = tp->t_rbuf.c_ptr; } else { /* may do block processing */ putcb(CMATCH((struct cblock *)cp), &tp->t_rawq); sysinfo.rawch += nc; /* allocate new rx buffer */ if ((tp->t_rbuf.c_ptr = getcf()->c_data) == ((struct cblock *)NULL)->c_data) { tp->t_rbuf.c_ptr = NULL; return; } tp->t_rbuf.c_count = cfreelist.c_size; tp->t_rbuf.c_size = cfreelist.c_size; } if (tp->t_rawq.c_cc > TTXOHI) { if (flg&IXOFF && !(tp->t_state&TBLOCK)) (*tp->t_proc)(tp, T_BLOCK); if (tp->t_rawq.c_cc > TTYHOG) { ttyflush(tp, FREAD); return; } } flg = lobyte(tp->t_lflag); if (tp->t_outq.c_cc > (tthiwat[tp->t_cflag&CBAUD] + TTECHI)) flg &= ~(ECHO|ECHOK|ECHONL|ECHOE); if (flg) while (nchar--) { c = *cp++; if (flg&ISIG) { if (c == tp->t_cc[VINTR]) { signal(tp->t_pgrp, SIGINT); if (!(flg&NOFLSH) && tp->t_hqcnt==0) ttyflush(tp, (FREAD|FWRITE)); continue; } if (c == tp->t_cc[VQUIT]) { signal(tp->t_pgrp, SIGQUIT); if (!(flg&NOFLSH) && tp->t_hqcnt==0) ttyflush(tp, (FREAD|FWRITE)); continue; } } if (flg&ICANON) { if (tp->t_state&CLESC) { flg |= LCLESC; tp->t_state &= ~CLESC; } if (c == '\n') { if (flg&ECHONL) flg |= ECHO; tp->t_delct++; } else if (c == '\\') { tp->t_state |= CLESC; if (flg&XCASE) { c |= QESC; if (flg&LCLESC) tp->t_state &= ~CLESC; } } else if (c == tp->t_cc[VEOL] || c == tp->t_cc[VEOL2]) tp->t_delct++; else if (!(flg&LCLESC)) { if (c == tp->t_cc[VERASE] && flg&ECHOE) { if (flg&ECHO) ttxputi(tp, '\b'); flg |= ECHO; ttxputi(tp, ' '); c = '\b'; } else if (c == tp->t_cc[VKILL] && flg&ECHOK) { if (flg&ECHO) ttxputi(tp, c); flg |= ECHO; c = '\n'; } else if (c == tp->t_cc[VEOF]) { flg &= ~ECHO; tp->t_delct++; } } } if (flg&ECHO) { ttxputi(tp, c); (*tp->t_proc)(tp, T_OUTPUT); } } if (!(flg&ICANON)) { tp->t_state &= ~RTO; if (tp->t_rawq.c_cc >= tp->t_cc[VMIN]) tp->t_delct = 1; else if (tp->t_cc[VTIME]) { if (!(tp->t_state&TACT)) tttimeo(tp); } } if (tp->t_delct && (tp->t_state&IASLP)) { tp->t_state &= ~IASLP; wakeup((caddr_t)&tp->t_rawq); } } /* * Interrupt interface to ttxput. * Checks for High Queue write in progress, and saves characters to be echoed. */ ttxputi(tp, c) register struct tty *tp; { if (tp->t_tmflag & QLOCKI) { (void) putc(c,&tempq); return; } else ttxput(tp, c, 0); } /* * Put character(s) on TTY output queue, adding delays, * expanding tabs, and handling the CR/NL bit. * It is called both from the base level for output, and from * interrupt level for echoing. */ /* VARARGS1 */ ttxput(tp, ucp, ncode) register struct tty *tp; union { struct ch { /* machine dependent union */ char dum[3]; unsigned char theaddr; } ch; int thechar; struct cblock *ptr; } ucp; { register c; register flg; register unsigned char *cp; register char *colp; int ctype; int cs; struct cblock *scf; /* KMC does all but XCASE, virt term needs CR info for t_col */ if (tp->t_state&EXTPROC) { if (tp->t_term || tp->t_lflag&XCASE) flg = tp->t_oflag&(OPOST|OLCUC|ONLRET|ONLCR); else flg = 0; } else flg = tp->t_oflag; if (ncode == 0) { ncode++; if (!(flg&OPOST)) { sysinfo.outch++; (void) putc(ucp.thechar, &tp->t_outq); return; } cp = (unsigned char *)&ucp.ch.theaddr; scf = NULL; } else { if (!(flg&OPOST)) { sysinfo.outch += ncode; putcb(ucp.ptr, &tp->t_outq); return; } cp = (unsigned char *)&ucp.ptr->c_data[ucp.ptr->c_first]; scf = ucp.ptr; } while (ncode--) { c = *cp++; if (c >= 0200) { /* spl5-0 */ if (c == QESC && !(tp->t_state&EXTPROC)) (void) putc(QESC, &tp->t_outq); sysinfo.outch++; (void) putc(c, &tp->t_outq); continue; } /* * Generate escapes for upper-case-only terminals. */ if (tp->t_lflag&XCASE) { colp = "({)}!|^~'`\\\\"; while(*colp++) if (c == *colp++) { ttxput(tp, '\\'|0200, 0); c = colp[-2]; break; } if ('A' <= c && c <= 'Z') ttxput(tp, '\\'|0200, 0); } if (flg&OLCUC && 'a' <= c && c <= 'z') c += 'A' - 'a'; cs = c; /* * Calculate delays. * The numbers here represent clock ticks * and are not necessarily optimal for all terminals. * The delays are indicated by characters above 0200. */ ctype = partab[c]; colp = &tp->t_col; c = 0; switch (ctype&077) { case 0: /* ordinary */ (*colp)++; case 1: /* non-printing */ break; case 2: /* backspace */ if (flg&BSDLY) c = 2; if (*colp) (*colp)--; break; case 3: /* line feed */ top: if (tp->t_term) { if (tp->t_vrow && tp->t_row >= tp->t_lrow) { ttyctl(UVSCN, tp); continue; } if (tp->t_tmflag & SNL) { ttyctl(NL, tp); continue; } } if (flg&ONLRET) goto cr; if (tp->t_row < tp->t_lrow) tp->t_row++; if (flg&ONLCR) { if ((!(tp->t_state&EXTPROC)) && !(flg&ONOCR && *colp==0)) { sysinfo.outch++; (void) putc('\r', &tp->t_outq); } goto cr; } nl: if (flg&NLDLY) c = 5; break; case 4: /* tab */ c = 8 - ((*colp)&07); *colp += c; if (!(tp->t_state&EXTPROC)) { ctype = flg&TABDLY; if (ctype == TAB0) { c = 0; } else if (ctype == TAB1) { if (c < 5) c = 0; } else if (ctype == TAB2) { c = 2; } else if (ctype == TAB3) { sysinfo.outch += c; do (void) putc(' ', &tp->t_outq); while (--c); continue; } } break; case 5: /* vertical tab */ if (flg&VTDLY) c = 0177; break; case 6: /* carriage return */ if (flg&OCRNL) { cs = '\n'; goto nl; } if (flg&ONOCR && *colp == 0) continue; cr: if (!(tp->t_state&EXTPROC)) { ctype = flg&CRDLY; if (ctype == CR1) { if (*colp) c = max((unsigned)((*colp>>4)+3), 6); } else if (ctype == CR2) { c = 5; } else if (ctype == CR3) { c = 9; } } *colp = 0; break; case 7: /* form feed */ if (flg&FFDLY) c = 0177; break; } sysinfo.outch++; if (tp->t_term && *colp >= 80 && tp->t_row >= tp->t_lrow && tp->t_tmflag & LCF) { ttyctl(VHOME, tp); ttyctl(DL, tp); ttyctl(LCA, tp, 79, tp->t_lrow-1); (*colp)++; } if (tp->t_term==0) (void) putc(cs, &tp->t_outq); else qputc(cs, &tp->t_outq); if (!(tp->t_state&EXTPROC)) { if (c) { if ((c < 32) && flg&OFILL) { if (flg&OFDEL) cs = 0177; else cs = 0; (void) putc(cs, &tp->t_outq); if (c > 3) (void) putc(cs, &tp->t_outq); } else { (void) putc(QESC, &tp->t_outq); (void) putc(c|0200, &tp->t_outq); } } } if (*colp >= 80 && tp->t_term && tp->t_tmflag&ANL) if (tp->t_tmflag&LCF) ttyctl(LCA, tp, 0, tp->t_row+1); else { if ((flg&ONLCR) == 0) ttxputi(tp,'\r'); cs = '\n'; goto top; } } if (scf != NULL) putcf(scf); } /* * Get next packet from output queue. * Called from xmit interrupt complete. */ ttout(tp) register struct tty *tp; { register struct ccblock *tbuf; register c; register char *cptr; register retval; extern ttrstrt(); if (tp->t_state&TTIOW && tp->t_outq.c_cc==0) { tp->t_state &= ~TTIOW; wakeup((caddr_t)&tp->t_oflag); } delay: tbuf = &tp->t_tbuf; if (hibyte(tp->t_lflag)) { if (tbuf->c_ptr) { putcf(CMATCH((struct cblock *)tbuf->c_ptr)); tbuf->c_ptr = NULL; } tp->t_state |= TIMEOUT; timeout(ttrstrt, (caddr_t)tp, (int)((hibyte(tp->t_lflag)&0177)+6)); hibyte(tp->t_lflag) = 0; return(0); } retval = 0; if (((tp->t_state&EXTPROC) || (!(tp->t_oflag&OPOST))) && tp->t_term==0) { if (tbuf->c_ptr) putcf(CMATCH((struct cblock *)tbuf->c_ptr)); if ((tbuf->c_ptr = (char *)getcb(&tp->t_outq)) == NULL) return(0); tbuf->c_count = ((struct cblock *)tbuf->c_ptr)->c_last - ((struct cblock *)tbuf->c_ptr)->c_first; tbuf->c_size = tbuf->c_count; tbuf->c_ptr = &((struct cblock *)tbuf->c_ptr)->c_data [((struct cblock *)tbuf->c_ptr)->c_first]; retval = CPRES; } else { /* watch for timing */ if (tbuf->c_ptr == NULL) { if ((tbuf->c_ptr = getcf()->c_data) == ((struct cblock *)NULL)->c_data) { tbuf->c_ptr = NULL; return(0); /* Add restart? */ } } tbuf->c_count = 0; cptr = tbuf->c_ptr; while ((c=getc(&tp->t_outq)) >= 0) { if (c == QESC) { if ((c = getc(&tp->t_outq)) < 0) break; if (c == HQEND) { if (tp->t_term) tp->t_hqcnt--; continue; } if (c > 0200 && !(tp->t_state&EXTPROC)) { hibyte(tp->t_lflag) = c; if (!retval) goto delay; break; } } retval = CPRES; *cptr++ = c; tbuf->c_count++; if (tbuf->c_count >= cfreelist.c_size) break; } tbuf->c_size = tbuf->c_count; } if (tp->t_state&OASLP && tp->t_outq.c_cc<=ttlowat[tp->t_cflag&CBAUD]) { tp->t_state &= ~OASLP; wakeup((caddr_t)&tp->t_outq); } return(retval); } tttimeo(tp) register struct tty *tp; { tp->t_state &= ~TACT; if (tp->t_lflag&ICANON || tp->t_cc[VTIME] == 0) return; if (tp->t_rawq.c_cc == 0 && tp->t_cc[VMIN]) return; if (tp->t_state&RTO) { tp->t_delct = 1; if (tp->t_state&IASLP) { tp->t_state &= ~IASLP; wakeup((caddr_t)&tp->t_rawq); } } else { tp->t_state |= RTO|TACT; timeout(tttimeo, (caddr_t)tp, (int)(tp->t_cc[VTIME]*(short)((short)v.v_hz/10))); } } /* * I/O control interface */ /* ARGSUSED */ ttioctl(tp, cmd, arg, mode) register struct tty *tp; { ushort chg; struct termcb termblk; register struct termcb *tbp; switch(cmd) { case LDOPEN: if (tp->t_rbuf.c_ptr == NULL) { /* allocate RX buffer */ while((tp->t_rbuf.c_ptr = getcf()->c_data) == ((struct cblock *)NULL)->c_data) { tp->t_rbuf.c_ptr = NULL; cfreelist.c_flag = 1; (void) sleep((caddr_t)&cfreelist, TTOPRI); } tp->t_rbuf.c_count = cfreelist.c_size; tp->t_rbuf.c_size = cfreelist.c_size; (*tp->t_proc)(tp, T_INPUT); } break; case LDCLOSE: spltty(); (*tp->t_proc)(tp, T_RESUME); SPL0(); ttywait(tp); ttyflush(tp, (FREAD|FWRITE)); if (tp->t_tbuf.c_ptr) { putcf(CMATCH((struct cblock *)tp->t_tbuf.c_ptr)); tp->t_tbuf.c_ptr = NULL; tp->t_tbuf.c_count = 0; tp->t_tbuf.c_size = 0; } if (tp->t_rbuf.c_ptr) { putcf(CMATCH((struct cblock *)tp->t_rbuf.c_ptr)); tp->t_rbuf.c_ptr = NULL; tp->t_rbuf.c_count = 0; tp->t_rbuf.c_size = 0; } tp->t_tmflag = 0; break; case LDCHG: chg = tp->t_lflag^arg; if (!(chg&ICANON)) break; spltty(); if (tp->t_canq.c_cc) { if (tp->t_rawq.c_cc) { tp->t_canq.c_cc += tp->t_rawq.c_cc; tp->t_canq.c_cl->c_next = tp->t_rawq.c_cf; tp->t_canq.c_cl = tp->t_rawq.c_cl; } tp->t_rawq = tp->t_canq; tp->t_canq = ttnulq; } tp->t_delct = tp->t_rawq.c_cc; SPL0(); break; case LDGETT: tbp = &termblk; tbp->st_flgs = tp->t_tmflag; tbp->st_termt = tp->t_term; tbp->st_crow = tp->t_row; tbp->st_ccol = tp->t_col; tbp->st_vrow = tp->t_vrow; tbp->st_lrow = tp->t_lrow; if (copyout((caddr_t)tbp, (caddr_t)arg, sizeof(termblk))) u.u_error = EFAULT; break; case LDSETT: tbp = &termblk; if (copyin((caddr_t)arg, (caddr_t)tbp, sizeof(termblk))) { u.u_error = EFAULT; break; } if ((unsigned)tbp->st_termt >= termcnt) { u.u_error = ENXIO; break; } if (tbp->st_termt) { (*termsw[tbp->st_termt].t_ioctl) (tp, tp->t_term==tbp->st_termt ? LDCHG : LDOPEN, tbp->st_vrow); if (u.u_error) break; tp->t_vrow = tbp->st_vrow; tp->t_term = tbp->st_termt; if (tbp->st_flgs&TM_SET) tp->t_tmflag = tbp->st_flgs & ~TM_SET; } else { tp->t_term = 0; } tp->t_state &= ~CLESC; break; default: break; } } /************** ADDITIONS FOR TERMINAL HANDLERS **********************/ /* * release the high priority queue for interrupts. * copy over any received characters while queue was locked. */ hqrelse(tp) register struct tty *tp; { register c; ttyctl(LCA, tp, colsave, rowsave); spltty(); while((c = getc(&tempq)) >= 0) ttxput(tp, c, 0); tp->t_tmflag &= ~QLOCKI; SPL0(); } /* * put a character on the output queue, * checking first to see if it is a ESC. */ qputc(c, qp) register c; struct clist *qp; { if (c == ESC) (void) putc(c, qp); (void) putc(c, qp); } #ifdef notdef /* simulate Up Variable SCreeN as common routine */ ttuvscn(tp) register struct tty *tp; { ttyctl(VHOME, tp); ttyctl(DL, tp); ttyctl(LCA, tp, 0, tp->t_lrow); } /* simulate Down Variable SCreeN as common routine */ ttdvscn(tp) register struct tty *tp; { ttyctl(VHOME, tp); ttyctl(IL, tp); } #endif char colpres, rowpres; /* VARARGS */ ttyctl(ac, tp, acol, arow) register struct tty *tp; { register char *colp; register c; int sps; c = ac; colp = &tp->t_col; sps = spltty(); colpres = *colp; rowpres = tp->t_row; switch(c) { case CUP: case DSCRL: if (tp->t_row == 0) goto out; tp->t_row--; break; case CDN: case USCRL: if (tp->t_row >= tp->t_lrow) goto out; tp->t_row++; break; case UVSCN: *colp = 0; tp->t_row = tp->t_lrow; break; case DVSCN: *colp = 0; tp->t_row = tp->t_vrow; break; case CRI: case STB: case SPB: if (*colp >= 79) goto out; (*colp)++; break; case CLE: if (*colp == 0) goto out; (*colp)--; break; case HOME: case CS: case CM: tp->t_row = 0; case DL: case IL: *colp = 0; break; case VHOME: *colp = 0; tp->t_row = tp->t_vrow; c = LCA; break; case LCA: *colp = acol; tp->t_row = arow; break; case ASEG: tp->t_row = (short)(tp->t_row+24)%(short)(tp->t_lrow+1); break; case NL: if (tp->t_row < tp->t_lrow) tp->t_row++; case CRTN: *colp = 0; break; case SVID: tp->t_dstat |= acol; c = DVID; break; case CVID: tp->t_dstat &= ~acol; c = DVID; break; case DVID: tp->t_dstat = acol; break; } (*termsw[tp->t_term].t_output)(c, tp); out: splx(sps); }