/*-
* Copyright (c) 1997 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Leo Weppelman.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*-
* Copyright (c) 1993, 1994, 1995, 1996, 1997
* Charles M. Hannum. All rights reserved.
*
* Interrupt processing and hardware flow control partly based on code from
* Onno van der Linden and Gordon Ross.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Charles M. Hannum.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) 1991 The Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)com.c 7.5 (Berkeley) 5/16/91
*/
/* Buffer size for character buffer */
#define RXBUFSIZE 2048 /* More than enough.. */
#define RXBUFMASK (RXBUFSIZE-1) /* Only iff previous is a power of 2 */
#define RXHIWAT (RXBUFSIZE >> 2)
static int
seropen(dev_t dev, int flag, int mode, struct lwp *l)
{
int unit = SERUNIT(dev);
struct ser_softc *sc;
struct tty *tp;
int s, s2;
int error = 0;
sc = device_lookup_private(&ser_cd, unit);
if (sc == NULL)
return ENXIO;
/* Fetch the current modem control status, needed later. */
sc->sc_msr = ~MFP->mf_gpip & (IO_SDCD|IO_SCTS|IO_SRI);
/* Add some entry points needed by the tty layer. */
tp->t_oproc = serstart;
tp->t_param = serparam;
tp->t_hwiflow = serhwiflow;
tp->t_dev = dev;
/*
* Initialize the termios status to the defaults. Add in the
* sticky bits from TIOCSFLAGS.
*/
t.c_ispeed = 0;
if (ISSET(sc->sc_hwflags, SER_HW_CONSOLE)) {
t.c_ospeed = CONSBAUD;
t.c_cflag = CONSCFLAG;
} else {
t.c_ospeed = TTYDEF_SPEED;
t.c_cflag = TTYDEF_CFLAG;
}
if (ISSET(sc->sc_swflags, TIOCFLAG_CLOCAL))
SET(t.c_cflag, CLOCAL);
if (ISSET(sc->sc_swflags, TIOCFLAG_CRTSCTS))
SET(t.c_cflag, CRTSCTS);
if (ISSET(sc->sc_swflags, TIOCFLAG_MDMBUF))
SET(t.c_cflag, MDMBUF);
tp->t_iflag = TTYDEF_IFLAG;
tp->t_oflag = TTYDEF_OFLAG;
tp->t_lflag = TTYDEF_LFLAG;
ttychars(tp);
(void)serparam(tp, &t);
ttsetwater(tp);
s2 = splhigh();
/*
* Turn on DTR. We must always do this, even if carrier is not
* present, because otherwise we'd have to use TIOCSDTR
* immediately after setting CLOCAL. We will drop DTR only on
* the next high-low transition of DCD, or by explicit request.
*/
ser_modem(sc, 1);
error = ttyopen(tp, SERDIALOUT(dev), ISSET(flag, O_NONBLOCK));
if (error)
goto bad;
error = (*tp->t_linesw->l_open)(dev, tp);
if (error)
goto bad;
return 0;
bad:
if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) {
/*
* We failed to open the device, and nobody else had it opened.
* Clean up the state as appropriate.
*/
ser_shutdown(sc);
}
return error;
}
static int
serclose(dev_t dev, int flag, int mode, struct lwp *l)
{
int unit = SERUNIT(dev);
struct ser_softc *sc = device_lookup_private(&ser_cd, unit);
struct tty *tp = sc->sc_tty;
/* XXX This is for cons.c. */
if (!ISSET(tp->t_state, TS_ISOPEN))
return 0;
(*tp->t_linesw->l_close)(tp, flag);
ttyclose(tp);
if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) {
/*
* Although we got a last close, the device may still be in
* use; e.g. if this was the dialout node, and there are still
* processes waiting for carrier on the non-dialout node.
*/
ser_shutdown(sc);
}
int
serparam(struct tty *tp, struct termios *t)
{
struct ser_softc *sc =
device_lookup_private(&ser_cd, SERUNIT(tp->t_dev));
int ospeed = serspeed(t->c_ospeed);
uint8_t ucr;
int s;
/* check requested parameters */
if (ospeed < 0)
return EINVAL;
if (t->c_ispeed && t->c_ispeed != t->c_ospeed)
return EINVAL;
sc->sc_rsr = RSR_ENAB;
sc->sc_tsr = TSR_ENAB;
ucr = UCR_CLKDIV;
switch (ISSET(t->c_cflag, CSIZE)) {
case CS5:
SET(ucr, UCR_5BITS);
break;
case CS6:
SET(ucr, UCR_6BITS);
break;
case CS7:
SET(ucr, UCR_7BITS);
break;
case CS8:
SET(ucr, UCR_8BITS);
break;
}
if (ISSET(t->c_cflag, PARENB)) {
SET(ucr, UCR_PENAB);
if (!ISSET(t->c_cflag, PARODD))
SET(ucr, UCR_PEVEN);
}
if (ISSET(t->c_cflag, CSTOPB))
SET(ucr, UCR_STOPB2);
else
SET(ucr, UCR_STOPB1);
s = splhigh();
sc->sc_ucr = ucr;
/*
* For the console, always force CLOCAL and !HUPCL, so that the port
* is always active.
*/
if (ISSET(sc->sc_swflags, TIOCFLAG_SOFTCAR) ||
ISSET(sc->sc_hwflags, SER_HW_CONSOLE)) {
SET(t->c_cflag, CLOCAL);
CLR(t->c_cflag, HUPCL);
}
/*
* If we're not in a mode that assumes a connection is present, then
* ignore carrier changes.
*/
if (ISSET(t->c_cflag, CLOCAL | MDMBUF))
sc->sc_msr_dcd = 0;
else
sc->sc_msr_dcd = MCR_DCD;
/*
* Set the flow control pins depending on the current flow control
* mode.
*/
if (ISSET(t->c_cflag, CRTSCTS)) {
sc->sc_mcr_dtr = MCR_DTR;
sc->sc_mcr_rts = MCR_RTS;
sc->sc_msr_cts = MCR_CTS;
sc->sc_r_hiwat = RXHIWAT;
} else if (ISSET(t->c_cflag, MDMBUF)) {
/*
* For DTR/DCD flow control, make sure we don't toggle DTR for
* carrier detection.
*/
sc->sc_mcr_dtr = 0;
sc->sc_mcr_rts = MCR_DTR;
sc->sc_msr_cts = MCR_DCD;
sc->sc_r_hiwat = RXHIWAT;
} else {
/*
* If no flow control, then always set RTS. This will make
* the other side happy if it mistakenly thinks we're doing
* RTS/CTS flow control.
*/
sc->sc_mcr_dtr = MCR_DTR | MCR_RTS;
sc->sc_mcr_rts = 0;
sc->sc_msr_cts = 0;
sc->sc_r_hiwat = 0;
if (ISSET(sc->sc_mcr, MCR_DTR))
SET(sc->sc_mcr, MCR_RTS);
else
CLR(sc->sc_mcr, MCR_RTS);
}
sc->sc_msr_mask = sc->sc_msr_cts | sc->sc_msr_dcd;
/* and copy to tty */
tp->t_ispeed = 0;
tp->t_ospeed = t->c_ospeed;
tp->t_cflag = t->c_cflag;
if (!sc->sc_heldchange) {
if (sc->sc_tx_busy) {
sc->sc_heldtbc = sc->sc_tbc;
sc->sc_tbc = 0;
sc->sc_heldchange = 1;
} else
ser_loadchannelregs(sc);
}
splx(s);
/*
* Update the tty layer's idea of the carrier bit, in case we changed
* CLOCAL or MDMBUF. We don't hang up here; we only do that if we
* lose carrier while carrier detection is on.
*/
(void)(*tp->t_linesw->l_modem)(tp, ISSET(sc->sc_msr, MCR_DCD));
int
serhwiflow(struct tty *tp, int block)
{
struct ser_softc *sc =
device_lookup_private(&ser_cd, SERUNIT(tp->t_dev));
int s;
if (sc->sc_mcr_rts == 0)
return 0;
s = splhigh();
if (block) {
/*
* The tty layer is asking us to block input.
* If we already did it, just return TRUE.
*/
if (sc->sc_rx_blocked)
goto out;
sc->sc_rx_blocked = 1;
} else {
/*
* The tty layer is asking us to resume input.
* The input ring is always empty by now.
*/
sc->sc_rx_blocked = 0;
}
ser_hwiflow(sc, block);
out:
splx(s);
return 1;
}
/*
* (un)block input via hw flowcontrol
*/
void
ser_hwiflow(struct ser_softc *sc, int block)
{
/*
* Stop output on a line.
*/
static void
serstop(struct tty *tp, int flag)
{
struct ser_softc *sc =
device_lookup_private(&ser_cd, SERUNIT(tp->t_dev));
int s;
s = splhigh();
if (ISSET(tp->t_state, TS_BUSY)) {
/* Stop transmitting at the next chunk. */
sc->sc_tbc = 0;
sc->sc_heldtbc = 0;
if (!ISSET(tp->t_state, TS_TTSTOP))
SET(tp->t_state, TS_FLUSH);
}
splx(s);
}
void
serdiag(void *arg)
{
struct ser_softc *sc = arg;
int overflows, floods;
int s;
/* If we were asserting flow control, then deassert it. */
sc->sc_rx_blocked = 1;
ser_hwiflow(sc, 1);
/* Clear any break condition set with TIOCSBRK. */
ser_break(sc, 0);
/*
* Hang up if necessary. Wait a bit, so the other side has time to
* notice even if we immediately open the port again.
*/
if (ISSET(tp->t_cflag, HUPCL)) {
ser_modem(sc, 0);
(void)tsleep(sc, TTIPRI, ttclos, hz);
}
get = sc->sc_rbget;
scc = cc = RXBUFSIZE - sc->sc_rbavail;
if (cc == RXBUFSIZE) {
sc->sc_floods++;
if (sc->sc_errors++ == 0)
callout_reset(&sc->sc_diag_ch, 60 * hz, serdiag, sc);
}
while (cc--) {
rsr = sc->sc_lbuf[get];
if (ISSET(rsr, RSR_BREAK)) {
#ifdef DDB
if (ISSET(sc->sc_hwflags, SER_HW_CONSOLE))
Debugger();
#endif
} else if (ISSET(rsr, RSR_OERR)) {
sc->sc_overflows++;
if (sc->sc_errors++ == 0)
callout_reset(&sc->sc_diag_ch, 60 * hz,
serdiag, sc);
}
code = sc->sc_rbuf[get] |
lsrmap[(rsr & (RSR_BREAK|RSR_FERR|RSR_PERR)) >> 3];
(*tp->t_linesw->l_rint)(code, tp);
get = (get + 1) & RXBUFMASK;
}
sc->sc_rbget = get;
s = splhigh();
sc->sc_rbavail += scc;
/*
* Buffers should be ok again, release possible block, but only if the
* tty layer isn't blocking too.
*/
if (sc->sc_rx_blocked && !ISSET(tp->t_state, TS_TBLOCK)) {
sc->sc_rx_blocked = 0;
ser_hwiflow(sc, 0);
}
splx(s);
}
if (ISSET(delta, sc->sc_msr_mask)) {
sc->sc_msr_delta |= delta;
/*
* Stop output immediately if we lose the output
* flow control signal or carrier detect.
*/
if (ISSET(~msr, sc->sc_msr_mask)) {
sc->sc_tbc = 0;
sc->sc_heldtbc = 0;
#ifdef SER_DEBUG
serstatus(sc, "sermintr ");
#endif
}
rsr = MFP->mf_rsr;
if (ISSET(rsr, RSR_BFULL|RSR_BREAK)) {
for (; ISSET(rsr, RSR_BFULL|RSR_BREAK) && cc > 0; cc--) {
sc->sc_rbuf[put] = MFP->mf_udr;
sc->sc_lbuf[put] = rsr;
put = (put + 1) & RXBUFMASK;
if ((rsr & RSR_BREAK) && (MFP->mf_rsr & RSR_BREAK))
rsr = 0;
else
rsr = MFP->mf_rsr;
}
/*
* Current string of incoming characters ended because
* no more data was available. Schedule a receive event
* if any data was received. Drop any characters that
* we couldn't handle.
*/
sc->sc_rbput = put;
sc->sc_rbavail = cc;
sc->sc_rx_ready = 1;
/*
* See if we are in danger of overflowing a buffer. If
* so, use hardware flow control to ease the pressure.
*/
if (sc->sc_rx_blocked == 0 &&
cc < sc->sc_r_hiwat) {
sc->sc_rx_blocked = 1;
ser_hwiflow(sc, 1);
}
/*
* If we're out of space, throw away any further input.
*/
if (!cc) {
while (ISSET(rsr, RSR_BFULL|RSR_BREAK)) {
rsr = MFP->mf_udr;
rsr = MFP->mf_rsr;
}
}
}
/*
* Done handling any receive interrupts. See if data can be
* transmitted as well. Schedule tx done event if no data left
* and tty was marked busy.
*/
tsr = MFP->mf_tsr;
if (ISSET(tsr, TSR_BE)) {
/*
* If we've delayed a parameter change, do it now, and restart
* output.
*/
if (sc->sc_heldchange) {
ser_loadchannelregs(sc);
sc->sc_heldchange = 0;
sc->sc_tbc = sc->sc_heldtbc;
sc->sc_heldtbc = 0;
}
/* Output the next character, if any. */
if (sc->sc_tbc > 0) {
MFP->mf_udr = *sc->sc_tba;
sc->sc_tbc--;
sc->sc_tba++;
} else if (sc->sc_tx_busy) {
sc->sc_tx_busy = 0;
sc->sc_tx_done = 1;
}
}
softint_schedule(sc->sc_sicookie);
return 1;
}
static int
serspeed(long speed)
{
#define divrnd(n, q) (((n)*2/(q)+1)/2) /* divide and round off */
int div, x, err;
if (speed <= 0)
return -1;
for (div = 4; div <= 64; div *= 4) {
x = divrnd((SER_FREQ / div), speed);
/*
* The value must fit in the timer-d dataregister. If
* not, try another delay-mode.
*/
if ((x / 2) > 255)
continue;
/*
* Baudrate to high for the interface or cannot be made
* within tolerance.
*/
if (x <= 0)
return -1;