freebsd-dev/sys/i386/isa/cx.c

921 lines
23 KiB
C
Raw Normal View History

/*
* Cronyx-Sigma adapter driver for FreeBSD.
* Supports PPP/HDLC protocol in synchronous mode,
* and asyncronous channels with full modem control.
*
* Copyright (C) 1994 Cronyx Ltd.
* Author: Serge Vakulenko, <vak@zebub.msk.su>
*
* This software is distributed with NO WARRANTIES, not even the implied
* warranties for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* Authors grant any other persons or organisations permission to use
* or modify this software as long as this message is kept with the software,
* all derivative works or modified versions.
*
* Version 1.2, Tue Nov 22 18:57:27 MSK 1994
*/
#undef DEBUG
#include "cx.h"
#if NCX > 0
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/mbuf.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <sys/conf.h>
#include <sys/proc.h>
#include <sys/tty.h>
#include <sys/errno.h>
#include <sys/syslog.h>
#include <sys/socket.h>
#include <net/if.h>
#ifdef __FreeBSD__
# if __FreeBSD__ < 2
# include <machine/pio.h>
# define RB_GETC(q) getc(q)
# else /* BSD 4.4 Lite */
# include <sys/devconf.h>
# endif
# define oproc_func_t void(*)(struct tty*)
#endif
#ifdef __bsdi__
# include <sys/ttystats.h>
# include <machine/inline.h>
# define tsleep(tp,pri,msg,x) ((tp)->t_state |= TS_WOPEN,\
ttysleep (tp, (caddr_t)&tp->t_rawq, pri, msg, x))
# define oproc_func_t int(*)()
# define timeout_func_t void(*)()
#endif
#if !defined (__FreeBSD__) || __FreeBSD__ >= 2
# define t_out t_outq
# define RB_LEN(q) ((q).c_cc)
# define RB_GETC(q) getc(&q)
#ifndef TSA_CARR_ON /* FreeBSD 2.x before not long after 2.0.5 */
# define TSA_CARR_ON(tp) tp
# define TSA_OLOWAT(q) ((caddr_t)&(q)->t_out)
#endif
#endif
#include <machine/cronyx.h>
#include <i386/isa/cxreg.h>
#ifdef DEBUG
# define print(s) printf s
#else
# define print(s) /*void*/
#endif
#define DMABUFSZ (6*256) /* buffer size */
#define BYTE *(unsigned char*)&
#define UNIT(u) ((u) & 077)
#define UNIT_CTL 077
extern cx_board_t cxboard [NCX]; /* adapter state structures */
extern cx_chan_t *cxchan [NCX*NCHAN]; /* unit to channel struct pointer */
#if __FreeBSD__ >= 2
extern struct kern_devconf kdc_cx [NCX];
struct tty cx_tty [NCX*NCHAN]; /* tty data */
#else
struct tty *cx_tty [NCX*NCHAN]; /* tty data */
#endif
void cxoproc (struct tty *tp);
int cxparam (struct tty *tp, struct termios *t);
void cxswitch (cx_chan_t *c, cx_soft_opt_t new);
int cxopen (dev_t dev, int flag, int mode, struct proc *p)
{
int unit = UNIT (dev);
cx_chan_t *c = cxchan[unit];
unsigned short port;
struct tty *tp;
int error = 0;
1995-05-30 08:16:23 +00:00
if (unit == UNIT_CTL) {
print (("cx: cxopen /dev/cronyx\n"));
return (0);
}
if (unit >= NCX*NCHAN || !c || c->type==T_NONE)
return (ENXIO);
port = c->chip->port;
print (("cx%d.%d: cxopen unit=%d\n", c->board->num, c->num, unit));
if (c->mode != M_ASYNC)
return (EBUSY);
if (! c->ttyp) {
#if __FreeBSD__ >= 2
c->ttyp = &cx_tty[unit];
#else
MALLOC (cx_tty[unit], struct tty*, sizeof (struct tty), M_DEVBUF, M_WAITOK);
bzero (cx_tty[unit], sizeof (*cx_tty[unit]));
c->ttyp = cx_tty[unit];
#endif
c->ttyp->t_oproc = (oproc_func_t) cxoproc;
c->ttyp->t_param = cxparam;
}
#ifdef __bsdi__
if (! c->ttydev) {
MALLOC (c->ttydev, struct ttydevice_tmp*,
sizeof (struct ttydevice_tmp), M_DEVBUF, M_WAITOK);
bzero (c->ttydev, sizeof (*c->ttydev));
strcpy (c->ttydev->tty_name, "cx");
c->ttydev->tty_unit = unit;
c->ttydev->tty_base = unit;
c->ttydev->tty_count = 1;
c->ttydev->tty_ttys = c->ttyp;
tty_attach (c->ttydev);
}
#endif
tp = c->ttyp;
tp->t_dev = dev;
if ((tp->t_state & TS_ISOPEN) && (tp->t_state & TS_XCLUDE) &&
p->p_ucred->cr_uid != 0)
return (EBUSY);
if (! (tp->t_state & TS_ISOPEN)) {
ttychars (tp);
if (tp->t_ispeed == 0) {
#ifdef __bsdi__
tp->t_termios = deftermios;
#else
tp->t_iflag = 0;
tp->t_oflag = 0;
tp->t_lflag = 0;
tp->t_cflag = CREAD | CS8 | HUPCL;
tp->t_ispeed = c->rxbaud;
tp->t_ospeed = c->txbaud;
#endif
}
cxparam (tp, &tp->t_termios);
ttsetwater (tp);
}
spltty ();
if (! (tp->t_state & TS_ISOPEN)) {
/*
* Compute optimal receiver buffer length.
* The best choice is rxbaud/400.
* Make it even, to avoid byte-wide DMA transfers.
* --------------------------
* Baud rate Buffer length
* --------------------------
* 300 4
* 1200 4
* 9600 24
* 19200 48
* 38400 96
* 57600 192
* 115200 288
* --------------------------
*/
int rbsz = (c->rxbaud + 800 - 1) / 800 * 2;
if (rbsz < 4)
rbsz = 4;
else if (rbsz > DMABUFSZ)
rbsz = DMABUFSZ;
/* Initialize channel, enable receiver. */
cx_cmd (port, CCR_INITCH | CCR_ENRX);
cx_cmd (port, CCR_INITCH | CCR_ENRX);
/* Start receiver. */
outw (ARBCNT(port), rbsz);
outw (BRBCNT(port), rbsz);
outw (ARBSTS(port), BSTS_OWN24);
outw (BRBSTS(port), BSTS_OWN24);
/* Enable interrupts. */
outb (IER(port), IER_RXD | IER_RET | IER_TXD | IER_MDM);
cx_chan_dtr (c, 1);
cx_chan_rts (c, 1);
}
if (cx_chan_cd (c))
Obtained from: partly from ancient patches of mine via 1.1.5 Introduce TS_CONNECTED and TS_ZOMBIE states. TS_CONNECTED is set while a connection is established. It is set while (TS_CARR_ON or CLOCAL is set) and TS_ZOMBIE is clear. TS_ZOMBIE is set for on to off transitions of TS_CARR_ON that occur when CLOCAL is clear and is cleared for off to on transitions of CLOCAL. I/o can only occur while TS_CONNECTED is set. TS_ZOMBIE prevents further i/o. Split the input-event sleep address TSA_CARR_ON(tp) into TSA_CARR_ON(tp) and TSA_HUP_OR_INPUT(tp). The former address is now used only for off to on carrier transitions and equivalent CLOCAL transitions. The latter is used for all input events, all carrier transitions and certain CLOCAL transitions. There are some harmless extra wakeups for rare connection- related events. Previously there were too many extra wakeups for non-rare input events. Drivers now call l_modem() instead of setting TS_CARR_ON directly to handle even the initial off to on transition of carrier. They should always have done this. l_modem() now handles TS_CONNECTED and TS_ZOMBIE as well as TS_CARR_ON. gnu/isdn/iitty.c: Set TS_CONNECTED for first open ourself to go with bogusly setting CLOCAL. i386/isa/syscons.c, i386/isa/pcvt/pcvt_drv.c: We fake carrier, so don't also fake CLOCAL. kern/tty.c: Testing TS_CONNECTED instead of TS_CARR_ON fixes TIOCCONS forgetting to test CLOCAL. TS_ISOPEN was tested instead, but that broke when we disabled the clearing of TS_ISOPEN for certain transitions of CLOCAL. Testing TS_CONNECTED fixes ttyselect() returning false success for output to devices in state !TS_CARR_ON && !CLOCAL. Optimize the other selwakeup() call (this is not related to the other changes). kern/tty_pty.c: ptcopen() can be declared in traditional C now that dev_t isn't short.
1995-07-31 21:02:00 +00:00
(*linesw[tp->t_line].l_modem)(tp, 1);
if (! (flag & O_NONBLOCK)) {
/* Lock the channel against cxconfig while we are
* waiting for carrier. */
c->sopt.lock = 1;
while (!(tp->t_cflag & CLOCAL) && !(tp->t_state & TS_CARR_ON))
if ((error = tsleep (TSA_CARR_ON(tp), TTIPRI | PCATCH,
"cxdcd", 0)))
break;
c->sopt.lock = 0; /* Unlock the channel. */
}
print (("cx%d.%d: cxopen done csr=%b\n", c->board->num, c->num,
inb(CSR(c->chip->port)), CSRA_BITS));
spl0 ();
if (error)
return (error);
#if __FreeBSD__ >= 2
error = (*linesw[tp->t_line].l_open) (dev, tp);
if (tp->t_state & TS_ISOPEN)
/* Mark the board busy on the first startup.
* Never goes idle. */
kdc_cx[c->board->num].kdc_state = DC_BUSY;
#else
error = (*linesw[tp->t_line].l_open) (dev, tp, 0);
#endif
return (error);
}
int cxclose (dev_t dev, int flag, int mode, struct proc *p)
{
int unit = UNIT (dev);
cx_chan_t *c = cxchan[unit];
struct tty *tp;
int s;
1995-05-30 08:16:23 +00:00
if (unit == UNIT_CTL)
return (0);
tp = c->ttyp;
(*linesw[tp->t_line].l_close) (tp, flag);
/* Disable receiver.
* Transmitter continues sending the queued data. */
s = spltty ();
outb (CAR(c->chip->port), c->num & 3);
outb (IER(c->chip->port), IER_TXD | IER_MDM);
cx_cmd (c->chip->port, CCR_DISRX);
/* Clear DTR and RTS. */
if ((tp->t_cflag & HUPCL) || ! (tp->t_state & TS_ISOPEN)) {
cx_chan_dtr (c, 0);
cx_chan_rts (c, 0);
}
/* Stop sending break. */
if (c->brk == BRK_SEND) {
c->brk = BRK_STOP;
if (! (tp->t_state & TS_BUSY))
cxoproc (tp);
}
splx (s);
ttyclose (tp);
return (0);
}
int cxread (dev_t dev, struct uio *uio, int flag)
{
int unit = UNIT (dev);
struct tty *tp;
1995-05-30 08:16:23 +00:00
if (unit == UNIT_CTL)
return (EIO);
tp = cxchan[unit]->ttyp;
return ((*linesw[tp->t_line].l_read) (tp, uio, flag));
}
1995-05-30 08:16:23 +00:00
int cxwrite (dev_t dev, struct uio *uio, int flag)
{
int unit = UNIT (dev);
struct tty *tp;
1995-05-30 08:16:23 +00:00
if (unit == UNIT_CTL)
return (EIO);
tp = cxchan[unit]->ttyp;
return ((*linesw[tp->t_line].l_write) (tp, uio, flag));
}
int cxioctl (dev_t dev, int cmd, caddr_t data, int flag, struct proc *p)
{
int unit = UNIT (dev);
cx_chan_t *c;
struct tty *tp;
int error, s;
unsigned char msv;
if (unit == UNIT_CTL) {
/* Process an ioctl request on /dev/cronyx */
cx_options_t *o = (cx_options_t*) data;
if (o->board >= NCX || o->channel >= NCHAN)
return (EINVAL);
c = &cxboard[o->board].chan[o->channel];
if (c->type == T_NONE)
return (ENXIO);
switch (cmd) {
default:
return (EINVAL);
case CXIOCSETMODE:
print (("cx%d.%d: CXIOCSETMODE\n", o->board, o->channel));
if (c->type == T_NONE)
return (EINVAL);
if (c->type == T_ASYNC && o->mode != M_ASYNC)
return (EINVAL);
if (o->mode == M_ASYNC)
switch (c->type) {
case T_SYNC_RS232:
case T_SYNC_V35:
case T_SYNC_RS449:
return (EINVAL);
}
/* Somebody is waiting for carrier? */
if (c->sopt.lock)
return (EBUSY);
/* /dev/ttyXX is already opened by someone? */
if (c->mode == M_ASYNC && c->ttyp &&
(c->ttyp->t_state & TS_ISOPEN))
return (EBUSY);
/* Network interface is up? */
if (c->mode != M_ASYNC && (c->ifp->if_flags & IFF_UP))
return (EBUSY);
c->mode = o->mode;
c->rxbaud = o->rxbaud;
c->txbaud = o->txbaud;
c->opt = o->opt;
c->aopt = o->aopt;
c->hopt = o->hopt;
c->bopt = o->bopt;
c->xopt = o->xopt;
switch (c->num) {
case 0: c->board->if0type = o->iftype; break;
case 8: c->board->if8type = o->iftype; break;
}
cxswitch (c, o->sopt);
s = spltty ();
cx_setup_chan (c);
outb (IER(c->chip->port), 0);
splx (s);
break;
case CXIOCGETMODE:
print (("cx%d.%d: CXIOCGETMODE\n", o->board, o->channel));
o->type = c->type;
o->mode = c->mode;
o->rxbaud = c->rxbaud;
o->txbaud = c->txbaud;
o->opt = c->opt;
o->aopt = c->aopt;
o->hopt = c->hopt;
o->bopt = c->bopt;
o->xopt = c->xopt;
o->sopt = c->sopt;
switch (c->num) {
case 0: o->iftype = c->board->if0type; break;
case 8: o->iftype = c->board->if8type; break;
}
break;
}
return (0);
}
c = cxchan[unit];
tp = c->ttyp;
if (! tp)
return (EINVAL);
#if __FreeBSD__ >= 2
error = (*linesw[tp->t_line].l_ioctl) (tp, cmd, data, flag, p);
#else
error = (*linesw[tp->t_line].l_ioctl) (tp, cmd, data, flag);
#endif
if (error >= 0)
return (error);
error = ttioctl (tp, cmd, data, flag);
if (error >= 0)
return (error);
s = spltty ();
switch (cmd) {
default:
splx (s);
return (ENOTTY);
case TIOCSBRK: /* Start sending line break */
c->brk = BRK_SEND;
if (! (tp->t_state & TS_BUSY))
cxoproc (tp);
break;
case TIOCCBRK: /* Stop sending line break */
c->brk = BRK_STOP;
if (! (tp->t_state & TS_BUSY))
cxoproc (tp);
break;
case TIOCSDTR: /* Set DTR */
cx_chan_dtr (c, 1);
break;
case TIOCCDTR: /* Clear DTR */
cx_chan_dtr (c, 0);
break;
case TIOCMSET: /* Set DTR/RTS */
cx_chan_dtr (c, (*(int*)data & TIOCM_DTR) ? 1 : 0);
cx_chan_rts (c, (*(int*)data & TIOCM_RTS) ? 1 : 0);
break;
case TIOCMBIS: /* Add DTR/RTS */
if (*(int*)data & TIOCM_DTR) cx_chan_dtr (c, 1);
if (*(int*)data & TIOCM_RTS) cx_chan_rts (c, 1);
break;
case TIOCMBIC: /* Clear DTR/RTS */
if (*(int*)data & TIOCM_DTR) cx_chan_dtr (c, 0);
if (*(int*)data & TIOCM_RTS) cx_chan_rts (c, 0);
break;
case TIOCMGET: /* Get modem status */
msv = inb (MSVR(c->chip->port));
*(int*)data = TIOCM_LE; /* always enabled while open */
if (msv & MSV_DSR) *(int*)data |= TIOCM_DSR;
if (msv & MSV_CTS) *(int*)data |= TIOCM_CTS;
if (msv & MSV_CD) *(int*)data |= TIOCM_CD;
if (c->dtr) *(int*)data |= TIOCM_DTR;
if (c->rts) *(int*)data |= TIOCM_RTS;
break;
}
splx (s);
return (0);
}
/*
* Fill transmitter buffer with data.
*/
void cxout (cx_chan_t *c, char b)
{
unsigned char *buf, *p, sym;
unsigned short port = c->chip->port, len = 0, cnt_port, sts_port;
struct tty *tp = c->ttyp;
int i;
if (! tp)
return;
/* Choose the buffer. */
if (b == 'A') {
buf = c->atbuf;
cnt_port = ATBCNT(port);
sts_port = ATBSTS(port);
} else {
buf = c->btbuf;
cnt_port = BTBCNT(port);
sts_port = BTBSTS(port);
}
/* Is it busy? */
if (inb (sts_port) & BSTS_OWN24) {
tp->t_state |= TS_BUSY;
return;
}
switch (c->brk) {
case BRK_SEND:
*buf++ = 0; /* extended transmit command */
*buf++ = 0x81; /* send break */
*buf++ = 0; /* extended transmit command */
*buf++ = 0x82; /* insert delay */
*buf++ = 250; /* 1/4 of second */
*buf++ = 0; /* extended transmit command */
*buf++ = 0x82; /* insert delay */
*buf++ = 250; /* + 1/4 of second */
len = 8;
c->brk = BRK_IDLE;
break;
case BRK_STOP:
*buf++ = 0; /* extended transmit command */
*buf++ = 0x83; /* stop break */
len = 2;
c->brk = BRK_IDLE;
break;
case BRK_IDLE:
len = RB_LEN (tp->t_out);
if (tp->t_iflag & IXOFF)
for (i=0, p=buf; i<len && p<buf+DMABUFSZ-1; ++i) {
sym = RB_GETC (tp->t_out);
/* Send XON/XOFF out of band. */
if (sym == tp->t_cc[VSTOP]) {
outb (STCR(port), STC_SNDSPC|STC_SSPC_2);
continue;
}
if (sym == tp->t_cc[VSTART]) {
outb (STCR(port), STC_SNDSPC|STC_SSPC_1);
continue;
}
/* Duplicate NULLs in ETC mode. */
if (! sym)
*p++ = 0;
*p++ = sym;
}
else
for (i=0, p=buf; i<len && p<buf+DMABUFSZ-1; ++i) {
sym = RB_GETC (tp->t_out);
/* Duplicate NULLs in ETC mode. */
if (! sym)
*p++ = 0;
*p++ = sym;
}
len = p - buf;
break;
}
/* Start transmitter. */
if (len) {
outw (cnt_port, len);
outb (sts_port, BSTS_INTR | BSTS_OWN24);
tp->t_state |= TS_BUSY;
print (("cx%d.%d: out %d bytes to %c\n",
c->board->num, c->num, len, b));
}
}
void cxoproc (struct tty *tp)
{
int unit = UNIT (tp->t_dev);
cx_chan_t *c = cxchan[unit];
unsigned short port = c->chip->port;
int s = spltty ();
/* Set current channel number */
outb (CAR(port), c->num & 3);
if (! (tp->t_state & (TS_TIMEOUT | TS_TTSTOP))) {
/* Start transmitter. */
if (! (inb (CSR(port)) & CSRA_TXEN))
cx_cmd (port, CCR_ENTX);
/* Determine the buffer order. */
if (inb (DMABSTS(port)) & DMABSTS_NTBUF) {
cxout (c, 'B');
cxout (c, 'A');
} else {
cxout (c, 'A');
cxout (c, 'B');
}
}
#if defined (__FreeBSD__) && __FreeBSD__ < 2
if (tp->t_state & (TS_SO_OCOMPLETE | TS_SO_OLOWAT) || tp->t_wsel)
ttwwakeup (tp);
#else /* FreeBSD 2.x and BSDI */
#ifndef TS_ASLEEP /* FreeBSD some time after 2.0.5 */
ttwwakeup(tp);
#else
if (RB_LEN (tp->t_out) <= tp->t_lowat) {
if (tp->t_state & TS_ASLEEP) {
tp->t_state &= ~TS_ASLEEP;
wakeup(TSA_OLOWAT(tp));
}
selwakeup(&tp->t_wsel);
}
#endif
#endif
/*
* Enable TXMPTY interrupt,
* to catch the case when the second buffer is empty.
*/
if ((inb (ATBSTS(port)) & BSTS_OWN24) &&
(inb (BTBSTS(port)) & BSTS_OWN24)) {
outb (IER(port), IER_RXD|IER_RET|IER_TXD|IER_TXMPTY|IER_MDM);
} else
outb (IER(port), IER_RXD|IER_RET|IER_TXD|IER_MDM);
splx (s);
}
int cxparam (struct tty *tp, struct termios *t)
{
int unit = UNIT (tp->t_dev);
cx_chan_t *c = cxchan[unit];
unsigned short port = c->chip->port;
int clock, period, s;
cx_cor1_async_t cor1;
1995-05-30 08:16:23 +00:00
if (t->c_ospeed == 0) {
/* Clear DTR and RTS. */
s = spltty ();
cx_chan_dtr (c, 0);
cx_chan_rts (c, 0);
splx (s);
print (("cx%d.%d: cxparam (hangup)\n", c->board->num, c->num));
return (0);
}
print (("cx%d.%d: cxparam\n", c->board->num, c->num));
/* Check requested parameters. */
if (t->c_ospeed < 300 || t->c_ospeed > 256*1024)
return(EINVAL);
if (t->c_ispeed && (t->c_ispeed < 300 || t->c_ispeed > 256*1024))
return(EINVAL);
#ifdef __bsdi__
/* CLOCAL flag set -- wakeup everybody who waits for CD. */
/* FreeBSD does this themselves. */
if (! (tp->t_cflag & CLOCAL) && (t->c_cflag & CLOCAL))
wakeup ((caddr_t) &tp->t_rawq);
#endif
/* And copy them to tty and channel structures. */
c->rxbaud = tp->t_ispeed = t->c_ispeed;
c->txbaud = tp->t_ospeed = t->c_ospeed;
tp->t_cflag = t->c_cflag;
/* Set character length and parity mode. */
BYTE cor1 = 0;
switch (t->c_cflag & CSIZE) {
default:
case CS8: cor1.charlen = 7; break;
case CS7: cor1.charlen = 6; break;
case CS6: cor1.charlen = 5; break;
case CS5: cor1.charlen = 4; break;
}
if (t->c_cflag & PARENB) {
cor1.parmode = PARM_NORMAL;
cor1.ignpar = 0;
cor1.parity = (t->c_cflag & PARODD) ? PAR_ODD : PAR_EVEN;
} else {
cor1.parmode = PARM_NOPAR;
cor1.ignpar = 1;
}
/* Enable/disable hardware CTS. */
c->aopt.cor2.ctsae = (t->c_cflag & CRTSCTS) ? 1 : 0;
/* Handle DSR as CTS. */
c->aopt.cor2.dsrae = (t->c_cflag & CRTSCTS) ? 1 : 0;
/* Enable extended transmit command mode.
* Unfortunately, there is no other method for sending break. */
c->aopt.cor2.etc = 1;
/* Enable/disable hardware XON/XOFF. */
c->aopt.cor2.ixon = (t->c_iflag & IXON) ? 1 : 0;
c->aopt.cor2.ixany = (t->c_iflag & IXANY) ? 1 : 0;
/* Set the number of stop bits. */
if (t->c_cflag & CSTOPB)
c->aopt.cor3.stopb = STOPB_2;
else
c->aopt.cor3.stopb = STOPB_1;
/* Disable/enable passing XON/XOFF chars to the host. */
c->aopt.cor3.scde = (t->c_iflag & IXON) ? 1 : 0;
c->aopt.cor3.flowct = (t->c_iflag & IXON) ? FLOWCC_NOTPASS : FLOWCC_PASS;
c->aopt.schr1 = t->c_cc[VSTART]; /* XON */
c->aopt.schr2 = t->c_cc[VSTOP]; /* XOFF */
/* Set current channel number. */
s = spltty ();
outb (CAR(port), c->num & 3);
/* Set up receiver clock values. */
cx_clock (c->chip->oscfreq, c->rxbaud, &clock, &period);
c->opt.rcor.clk = clock;
outb (RCOR(port), BYTE c->opt.rcor);
outb (RBPR(port), period);
/* Set up transmitter clock values. */
cx_clock (c->chip->oscfreq, c->txbaud, &clock, &period);
c->opt.tcor.clk = clock;
c->opt.tcor.ext1x = 0;
outb (TCOR(port), BYTE c->opt.tcor);
outb (TBPR(port), period);
outb (COR2(port), BYTE c->aopt.cor2);
outb (COR3(port), BYTE c->aopt.cor3);
outb (SCHR1(port), c->aopt.schr1);
outb (SCHR2(port), c->aopt.schr2);
if (BYTE c->aopt.cor1 != BYTE cor1) {
BYTE c->aopt.cor1 = BYTE cor1;
outb (COR1(port), BYTE c->aopt.cor1);
/* Any change to COR1 require reinitialization. */
/* Unfortunately, it may cause transmitter glitches... */
cx_cmd (port, CCR_INITCH);
}
splx (s);
return (0);
}
struct tty *cxdevtotty (dev_t dev)
{
int unit = UNIT(dev);
if (unit == UNIT_CTL)
return (NULL);
if (unit > NCX*NCHAN)
return (NULL);
return (cxchan[unit]->ttyp);
}
1995-05-30 08:16:23 +00:00
int cxselect (dev_t dev, int flag, struct proc *p)
{
int unit = UNIT (dev);
if (unit == UNIT_CTL)
return (0);
if (unit > NCX*NCHAN)
return (ENXIO);
return (ttyselect(cxchan[unit]->ttyp, flag, p));
}
/*
* Stop output on a line
*/
void cxstop (struct tty *tp, int flag)
{
cx_chan_t *c = cxchan[UNIT(tp->t_dev)];
unsigned short port = c->chip->port;
int s = spltty ();
if (tp->t_state & TS_BUSY) {
print (("cx%d.%d: cxstop\n", c->board->num, c->num));
/* Set current channel number */
outb (CAR(port), c->num & 3);
/* Stop transmitter */
cx_cmd (port, CCR_DISTX);
}
splx (s);
}
/*
* Handle receive interrupts, including receive errors and
* receive timeout interrupt.
*/
int cxrinta (cx_chan_t *c)
{
unsigned short port = c->chip->port;
unsigned short len = 0, risr = inw (RISR(port)), reoir = 0;
struct tty *tp = c->ttyp;
/* Compute optimal receiver buffer length. */
int rbsz = (c->rxbaud + 800 - 1) / 800 * 2;
if (rbsz < 4)
rbsz = 4;
else if (rbsz > DMABUFSZ)
rbsz = DMABUFSZ;
if (risr & RISA_TIMEOUT) {
unsigned long rcbadr = (unsigned short) inw (RCBADRL(port)) |
(long) inw (RCBADRU(port)) << 16;
unsigned char *buf = 0;
unsigned short cnt_port = 0, sts_port = 0;
if (rcbadr >= c->brphys && rcbadr < c->brphys+DMABUFSZ) {
buf = c->brbuf;
len = rcbadr - c->brphys;
cnt_port = BRBCNT(port);
sts_port = BRBSTS(port);
} else if (rcbadr >= c->arphys && rcbadr < c->arphys+DMABUFSZ) {
buf = c->arbuf;
len = rcbadr - c->arphys;
cnt_port = ARBCNT(port);
sts_port = ARBSTS(port);
} else
printf ("cx%d.%d: timeout: invalid buffer address\n",
c->board->num, c->num);
if (len) {
print (("cx%d.%d: async receive timeout (%d bytes), risr=%b, arbsts=%b, brbsts=%b\n",
c->board->num, c->num, len, risr, RISA_BITS,
inb (ARBSTS(port)), BSTS_BITS, inb (BRBSTS(port)), BSTS_BITS));
if (tp && (tp->t_state & TS_ISOPEN)) {
int i;
void (*rint)() = (void(*)())
linesw[tp->t_line].l_rint;
for (i=0; i<len; ++i)
(*rint) (buf[i], tp);
}
/* Restart receiver. */
outw (cnt_port, rbsz);
outb (sts_port, BSTS_OWN24);
}
return (REOI_TERMBUFF);
}
print (("cx%d.%d: async receive interrupt, risr=%b, arbsts=%b, brbsts=%b\n",
c->board->num, c->num, risr, RISA_BITS,
inb (ARBSTS(port)), BSTS_BITS, inb (BRBSTS(port)), BSTS_BITS));
if (risr & RIS_BUSERR)
printf ("cx%d.%d: receive bus error\n", c->board->num, c->num);
1995-03-29 21:26:37 +00:00
if (risr & (RIS_OVERRUN | RISA_PARERR | RISA_FRERR | RISA_BREAK)) {
int err = 0;
1995-03-31 11:04:22 +00:00
if (risr & RIS_OVERRUN)
1995-03-29 21:26:37 +00:00
err |= TTY_OE;
if (risr & RISA_PARERR)
err |= TTY_PE;
if (risr & RISA_FRERR)
err |= TTY_FE;
if (risr & RISA_BREAK)
err |= TTY_BI;
print (("cx%d.%d: receive error %x\n", c->board->num, c->num, err));
if (tp && (tp->t_state & TS_ISOPEN))
1995-03-29 21:26:37 +00:00
(*linesw[tp->t_line].l_rint) (err, tp);
}
/* Discard exception characters. */
if ((risr & RISA_SCMASK) && (tp->t_iflag & IXON))
reoir |= REOI_DISCEXC;
/* Handle received data. */
if ((risr & RIS_EOBUF) && tp && (tp->t_state & TS_ISOPEN)) {
void (*rint)() = (void(*)()) linesw[tp->t_line].l_rint;
unsigned char *buf;
int i;
len = (risr & RIS_BB) ? inw(BRBCNT(port)) : inw(ARBCNT(port));
print (("cx%d.%d: async: %d bytes received\n",
c->board->num, c->num, len));
buf = (risr & RIS_BB) ? c->brbuf : c->arbuf;
for (i=0; i<len; ++i)
(*rint) (buf[i], tp);
}
/* Restart receiver. */
if (! (inb (ARBSTS(port)) & BSTS_OWN24)) {
outw (ARBCNT(port), rbsz);
outb (ARBSTS(port), BSTS_OWN24);
}
if (! (inb (BRBSTS(port)) & BSTS_OWN24)) {
outw (BRBCNT(port), rbsz);
outb (BRBSTS(port), BSTS_OWN24);
}
return (reoir);
}
/*
* Handle transmit interrupt.
*/
void cxtinta (cx_chan_t *c)
{
struct tty *tp = c->ttyp;
unsigned short port = c->chip->port;
unsigned char tisr = inb (TISR(port));
print (("cx%d.%d: async transmit interrupt, tisr=%b, atbsts=%b, btbsts=%b\n",
c->board->num, c->num, tisr, TIS_BITS,
inb (ATBSTS(port)), BSTS_BITS, inb (BTBSTS(port)), BSTS_BITS));
if (tisr & TIS_BUSERR)
printf ("cx%d.%d: transmit bus error\n",
c->board->num, c->num);
else if (tisr & TIS_UNDERRUN)
printf ("cx%d.%d: transmit underrun error\n",
c->board->num, c->num);
if (tp) {
tp->t_state &= ~(TS_BUSY | TS_FLUSH);
if (tp->t_line)
(*linesw[tp->t_line].l_start) (tp);
else
cxoproc (tp);
}
}
/*
* Handle modem interrupt.
*/
void cxmint (cx_chan_t *c)
{
unsigned short port = c->chip->port;
unsigned char misr = inb (MISR(port));
unsigned char msvr = inb (MSVR(port));
struct tty *tp = c->ttyp;
if (c->mode != M_ASYNC) {
printf ("cx%d.%d: unexpected modem interrupt, misr=%b, msvr=%b\n",
c->board->num, c->num, misr, MIS_BITS, msvr, MSV_BITS);
return;
}
print (("cx%d.%d: modem interrupt, misr=%b, msvr=%b\n",
c->board->num, c->num, misr, MIS_BITS, msvr, MSV_BITS));
/* Ignore DSR events. */
/* Ignore RTC/CTS events, handled by hardware. */
/* Handle carrier detect/loss. */
if (tp && (misr & MIS_CCD))
(*linesw[tp->t_line].l_modem) (tp, (msvr & MSV_CD) != 0);
}
/*
* Recover after lost transmit interrupts.
*/
void cxtimeout (caddr_t a)
{
cx_board_t *b;
cx_chan_t *c;
struct tty *tp;
int s;
for (b=cxboard; b<cxboard+NCX; ++b)
for (c=b->chan; c<b->chan+NCHAN; ++c) {
tp = c->ttyp;
if (c->type==T_NONE || c->mode!=M_ASYNC || !tp)
continue;
s = spltty ();
if (tp->t_state & TS_BUSY) {
tp->t_state &= ~TS_BUSY;
if (tp->t_line)
(*linesw[tp->t_line].l_start) (tp);
else
cxoproc (tp);
}
splx (s);
}
timeout ((timeout_func_t) cxtimeout, 0, hz*5);
}
#endif /* NCX */