/* :set ts=4 * ndgb.c -- Digiboard driver main file * * This is part of the ndgb Digiboard PC/Xi, PC/Xe, PC/Xem and PCI/Xr driver * package for FreeBSD. * * Acknowledgments. The ndgb driver is based on: * 1. Serge Babkin's original PC/Xi PC/Xe dgb driver (which in turn * was based on Bruce Evans' FreeBSD sio driver and Troy De Jongh's * Linux driver). * 2. PCI support is based on the example provided by David Greenman's Cyclades Y PCI driver. * 3. The LKM module (for runtime card intialization) is based * on Terrence R. Lambert's 1993 miscmod.c example. * 4. FreeBSD 3.0 support was contributed by Eric Hernes. * * * PEB/DBU May/June 1998 : Added PC/Xem and PCI Xr support and LKM-based * adapter initialization. */ /*- * dgb.c $Id: dgb.c,v 1.20.2.1 1996/12/19 14:57:34 davidg Exp $ * * Digiboard driver. * * Stage 1. "Better than nothing". * Stage 2. "Gee, it works!". * * Based on sio driver by Bruce Evans and on Linux driver by Troy * De Jongh or * which is under GNU General Public License version 2 so this driver * is forced to be under GPL 2 too. * * Written by Serge Babkin, * Joint Stock Commercial Bank "Chelindbank" * (Chelyabinsk, Russia) * babkin@hq.icb.chel.su * * Assorted hacks to make it more functional and working under 3.0-current. * Fixed broken routines to prevent processes hanging on closed (thanks * to Bruce for his patience and assistance). Thanks also to Maxim Bolotin * for his patches which did most of the work to get this * running under 2.2/3.0-current. * Implemented ioctls: TIOCMSDTRWAIT, TIOCMGDTRWAIT, TIOCTIMESTAMP & * TIOCDCDTIMESTAMP. * Sysctl debug flag is now a bitflag, to filter noise during debugging. * David L. Nugent */ #include "ndgb.h" #if NNDGB > 0 #include #include static int dgbprobe( struct isa_device *dev ) { return DGBdgbprobe( dev ); } static int dgbattach( struct isa_device *dev ) { return DGBdgbattach( dev ); } /* xlat bsd termios flags to dgb sys-v style */ static tcflag_t dgbflags(struct dbgflagtbl *tbl, tcflag_t input) { tcflag_t output = 0; int i; for (i=0; tbl[i].in_mask != (tcflag_t)-1; i++) { if ((input & tbl[i].in_mask) == tbl[i].in_val) output |= tbl[i].out_val; } return output; } /* ARGSUSED */ static int dgbopen(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { struct dgb_softc *sc; struct tty *tp; int unit; int mynor; int pnum; struct dgb_p *port; int s,cs; int error; volatile struct board_chan *bc; printf( "dgbopen Top\n" ); error=0; mynor=minor(dev); unit=MINOR_TO_UNIT(mynor); pnum=MINOR_TO_PORT(mynor); if(unit >= NNDGB) { DPRINT2(DB_EXCEPT,"dgb%d: try to open a nonexisting card\n",unit); printf( "dgbopen return1\n" ); return ENXIO; } sc=&dgb_softc[unit]; if(sc->status!=ENABLED) { DPRINT2(DB_EXCEPT,"dgb%d: try to open a disabled card\n",unit); printf( "dgbopen return2\n" ); return ENXIO; } if(pnum>=sc->numports) { DPRINT3(DB_EXCEPT,"dgb%d: try to open non-existing port %d\n",unit,pnum); printf( "dgbopen return3\n" ); return ENXIO; } if(mynor & CONTROL_MASK) { printf( "dgbopen return4\n" ); return 0; } tp=&sc->ttys[pnum]; port=&sc->ports[pnum]; bc=port->brdchan; open_top: s=spltty(); while(port->closing) { error=tsleep(&port->closing, TTOPRI|PCATCH, "dgocl", 0); if(error) { printf( "dgbopen goto out 1\n" ); DPRINT4(DB_OPEN,"dgb%d: port%d: tsleep(dgocl) error=%d\n",unit,pnum,error); goto out; } } if (tp->t_state & TS_ISOPEN) { /* * The device is open, so everything has been initialized. * Handle conflicts. */ if (mynor & CALLOUT_MASK) { if (!port->active_out) { error = EBUSY; DPRINT4(DB_OPEN,"dgb%d: port%d: BUSY error=%d\n",unit,pnum,error); printf( "dgbopen goto out 2\n" ); goto out; } } else { if (port->active_out) { if (flag & O_NONBLOCK) { error = EBUSY; DPRINT4(DB_OPEN,"dgb%d: port%d: BUSY error=%d\n",unit,pnum,error); printf( "dgbopen goto out 3\n" ); goto out; } error = tsleep(&port->active_out, TTIPRI | PCATCH, "dgbi", 0); if (error != 0) { DPRINT4(DB_OPEN,"dgb%d: port%d: tsleep(dgbi) error=%d\n", unit,pnum,error); printf( "dgbopen goto out 4\n" ); goto out; } splx(s); goto open_top; } } if (tp->t_state & TS_XCLUDE && p->p_ucred->cr_uid != 0) { error = EBUSY; printf( "dgbopen goto out 5\n" ); goto out; } } else { printf( "Device isn't open top\n" ); /* * The device isn't open, so there are no conflicts. * Initialize it. Initialization is done twice in many * cases: to preempt sleeping callin opens if we are * callout, and to complete a callin open after DCD rises. */ tp->t_oproc=dgbstart; tp->t_param=dgbparam; tp->t_dev=dev; tp->t_termios= (mynor & CALLOUT_MASK) ? port->it_out : port->it_in; cs=splclock(); DGBsetwin(sc,0); port->imodem=bc->mstat; bc->rout=bc->rin; /* clear input queue */ /* PEB: July '98 -- For debugging purposes */ printf( "Resetting buffer pointers\n" ); bc->rout=bc->rin=0; /* clear input queue */ bc->tout=bc->tin=0; /* clear output queue */ bc->ilow=0;bc->iempty=0; bc->idata=1; #ifdef PRINT_BUFSIZE printf("dgb buffers tx=%x:%x rx=%x:%x\n",bc->tseg,bc->tmax,bc->rseg,bc->rmax); #endif printf("dgb buffers tx=%x:%x rx=%x:%x\n",bc->tseg,bc->tmax,bc->rseg,bc->rmax); DGBhidewin(sc); splx(cs); port->wopeners++; error=dgbparam(tp, &tp->t_termios); port->wopeners--; if(error!=0) { DPRINT4(DB_OPEN,"dgb%d: port%d: dgbparam error=%d\n",unit,pnum,error); printf( "dgbopen goto out 6\n" ); goto out; } ttsetwater(tp); /* handle fake DCD for callout devices */ /* and initial DCD */ if( (port->imodem & port->dcd) || mynor & CALLOUT_MASK ) linesw[tp->t_line].l_modem(tp,1); } /* * Wait for DCD if necessary. */ if (!(tp->t_state & TS_CARR_ON) && !(mynor & CALLOUT_MASK) && !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) { ++port->wopeners; error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "dgdcd", 0); --port->wopeners; if (error != 0) { DPRINT4(DB_OPEN,"dgb%d: port%d: tsleep(dgdcd) error=%d\n",unit,pnum,error); printf( "dgbopen goto out 7\n" ); goto out; } splx(s); goto open_top; } error = linesw[tp->t_line].l_open(dev, tp); disc_optim(tp,&tp->t_termios); DPRINT4(DB_OPEN,"dgb%d: port%d: l_open error=%d\n",unit,pnum,error); if (tp->t_state & TS_ISOPEN && mynor & CALLOUT_MASK) port->active_out = TRUE; port->used=1; /* If any port is open (i.e. the open() call is completed for it) * the device is busy */ out: printf( "dgbopen out\n" ); disc_optim(tp,&tp->t_termios); splx(s); if( !(tp->t_state & TS_ISOPEN) && port->wopeners==0 ) dgbhardclose(port); DPRINT4(DB_OPEN,"dgb%d: port%d: open() returns %d\n",unit,pnum,error); printf( "dgbopen returning error:%d\n",error ); return error; } /*ARGSUSED*/ static int dgbclose(dev, flag, mode, p) dev_t dev; int flag; int mode; struct proc *p; { int mynor; struct tty *tp; int unit, pnum; struct dgb_softc *sc; struct dgb_p *port; int s; int i; mynor=minor(dev); if(mynor & CONTROL_MASK) return 0; unit=MINOR_TO_UNIT(mynor); pnum=MINOR_TO_PORT(mynor); sc=&dgb_softc[unit]; tp=&sc->ttys[pnum]; port=sc->ports+pnum; DPRINT3(DB_CLOSE,"dgb%d: port%d: closing\n",unit,pnum); DPRINT3(DB_CLOSE,"dgb%d: port%d: draining port\n",unit,pnum); dgb_drain_or_flush(port); s=spltty(); port->closing=1; DPRINT3(DB_CLOSE,"dgb%d: port%d: closing line disc\n",unit,pnum); linesw[tp->t_line].l_close(tp,flag); disc_optim(tp,&tp->t_termios); DPRINT3(DB_CLOSE,"dgb%d: port%d: hard closing\n",unit,pnum); dgbhardclose(port); DPRINT3(DB_CLOSE,"dgb%d: port%d: closing tty\n",unit,pnum); ttyclose(tp); port->closing=0; wakeup(&port->closing); port->used=0; /* mark the card idle when all ports are closed */ for(i=0; inumports; i++) if(sc->ports[i].used) break; splx(s); DPRINT3(DB_CLOSE,"dgb%d: port%d: closed\n",unit,pnum); wakeup(TSA_CARR_ON(tp)); wakeup(&port->active_out); port->active_out=0; DPRINT3(DB_CLOSE,"dgb%d: port%d: close exit\n",unit,pnum); return 0; } static void dgbhardclose(port) struct dgb_p *port; { struct dgb_softc *sc=&dgb_softc[port->unit]; volatile struct board_chan *bc=port->brdchan; int cs; cs=splclock(); port->do_timestamp = 0; DGBsetwin(sc,0); bc->idata=0; bc->iempty=0; bc->ilow=0; if(port->tty->t_cflag & HUPCL) { port->omodem &= ~(RTS|DTR); fepcmd(port, SETMODEM, 0, DTR|RTS, 0, 1); } DGBhidewin(sc); splx(cs); timeout(dgb_pause, &port->brdchan, hz/2); tsleep(&port->brdchan, TTIPRI | PCATCH, "dgclo", 0); } static void dgb_pause(chan) void *chan; { wakeup((caddr_t)chan); } static int dgbread(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; struct tty *tp; int error, unit, pnum; mynor=minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); unit=MINOR_TO_UNIT(mynor); pnum=MINOR_TO_PORT(mynor); tp=&dgb_softc[unit].ttys[pnum]; error=linesw[tp->t_line].l_read(tp, uio, flag); DPRINT4(DB_RD,"dgb%d: port%d: read() returns %d\n",unit,pnum,error); return error; } static int dgbwrite(dev, uio, flag) dev_t dev; struct uio *uio; int flag; { int mynor; struct tty *tp; int error, unit, pnum; mynor=minor(dev); if (mynor & CONTROL_MASK) return (ENODEV); unit=MINOR_TO_UNIT(mynor); pnum=MINOR_TO_PORT(mynor); tp=&dgb_softc[unit].ttys[pnum]; error=linesw[tp->t_line].l_write(tp, uio, flag); DPRINT4(DB_WR,"dgb%d: port%d: write() returns %d\n",unit,pnum,error); printf("dgb%d: port%d: write() returns %d\n",unit,pnum,error); return error; } void dgbpoll( void *unit_c ) { int unit=(int)unit_c; int pnum; struct dgb_p *port; struct dgb_softc *sc=&dgb_softc[unit]; int head, tail; u_char *eventbuf; int event, mstat, lstat; volatile struct board_chan *bc; struct tty *tp; int rhead, rtail; int whead, wtail; int size; int c=0; u_char *ptr; int ocount; int ibuf_full,obuf_full; int nchars; BoardMemWinState ws=DGBbmws_get(sc); if(sc->status==DISABLED) { printf("dgb%d: polling of disabled board stopped\n",unit); return; } DGBsetwin(sc,0); head=sc->mailbox->ein; tail=sc->mailbox->eout; while(head!=tail) { if(head >= FEP_IMAX-FEP_ISTART || tail >= FEP_IMAX-FEP_ISTART || (head|tail) & 03 ) { printf("dgb%d: event queue's head or tail is wrong! hd=%d,tl=%d\n", unit,head,tail); break; } eventbuf=sc->vmem+tail+FEP_ISTART; pnum=eventbuf[0]; event=eventbuf[1]; mstat=eventbuf[2]; lstat=eventbuf[3]; port=&sc->ports[pnum]; bc=port->brdchan; tp=&sc->ttys[pnum]; if(pnum>=sc->numports || port->status==DISABLED) { printf("dgb%d: port%d: got event on nonexisting port\n",unit,pnum); } else if(port->used || port->wopeners>0 ) { int wrapmask=port->rxbufsize-1; if( !(event & ALL_IND) ) printf("dgb%d: port%d: ? event 0x%x mstat 0x%x lstat 0x%x\n", unit, pnum, event, mstat, lstat); if(event & DATA_IND) { DPRINT3(DB_DATA,"dgb%d: port%d: DATA_IND\n",unit,pnum); rhead=bc->rin & wrapmask; rtail=bc->rout & wrapmask; printf("dgb%d: port%d: DATA_IND, rhead:%d, rtail:%d\n",unit,pnum, rhead, rtail); if( !(tp->t_cflag & CREAD) || !port->used ) { bc->rout=rhead; printf( "goto end CREAD\n" ); goto end_of_data; } if(bc->orun) { printf("dgb%d: port%d: overrun\n", unit, pnum); bc->orun=0; } if(!(tp->t_state & TS_ISOPEN)) { printf( "goto end 2\n" ); goto end_of_data; } for(ibuf_full=FALSE; rhead!=rtail && !ibuf_full;) { DPRINT5(DB_RXDATA,"dgb%d: port%d: p rx head=%d tail=%d\n", unit,pnum,rhead,rtail); /* "size" is the number of bytes available between here and the buffer wrappoint */ /*if(rhead>rtail)*/ if(rhead>=rtail) size=rhead-rtail; else size=port->rxbufsize-rtail; printf( "dgb%d: port%d: p rx head=%d tail=%d,size:%d, tin:%d, tout:%d\n", unit,pnum,rhead,rtail,size, bc->tin, bc->tout); ptr=port->rxptr+rtail; /* Helg: */ if( tp->t_rawq.c_cc + size > DGB_IBUFSIZE ) { size=DGB_IBUFSIZE-tp->t_rawq.c_cc; printf( "Newsize:%d\n", size); DPRINT1(DB_RXDATA,"*"); ibuf_full=TRUE; } if(size) { /*if (0) */ if (tp->t_state & TS_CAN_BYPASS_L_RINT) { printf( "TS_CAN_BYPASS\n" ); DPRINT1(DB_RXDATA,"!"); DGBtowin(sc,port->rxwin); tk_nin += size; tk_rawcc += size; tp->t_rawcc += size; /*b_to_q(ptr,size,&tp->t_rawq);*/ nchars = b_to_q(ptr,size,&tp->t_rawq); if( nchars ) { printf( "dgbpoll b_to_q failed to write %d bytes, adjusting...\n", nchars ); tk_nin -= nchars; tk_rawcc -= nchars; tp->t_rawcc -= nchars; size -= nchars; if( size < 0 ) size = 0; } DGBsetwin(sc,0); } else { int i=size; unsigned char chr; printf( "Not TS_CAN_BYPASS\n" ); do { DGBtowin(sc,port->rxwin); chr= *ptr++; DGBhidewin(sc); (*linesw[tp->t_line].l_rint)(chr, tp); } while (--i > 0 ); DGBsetwin(sc,0); } } rtail= (rtail + size) & wrapmask; bc->rout=rtail; rhead=bc->rin & wrapmask; DGBhidewin(sc); ttwakeup(tp); DGBsetwin(sc,0); } end_of_data: } if(event & MODEMCHG_IND) { DPRINT3(DB_MODEM,"dgb%d: port%d: MODEMCHG_IND\n",unit,pnum); printf( "dgb%d: port%d: MODEMCHG_IND, mstat:%xx\n",unit,pnum, mstat); port->imodem=mstat; if(mstat & port->dcd) { printf( "DCD event: mstat:%xx, port->dcd:%xx\n", mstat, port->dcd ); DGBhidewin(sc); linesw[tp->t_line].l_modem(tp,1); DGBsetwin(sc,0); printf( "DCD event: calling wakeup TSA_CARR_ON\n" ); wakeup(TSA_CARR_ON(tp)); printf( "DCD event: back from calling wakeup TSA_CARR_ON\n" ); } else { printf( "NOT A DCD event: mstat:%xx, port->dcd:%xx\n", mstat, port->dcd ); DGBhidewin(sc); linesw[tp->t_line].l_modem(tp,0); DGBsetwin(sc,0); if( port->draining) { printf( "DRAINING: port->draining:%d\n", port->draining ); port->draining=0; printf( "DCD event: calling wakeup draining\n" ); wakeup(&port->draining); printf( "DCD event: back from calling wakeup draining\n" ); } } } if(event & BREAK_IND) { printf( "dgb%d: port%d: BREAK_IND\n",unit,pnum); if((tp->t_state & TS_ISOPEN) && (tp->t_iflag & IGNBRK)) { DPRINT3(DB_BREAK,"dgb%d: port%d: BREAK_IND\n",unit,pnum); DGBhidewin(sc); linesw[tp->t_line].l_rint(TTY_BI, tp); DGBsetwin(sc,0); } } /* Helg: with output flow control */ if(event & (LOWTX_IND | EMPTYTX_IND) ) { printf("dgb%d: port%d: LOWTX_IND or EMPTYTX_IND\n",unit,pnum); DPRINT3(DB_TXDATA,"dgb%d: port%d: LOWTX_IND or EMPTYTX_IND\n",unit,pnum); if( (event & EMPTYTX_IND ) && tp->t_outq.c_cc==0 && port->draining) { port->draining=0; wakeup(&port->draining); bc->ilow=0; bc->iempty=0; } else { int wrapmask=port->txbufsize-1; for(obuf_full=FALSE; tp->t_outq.c_cc!=0 && !obuf_full; ) { int s; /* add "last-minute" data to write buffer */ if(!(tp->t_state & TS_BUSY)) { DGBhidewin(sc); #ifndef TS_ASLEEP /* post 2.0.5 FreeBSD */ ttwwakeup(tp); #else if(tp->t_outq.c_cc <= tp->t_lowat) { if(tp->t_state & TS_ASLEEP) { tp->t_state &= ~TS_ASLEEP; wakeup(TSA_OLOWAT(tp)); } /* selwakeup(&tp->t_wsel); */ } #endif DGBsetwin(sc,0); } s=spltty(); whead=bc->tin & wrapmask; wtail=bc->tout & wrapmask; if(wheadtxbufsize-whead; if(wtail==0) size--; } if(size==0) { DPRINT5(DB_WR,"dgb: head=%d tail=%d size=%d full=%d\n", whead,wtail,size,obuf_full); /*printf("dgb: head=%d tail=%d size=%d full=%d\n", whead,wtail,size,obuf_full);*/ bc->iempty=1; bc->ilow=1; obuf_full=TRUE; splx(s); break; } DGBtowin(sc,port->txwin); ocount=q_to_b(&tp->t_outq, port->txptr+whead, size); whead+=ocount; DGBsetwin(sc,0); bc->tin=whead; bc->tin=whead & wrapmask; splx(s); } if(obuf_full) { DPRINT1(DB_WR," +BUSY\n"); tp->t_state|=TS_BUSY; } else { DPRINT1(DB_WR," -BUSY\n"); DGBhidewin(sc); #ifndef TS_ASLEEP /* post 2.0.5 FreeBSD */ /* should clear TS_BUSY before ttwwakeup */ if(tp->t_state & TS_BUSY) { tp->t_state &= ~TS_BUSY; linesw[tp->t_line].l_start(tp); ttwwakeup(tp); } #else if(tp->t_state & TS_ASLEEP) { tp->t_state &= ~TS_ASLEEP; wakeup(TSA_OLOWAT(tp)); } tp->t_state &= ~TS_BUSY; #endif DGBsetwin(sc,0); } } end_of_buffer: } bc->idata=1; /* require event on incoming data */ } else { bc=port->brdchan; printf("dgb%d: port%d: got event 0x%x on closed port\n", unit,pnum,event); DPRINT4(DB_EXCEPT,"dgb%d: port%d: got event 0x%x on closed port\n", unit,pnum,event); bc->rout=bc->rin; bc->idata=bc->iempty=bc->ilow=0; } tail= (tail+4) & (FEP_IMAX-FEP_ISTART-4); } sc->mailbox->eout=tail; DGBbmws_set(sc, ws); timeout(dgbpoll, unit_c, hz/POLLSPERSEC); } #if 0 static void dgbintr(unit) int unit; { } #endif static int dgbioctl(dev, cmd, data, flag, p) dev_t dev; int cmd; caddr_t data; int flag; struct proc *p; { struct dgb_softc *sc; int unit, pnum; struct dgb_p *port; int mynor; struct tty *tp; volatile struct board_chan *bc; int error; int s,cs; int tiocm_xxx; #if defined(COMPAT_43) || defined(COMPAT_SUNOS) int oldcmd; struct termios term; #endif BoardMemWinState ws; mynor=minor(dev); unit=MINOR_TO_UNIT(mynor); pnum=MINOR_TO_PORT(mynor); sc=&dgb_softc[unit]; port=&sc->ports[pnum]; tp=&sc->ttys[pnum]; bc=port->brdchan; ws=DGBbmws_get(sc); if (mynor & CONTROL_MASK) { struct termios *ct; switch (mynor & CONTROL_MASK) { case CONTROL_INIT_STATE: ct = mynor & CALLOUT_MASK ? &port->it_out : &port->it_in; break; case CONTROL_LOCK_STATE: ct = mynor & CALLOUT_MASK ? &port->lt_out : &port->lt_in; break; default: return (ENODEV); /* /dev/nodev */ } switch (cmd) { case TIOCSETA: error = suser(p->p_ucred, &p->p_acflag); if (error != 0) return (error); *ct = *(struct termios *)data; return (0); case TIOCGETA: *(struct termios *)data = *ct; return (0); case TIOCGETD: *(int *)data = TTYDISC; return (0); case TIOCGWINSZ: bzero(data, sizeof(struct winsize)); return (0); default: return (ENOTTY); } } #if defined(COMPAT_43) || defined(COMPAT_SUNOS) term = tp->t_termios; if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) { DPRINT6(DB_PARAM,"dgb%d: port%d: dgbioctl-ISNOW c=0x%x i=0x%x l=0x%x\n",unit,pnum,term.c_cflag,term.c_iflag,term.c_lflag); } oldcmd = cmd; error = ttsetcompat(tp, &cmd, data, &term); if (error != 0) return (error); if (cmd != oldcmd) data = (caddr_t)&term; #endif if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) { int cc; struct termios *dt = (struct termios *)data; struct termios *lt = mynor & CALLOUT_MASK ? &port->lt_out : &port->lt_in; DPRINT6(DB_PARAM,"dgb%d: port%d: dgbioctl-TOSET c=0x%x i=0x%x l=0x%x\n",unit,pnum,dt->c_cflag,dt->c_iflag,dt->c_lflag); dt->c_iflag = (tp->t_iflag & lt->c_iflag) | (dt->c_iflag & ~lt->c_iflag); dt->c_oflag = (tp->t_oflag & lt->c_oflag) | (dt->c_oflag & ~lt->c_oflag); dt->c_cflag = (tp->t_cflag & lt->c_cflag) | (dt->c_cflag & ~lt->c_cflag); dt->c_lflag = (tp->t_lflag & lt->c_lflag) | (dt->c_lflag & ~lt->c_lflag); for (cc = 0; cc < NCCS; ++cc) if (lt->c_cc[cc] != 0) dt->c_cc[cc] = tp->t_cc[cc]; if (lt->c_ispeed != 0) dt->c_ispeed = tp->t_ispeed; if (lt->c_ospeed != 0) dt->c_ospeed = tp->t_ospeed; } if(cmd==TIOCSTOP) { cs=splclock(); DGBsetwin(sc,0); fepcmd(port, PAUSETX, 0, 0, 0, 0); DGBbmws_set(sc, ws); splx(cs); return 0; } else if(cmd==TIOCSTART) { cs=splclock(); DGBsetwin(sc,0); fepcmd(port, RESUMETX, 0, 0, 0, 0); DGBbmws_set(sc, ws); splx(cs); return 0; } if(cmd==TIOCSETAW || cmd==TIOCSETAF) port->mustdrain=1; error = linesw[tp->t_line].l_ioctl(tp, cmd, data, flag, p); if (error >= 0) return error; s = spltty(); error = ttioctl(tp, cmd, data, flag); disc_optim(tp,&tp->t_termios); port->mustdrain=0; if (error >= 0) { splx(s); if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) { DPRINT6(DB_PARAM,"dgb%d: port%d: dgbioctl-RES c=0x%x i=0x%x l=0x%x\n",unit,pnum,tp->t_cflag,tp->t_iflag,tp->t_lflag); } return error; } switch (cmd) { case TIOCSBRK: /* Helg: commented */ /* error=dgbdrain(port);*/ if(error!=0) { splx(s); return error; } cs=splclock(); DGBsetwin(sc,0); /* now it sends 250 millisecond break because I don't know */ /* how to send an infinite break */ fepcmd(port, SENDBREAK, 250, 0, 10, 0); DGBhidewin(sc); splx(cs); break; case TIOCCBRK: /* now it's empty */ break; case TIOCSDTR: DPRINT3(DB_MODEM,"dgb%d: port%d: set DTR\n",unit,pnum); port->omodem |= DTR; cs=splclock(); DGBsetwin(sc,0); fepcmd(port, SETMODEM, port->omodem, RTS, 0, 1); if( !(bc->mstat & DTR) ) { DPRINT3(DB_MODEM,"dgb%d: port%d: DTR is off\n",unit,pnum); } DGBhidewin(sc); splx(cs); break; case TIOCCDTR: DPRINT3(DB_MODEM,"dgb%d: port%d: reset DTR\n",unit,pnum); port->omodem &= ~DTR; cs=splclock(); DGBsetwin(sc,0); fepcmd(port, SETMODEM, port->omodem, RTS|DTR, 0, 1); if( bc->mstat & DTR ) { DPRINT3(DB_MODEM,"dgb%d: port%d: DTR is on\n",unit,pnum); } DGBhidewin(sc); splx(cs); break; case TIOCMSET: if(*(int *)data & TIOCM_DTR) port->omodem |=DTR; else port->omodem &=~DTR; if(*(int *)data & TIOCM_RTS) port->omodem |=RTS; else port->omodem &=~RTS; cs=splclock(); DGBsetwin(sc,0); fepcmd(port, SETMODEM, port->omodem, RTS|DTR, 0, 1); DGBhidewin(sc); splx(cs); break; case TIOCMBIS: if(*(int *)data & TIOCM_DTR) port->omodem |=DTR; if(*(int *)data & TIOCM_RTS) port->omodem |=RTS; cs=splclock(); DGBsetwin(sc,0); fepcmd(port, SETMODEM, port->omodem, RTS|DTR, 0, 1); DGBhidewin(sc); splx(cs); break; case TIOCMBIC: if(*(int *)data & TIOCM_DTR) port->omodem &=~DTR; if(*(int *)data & TIOCM_RTS) port->omodem &=~RTS; cs=splclock(); DGBsetwin(sc,0); fepcmd(port, SETMODEM, port->omodem, RTS|DTR, 0, 1); DGBhidewin(sc); splx(cs); break; case TIOCMGET: DGBsetwin(sc,0); port->imodem=bc->mstat; DGBhidewin(sc); tiocm_xxx = TIOCM_LE; /* XXX - always enabled while open */ DPRINT3(DB_MODEM,"dgb%d: port%d: modem stat -- ",unit,pnum); if (port->imodem & DTR) { DPRINT1(DB_MODEM,"DTR "); tiocm_xxx |= TIOCM_DTR; } if (port->imodem & RTS) { DPRINT1(DB_MODEM,"RTS "); tiocm_xxx |= TIOCM_RTS; } if (port->imodem & CTS) { DPRINT1(DB_MODEM,"CTS "); tiocm_xxx |= TIOCM_CTS; } if (port->imodem & port->dcd) { DPRINT1(DB_MODEM,"DCD "); tiocm_xxx |= TIOCM_CD; } if (port->imodem & port->dsr) { DPRINT1(DB_MODEM,"DSR "); tiocm_xxx |= TIOCM_DSR; } if (port->imodem & RI) { DPRINT1(DB_MODEM,"RI "); tiocm_xxx |= TIOCM_RI; } *(int *)data = tiocm_xxx; DPRINT1(DB_MODEM,"--\n"); break; case TIOCMSDTRWAIT: /* must be root since the wait applies to following logins */ error = suser(p->p_ucred, &p->p_acflag); if (error != 0) { splx(s); return (error); } port->close_delay = *(int *)data * hz / 100; break; case TIOCMGDTRWAIT: *(int *)data = port->close_delay * 100 / hz; break; case TIOCTIMESTAMP: port->do_timestamp = TRUE; *(struct timeval *)data = port->timestamp; break; case TIOCDCDTIMESTAMP: port->do_dcd_timestamp = TRUE; *(struct timeval *)data = port->dcd_timestamp; break; default: DGBbmws_set(sc, ws); splx(s); return ENOTTY; } DGBbmws_set(sc, ws); splx(s); return 0; } static void wakeflush(p) void *p; { struct dgb_p *port=p; wakeup(&port->draining); } /* wait for the output to drain */ static int dgbdrain(port) struct dgb_p *port; { struct dgb_softc *sc=&dgb_softc[port->unit]; volatile struct board_chan *bc=port->brdchan; int error; int head, tail; BoardMemWinState ws=DGBbmws_get(sc); DGBsetwin(sc,0); bc->iempty=1; tail=bc->tout; head=bc->tin; while(tail!=head) { DPRINT5(DB_WR,"dgb%d: port%d: drain: head=%d tail=%d\n", port->unit, port->pnum, head, tail); DGBhidewin(sc); port->draining=1; timeout(wakeflush,port, hz); error=tsleep(&port->draining, TTIPRI | PCATCH, "dgdrn", 0); port->draining=0; DGBsetwin(sc,0); if (error != 0) { DPRINT4(DB_WR,"dgb%d: port%d: tsleep(dgdrn) error=%d\n", port->unit,port->pnum,error); bc->iempty=0; DGBbmws_set(sc, ws); return error; } tail=bc->tout; head=bc->tin; } DPRINT5(DB_WR,"dgb%d: port%d: drain: head=%d tail=%d\n", port->unit, port->pnum, head, tail); DGBbmws_set(sc, ws); return 0; } /* wait for the output to drain */ /* or simply clear the buffer it it's stopped */ static void dgb_drain_or_flush(port) struct dgb_p *port; { struct tty *tp=port->tty; struct dgb_softc *sc=&dgb_softc[port->unit]; volatile struct board_chan *bc=port->brdchan; int error; int lasttail; int head, tail; DGBsetwin(sc,0); lasttail=-1; bc->iempty=1; tail=bc->tout; head=bc->tin; while(tail!=head /* && tail!=lasttail */ ) { DPRINT5(DB_WR,"dgb%d: port%d: flush: head=%d tail=%d\n", port->unit, port->pnum, head, tail); /* if there is no carrier simply clean the buffer */ if( !(tp->t_state & TS_CARR_ON) ) { bc->tout=bc->tin=0; bc->iempty=0; DGBhidewin(sc); return; } DGBhidewin(sc); port->draining=1; timeout(wakeflush,port, hz); error=tsleep(&port->draining, TTIPRI | PCATCH, "dgfls", 0); port->draining=0; DGBsetwin(sc,0); if (error != 0) { DPRINT4(DB_WR,"dgb%d: port%d: tsleep(dgfls) error=%d\n", port->unit,port->pnum,error); /* silently clean the buffer */ bc->tout=bc->tin=0; bc->iempty=0; DGBhidewin(sc); return; } lasttail=tail; tail=bc->tout; head=bc->tin; } DGBhidewin(sc); DPRINT5(DB_WR,"dgb%d: port%d: flush: head=%d tail=%d\n", port->unit, port->pnum, head, tail); } static int dgbparam(tp, t) struct tty *tp; struct termios *t; { int dev=tp->t_dev; int mynor=minor(dev); int unit=MINOR_TO_UNIT(dev); int pnum=MINOR_TO_PORT(dev); struct dgb_softc *sc=&dgb_softc[unit]; struct dgb_p *port=&sc->ports[pnum]; volatile struct board_chan *bc=port->brdchan; int cflag; int head; int mval; int iflag; int hflow; int s,cs; BoardMemWinState ws=DGBbmws_get(sc); DPRINT6(DB_PARAM,"dgb%d: port%d: dgbparm c=0x%x i=0x%x l=0x%x\n",unit,pnum,t->c_cflag,t->c_iflag,t->c_lflag); if(port->mustdrain) { DPRINT3(DB_PARAM,"dgb%d: port%d: must call dgbdrain()\n",unit,pnum); dgbdrain(port); } cflag=ttspeedtab(t->c_ospeed, dgbspeedtab); if (t->c_ispeed == 0) t->c_ispeed = t->c_ospeed; if (cflag < 0 /* || cflag > 0 && t->c_ispeed != t->c_ospeed */) { DPRINT4(DB_PARAM,"dgb%d: port%d: invalid cflag=0%o\n",unit,pnum,cflag); return (EINVAL); } cs=splclock(); DGBsetwin(sc,0); if(cflag==0) { /* hangup */ DPRINT3(DB_PARAM,"dgb%d: port%d: hangup\n",unit,pnum); head=bc->rin; bc->rout=head; head=bc->tin; fepcmd(port, STOUT, (unsigned)head, 0, 0, 0); mval= port->omodem & ~(DTR|RTS); } else { cflag |= dgbflags(dgb_cflags, t->c_cflag); if(cflag!=port->fepcflag) { port->fepcflag=cflag; DPRINT6(DB_PARAM,"dgb%d: port%d: set cflag=0x%x c=0x%x\n", unit,pnum,cflag&(FEP_CBAUD|FEP_FASTBAUD),cflag, t->c_cflag&~CRTSCTS); fepcmd(port, SETCTRLFLAGS, (unsigned)cflag, 0, 0, 0); } mval= port->omodem | (DTR|RTS); } iflag=dgbflags(dgb_iflags, t->c_iflag); if(iflag!=port->fepiflag) { port->fepiflag=iflag; DPRINT5(DB_PARAM,"dgb%d: port%d: set iflag=0x%x c=0x%x\n",unit,pnum,iflag,t->c_iflag); fepcmd(port, SETIFLAGS, (unsigned)iflag, 0, 0, 0); } bc->mint=port->dcd; hflow=dgbflags(dgb_flow, t->c_cflag); if(hflow!=port->hflow) { port->hflow=hflow; DPRINT5(DB_PARAM,"dgb%d: port%d: set hflow=0x%x f=0x%x\n",unit,pnum,hflow,t->c_cflag&CRTSCTS); fepcmd(port, SETHFLOW, (unsigned)hflow, 0xff, 0, 1); } if(port->omodem != mval) { DPRINT5(DB_PARAM,"dgb%d: port%d: setting modem parameters 0x%x was 0x%x\n", unit,pnum,mval,port->omodem); port->omodem=mval; fepcmd(port, SETMODEM, (unsigned)mval, RTS|DTR, 0, 1); } if(port->fepstartc!=t->c_cc[VSTART] || port->fepstopc!=t->c_cc[VSTOP]) { DPRINT5(DB_PARAM,"dgb%d: port%d: set startc=%d, stopc=%d\n",unit,pnum,t->c_cc[VSTART],t->c_cc[VSTOP]); port->fepstartc=t->c_cc[VSTART]; port->fepstopc=t->c_cc[VSTOP]; fepcmd(port, SONOFFC, port->fepstartc, port->fepstopc, 0, 1); } DGBbmws_set(sc, ws); splx(cs); return 0; } static void dgbstart(tp) struct tty *tp; { int unit; int pnum; struct dgb_p *port; struct dgb_softc *sc; volatile struct board_chan *bc; int head, tail; int size, ocount; int s; int wmask; BoardMemWinState ws; unit=MINOR_TO_UNIT(minor(tp->t_dev)); pnum=MINOR_TO_PORT(minor(tp->t_dev)); sc=&dgb_softc[unit]; port=&sc->ports[pnum]; bc=port->brdchan; ws=DGBbmws_get(sc); wmask=port->txbufsize-1; s=spltty(); while( tp->t_outq.c_cc!=0 ) { int cs; #ifndef TS_ASLEEP /* post 2.0.5 FreeBSD */ ttwwakeup(tp); #else if(tp->t_outq.c_cc <= tp->t_lowat) { if(tp->t_state & TS_ASLEEP) { tp->t_state &= ~TS_ASLEEP; wakeup(TSA_OLOWAT(tp)); } /*selwakeup(&tp->t_wsel);*/ } #endif cs=splclock(); DGBsetwin(sc,0); head=bc->tin & wmask; do { tail=bc->tout; } while (tail != bc->tout); tail=bc->tout & wmask; DPRINT5(DB_WR,"dgb%d: port%d: s tx head=%d tail=%d\n",unit,pnum,head,tail); /*printf("dgb%d: port%d: dgbstart tx head=%d tail=%d\n",unit,pnum,head,tail);*/ #ifdef LEAVE_FREE_CHARS if(tail>head) { size=tail-head-LEAVE_FREE_CHARS; if (size <0) size==0; } else { size=port->txbufsize-head; if(tail+port->txbufsize < head) size==0; } } #else if(tail>head) { size=tail-head-1; } else { size=port->txbufsize-head/*-1*/; if(tail==0) size--; } #endif if(size==0) { bc->iempty=1; bc->ilow=1; splx(cs); DGBbmws_set(sc, ws); tp->t_state|=TS_BUSY; splx(s); return; } DGBtowin(sc,port->txwin); ocount=q_to_b(&tp->t_outq, port->txptr+head, size); head+=ocount; if(head>=port->txbufsize) head-=port->txbufsize; DGBsetwin(sc,0); bc->tin=head; DPRINT5(DB_WR,"dgb%d: port%d: tx avail=%d count=%d\n",unit,pnum,size,ocount); DGBhidewin(sc); splx(cs); } DGBbmws_set(sc, ws); splx(s); #ifndef TS_ASLEEP /* post 2.0.5 FreeBSD */ if(tp->t_state & TS_BUSY) { tp->t_state&=~TS_BUSY; linesw[tp->t_line].l_start(tp); ttwwakeup(tp); } #else if(tp->t_state & TS_ASLEEP) { tp->t_state &= ~TS_ASLEEP; wakeup(TSA_OLOWAT(tp)); } tp->t_state&=~TS_BUSY; #endif } void dgbstop(tp, rw) struct tty *tp; int rw; { int unit; int pnum; struct dgb_p *port; struct dgb_softc *sc; volatile struct board_chan *bc; int head; int s; BoardMemWinState ws; unit=MINOR_TO_UNIT(minor(tp->t_dev)); pnum=MINOR_TO_PORT(minor(tp->t_dev)); sc=&dgb_softc[unit]; port=&sc->ports[pnum]; bc=port->brdchan; ws=DGBbmws_get(sc); DPRINT3(DB_WR,"dgb%d: port%d: stop\n",port->unit, port->pnum); s = spltty(); DGBsetwin(sc,0); if (rw & FWRITE) { /* clear output queue */ bc->tout=bc->tin=0; bc->ilow=0;bc->iempty=0; } if (rw & FREAD) { /* clear input queue */ bc->rout=bc->rin; bc->idata=1; } DGBhidewin(sc); DGBbmws_set(sc, ws); splx(s); dgbstart(tp); } struct tty * dgbdevtotty(dev) dev_t dev; { int mynor, pnum, unit; struct dgb_softc *sc; mynor = minor(dev); if (mynor & CONTROL_MASK) return (NULL); unit = MINOR_TO_UNIT(mynor); if ((u_int) unit >= NNDGB) return (NULL); pnum = MINOR_TO_PORT(mynor); sc = &dgb_softc[unit]; if (pnum >= sc->numports) return (NULL); return (&sc->ttys[pnum]); } void fepcmd( struct dgb_p *port, unsigned cmd, unsigned op1, unsigned op2, unsigned ncmds, unsigned bytecmd ) { struct dgb_softc *sc=&dgb_softc[port->unit]; u_char *mem=sc->vmem; unsigned tail, head; int count, n; if(port->status==DISABLED) { printf("dgb%d: port%d: FEP command on disabled port\n", port->unit, port->pnum); return; } /* DGBsetwin(sc,0); Require this to be set by caller */ head=sc->mailbox->cin; if(head>=(FEP_CMAX-FEP_CSTART) || (head & 3)) { printf("dgb%d: port%d: wrong pointer head of command queue : 0x%x\n", port->unit, port->pnum, head); return; } mem[head+FEP_CSTART+0]=cmd; mem[head+FEP_CSTART+1]=port->pnum; if(bytecmd) { mem[head+FEP_CSTART+2]=op1; mem[head+FEP_CSTART+3]=op2; } else { mem[head+FEP_CSTART+2]=op1&0xff; mem[head+FEP_CSTART+3]=(op1>>8)&0xff; } DPRINT7(DB_FEP,"dgb%d: port%d: %s cmd=0x%x op1=0x%x op2=0x%x\n", port->unit, port->pnum, (bytecmd)?"byte":"word", cmd, mem[head+FEP_CSTART+2], mem[head+FEP_CSTART+3]); head=(head+4) & (FEP_CMAX-FEP_CSTART-4); sc->mailbox->cin=head; count=FEPTIMEOUT; while (count-- != 0) { head=sc->mailbox->cin; tail=sc->mailbox->cout; n = (head-tail) & (FEP_CMAX-FEP_CSTART-4); if(n <= ncmds * (sizeof(ushort)*4)) return; } printf("dgb%d(%d): timeout on FEP cmd=0x%x\n", port->unit, port->pnum, cmd); } static void disc_optim(tp, t) struct tty *tp; struct termios *t; { /* * XXX can skip a lot more cases if Smarts. Maybe * (IGNCR | ISTRIP | IXON) in c_iflag. But perhaps we * shouldn't skip if (TS_CNTTB | TS_LNCH) is set in t_state. */ if (!(t->c_iflag & (ICRNL | IGNCR | IMAXBEL | INLCR | ISTRIP | IXON)) && (!(t->c_iflag & BRKINT) || (t->c_iflag & IGNBRK)) && (!(t->c_iflag & PARMRK) || (t->c_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK)) && !(t->c_lflag & (ECHO | ICANON | IEXTEN | ISIG | PENDIN)) && linesw[tp->t_line].l_rint == ttyinput) tp->t_state |= TS_CAN_BYPASS_L_RINT; else tp->t_state &= ~TS_CAN_BYPASS_L_RINT; } static dgb_devsw_installed = 0; static void dgb_drvinit(void *unused) { dev_t dev; if( ! dgb_devsw_installed ) { dev = makedev(CDEV_MAJOR, 0); cdevsw_add(&dev,&dgb_cdevsw, NULL); dgb_devsw_installed = 1; } } SYSINIT(dgbdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,dgb_drvinit,NULL) #endif /* NNDGB > 0 */