New version that handles hardware flow-control (CRTSCTS) at the input

to the ring (or silo).  This eliminates ring overrun with crtscts.
Also handle deferred register changes earlier, and misc. cleanup.
Give credit to: Bill Studenmund and Ian Dall (Thanks!)
This commit is contained in:
gwr 1996-05-17 22:49:23 +00:00
parent bcc0cb3fa0
commit 9c5d297b0f
1 changed files with 276 additions and 126 deletions

View File

@ -1,4 +1,4 @@
/* $NetBSD: z8530tty.c,v 1.7 1996/05/17 19:30:37 gwr Exp $ */
/* $NetBSD: z8530tty.c,v 1.8 1996/05/17 22:49:23 gwr Exp $ */
/*
* Copyright (c) 1994 Gordon W. Ross
@ -50,6 +50,19 @@
*
* This is the "slave" driver that will be attached to
* the "zsc" driver for plain "tty" async. serial lines.
*
* Credits, history:
*
* The original version of this code was the sparc/dev/zs.c driver
* as distributed with the Berkeley 4.4 Lite release. Since then,
* Gordon Ross reorganized the code into the current parent/child
* driver scheme, separating the Sun keyboard and mouse support
* into independent child drivers.
*
* RTS/CTS flow-control support was a collaboration of:
* Gordon Ross <gwr@netbsd.org>,
* Bill Studenmund <wrstuden@loki.stanford.edu>
* Ian Dall <Ian.Dall@dsto.defence.gov.au>
*/
#include <sys/param.h>
@ -95,6 +108,9 @@ extern int zs_check_kgdb();
*/
int zstty_rbuf_size = ZSTTY_RING_SIZE;
/* This should usually be 3/4 of ZSTTY_RING_SIZE */
int zstty_rbuf_hiwat = (ZSTTY_RING_SIZE - (ZSTTY_RING_SIZE >> 2));
struct zstty_softc {
struct device zst_dev; /* required first: base device */
struct tty *zst_tty;
@ -103,22 +119,6 @@ struct zstty_softc {
int zst_hwflags; /* see z8530var.h */
int zst_swflags; /* TIOCFLAG_SOFTCAR, ... <ttycom.h> */
/* Flags to communicate with zstty_softint() */
volatile int zst_intr_flags;
#define INTR_RX_OVERRUN 1
#define INTR_TX_EMPTY 2
#define INTR_ST_CHECK 4
/*
* The transmit byte count and address are used for pseudo-DMA
* output in the hardware interrupt code. PDMA can be suspended
* to get pending changes done; heldtbc is used for this. It can
* also be stopped for ^S; this sets TS_TTSTOP in tp->t_state.
*/
int zst_tbc; /* transmit byte count */
caddr_t zst_tba; /* transmit buffer address */
int zst_heldtbc; /* held tbc while xmission stopped */
/*
* Printing an overrun error message often takes long enough to
* cause another overrun, so we only print one per second.
@ -129,10 +129,31 @@ struct zstty_softc {
/*
* The receive ring buffer.
*/
u_int zst_rbget; /* ring buffer `get' index */
volatile u_int zst_rbput; /* ring buffer `put' index */
u_int zst_ringmask;
int zst_rbget; /* ring buffer `get' index */
volatile int zst_rbput; /* ring buffer `put' index */
int zst_ringmask;
int zst_rbhiwat;
u_short *zst_rbuf; /* rr1, data pairs */
/*
* The transmit byte count and address are used for pseudo-DMA
* output in the hardware interrupt code. PDMA can be suspended
* to get pending changes done; heldtbc is used for this. It can
* also be stopped for ^S; this sets TS_TTSTOP in tp->t_state.
*/
int zst_tbc; /* transmit byte count */
caddr_t zst_tba; /* transmit buffer address */
int zst_heldtbc; /* held tbc while xmission stopped */
/* Flags to communicate with zstty_softint() */
volatile char zst_rx_blocked; /* input block at ring */
volatile char zst_rx_overrun; /* ring overrun */
volatile char zst_tx_busy; /* working on an output chunk */
volatile char zst_tx_done; /* done with one output chunk */
volatile char zst_tx_stopped; /* H/W level stop (lost CTS) */
volatile char zst_st_check; /* got a status interrupt */
char pad[2];
};
@ -156,6 +177,8 @@ cdev_decl(zs); /* open, close, read, write, ioctl, stop, ... */
static void zsstart(struct tty *);
static int zsparam(struct tty *, struct termios *);
static void zs_modem(struct zstty_softc *zst, int onoff);
static int zshwiflow(struct tty *, int);
static void zs_hwiflow(struct zstty_softc *, int);
/*
* zstty_match: how is this zs channel configured?
@ -234,8 +257,10 @@ zstty_attach(parent, self, aux)
tp->t_dev = dev;
tp->t_oproc = zsstart;
tp->t_param = zsparam;
tp->t_hwiflow = zshwiflow;
zst->zst_tty = tp;
zst->zst_rbhiwat = zstty_rbuf_size; /* impossible value */
zst->zst_ringmask = zstty_rbuf_size - 1;
zst->zst_rbuf = malloc(zstty_rbuf_size * sizeof(zst->zst_rbuf[0]),
M_DEVBUF, M_WAITOK);
@ -564,6 +589,13 @@ zsstart(tp)
if (tp->t_state & (TS_TIMEOUT | TS_BUSY | TS_TTSTOP))
goto out;
/*
* If under CRTSCTS hfc and halted, do nothing
*/
if (tp->t_cflag & CRTSCTS)
if (zst->zst_tx_stopped)
goto out;
/*
* If there are sleepers, and output has drained below low
* water mark, awaken.
@ -577,15 +609,16 @@ zsstart(tp)
}
nch = ndqb(&tp->t_outq, 0); /* XXX */
(void) splzs();
if (nch) {
register char *p = tp->t_outq.c_cf;
/* mark busy, enable tx done interrupts, & send first byte */
tp->t_state |= TS_BUSY;
(void) splzs();
zst->zst_tx_busy = 1;
cs->cs_preg[1] |= ZSWR1_TIE;
cs->cs_creg[1] |= ZSWR1_TIE;
cs->cs_creg[1] = cs->cs_preg[1];
zs_write_reg(cs, 1, cs->cs_creg[1]);
zs_write_data(cs, *p);
zst->zst_tba = p + 1;
@ -595,9 +628,8 @@ zsstart(tp)
* Nothing to send, turn off transmit done interrupts.
* This is useful if something is doing polled output.
*/
(void) splzs();
cs->cs_preg[1] &= ~ZSWR1_TIE;
cs->cs_creg[1] &= ~ZSWR1_TIE;
cs->cs_creg[1] = cs->cs_preg[1];
zs_write_reg(cs, 1, cs->cs_creg[1]);
}
out:
@ -623,8 +655,11 @@ zsstop(tp, flag)
if (tp->t_state & TS_BUSY) {
/*
* Device is transmitting; must stop it.
* Also clear _heldtbc to prevent any
* flow-control event from resuming.
*/
zst->zst_tbc = 0;
zst->zst_heldtbc = 0;
if ((tp->t_state & TS_TTSTOP) == 0)
tp->t_state |= TS_FLUSH;
}
@ -651,6 +686,7 @@ zsparam(tp, t)
zst = zstty_cd.cd_devs[minor(tp->t_dev)];
cs = zst->zst_cs;
/* XXX: Need to use an MD function for this. */
bps = t->c_ospeed;
if (bps < 0 || (t->c_ispeed && t->c_ispeed != bps))
return (EINVAL);
@ -707,18 +743,7 @@ zsparam(tp, t)
break;
}
/*
* Output hardware flow control on the chip is horrendous: if
* carrier detect drops, the receiver is disabled. Hence we
* can only do this when the carrier is on.
*/
tmp3 |= ZSWR3_RX_ENABLE;
if (cflag & CCTS_OFLOW) {
if (zs_read_csr(cs) & ZSRR0_DCD)
tmp3 |= ZSWR3_HFC;
}
cs->cs_preg[3] = tmp3;
cs->cs_preg[3] = tmp3 | ZSWR3_RX_ENABLE;
cs->cs_preg[5] = tmp5 | ZSWR5_TX_ENABLE | ZSWR5_DTR | ZSWR5_RTS;
tmp4 = ZSWR4_CLK_X16 | (cflag & CSTOPB ? ZSWR4_TWOSB : ZSWR4_ONESB);
@ -728,15 +753,29 @@ zsparam(tp, t)
tmp4 |= ZSWR4_PARENB;
cs->cs_preg[4] = tmp4;
/*
* Output hardware flow control on the chip is horrendous:
* if carrier detect drops, the receiver is disabled.
* Therefore, NEVER set the HFC bit, and instead use
* the status interrupts to detect CTS changes.
*/
if (cflag & CRTSCTS) {
zst->zst_rbhiwat = zstty_rbuf_hiwat;
cs->cs_preg[15] |= ZSWR15_CTS_IE;
} else {
zst->zst_rbhiwat = zstty_rbuf_size; /* impossible value */
cs->cs_preg[15] &= ~ZSWR15_CTS_IE;
}
/*
* If nothing is being transmitted, set up new current values,
* else mark them as pending.
*/
if (cs->cs_heldchange == 0) {
if (tp->t_state & TS_BUSY) {
if (zst->zst_tx_busy) {
zst->zst_heldtbc = zst->zst_tbc;
zst->zst_tbc = 0;
cs->cs_heldchange = 1;
cs->cs_heldchange = 0xFFFF;
} else {
zs_loadchannelregs(cs);
}
@ -771,37 +810,113 @@ zs_modem(zst, onoff)
s = splzs();
cs->cs_preg[5] = (cs->cs_preg[5] | bis) & and;
if (cs->cs_heldchange == 0) {
if (tp->t_state & TS_BUSY) {
if (zst->zst_tx_busy) {
zst->zst_heldtbc = zst->zst_tbc;
zst->zst_tbc = 0;
cs->cs_heldchange = 1;
cs->cs_heldchange = (1<<5);
} else {
cs->cs_creg[5] = (cs->cs_creg[5] | bis) & and;
cs->cs_creg[5] = cs->cs_preg[5];
zs_write_reg(cs, 5, cs->cs_creg[5]);
}
}
splx(s);
}
/*
* Try to block or unblock input using hardware flow-control.
* This is called by kern/tty.c if MDMBUF|CRTSCTS is set, and
* if this function returns non-zero, the TS_TBLOCK flag will
* be set or cleared according to the "stop" arg passed.
*/
int
zshwiflow(tp, stop)
struct tty *tp;
int stop;
{
register struct zstty_softc *zst;
int s;
zst = zstty_cd.cd_devs[minor(tp->t_dev)];
s = splzs();
if (stop) {
/*
* The tty layer is asking us to block input.
* If we already did it, just return TRUE.
*/
if (zst->zst_rx_blocked)
goto out;
zst->zst_rx_blocked = 1;
} else {
/*
* The tty layer is asking us to resume input.
* The input ring is always empty by now.
*/
zst->zst_rx_blocked = 0;
}
zs_hwiflow(zst, stop);
out:
splx(s);
return 1;
}
/*
* Internal version of zshwiflow
* called at splzs
*/
static void
zs_hwiflow(zst, stop)
register struct zstty_softc *zst;
int stop;
{
register struct zs_chanstate *cs;
register struct tty *tp;
register int bis, and;
cs = zst->zst_cs;
tp = zst->zst_tty;
if (stop) {
/* Block input (Lower RTS) */
bis = 0;
and = ~ZSWR5_RTS;
} else {
/* Unblock input (Raise RTS) */
bis = ZSWR5_RTS;
and = ~0;
}
cs->cs_preg[5] = (cs->cs_preg[5] | bis) & and;
if (cs->cs_heldchange == 0) {
if (zst->zst_tx_busy) {
zst->zst_heldtbc = zst->zst_tbc;
zst->zst_tbc = 0;
cs->cs_heldchange = (1<<5);
} else {
cs->cs_creg[5] = cs->cs_preg[5];
zs_write_reg(cs, 5, cs->cs_creg[5]);
}
}
}
/****************************************************************
* Interface to the lower layer (zscc)
****************************************************************/
/*
* XXX: need to do input flow-control to avoid ring overrun.
*/
/*
* receiver ready interrupt. (splzs)
* receiver ready interrupt.
* called at splzs
*/
static void
zstty_rxint(cs)
register struct zs_chanstate *cs;
{
register struct zstty_softc *zst;
register put, put_next, ringmask;
register int cc, put, put_next, ringmask;
register u_char c, rr0, rr1;
register u_short ch_rr1;
zst = cs->cs_private;
put = zst->zst_rbput;
@ -815,18 +930,21 @@ nextchar:
*/
rr1 = zs_read_reg(cs, 1);
c = zs_read_data(cs);
ch_rr1 = (c << 8) | rr1;
if (rr1 & (ZSRR1_FE | ZSRR1_DO | ZSRR1_PE)) {
if (ch_rr1 & (ZSRR1_FE | ZSRR1_DO | ZSRR1_PE)) {
/* Clear the receive error. */
zs_write_csr(cs, ZSWR0_RESET_ERRORS);
}
zst->zst_rbuf[put] = (c << 8) | rr1;
/* XXX: Check for the stop character? */
zst->zst_rbuf[put] = ch_rr1;
put_next = (put + 1) & ringmask;
/* Would overrun if increment makes (put==get). */
if (put_next == zst->zst_rbget) {
zst->zst_intr_flags |= INTR_RX_OVERRUN;
zst->zst_rx_overrun = 1;
} else {
/* OK, really increment. */
put = put_next;
@ -840,6 +958,17 @@ nextchar:
/* Done reading. */
zst->zst_rbput = put;
/*
* If ring is getting too full, try to block input.
*/
cc = put - zst->zst_rbget;
if (cc < 0)
cc += zstty_rbuf_size;
if ((cc > zst->zst_rbhiwat) && (zst->zst_rx_blocked == 0)) {
zst->zst_rx_blocked = 1;
zs_hwiflow(zst, 1);
}
/* Ask for softint() call. */
cs->cs_softreq = 1;
}
@ -855,7 +984,26 @@ zstty_txint(cs)
register int count;
zst = cs->cs_private;
count = zst->zst_tbc;
/*
* If we suspended output for a "held" change,
* then handle that now and resume.
* Do flow-control changes ASAP.
* When the only change is for flow control,
* avoid hitting other registers, because that
* often makes the stupid zs drop input...
*/
if (cs->cs_heldchange) {
if (cs->cs_heldchange == (1<<5)) {
/* Avoid whacking the chip... */
cs->cs_creg[5] = cs->cs_preg[5];
zs_write_reg(cs, 5, cs->cs_creg[5]);
} else
zs_loadchannelregs(cs);
cs->cs_heldchange = 0;
count = zst->zst_heldtbc;
} else
count = zst->zst_tbc;
/*
* If our transmit buffer still has data,
@ -872,7 +1020,8 @@ zstty_txint(cs)
zs_write_csr(cs, ZSWR0_RESET_TXINT);
/* Ask the softint routine for more output. */
zst->zst_intr_flags |= INTR_TX_EMPTY;
zst->zst_tx_busy = 0;
zst->zst_tx_done = 1;
cs->cs_softreq = 1;
}
@ -893,26 +1042,6 @@ zstty_stint(cs)
rr0 = zs_read_csr(cs);
zs_write_csr(cs, ZSWR0_RESET_STATUS);
/*
* The chip's hardware flow control is, as noted in zsreg.h,
* busted---if the DCD line goes low the chip shuts off the
* receiver (!). If we want hardware CTS flow control but do
* not have it, and carrier is now on, turn HFC on; if we have
* HFC now but carrier has gone low, turn it off.
*/
if (rr0 & ZSRR0_DCD) {
if (tp->t_cflag & CCTS_OFLOW &&
(cs->cs_creg[3] & ZSWR3_HFC) == 0) {
cs->cs_creg[3] |= ZSWR3_HFC;
zs_write_reg(cs, 3, cs->cs_creg[3]);
}
} else {
if (cs->cs_creg[3] & ZSWR3_HFC) {
cs->cs_creg[3] &= ~ZSWR3_HFC;
zs_write_reg(cs, 3, cs->cs_creg[3]);
}
}
/*
* Check here for console break, so that we can abort
* even when interrupts are locking up the machine.
@ -924,8 +1053,21 @@ zstty_stint(cs)
return;
}
/*
* Need to handle CTS output flow control here.
* Output remains stopped as long as either the
* zst_tx_stopped or TS_TTSTOP flag is set.
* Never restart here; the softint routine will
* do that after things are ready to move.
*/
if (((rr0 & ZSRR0_CTS) == 0) && (tp->t_cflag & CRTSCTS)) {
zst->zst_tbc = 0;
zst->zst_heldtbc = 0;
zst->zst_tx_stopped = 1;
}
cs->cs_rr0_new = rr0;
zst->zst_intr_flags |= INTR_ST_CHECK;
zst->zst_st_check = 1;
/* Ask for softint() call. */
cs->cs_softreq = 1;
@ -950,6 +1092,15 @@ zsoverrun(zst, ptime, what)
/*
* Software interrupt. Called at zssoft
*
* The main job to be done here is to empty the input ring
* by passing its contents up to the tty layer. The ring is
* always emptied during this operation, therefore the ring
* must not be larger than the space after "high water" in
* the tty layer, or the tty layer might drop our input.
*
* Note: an "input blockage" condition is assumed to exist if
* EITHER the TS_TBLOCK flag or zst_rx_blocked flag is set.
*/
static void
zstty_softint(cs)
@ -959,28 +1110,23 @@ zstty_softint(cs)
register struct linesw *line;
register struct tty *tp;
register int get, c, s;
int intr_flags, ringmask;
int ringmask, overrun;
register u_short ring_data;
register u_char rr0, rr1;
register u_char rr0, rr1, delta;
zst = cs->cs_private;
tp = zst->zst_tty;
line = &linesw[tp->t_line];
ringmask = zst->zst_ringmask;
/* Atomically get and clear flags. */
s = splzs();
intr_flags = zst->zst_intr_flags;
zst->zst_intr_flags = 0;
overrun = 0;
/*
* Lower to tty priority while servicing the ring.
* Raise to tty priority while servicing the ring.
*/
(void) spltty();
s = spltty();
if (intr_flags & INTR_RX_OVERRUN) {
/* May turn this on again below. */
intr_flags &= ~INTR_RX_OVERRUN;
if (zst->zst_rx_overrun) {
zst->zst_rx_overrun = 0;
zsoverrun(zst, &zst->zst_rotime, "ring");
}
@ -993,7 +1139,7 @@ zstty_softint(cs)
get = (get + 1) & ringmask;
if (ring_data & ZSRR1_DO)
intr_flags |= INTR_RX_OVERRUN;
overrun++;
/* low byte of ring_data is rr1 */
c = (ring_data >> 8) & 0xff;
if (ring_data & ZSRR1_FE)
@ -1010,57 +1156,61 @@ zstty_softint(cs)
* copying char/status pairs from the ring, which
* means this was a hardware (fifo) overrun.
*/
if (intr_flags & INTR_RX_OVERRUN) {
if (overrun) {
zsoverrun(zst, &zst->zst_fotime, "fifo");
}
if (intr_flags & INTR_TX_EMPTY) {
/*
* The transmitter output buffer count is zero.
* If we suspended output for a "held" change,
* then handle that now and resume. Otherwise,
* try to start a new output chunk.
*/
if (cs->cs_heldchange) {
(void) splzs();
rr0 = zs_read_csr(cs);
if ((rr0 & ZSRR0_DCD) == 0)
cs->cs_preg[3] &= ~ZSWR3_HFC;
zs_loadchannelregs(cs);
(void) spltty();
cs->cs_heldchange = 0;
if (zst->zst_heldtbc &&
(tp->t_state & TS_TTSTOP) == 0)
{
zst->zst_tbc = zst->zst_heldtbc - 1;
zs_write_data(cs, *zst->zst_tba);
zst->zst_tba++;
goto tx_resumed;
/*
* We have emptied the input ring. Maybe unblock input.
* Note: an "input blockage" condition is assumed to exist
* when EITHER zst_rx_blocked or the TS_TBLOCK flag is set,
* so unblock here ONLY if TS_TBLOCK has not been set.
*/
if (zst->zst_rx_blocked && ((tp->t_state & TS_TBLOCK) == 0)) {
(void) splzs();
zst->zst_rx_blocked = 0;
zs_hwiflow(zst, 0); /* unblock input */
(void) spltty();
}
/*
* Do any deferred work for status interrupts.
* The rr0 was saved in the h/w interrupt to
* avoid another splzs in here.
*/
if (zst->zst_st_check) {
zst->zst_st_check = 0;
rr0 = cs->cs_rr0_new;
delta = rr0 ^ cs->cs_rr0;
cs->cs_rr0 = rr0;
if (delta & ZSRR0_DCD) {
c = ((rr0 & ZSRR0_DCD) != 0);
if (line->l_modem(tp, c) == 0)
zs_modem(zst, c);
}
if ((delta & ZSRR0_CTS) && (tp->t_cflag & CRTSCTS)) {
/*
* Only do restart here. Stop is handled
* at the h/w interrupt level.
*/
if (rr0 & ZSRR0_CTS) {
zst->zst_tx_stopped = 0;
tp->t_state &= ~TS_TTSTOP;
(*line->l_start)(tp);
}
}
}
if (zst->zst_tx_done) {
zst->zst_tx_done = 0;
tp->t_state &= ~TS_BUSY;
if (tp->t_state & TS_FLUSH)
tp->t_state &= ~TS_FLUSH;
else
ndflush(&tp->t_outq, zst->zst_tba -
(caddr_t) tp->t_outq.c_cf);
(caddr_t) tp->t_outq.c_cf);
line->l_start(tp);
tx_resumed:
}
if (intr_flags & INTR_ST_CHECK) {
/*
* Status line change. HFC bit is run in
* hardware interrupt, to avoid locking
* at splzs here.
*/
rr0 = cs->cs_rr0_new;
if ((rr0 ^ cs->cs_rr0) & ZSRR0_DCD) {
c = ((rr0 & ZSRR0_DCD) != 0);
if (line->l_modem(tp, c) == 0)
zs_modem(zst, c);
}
cs->cs_rr0 = rr0;
}
splx(s);