3683e698d2
Move dtrwait logic to the generic TTY level.
1991 lines
52 KiB
C
1991 lines
52 KiB
C
/*
|
|
* Device tsfsdriver for Specialix I/O8+ multiport serial card.
|
|
*
|
|
* Copyright 2003 Frank Mayhar <frank@exit.com>
|
|
*
|
|
* Derived from the "si" driver by Peter Wemm <peter@netplex.com.au>, using
|
|
* lots of information from the Linux "specialix" driver by Roger Wolff
|
|
* <R.E.Wolff@BitWizard.nl> and from the Intel CD1865 "Intelligent Eight-
|
|
* Channel Communications Controller" datasheet. Roger was also nice
|
|
* enough to answer numerous questions about stuff specific to the I/O8+
|
|
* not covered by the CD1865 datasheet.
|
|
*
|
|
* 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
|
|
* notices, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notices, this list of conditions and the foljxowing disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY ``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 AUTHORS BE LIABLE.
|
|
*
|
|
* $FreeBSD$
|
|
*/
|
|
|
|
|
|
/* Main tty driver routines for the Specialix I/O8+ device driver. */
|
|
|
|
#include "opt_compat.h"
|
|
#include "opt_debug_sx.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#ifndef BURN_BRIDGES
|
|
#if defined(COMPAT_43)
|
|
#include <sys/ioctl_compat.h>
|
|
#endif
|
|
#endif
|
|
#include <sys/tty.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/fcntl.h>
|
|
#include <sys/dkstat.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/bus.h>
|
|
#include <machine/bus.h>
|
|
#include <sys/rman.h>
|
|
#include <machine/resource.h>
|
|
|
|
#include <machine/clock.h>
|
|
|
|
#include <vm/vm.h>
|
|
#include <vm/pmap.h>
|
|
|
|
#include <machine/stdarg.h>
|
|
|
|
#include <dev/sx/cd1865.h>
|
|
#include <dev/sx/sxvar.h>
|
|
#include <dev/sx/sx.h>
|
|
#include <dev/sx/sx_util.h>
|
|
|
|
#define SX_BROKEN_CTS
|
|
|
|
enum sx_mctl { GET, SET, BIS, BIC };
|
|
|
|
static int sx_modem(struct sx_softc *, struct sx_port *, enum sx_mctl, int);
|
|
static void sx_write_enable(struct sx_port *, int);
|
|
static void sx_start(struct tty *);
|
|
static void sx_stop(struct tty *, int);
|
|
static void sxhardclose(struct sx_port *pp);
|
|
static void sxdtrwakeup(void *chan);
|
|
static void sx_shutdown_chan(struct sx_port *);
|
|
|
|
#ifdef SX_DEBUG
|
|
static char *sx_mctl2str(enum sx_mctl cmd);
|
|
#endif
|
|
|
|
static int sxparam(struct tty *, struct termios *);
|
|
|
|
static void sx_modem_state(struct sx_softc *sc, struct sx_port *pp, int card);
|
|
|
|
static d_open_t sxopen;
|
|
static d_close_t sxclose;
|
|
static d_write_t sxwrite;
|
|
static d_ioctl_t sxioctl;
|
|
|
|
#define CDEV_MAJOR 185
|
|
static struct cdevsw sx_cdevsw = {
|
|
.d_version = D_VERSION,
|
|
.d_open = sxopen,
|
|
.d_close = sxclose,
|
|
.d_write = sxwrite,
|
|
.d_ioctl = sxioctl,
|
|
.d_name = "sx",
|
|
.d_flags = D_TTY | D_NEEDGIANT,
|
|
};
|
|
|
|
static int sx_debug = 0; /* DBG_ALL|DBG_PRINTF|DBG_MODEM|DBG_IOCTL|DBG_PARAM;e */
|
|
SYSCTL_INT(_machdep, OID_AUTO, sx_debug, CTLFLAG_RW, &sx_debug, 0, "");
|
|
|
|
static struct tty *sx__tty;
|
|
|
|
static int sx_numunits;
|
|
|
|
devclass_t sx_devclass;
|
|
|
|
/*
|
|
* See sx.h for these values.
|
|
*/
|
|
static struct speedtab bdrates[] = {
|
|
{ B75, CLK75, },
|
|
{ B110, CLK110, },
|
|
{ B150, CLK150, },
|
|
{ B300, CLK300, },
|
|
{ B600, CLK600, },
|
|
{ B1200, CLK1200, },
|
|
{ B2400, CLK2400, },
|
|
{ B4800, CLK4800, },
|
|
{ B9600, CLK9600, },
|
|
{ B19200, CLK19200, },
|
|
{ B38400, CLK38400, },
|
|
{ B57600, CLK57600, },
|
|
{ B115200, CLK115200, },
|
|
{ -1, -1 },
|
|
};
|
|
|
|
|
|
/*
|
|
* Approximate (rounded) character per second rates. Translated at card
|
|
* initialization time to characters per clock tick.
|
|
*/
|
|
static int done_chartimes = 0;
|
|
static struct speedtab chartimes[] = {
|
|
{ B75, 8, },
|
|
{ B110, 11, },
|
|
{ B150, 15, },
|
|
{ B300, 30, },
|
|
{ B600, 60, },
|
|
{ B1200, 120, },
|
|
{ B2400, 240, },
|
|
{ B4800, 480, },
|
|
{ B9600, 960, },
|
|
{ B19200, 1920, },
|
|
{ B38400, 3840, },
|
|
{ B57600, 5760, },
|
|
{ B115200, 11520, },
|
|
{ -1, -1 },
|
|
};
|
|
static volatile int in_interrupt = 0; /* Inside interrupt handler? */
|
|
|
|
static int sx_flags; /* The flags we were configured with. */
|
|
SYSCTL_INT(_machdep, OID_AUTO, sx_flags, CTLFLAG_RW, &sx_flags, 0, "");
|
|
|
|
#ifdef POLL
|
|
static int sx_pollrate; /* in addition to irq */
|
|
static int sx_realpoll = 0; /* poll HW on timer */
|
|
|
|
SYSCTL_INT(_machdep, OID_AUTO, sx_pollrate, CTLFLAG_RW, &sx_pollrate, 0, "");
|
|
SYSCTL_INT(_machdep, OID_AUTO, sx_realpoll, CTLFLAG_RW, &sx_realpoll, 0, "");
|
|
|
|
static int init_finished = 0;
|
|
static void sx_poll(void *);
|
|
#endif
|
|
|
|
/*
|
|
* sxattach()
|
|
* Initialize and attach the card, initialize the driver.
|
|
*
|
|
* Description:
|
|
* This is the standard attach routine. It initializes the I/O8+
|
|
* card, identifies the chip on that card, then allocates and
|
|
* initializes the various data structures used by the driver
|
|
* itself.
|
|
*/
|
|
int
|
|
sxattach(
|
|
device_t dev)
|
|
{
|
|
int unit;
|
|
struct sx_softc *sc;
|
|
struct tty *tp;
|
|
struct speedtab *spt;
|
|
int chip, x, y;
|
|
char rev;
|
|
int error;
|
|
|
|
sc = device_get_softc(dev);
|
|
unit = device_get_unit(dev);
|
|
sx_flags = device_get_flags(dev);
|
|
|
|
if (sx_numunits < unit + 1)
|
|
sx_numunits = unit + 1;
|
|
|
|
DPRINT((0, DBG_AUTOBOOT, "sx%d: sxattach\n", unit));
|
|
|
|
/* Reset the CD1865. */
|
|
if ((error = sx_init_cd1865(sc, unit)) != 0) {
|
|
return(error);
|
|
}
|
|
|
|
/*
|
|
* ID the chip:
|
|
*
|
|
* Chip revcode pkgtype
|
|
* GFRCR SRCR bit 7
|
|
* CD180 rev B 0x81 0
|
|
* CD180 rev C 0x82 0
|
|
* CD1864 rev A 0x82 1
|
|
* CD1865 rev A 0x83 1 -- Do not use!!! Does not work.
|
|
* CD1865 rev B 0x84 1
|
|
* -- Thanks to Gwen Wang, Cirrus Logic (via Roger Wollf).
|
|
*/
|
|
switch (sx_cd1865_in(sc, CD1865_GFRCR)) {
|
|
case 0x82:
|
|
chip = 1864;
|
|
rev = 'A';
|
|
break;
|
|
case 0x83:
|
|
chip = 1865;
|
|
rev = 'A';
|
|
break;
|
|
case 0x84:
|
|
chip = 1865;
|
|
rev = 'B';
|
|
break;
|
|
case 0x85:
|
|
chip = 1865;
|
|
rev = 'C';
|
|
break;
|
|
default:
|
|
chip = -1;
|
|
rev = '\0';
|
|
break;
|
|
}
|
|
|
|
if (bootverbose && chip != -1)
|
|
printf("sx%d: Specialix I/O8+ CD%d processor rev %c\n",
|
|
unit, chip, rev);
|
|
DPRINT((0, DBG_AUTOBOOT, "sx%d: GFRCR 0x%02x\n",
|
|
unit, sx_cd1865_in(sc, CD1865_GFRCR)));
|
|
#ifdef POLL
|
|
if (sx_pollrate == 0) {
|
|
sx_pollrate = POLLHZ; /* in addition to irq */
|
|
#ifdef REALPOLL
|
|
sx_realpoll = 1; /* scan always */
|
|
#endif
|
|
}
|
|
#endif
|
|
sc->sc_ports = (struct sx_port *)malloc(
|
|
sizeof(struct sx_port) * SX_NUMCHANS,
|
|
M_DEVBUF,
|
|
M_NOWAIT);
|
|
if (sc->sc_ports == NULL) {
|
|
printf("sx%d: No memory for sx_port structs!\n", unit);
|
|
return(EINVAL);
|
|
}
|
|
bzero(sc->sc_ports, sizeof(struct sx_port) * SX_NUMCHANS);
|
|
/*
|
|
* Allocate tty structures for the channels.
|
|
*/
|
|
tp = (struct tty *)malloc(sizeof(struct tty) * SX_NUMCHANS,
|
|
M_DEVBUF,
|
|
M_NOWAIT);
|
|
if (tp == NULL) {
|
|
free(sc->sc_ports, M_DEVBUF);
|
|
printf("sx%d: No memory for tty structs!\n", unit);
|
|
return(EINVAL);
|
|
}
|
|
bzero(tp, sizeof(struct tty) * SX_NUMCHANS);
|
|
sx__tty = tp;
|
|
/*
|
|
* Initialize the channels.
|
|
*/
|
|
for (x = 0; x < SX_NUMCHANS; x++) {
|
|
sc->sc_ports[x].sp_chan = x;
|
|
sc->sc_ports[x].sp_tty = tp++;
|
|
sc->sc_ports[x].sp_state = 0; /* internal flag */
|
|
sc->sc_ports[x].sp_iin.c_iflag = TTYDEF_IFLAG;
|
|
sc->sc_ports[x].sp_iin.c_oflag = TTYDEF_OFLAG;
|
|
sc->sc_ports[x].sp_iin.c_cflag = TTYDEF_CFLAG;
|
|
sc->sc_ports[x].sp_iin.c_lflag = TTYDEF_LFLAG;
|
|
termioschars(&sc->sc_ports[x].sp_iin);
|
|
sc->sc_ports[x].sp_iin.c_ispeed = TTYDEF_SPEED;;
|
|
sc->sc_ports[x].sp_iin.c_ospeed = TTYDEF_SPEED;;
|
|
sc->sc_ports[x].sp_iout = sc->sc_ports[x].sp_iin;
|
|
}
|
|
if (done_chartimes == 0) {
|
|
for (spt = chartimes ; spt->sp_speed != -1; spt++) {
|
|
if ((spt->sp_code /= hz) == 0)
|
|
spt->sp_code = 1;
|
|
}
|
|
done_chartimes = 1;
|
|
}
|
|
/*
|
|
* Set up the known devices.
|
|
*/
|
|
y = unit * (1 << SX_CARDSHIFT);
|
|
for (x = 0; x < SX_NUMCHANS; x++) {
|
|
register int num;
|
|
|
|
/* DTR/RTS -> RTS devices. */
|
|
num = x + y;
|
|
make_dev(&sx_cdevsw, x, 0, 0, 0600, "ttyG%02d", x+y);
|
|
make_dev(&sx_cdevsw, x + 0x00080, 0, 0, 0600, "cuaG%02d", num);
|
|
make_dev(&sx_cdevsw, x + 0x10000, 0, 0, 0600, "ttyiG%02d", num);
|
|
make_dev(&sx_cdevsw, x + 0x10080, 0, 0, 0600, "cuaiG%02d", num);
|
|
make_dev(&sx_cdevsw, x + 0x20000, 0, 0, 0600, "ttylG%02d", num);
|
|
make_dev(&sx_cdevsw, x + 0x20080, 0, 0, 0600, "cualG%02d", num);
|
|
/* DTR/RTS -> DTR devices. */
|
|
num += SX_NUMCHANS;
|
|
make_dev(&sx_cdevsw, x + 0x00008, 0, 0, 0600, "ttyG%02d", num);
|
|
make_dev(&sx_cdevsw, x + 0x00088, 0, 0, 0600, "cuaG%02d", num);
|
|
make_dev(&sx_cdevsw, x + 0x10008, 0, 0, 0600, "ttyiG%02d", num);
|
|
make_dev(&sx_cdevsw, x + 0x10088, 0, 0, 0600, "cuaiG%02d", num);
|
|
make_dev(&sx_cdevsw, x + 0x20008, 0, 0, 0600, "ttylG%02d", num);
|
|
make_dev(&sx_cdevsw, x + 0x20088, 0, 0, 0600, "cualG%02d", num);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* sxopen()
|
|
* Open a port on behalf of a user.
|
|
*
|
|
* Description:
|
|
* This is the standard open routine.
|
|
*/
|
|
static int
|
|
sxopen(
|
|
struct cdev *dev,
|
|
int flag,
|
|
int mode,
|
|
d_thread_t *p)
|
|
{
|
|
int oldspl, error;
|
|
int card, chan;
|
|
struct sx_softc *sc;
|
|
struct tty *tp;
|
|
struct sx_port *pp;
|
|
int mynor = minor(dev);
|
|
|
|
card = SX_MINOR2CARD(mynor);
|
|
if ((sc = devclass_get_softc(sx_devclass, card)) == NULL)
|
|
return (ENXIO);
|
|
chan = SX_MINOR2CHAN(mynor);
|
|
if (chan >= SX_NUMCHANS) {
|
|
DPRINT((0, DBG_OPEN|DBG_FAIL, "sx%d: nchans %d\n",
|
|
card, SX_NUMCHANS));
|
|
return(ENXIO);
|
|
}
|
|
#ifdef POLL
|
|
/*
|
|
* We've now got a device, so start the poller.
|
|
*/
|
|
if (init_finished == 0) {
|
|
timeout(sx_poll, (caddr_t)0L, sx_pollrate);
|
|
init_finished = 1;
|
|
}
|
|
#endif
|
|
/* initial/lock device */
|
|
if (DEV_IS_STATE(mynor)) {
|
|
return(0);
|
|
}
|
|
pp = &(sc->sc_ports[chan]);
|
|
tp = pp->sp_tty; /* the "real" tty */
|
|
dev->si_tty = tp;
|
|
DPRINT((pp, DBG_ENTRY|DBG_OPEN, "sxopen(%s,%x,%x,%x)\n",
|
|
devtoname(dev), flag, mode, p));
|
|
|
|
oldspl = spltty(); /* Keep others out */
|
|
error = 0;
|
|
/*
|
|
* The minor also indicates whether the DTR pin on this port is wired
|
|
* as DTR or as RTS. Default is zero, wired as RTS.
|
|
*/
|
|
if (DEV_DTRPIN(mynor))
|
|
pp->sp_state |= SX_SS_DTRPIN;
|
|
else
|
|
pp->sp_state &= ~SX_SS_DTRPIN;
|
|
pp->sp_state &= SX_SS_XMIT; /* Turn off "transmitting" flag. */
|
|
open_top:
|
|
/*
|
|
* If DTR is off and we actually do have a DTR pin, sleep waiting for
|
|
* it to assert.
|
|
*/
|
|
while (pp->sp_state & SX_SS_DTR_OFF && SX_DTRPIN(pp)) {
|
|
error = tsleep(&tp->t_dtr_wait, TTIPRI|PCATCH, "sxdtr", 0);
|
|
if (error != 0)
|
|
goto out;
|
|
}
|
|
|
|
if (tp->t_state & TS_ISOPEN) {
|
|
/*
|
|
* The device is open, so everything has been initialized.
|
|
* Handle conflicts.
|
|
*/
|
|
if (DEV_IS_CALLOUT(mynor)) {
|
|
if (!pp->sp_active_out) {
|
|
error = EBUSY;
|
|
goto out;
|
|
}
|
|
}
|
|
else {
|
|
if (pp->sp_active_out) {
|
|
if (flag & O_NONBLOCK) {
|
|
error = EBUSY;
|
|
goto out;
|
|
}
|
|
error = tsleep(&pp->sp_active_out,
|
|
TTIPRI|PCATCH,
|
|
"sxbi", 0);
|
|
if (error != 0)
|
|
goto out;
|
|
goto open_top;
|
|
}
|
|
}
|
|
if (tp->t_state & TS_XCLUDE && suser(p)) {
|
|
DPRINT((pp, DBG_OPEN|DBG_FAIL,
|
|
"already open and EXCLUSIVE set\n"));
|
|
error = EBUSY;
|
|
goto out;
|
|
}
|
|
} else {
|
|
/*
|
|
* The device isn't open, so there are no conflicts.
|
|
* Initialize it. Avoid sleep... :-)
|
|
*/
|
|
DPRINT((pp, DBG_OPEN, "first open\n"));
|
|
tp->t_oproc = sx_start;
|
|
tp->t_stop = sx_stop;
|
|
tp->t_param = sxparam;
|
|
tp->t_dev = dev;
|
|
tp->t_termios = mynor & SX_CALLOUT_MASK
|
|
? pp->sp_iout : pp->sp_iin;
|
|
|
|
(void)sx_modem(sc, pp, SET, TIOCM_DTR|TIOCM_RTS);
|
|
|
|
++pp->sp_wopeners; /* in case of sleep in sxparam */
|
|
|
|
error = sxparam(tp, &tp->t_termios);
|
|
|
|
--pp->sp_wopeners;
|
|
if (error != 0)
|
|
goto out;
|
|
/* XXX: we should goto_top if sxparam slept */
|
|
|
|
/* set initial DCD state */
|
|
if (DEV_IS_CALLOUT(mynor) ||
|
|
(sx_modem(sc, pp, GET, 0) & TIOCM_CD)) {
|
|
ttyld_modem(tp, 1);
|
|
}
|
|
}
|
|
/* whoops! we beat the close! */
|
|
if (pp->sp_state & SX_SS_CLOSING) {
|
|
/* try and stop it from proceeding to bash the hardware */
|
|
pp->sp_state &= ~SX_SS_CLOSING;
|
|
}
|
|
/*
|
|
* Wait for DCD if necessary
|
|
*/
|
|
if (!(tp->t_state & TS_CARR_ON) && !DEV_IS_CALLOUT(mynor) &&
|
|
!(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) {
|
|
++pp->sp_wopeners;
|
|
DPRINT((pp, DBG_OPEN, "sleeping for carrier\n"));
|
|
error = tsleep(TSA_CARR_ON(tp), TTIPRI|PCATCH, "sxdcd", 0);
|
|
--pp->sp_wopeners;
|
|
if (error != 0)
|
|
goto out;
|
|
goto open_top;
|
|
}
|
|
|
|
error = ttyld_open(tp, dev);
|
|
ttyldoptim(tp);
|
|
if (tp->t_state & TS_ISOPEN && DEV_IS_CALLOUT(mynor))
|
|
pp->sp_active_out = TRUE;
|
|
|
|
pp->sp_state |= SX_SS_OPEN; /* made it! */
|
|
|
|
out:
|
|
splx(oldspl);
|
|
|
|
DPRINT((pp, DBG_OPEN, "leaving sxopen\n"));
|
|
|
|
if (!(tp->t_state & TS_ISOPEN) && pp->sp_wopeners == 0)
|
|
sxhardclose(pp);
|
|
|
|
return(error);
|
|
}
|
|
|
|
/*
|
|
* sxclose()
|
|
* Close a port for a user.
|
|
*
|
|
* Description:
|
|
* This is the standard close routine.
|
|
*/
|
|
static int
|
|
sxclose(
|
|
struct cdev *dev,
|
|
int flag,
|
|
int mode,
|
|
d_thread_t *p)
|
|
{
|
|
struct sx_port *pp;
|
|
struct tty *tp;
|
|
int oldspl;
|
|
int error = 0;
|
|
int mynor = minor(dev);
|
|
|
|
if (DEV_IS_SPECIAL(mynor))
|
|
return(0);
|
|
|
|
oldspl = spltty();
|
|
|
|
pp = MINOR2PP(mynor);
|
|
tp = pp->sp_tty;
|
|
|
|
DPRINT((pp, DBG_ENTRY|DBG_CLOSE, "sxclose(%s,%x,%x,%x) sp_state:%x\n",
|
|
devtoname(dev), flag, mode, p, pp->sp_state));
|
|
|
|
/* did we sleep and lose a race? */
|
|
if (pp->sp_state & SX_SS_CLOSING) {
|
|
/* error = ESOMETING? */
|
|
goto out;
|
|
}
|
|
|
|
/* begin race detection.. */
|
|
pp->sp_state |= SX_SS_CLOSING;
|
|
|
|
sx_write_enable(pp, 0); /* block writes for ttywait() */
|
|
|
|
/* THIS MAY SLEEP IN TTYWAIT!!! */
|
|
ttyld_close(tp, flag);
|
|
|
|
sx_write_enable(pp, 1);
|
|
|
|
/* did we sleep and somebody started another open? */
|
|
if (!(pp->sp_state & SX_SS_CLOSING)) {
|
|
/* error = ESOMETING? */
|
|
goto out;
|
|
}
|
|
/* ok. we are now still on the right track.. nuke the hardware */
|
|
|
|
sxhardclose(pp);
|
|
ttyclose(tp);
|
|
pp->sp_state &= ~SX_SS_OPEN;
|
|
|
|
out:
|
|
DPRINT((pp, DBG_CLOSE|DBG_EXIT, "sxclose out\n"));
|
|
splx(oldspl);
|
|
return(error);
|
|
}
|
|
|
|
/*
|
|
* sxhardclose()
|
|
* Do hard-close processing.
|
|
*
|
|
* Description:
|
|
* Called on last close. Handle DTR and RTS, do cleanup. If we have
|
|
* pending output in the FIFO, wait for it to clear before we shut down
|
|
* the hardware.
|
|
*/
|
|
static void
|
|
sxhardclose(
|
|
struct sx_port *pp)
|
|
{
|
|
struct sx_softc *sc;
|
|
struct tty *tp;
|
|
int oldspl, dcd;
|
|
|
|
oldspl = spltty();
|
|
|
|
DPRINT((pp, DBG_CLOSE, "sxhardclose sp_state:%x\n", pp->sp_state));
|
|
tp = pp->sp_tty;
|
|
sc = PP2SC(pp);
|
|
dcd = sx_modem(sc, pp, GET, 0) & TIOCM_CD;
|
|
if (tp->t_cflag & HUPCL ||
|
|
(!pp->sp_active_out && !dcd && !(pp->sp_iin.c_cflag && CLOCAL)) ||
|
|
!(tp->t_state & TS_ISOPEN)) {
|
|
disable_intr();
|
|
sx_cd1865_out(sc, CD1865_CAR, pp->sp_chan);
|
|
if (sx_cd1865_in(sc, CD1865_IER|SX_EI) & CD1865_IER_TXRDY) {
|
|
sx_cd1865_bic(sc, CD1865_IER, CD1865_IER_TXRDY);
|
|
sx_cd1865_bis(sc, CD1865_IER, CD1865_IER_TXEMPTY);
|
|
enable_intr();
|
|
splx(oldspl);
|
|
ttysleep(tp, (caddr_t)pp,
|
|
TTOPRI|PCATCH, "sxclose", tp->t_timeout);
|
|
oldspl = spltty();
|
|
}
|
|
else {
|
|
enable_intr();
|
|
}
|
|
(void)sx_modem(sc, pp, BIC, TIOCM_DTR|TIOCM_RTS);
|
|
/*
|
|
* If we should hold DTR off for a bit and we actually have a
|
|
* DTR pin to hold down, schedule sxdtrwakeup().
|
|
*/
|
|
if (tp->t_dtr_wait != 0 && SX_DTRPIN(pp)) {
|
|
timeout(sxdtrwakeup, pp, tp->t_dtr_wait);
|
|
pp->sp_state |= SX_SS_DTR_OFF;
|
|
}
|
|
|
|
}
|
|
(void)sx_shutdown_chan(pp); /* Turn off the hardware. */
|
|
pp->sp_active_out = FALSE;
|
|
wakeup((caddr_t)&pp->sp_active_out);
|
|
wakeup(TSA_CARR_ON(tp));
|
|
|
|
splx(oldspl);
|
|
}
|
|
|
|
|
|
/*
|
|
* called at splsoftclock()...
|
|
*/
|
|
static void
|
|
sxdtrwakeup(void *chan)
|
|
{
|
|
struct sx_port *pp;
|
|
int oldspl;
|
|
|
|
oldspl = spltty();
|
|
pp = (struct sx_port *)chan;
|
|
pp->sp_state &= ~SX_SS_DTR_OFF;
|
|
wakeup(&pp->sp_tty->t_dtr_wait);
|
|
splx(oldspl);
|
|
}
|
|
|
|
/*
|
|
* sxwrite()
|
|
* Handle a write to a port on the I/O8+.
|
|
*
|
|
* Description:
|
|
* This just hands processing off to the line discipline.
|
|
*/
|
|
static int
|
|
sxwrite(
|
|
struct cdev *dev,
|
|
struct uio *uio,
|
|
int flag)
|
|
{
|
|
struct sx_softc *sc;
|
|
struct sx_port *pp;
|
|
struct tty *tp;
|
|
int error = 0;
|
|
int mynor = minor(dev);
|
|
int oldspl;
|
|
|
|
pp = MINOR2PP(mynor);
|
|
sc = PP2SC(pp);
|
|
tp = pp->sp_tty;
|
|
DPRINT((pp, DBG_WRITE, "sxwrite %s %x %x\n", devtoname(dev), uio, flag));
|
|
|
|
oldspl = spltty();
|
|
/*
|
|
* If writes are currently blocked, wait on the "real" tty
|
|
*/
|
|
while (pp->sp_state & SX_SS_BLOCKWRITE) {
|
|
pp->sp_state |= SX_SS_WAITWRITE;
|
|
DPRINT((pp, DBG_WRITE, "sxwrite sleep on SX_SS_BLOCKWRITE\n"));
|
|
if ((error = ttysleep(tp,
|
|
(caddr_t)pp,
|
|
TTOPRI|PCATCH,
|
|
"sxwrite",
|
|
tp->t_timeout))) {
|
|
if (error == EWOULDBLOCK)
|
|
error = EIO;
|
|
goto out;
|
|
}
|
|
}
|
|
error = ttyld_write(tp, uio, flag);
|
|
out: splx(oldspl);
|
|
DPRINT((pp, DBG_WRITE, "sxwrite out\n"));
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* sxioctl()
|
|
* Handle ioctl() processing.
|
|
*
|
|
* Description:
|
|
* This is the standard serial ioctl() routine. It was cribbed almost
|
|
* entirely from the si(4) driver. Thanks, Peter.
|
|
*/
|
|
static int
|
|
sxioctl(
|
|
struct cdev *dev,
|
|
u_long cmd,
|
|
caddr_t data,
|
|
int flag,
|
|
d_thread_t *p)
|
|
{
|
|
struct sx_softc *sc;
|
|
struct sx_port *pp;
|
|
struct tty *tp;
|
|
int error;
|
|
int mynor = minor(dev);
|
|
int oldspl;
|
|
int blocked = 0;
|
|
#ifndef BURN_BRIDGES
|
|
#if defined(COMPAT_43)
|
|
u_long oldcmd;
|
|
|
|
struct termios term;
|
|
#endif
|
|
#endif
|
|
|
|
pp = MINOR2PP(mynor);
|
|
tp = pp->sp_tty;
|
|
|
|
DPRINT((pp, DBG_ENTRY|DBG_IOCTL, "sxioctl %s %lx %x %x\n",
|
|
devtoname(dev), cmd, data, flag));
|
|
if (DEV_IS_STATE(mynor)) {
|
|
struct termios *ct;
|
|
|
|
switch (mynor & SX_STATE_MASK) {
|
|
case SX_INIT_STATE_MASK:
|
|
ct = DEV_IS_CALLOUT(mynor) ? &pp->sp_iout :
|
|
&pp->sp_iin;
|
|
break;
|
|
case SX_LOCK_STATE_MASK:
|
|
ct = DEV_IS_CALLOUT(mynor) ? &pp->sp_lout :
|
|
&pp->sp_lin;
|
|
break;
|
|
default:
|
|
return(ENODEV);
|
|
}
|
|
switch (cmd) {
|
|
case TIOCSETA:
|
|
error = suser(p);
|
|
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);
|
|
}
|
|
}
|
|
/*
|
|
* Do the old-style ioctl compat routines...
|
|
*/
|
|
#ifndef BURN_BRIDGES
|
|
#if defined(COMPAT_43)
|
|
term = tp->t_termios;
|
|
oldcmd = cmd;
|
|
error = ttsetcompat(tp, &cmd, data, &term);
|
|
if (error != 0)
|
|
return(error);
|
|
if (cmd != oldcmd)
|
|
data = (caddr_t)&term;
|
|
#endif
|
|
#endif
|
|
/*
|
|
* Do the initial / lock state business
|
|
*/
|
|
if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) {
|
|
int cc;
|
|
struct termios *dt = (struct termios *)data;
|
|
struct termios *lt = mynor & SX_CALLOUT_MASK
|
|
? &pp->sp_lout : &pp->sp_lin;
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Block user-level writes to give the ttywait()
|
|
* a chance to completely drain for commands
|
|
* that require the port to be in a quiescent state.
|
|
*/
|
|
switch (cmd) {
|
|
case TIOCSETAW:
|
|
case TIOCSETAF:
|
|
case TIOCDRAIN:
|
|
#ifndef BURN_BRIDGES
|
|
#ifdef COMPAT_43
|
|
case TIOCSETP:
|
|
#endif
|
|
#endif
|
|
blocked++; /* block writes for ttywait() and sxparam() */
|
|
sx_write_enable(pp, 0);
|
|
}
|
|
|
|
error = ttyioctl(dev, cmd, data, flag, p);
|
|
ttyldoptim(tp);
|
|
if (error != ENOTTY)
|
|
goto out;
|
|
|
|
oldspl = spltty();
|
|
|
|
sc = PP2SC(pp); /* Need this to do I/O to the card. */
|
|
error = 0;
|
|
switch (cmd) {
|
|
case TIOCSBRK: /* Send BREAK. */
|
|
DPRINT((pp, DBG_IOCTL, "sxioctl %s BRK S\n",
|
|
devtoname(dev)));
|
|
/*
|
|
* If there's already a break state change pending or
|
|
* we're already sending a break, just ignore this.
|
|
* Otherwise, just set our flag and start the
|
|
* transmitter.
|
|
*/
|
|
if (!SX_DOBRK(pp) && !SX_BREAK(pp)) {
|
|
pp->sp_state |= SX_SS_DOBRK;
|
|
sx_start(tp);
|
|
}
|
|
break;
|
|
case TIOCCBRK: /* Stop sending BREAK. */
|
|
DPRINT((pp, DBG_IOCTL, "sxioctl %s BRK E\n",
|
|
devtoname(dev)));
|
|
/*
|
|
* If a break is going, set our flag so we turn it off
|
|
* when we can, then kick the transmitter. If a break
|
|
* isn't going and the flag is set, turn it off.
|
|
*/
|
|
if (SX_BREAK(pp)) {
|
|
pp->sp_state |= SX_SS_DOBRK;
|
|
sx_start(tp);
|
|
}
|
|
else {
|
|
if (SX_DOBRK(pp))
|
|
pp->sp_state &= SX_SS_DOBRK;
|
|
}
|
|
break;
|
|
case TIOCSDTR: /* Assert DTR. */
|
|
DPRINT((pp, DBG_IOCTL, "sxioctl %s +DTR\n",
|
|
devtoname(dev)));
|
|
if (SX_DTRPIN(pp)) /* Using DTR? */
|
|
(void)sx_modem(sc, pp, SET, TIOCM_DTR);
|
|
break;
|
|
case TIOCCDTR: /* Clear DTR. */
|
|
DPRINT((pp, DBG_IOCTL, "sxioctl(%s) -DTR\n",
|
|
devtoname(dev)));
|
|
if (SX_DTRPIN(pp)) /* Using DTR? */
|
|
(void)sx_modem(sc, pp, SET, 0);
|
|
break;
|
|
case TIOCMSET: /* Force all modem signals. */
|
|
DPRINT((pp, DBG_IOCTL, "sxioctl %s =%x\n",
|
|
devtoname(dev), *(int *)data));
|
|
(void)sx_modem(sc, pp, SET, *(int *)data);
|
|
break;
|
|
case TIOCMBIS: /* Set (some) modem signals. */
|
|
DPRINT((pp, DBG_IOCTL, "sxioctl %s +%x\n",
|
|
devtoname(dev), *(int *)data));
|
|
(void)sx_modem(sc, pp, BIS, *(int *)data);
|
|
break;
|
|
case TIOCMBIC: /* Clear (some) modem signals. */
|
|
DPRINT((pp, DBG_IOCTL, "sxioctl %s -%x\n",
|
|
devtoname(dev), *(int *)data));
|
|
(void)sx_modem(sc, pp, BIC, *(int *)data);
|
|
break;
|
|
case TIOCMGET: /* Get state of modem signals. */
|
|
*(int *)data = sx_modem(sc, pp, GET, 0);
|
|
DPRINT((pp, DBG_IOCTL, "sxioctl(%s) got signals 0x%x\n",
|
|
devtoname(dev), *(int *)data));
|
|
break;
|
|
default:
|
|
error = ENOTTY;
|
|
}
|
|
splx(oldspl);
|
|
|
|
out: DPRINT((pp, DBG_IOCTL|DBG_EXIT, "sxioctl out %d\n", error));
|
|
if (blocked)
|
|
sx_write_enable(pp, 1);
|
|
return(error);
|
|
}
|
|
|
|
/*
|
|
* sxparam()
|
|
* Configure line parameters.
|
|
*
|
|
* Description:
|
|
* Configure the bitrate, wordsize, flow control and various other serial
|
|
* port parameters for this line.
|
|
*
|
|
* Environment:
|
|
* Called at spltty(); this may sleep, does not flush nor wait for drain,
|
|
* nor block writes. Caller must arrange this if it's important..
|
|
*/
|
|
static int
|
|
sxparam(
|
|
struct tty *tp,
|
|
struct termios *t)
|
|
{
|
|
struct sx_softc *sc;
|
|
struct sx_port *pp = TP2PP(tp);
|
|
int oldspl, cflag, iflag, oflag, lflag;
|
|
int error = 0;
|
|
int ispd = 0;
|
|
int ospd = 0;
|
|
unsigned char val, cor1, cor2, cor3, ier;
|
|
|
|
sc = PP2SC(pp);
|
|
DPRINT((pp, DBG_ENTRY|DBG_PARAM, "sxparam %x/%x\n", tp, t));
|
|
cflag = t->c_cflag;
|
|
iflag = t->c_iflag;
|
|
oflag = t->c_oflag;
|
|
lflag = t->c_lflag;
|
|
DPRINT((pp, DBG_PARAM, "OF 0x%x CF 0x%x IF 0x%x LF 0x%x\n",
|
|
oflag, cflag, iflag, lflag));
|
|
|
|
/* If the port isn't hung up... */
|
|
if (t->c_ospeed != 0) {
|
|
/* Convert bit rate to hardware divisor values. */
|
|
ospd = ttspeedtab(t->c_ospeed, bdrates);
|
|
ispd = t->c_ispeed ? ttspeedtab(t->c_ispeed, bdrates) : ospd;
|
|
/* We only allow standard bit rates. */
|
|
if (ospd < 0 || ispd < 0)
|
|
return(EINVAL);
|
|
}
|
|
oldspl = spltty(); /* Block other activity. */
|
|
cor1 = 0;
|
|
cor2 = 0;
|
|
cor3 = 0;
|
|
ier = CD1865_IER_RXD | CD1865_IER_CD;
|
|
#ifdef notyet
|
|
/* We don't yet handle this stuff. */
|
|
val = 0;
|
|
if (iflag & IGNBRK) /* Breaks */
|
|
val |= BR_IGN;
|
|
if (iflag & BRKINT) /* Interrupt on break? */
|
|
val |= BR_INT;
|
|
if (iflag & PARMRK) /* Parity mark? */
|
|
val |= BR_PARMRK;
|
|
#endif /* notyet */
|
|
/*
|
|
* If the device isn't hung up, set the serial port bitrates.
|
|
*/
|
|
if (t->c_ospeed != 0) {
|
|
disable_intr();
|
|
sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan);
|
|
sx_cd1865_out(sc, CD1865_RBPRH|SX_EI, (ispd >> 8) & 0xff);
|
|
sx_cd1865_out(sc, CD1865_RBPRL|SX_EI, ispd & 0xff);
|
|
sx_cd1865_out(sc, CD1865_TBPRH|SX_EI, (ospd >> 8) & 0xff);
|
|
sx_cd1865_out(sc, CD1865_TBPRL|SX_EI, ospd & 0xff);
|
|
enable_intr();
|
|
}
|
|
if (cflag & CSTOPB) /* Two stop bits? */
|
|
cor1 |= CD1865_COR1_2SB; /* Yep. */
|
|
/*
|
|
* Parity settings.
|
|
*/
|
|
val = 0;
|
|
if (cflag & PARENB) { /* Parity enabled? */
|
|
val = CD1865_COR1_NORMPAR; /* Turn on normal parity handling. */
|
|
if (cflag & PARODD) /* Odd Parity? */
|
|
val |= CD1865_COR1_ODDP; /* Turn it on. */
|
|
}
|
|
else
|
|
val = CD1865_COR1_NOPAR; /* Turn off parity detection. */
|
|
cor1 |= val;
|
|
if (iflag & IGNPAR) /* Ignore chars with parity errors? */
|
|
cor1 |= CD1865_COR1_IGNORE;
|
|
/*
|
|
* Set word length.
|
|
*/
|
|
if ((cflag & CS8) == CS8)
|
|
val = CD1865_COR1_8BITS;
|
|
else if ((cflag & CS7) == CS7)
|
|
val = CD1865_COR1_7BITS;
|
|
else if ((cflag & CS6) == CS6)
|
|
val = CD1865_COR1_6BITS;
|
|
else
|
|
val = CD1865_COR1_5BITS;
|
|
cor1 |= val;
|
|
/*
|
|
* Enable hardware RTS/CTS flow control. We can handle output flow
|
|
* control at any time, since we have a dedicated CTS pin.
|
|
* Unfortunately, though, the RTS pin is really the DTR pin. This
|
|
* means that we can't ever use the automatic input flow control of
|
|
* the CD1865 and that we can only use the pin for input flow
|
|
* control when it's wired as RTS.
|
|
*/
|
|
if (cflag & CCTS_OFLOW) { /* Output flow control... */
|
|
pp->sp_state |= SX_SS_OFLOW;
|
|
#ifdef SX_BROKEN_CTS
|
|
disable_intr();
|
|
sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan);
|
|
if (sx_cd1865_in(sc, CD1865_MSVR|SX_EI) & CD1865_MSVR_CTS) {
|
|
enable_intr();
|
|
pp->sp_state |= SX_SS_OSTOP;
|
|
sx_write_enable(pp, 0); /* Block writes. */
|
|
}
|
|
else {
|
|
enable_intr();
|
|
}
|
|
ier |= CD1865_IER_CTS;
|
|
#else /* SX_BROKEN_CTS */
|
|
cor2 |= CD1865_COR2_CTSAE; /* Set CTS automatic enable. */
|
|
#endif /* SX_BROKEN_CTS */
|
|
}
|
|
else {
|
|
pp->sp_state &= ~SX_SS_OFLOW;
|
|
}
|
|
if (cflag & CRTS_IFLOW && !SX_DTRPIN(pp)) /* Input flow control. */
|
|
pp->sp_state |= SX_SS_IFLOW;
|
|
else
|
|
pp->sp_state &= ~SX_SS_IFLOW;
|
|
if (iflag & IXANY)
|
|
cor2 |= CD1865_COR2_IXM; /* Any character is XON. */
|
|
if (iflag & IXOFF) {
|
|
cor2 |= CD1865_COR2_TXIBE; /* Enable inband flow control.*/
|
|
cor3 |= CD1865_COR3_FCT | CD1865_COR3_SCDE; /* Hide from host */
|
|
disable_intr();
|
|
sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); /* Sel chan.*/
|
|
sx_cd1865_out(sc, CD1865_SCHR1|SX_EI, t->c_cc[VSTART]);
|
|
sx_cd1865_out(sc, CD1865_SCHR2|SX_EI, t->c_cc[VSTOP]);
|
|
sx_cd1865_out(sc, CD1865_SCHR3|SX_EI, t->c_cc[VSTART]);
|
|
sx_cd1865_out(sc, CD1865_SCHR4|SX_EI, t->c_cc[VSTOP]);
|
|
enable_intr();
|
|
}
|
|
/*
|
|
* All set, now program the hardware.
|
|
*/
|
|
disable_intr();
|
|
sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); /* Select channel. */
|
|
sx_cd1865_out(sc, CD1865_COR1|SX_EI, cor1);
|
|
sx_cd1865_out(sc, CD1865_COR2|SX_EI, cor2);
|
|
sx_cd1865_out(sc, CD1865_COR3|SX_EI, cor3);
|
|
sx_cd1865_wait_CCR(sc, SX_EI);
|
|
sx_cd1865_out(sc, CD1865_CCR|SX_EI,
|
|
CD1865_CCR_CORCHG1|CD1865_CCR_CORCHG2|CD1865_CCR_CORCHG3);
|
|
sx_cd1865_wait_CCR(sc, SX_EI);
|
|
enable_intr();
|
|
if (SX_DTRPIN(pp))
|
|
val = TIOCM_DTR;
|
|
else
|
|
val = TIOCM_RTS;
|
|
if (t->c_ospeed == 0) /* Clear DTR/RTS if we're hung up. */
|
|
(void)sx_modem(sc, pp, BIC, val);
|
|
else /* If we were hung up, we may have to */
|
|
(void)sx_modem(sc, pp, BIS, val); /* re-enable the signal. */
|
|
/*
|
|
* Last, enable the receiver and transmitter and turn on the
|
|
* interrupts we need (receive, carrier-detect and possibly CTS
|
|
* (iff we're built with SX_BROKEN_CTS and CCTS_OFLOW is on).
|
|
*/
|
|
disable_intr();
|
|
sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); /* Select channel. */
|
|
sx_cd1865_wait_CCR(sc, SX_EI);
|
|
sx_cd1865_out(sc, CD1865_CCR|SX_EI, CD1865_CCR_RXEN|CD1865_CCR_TXEN);
|
|
sx_cd1865_wait_CCR(sc, SX_EI);
|
|
sx_cd1865_out(sc, CD1865_IER|SX_EI, ier);
|
|
enable_intr();
|
|
DPRINT((pp, DBG_PARAM, "sxparam out\n"));
|
|
splx(oldspl);
|
|
return(error);
|
|
}
|
|
|
|
/*
|
|
* sx_write_enable()
|
|
* Enable/disable writes to a card channel.
|
|
*
|
|
* Description:
|
|
* Set or clear the SX_SS_BLOCKWRITE flag in sp_state to block or allow
|
|
* writes to a serial port on the card. When we enable writes, we
|
|
* wake up anyone sleeping on SX_SS_WAITWRITE for this channel.
|
|
*
|
|
* Parameters:
|
|
* flag 0 - disable writes.
|
|
* 1 - enable writes.
|
|
*/
|
|
static void
|
|
sx_write_enable(
|
|
struct sx_port *pp,
|
|
int flag)
|
|
{
|
|
int oldspl;
|
|
|
|
oldspl = spltty(); /* Keep interrupts out. */
|
|
if (flag) { /* Enable writes to the channel? */
|
|
pp->sp_state &= ~SX_SS_BLOCKWRITE; /* Clear our flag. */
|
|
if (pp->sp_state & SX_SS_WAITWRITE) { /* Sleepers? */
|
|
pp->sp_state &= ~SX_SS_WAITWRITE; /* Clear their flag */
|
|
wakeup((caddr_t)pp); /* & wake them up. */
|
|
}
|
|
}
|
|
else /* Disabling writes. */
|
|
pp->sp_state |= SX_SS_BLOCKWRITE; /* Set our flag. */
|
|
splx(oldspl);
|
|
}
|
|
|
|
/*
|
|
* sx_shutdown_chan()
|
|
* Shut down a channel on the I/O8+.
|
|
*
|
|
* Description:
|
|
* This does all hardware shutdown processing for a channel on the I/O8+.
|
|
* It is called from sxhardclose(). We reset the channel and turn off
|
|
* interrupts.
|
|
*/
|
|
static void
|
|
sx_shutdown_chan(
|
|
struct sx_port *pp)
|
|
{
|
|
int s;
|
|
struct sx_softc *sc;
|
|
|
|
DPRINT((pp, DBG_ENTRY, "sx_shutdown_chan %x %x\n", pp, pp->sp_state));
|
|
sc = PP2SC(pp);
|
|
s = spltty();
|
|
disable_intr();
|
|
sx_cd1865_out(sc, CD1865_CAR, pp->sp_chan); /* Select channel. */
|
|
sx_cd1865_wait_CCR(sc, 0); /* Wait for any commands to complete. */
|
|
sx_cd1865_out(sc, CD1865_CCR, CD1865_CCR_SOFTRESET); /* Reset chan. */
|
|
sx_cd1865_wait_CCR(sc, 0);
|
|
sx_cd1865_out(sc, CD1865_IER, 0); /* Disable all interrupts. */
|
|
enable_intr();
|
|
splx(s);
|
|
}
|
|
|
|
/*
|
|
* sx_modem()
|
|
* Set/Get state of modem control lines.
|
|
*
|
|
* Description:
|
|
* Get and set the state of the modem control lines that we have available
|
|
* on the I/O8+. The only lines we are guaranteed to have are CD and CTS.
|
|
* We have DTR if the "DTR/RTS pin is DTR" flag is set, otherwise we have
|
|
* RTS through the DTR pin.
|
|
*/
|
|
static int
|
|
sx_modem(
|
|
struct sx_softc *sc,
|
|
struct sx_port *pp,
|
|
enum sx_mctl cmd,
|
|
int bits)
|
|
{
|
|
int s, x;
|
|
|
|
DPRINT((pp, DBG_ENTRY|DBG_MODEM, "sx_modem %x/%s/%x\n",
|
|
pp, sx_mctl2str(cmd), bits));
|
|
s = spltty(); /* Block interrupts. */
|
|
disable_intr();
|
|
sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan); /* Select our port. */
|
|
x = sx_cd1865_in(sc, CD1865_MSVR|SX_EI); /* Get the current signals. */
|
|
#ifdef SX_DEBUG
|
|
DPRINT((pp, DBG_MODEM, "sx_modem MSVR 0x%x, CCSR %x GIVR %x SRSR %x\n",
|
|
x, sx_cd1865_in(sc, CD1865_CCSR|SX_EI),
|
|
sx_cd1865_in(sc, CD1865_GIVR|SX_EI),
|
|
sx_cd1865_in(sc, CD1865_SRSR|SX_EI)));
|
|
#endif
|
|
enable_intr(); /* Allow other interrupts. */
|
|
switch (cmd) {
|
|
case GET:
|
|
bits = TIOCM_LE;
|
|
if ((x & CD1865_MSVR_CD) == 0)
|
|
bits |= TIOCM_CD;
|
|
if ((x & CD1865_MSVR_CTS) == 0)
|
|
bits |= TIOCM_CTS;
|
|
if ((x & CD1865_MSVR_DTR) == 0) {
|
|
if (SX_DTRPIN(pp)) /* Odd pin is DTR? */
|
|
bits |= TIOCM_DTR; /* Report DTR. */
|
|
else /* Odd pin is RTS. */
|
|
bits |= TIOCM_RTS; /* Report RTS. */
|
|
}
|
|
splx(s);
|
|
return(bits);
|
|
case SET:
|
|
x = CD1865_MSVR_OFF;
|
|
if ((bits & TIOCM_RTS && !SX_DTRPIN(pp)) ||
|
|
(bits & TIOCM_DTR && SX_DTRPIN(pp)))
|
|
x &= ~CD1865_MSVR_DTR;
|
|
break;
|
|
case BIS:
|
|
if ((bits & TIOCM_RTS && !SX_DTRPIN(pp)) ||
|
|
(bits & TIOCM_DTR && SX_DTRPIN(pp)))
|
|
x &= ~CD1865_MSVR_DTR;
|
|
break;
|
|
case BIC:
|
|
if ((bits & TIOCM_RTS && !SX_DTRPIN(pp)) ||
|
|
(bits & TIOCM_DTR && SX_DTRPIN(pp)))
|
|
x |= CD1865_MSVR_DTR;
|
|
break;
|
|
}
|
|
DPRINT((pp, DBG_MODEM, "sx_modem MSVR=0x%x\n", x));
|
|
disable_intr();
|
|
/*
|
|
* Set the new modem signals.
|
|
*/
|
|
sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan);
|
|
sx_cd1865_out(sc, CD1865_MSVR|SX_EI, x);
|
|
enable_intr();
|
|
splx(s);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef POLL
|
|
|
|
/*
|
|
* sx_poll()
|
|
* Poller to catch missed interrupts.
|
|
*
|
|
* Description:
|
|
* Only used if we're complied with POLL. This routine is called every
|
|
* sx_pollrate ticks to check for missed interrupts. We check each card
|
|
* in the system; if we missed an interrupt, we complain about each one
|
|
* and later call sx_intr() to handle them.
|
|
*/
|
|
static void
|
|
sx_poll(
|
|
void *dummy)
|
|
{
|
|
struct sx_softc *sc;
|
|
struct sx_port *pp;
|
|
int card, lost, oldspl, chan;
|
|
|
|
DPRINT((0, DBG_POLL, "sx_poll\n"));
|
|
oldspl = spltty();
|
|
if (in_interrupt)
|
|
goto out;
|
|
lost = 0;
|
|
for (card = 0; card < sx_numunits; card++) {
|
|
sc = devclass_get_softc(sx_devclass, card);
|
|
if (sc == NULL)
|
|
continue;
|
|
if (sx_cd1865_in(sc, CD1865_SRSR|SX_EI) & CD1865_SRSR_REQint) {
|
|
printf("sx%d: lost interrupt\n", card);
|
|
lost++;
|
|
}
|
|
/*
|
|
* Gripe about no input flow control.
|
|
*/
|
|
for (chan = 0; chan < SX_NUMCHANS; pp++, chan++) {
|
|
pp = &(sc->sc_ports[chan]);
|
|
if (pp->sp_delta_overflows > 0) {
|
|
printf("sx%d: %d tty level buffer overflows\n",
|
|
card, pp->sp_delta_overflows);
|
|
pp->sp_delta_overflows = 0;
|
|
}
|
|
}
|
|
}
|
|
if (lost || sx_realpoll)
|
|
sx_intr(NULL); /* call intr with fake vector */
|
|
out: splx(oldspl);
|
|
timeout(sx_poll, (caddr_t)0L, sx_pollrate);
|
|
}
|
|
|
|
#endif /* POLL */
|
|
|
|
|
|
/*
|
|
* sx_transmit()
|
|
* Handle transmit request interrupt.
|
|
*
|
|
* Description:
|
|
* This routine handles the transmit request interrupt from the CD1865
|
|
* chip on the I/O8+ card. The CD1865 interrupts us for a transmit
|
|
* request under two circumstances: When the last character in the
|
|
* transmit FIFO is sent and the channel is ready for more characters
|
|
* ("transmit ready"), or when the last bit of the last character in the
|
|
* FIFO is actually transmitted ("transmit empty"). In the former case,
|
|
* we just pass processing off to sx_start() (via the line discipline)
|
|
* to queue more characters. In the latter case, we were waiting for
|
|
* the line to flush in sxhardclose() so we need to wake the sleeper.
|
|
*/
|
|
static void
|
|
sx_transmit(
|
|
struct sx_softc *sc,
|
|
struct sx_port *pp,
|
|
int card)
|
|
{
|
|
struct tty *tp;
|
|
unsigned char flags;
|
|
|
|
tp = pp->sp_tty;
|
|
/*
|
|
* Let others know what we're doing.
|
|
*/
|
|
pp->sp_state |= SX_SS_IXMIT;
|
|
/*
|
|
* Get the service request enable register to see what we're waiting
|
|
* for.
|
|
*/
|
|
flags = sx_cd1865_in(sc, CD1865_SRER|SX_EI);
|
|
|
|
DPRINT((pp, DBG_TRANSMIT, "sx_xmit %x SRER %x\n", tp, flags));
|
|
/*
|
|
* "Transmit ready." The transmit FIFO is empty (but there are still
|
|
* two characters being transmitted), so we need to tell the line
|
|
* discipline to send more.
|
|
*/
|
|
if (flags & CD1865_IER_TXRDY) {
|
|
ttyld_start(tp);
|
|
pp->sp_state &= ~SX_SS_IXMIT;
|
|
DPRINT((pp, DBG_TRANSMIT, "sx_xmit TXRDY out\n"));
|
|
return;
|
|
}
|
|
/*
|
|
* "Transmit empty." The transmitter is completely empty; turn off the
|
|
* service request and wake up the guy in sxhardclose() who is waiting
|
|
* for this.
|
|
*/
|
|
if (flags & CD1865_IER_TXEMPTY) {
|
|
flags &= ~CD1865_IER_TXEMPTY;
|
|
sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan);
|
|
sx_cd1865_out(sc, CD1865_SRER|SX_EI, flags);
|
|
wakeup((caddr_t)pp);
|
|
}
|
|
pp->sp_state &= ~SX_SS_IXMIT;
|
|
DPRINT((pp, DBG_TRANSMIT, "sx_xmit out\n"));
|
|
}
|
|
|
|
/*
|
|
* sx_modem_state()
|
|
* Handle modem state-change request interrupt.
|
|
*
|
|
* Description:
|
|
* Handles changed modem signals CD and CTS. We pass the CD change
|
|
* off to the line discipline. We can't handle DSR since there isn't a
|
|
* pin for it.
|
|
*/
|
|
static void
|
|
sx_modem_state(
|
|
struct sx_softc *sc,
|
|
struct sx_port *pp,
|
|
int card)
|
|
{
|
|
struct tty *tp;
|
|
unsigned char mcr;
|
|
|
|
/*
|
|
* Let others know what we're doing.
|
|
*/
|
|
pp->sp_state |= SX_SS_IMODEM;
|
|
tp = pp->sp_tty;
|
|
/* Grab the Modem Change Register. */
|
|
mcr = sx_cd1865_in(sc, CD1865_MCR|SX_EI);
|
|
DPRINT((pp, DBG_MODEM_STATE,
|
|
"sx_mdmst %x st %x sp %x mcr %x\n",
|
|
tp, tp->t_state, pp->sp_state, mcr));
|
|
if (mcr & CD1865_MCR_CDCHG) { /* CD changed? */
|
|
if ((sx_cd1865_in(sc, CD1865_MSVR) & CD1865_MSVR_CD) == 0) {
|
|
DPRINT((pp, DBG_INTR, "modem carr on t_line %d\n",
|
|
tp->t_line));
|
|
(void)ttyld_modem(tp, 1);
|
|
}
|
|
else { /* CD went down. */
|
|
DPRINT((pp, DBG_INTR, "modem carr off\n"));
|
|
if (ttyld_modem(tp, 0))
|
|
(void)sx_modem(sc, pp, SET, 0);
|
|
}
|
|
}
|
|
#ifdef SX_BROKEN_CTS
|
|
if (mcr & CD1865_MCR_CTSCHG) { /* CTS changed? */
|
|
if (sx_cd1865_in(sc, CD1865_MSVR|SX_EI) & CD1865_MSVR_CTS) {
|
|
pp->sp_state |= SX_SS_OSTOP;
|
|
sx_cd1865_bic(sc, CD1865_IER|SX_EI, CD1865_IER_TXRDY);
|
|
sx_write_enable(pp, 0); /* Block writes. */
|
|
}
|
|
else {
|
|
pp->sp_state &= ~SX_SS_OSTOP;
|
|
sx_cd1865_bis(sc, CD1865_IER|SX_EI, CD1865_IER_TXRDY);
|
|
sx_write_enable(pp, 1); /* Unblock writes. */
|
|
}
|
|
}
|
|
#endif /* SX_BROKEN_CTS */
|
|
/* Clear state-change indicator bits. */
|
|
sx_cd1865_out(sc, CD1865_MCR|SX_EI, 0);
|
|
pp->sp_state &= ~SX_SS_IMODEM;
|
|
}
|
|
|
|
/*
|
|
* sx_receive()
|
|
* Handle receive request interrupt.
|
|
*
|
|
* Description:
|
|
* Handle a receive request interrupt from the CD1865. This is just a
|
|
* standard "we have characters to process" request, we don't have to
|
|
* worry about exceptions like BREAK and such. Exceptions are handled
|
|
* by sx_receive_exception().
|
|
*/
|
|
static void
|
|
sx_receive(
|
|
struct sx_softc *sc,
|
|
struct sx_port *pp,
|
|
int card)
|
|
{
|
|
struct tty *tp;
|
|
unsigned char count;
|
|
int i, x;
|
|
static unsigned char sx_rxbuf[SX_BUFFERSIZE]; /* input staging area */
|
|
|
|
tp = pp->sp_tty;
|
|
DPRINT((pp, DBG_RECEIVE,
|
|
"sx_rcv %x st %x sp %x\n",
|
|
tp, tp->t_state, pp->sp_state));
|
|
/*
|
|
* Let others know what we're doing.
|
|
*/
|
|
pp->sp_state |= SX_SS_IRCV;
|
|
/*
|
|
* How many characters are waiting for us?
|
|
*/
|
|
count = sx_cd1865_in(sc, CD1865_RDCR|SX_EI);
|
|
if (count == 0) /* None? Bail. */
|
|
return;
|
|
DPRINT((pp, DBG_RECEIVE, "sx_receive count %d\n", count));
|
|
/*
|
|
* Pull the characters off the card into our local buffer, then
|
|
* process that.
|
|
*/
|
|
for (i = 0; i < count; i++)
|
|
sx_rxbuf[i] = sx_cd1865_in(sc, CD1865_RDR|SX_EI);
|
|
/*
|
|
* If we're not open and connected, bail.
|
|
*/
|
|
if (!(tp->t_state & TS_CONNECTED && tp->t_state & TS_ISOPEN)) {
|
|
pp->sp_state &= ~SX_SS_IRCV;
|
|
DPRINT((pp, DBG_RECEIVE, "sx_rcv not open\n"));
|
|
return;
|
|
}
|
|
/*
|
|
* If the tty input buffers are blocked and we have an RTS pin,
|
|
* drop RTS and bail.
|
|
*/
|
|
if (tp->t_state & TS_TBLOCK) {
|
|
if (!SX_DTRPIN(pp) && SX_IFLOW(pp)) {
|
|
(void)sx_modem(sc, pp, BIC, TIOCM_RTS);
|
|
pp->sp_state |= SX_SS_ISTOP;
|
|
}
|
|
pp->sp_state &= ~SX_SS_IRCV;
|
|
return;
|
|
}
|
|
if (tp->t_state & TS_CAN_BYPASS_L_RINT) {
|
|
DPRINT((pp, DBG_RECEIVE, "sx_rcv BYPASS\n"));
|
|
/*
|
|
* Avoid the grotesquely inefficient lineswitch routine
|
|
* (ttyinput) in "raw" mode. It usually takes about 450
|
|
* instructions (that's without canonical processing or
|
|
* echo!). slinput is reasonably fast (usually 40
|
|
* instructions plus call overhead).
|
|
*/
|
|
if (tp->t_rawq.c_cc + count >= SX_I_HIGH_WATER &&
|
|
(tp->t_cflag & CRTS_IFLOW || tp->t_iflag & IXOFF) &&
|
|
!(tp->t_state & TS_TBLOCK)) {
|
|
ttyblock(tp);
|
|
DPRINT((pp, DBG_RECEIVE, "sx_rcv block\n"));
|
|
}
|
|
tk_nin += count;
|
|
tk_rawcc += count;
|
|
tp->t_rawcc += count;
|
|
|
|
pp->sp_delta_overflows +=
|
|
b_to_q((char *)sx_rxbuf, count, &tp->t_rawq);
|
|
ttwakeup(tp);
|
|
/*
|
|
* If we were stopped and need to start again because of this
|
|
* receive, kick the output routine to get things going again.
|
|
*/
|
|
if (tp->t_state & TS_TTSTOP && (tp->t_iflag & IXANY ||
|
|
tp->t_cc[VSTART] == tp->t_cc[VSTOP])) {
|
|
tp->t_state &= ~TS_TTSTOP;
|
|
tp->t_lflag &= ~FLUSHO;
|
|
sx_start(tp);
|
|
}
|
|
}
|
|
else {
|
|
DPRINT((pp, DBG_RECEIVE, "sx_rcv l_rint\n"));
|
|
/*
|
|
* It'd be nice to not have to go through the function call
|
|
* overhead for each char here. It'd be nice to block input
|
|
* it, saving a loop here and the call/return overhead.
|
|
*/
|
|
for (x = 0; x < count; x++) {
|
|
i = sx_rxbuf[x];
|
|
if (ttyld_rint(tp, i) == -1)
|
|
pp->sp_delta_overflows++;
|
|
}
|
|
}
|
|
pp->sp_state &= ~SX_SS_IRCV;
|
|
DPRINT((pp, DBG_RECEIVE, "sx_rcv out\n"));
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* sx_receive_exception()
|
|
* Handle receive exception request interrupt processing.
|
|
*
|
|
* Description:
|
|
* Handle a receive exception request interrupt from the CD1865.
|
|
* Possible exceptions include BREAK, overrun, receiver timeout
|
|
* and parity and frame errors. We don't handle receiver timeout,
|
|
* we just complain. The rest are passed to ttyinput().
|
|
*/
|
|
static void
|
|
sx_receive_exception(
|
|
struct sx_softc *sc,
|
|
struct sx_port *pp,
|
|
int card)
|
|
{
|
|
struct tty *tp;
|
|
unsigned char st;
|
|
int ch, isopen;
|
|
|
|
tp = pp->sp_tty;
|
|
/*
|
|
* Let others know what we're doing.
|
|
*/
|
|
pp->sp_state |= SX_SS_IRCVEXC;
|
|
/*
|
|
* Check to see whether we should receive characters.
|
|
*/
|
|
if (tp->t_state & TS_CONNECTED &&
|
|
tp->t_state & TS_ISOPEN)
|
|
isopen = 1;
|
|
else
|
|
isopen = 0;
|
|
|
|
st = sx_cd1865_in(sc, CD1865_RCSR|SX_EI); /* Get the character status.*/
|
|
ch = (int)sx_cd1865_in(sc, CD1865_RDR|SX_EI); /* Get the character. */
|
|
DPRINT((pp, DBG_RECEIVE_EXC,
|
|
"sx_rexc %x st %x sp %x st 0x%x ch 0x%x ('%c')\n",
|
|
tp, tp->t_state, pp->sp_state, st, ch, ch));
|
|
/* If there's no status or the tty isn't open, bail. */
|
|
if (!st || !isopen) {
|
|
pp->sp_state &= ~SX_SS_IRCVEXC;
|
|
DPRINT((pp, DBG_RECEIVE_EXC, "sx_rexc not open\n"));
|
|
return;
|
|
}
|
|
if (st & CD1865_RCSR_TOUT) /* Receiver timeout; just complain. */
|
|
printf("sx%d: port %d: Receiver timeout.\n", card, pp->sp_chan);
|
|
else if (st & CD1865_RCSR_BREAK)
|
|
ch |= TTY_BI;
|
|
else if (st & CD1865_RCSR_PE)
|
|
ch |= TTY_PE;
|
|
else if (st & CD1865_RCSR_FE)
|
|
ch |= TTY_FE;
|
|
else if (st & CD1865_RCSR_OE)
|
|
ch |= TTY_OE;
|
|
ttyld_rint(tp, ch);
|
|
pp->sp_state &= ~SX_SS_IRCVEXC;
|
|
}
|
|
|
|
/*
|
|
* sx_intr()
|
|
* Field interrupts from the I/O8+.
|
|
*
|
|
* Description:
|
|
* The interrupt handler polls ALL ports on ALL adapters each time
|
|
* it is called.
|
|
*/
|
|
void
|
|
sx_intr(
|
|
void *arg)
|
|
{
|
|
struct sx_softc *sc;
|
|
struct sx_port *pp = NULL;
|
|
int card;
|
|
unsigned char ack;
|
|
|
|
sc = arg;
|
|
|
|
DPRINT((0, arg == NULL ? DBG_POLL:DBG_INTR, "sx_intr\n"));
|
|
if (in_interrupt)
|
|
return;
|
|
in_interrupt = 1;
|
|
|
|
/*
|
|
* When we get an int we poll all the channels and do ALL pending
|
|
* work, not just the first one we find. This allows all cards to
|
|
* share the same vector.
|
|
*
|
|
* On the other hand, if we're sharing the vector with something
|
|
* that's not an I/O8+, we may be making extra work for ourselves.
|
|
*/
|
|
for (card = 0; card < sx_numunits; card++) {
|
|
unsigned char st;
|
|
|
|
sc = devclass_get_softc(sx_devclass, card);
|
|
if (sc == NULL)
|
|
continue;
|
|
/*
|
|
* Check the Service Request Status Register to see who
|
|
* interrupted us and why. May be a receive, transmit or
|
|
* modem-signal-change interrupt. Reading the appropriate
|
|
* Request Acknowledge Register acknowledges the request and
|
|
* gives us the contents of the Global Service Vector Register,
|
|
* which in a daisy-chained configuration (not ours) uniquely
|
|
* identifies the particular CD1865 and gives us the request
|
|
* type. We mask off the ID part and use the rest.
|
|
*
|
|
* From the CD1865 specs, it appears that only one request can
|
|
* happen at a time, but in testing it's pretty obvious that
|
|
* the specs lie. Or perhaps we're just slow enough that the
|
|
* requests pile up. Regardless, if we try to process more
|
|
* than one at a time without clearing the previous request
|
|
* (writing zero to EOIR) first, we hang the card. Thus the
|
|
* "else if" logic here.
|
|
*/
|
|
while ((st = (sx_cd1865_in(sc, CD1865_SRSR|SX_EI)) &
|
|
CD1865_SRSR_REQint)) {
|
|
/*
|
|
* Transmit request interrupt.
|
|
*/
|
|
if (st & CD1865_SRSR_TREQint) {
|
|
ack = sx_cd1865_in(sc, CD1865_TRAR|SX_EI) &
|
|
CD1865_GIVR_ITMASK;
|
|
pp = sx_int_port(sc, card);
|
|
if (pp == NULL) /* Bad channel. */
|
|
goto skip;
|
|
pp->sp_state |= SX_SS_INTR; /* In interrupt. */
|
|
if (ack == CD1865_GIVR_IT_TX)
|
|
sx_transmit(sc, pp, card);
|
|
else
|
|
printf("sx%d: Bad transmit ack 0x%02x.\n",
|
|
card, ack);
|
|
}
|
|
/*
|
|
* Modem signal change request interrupt.
|
|
*/
|
|
else if (st & CD1865_SRSR_MREQint) {
|
|
ack = sx_cd1865_in(sc, CD1865_MRAR|SX_EI) &
|
|
CD1865_GIVR_ITMASK;
|
|
pp = sx_int_port(sc, card);
|
|
if (pp == NULL) /* Bad channel. */
|
|
goto skip;
|
|
pp->sp_state |= SX_SS_INTR; /* In interrupt. */
|
|
if (ack == CD1865_GIVR_IT_MODEM)
|
|
sx_modem_state(sc, pp, card);
|
|
else
|
|
printf("sx%d: Bad modem ack 0x%02x.\n",
|
|
card, ack);
|
|
}
|
|
/*
|
|
* Receive request interrupt.
|
|
*/
|
|
else if (st & CD1865_SRSR_RREQint) {
|
|
ack = sx_cd1865_in(sc, CD1865_RRAR|SX_EI) &
|
|
CD1865_GIVR_ITMASK;
|
|
pp = sx_int_port(sc, card);
|
|
if (pp == NULL) /* Bad channel. */
|
|
goto skip;
|
|
pp->sp_state |= SX_SS_INTR; /* In interrupt. */
|
|
if (ack == CD1865_GIVR_IT_RCV)
|
|
sx_receive(sc, pp, card);
|
|
else if (ack == CD1865_GIVR_IT_REXC)
|
|
sx_receive_exception(sc, pp, card);
|
|
else
|
|
printf("sx%d: Bad receive ack 0x%02x.\n",
|
|
card, ack);
|
|
}
|
|
/*
|
|
* None of the above; this is a "can't happen," but
|
|
* you never know...
|
|
*/
|
|
else {
|
|
printf("sx%d: Bad service request 0x%02x.\n",
|
|
card, st);
|
|
}
|
|
pp->sp_state &= ~SX_SS_INTR;
|
|
skip: sx_cd1865_out(sc, CD1865_EOIR|SX_EI, 0); /* EOI. */
|
|
} /* while (st & CD1865_SRSR_REQint) */
|
|
} /* for (card = 0; card < sx_numunits; card++) */
|
|
in_interrupt = 0;
|
|
DPRINT((0, arg == NULL ? DBG_POLL:DBG_INTR, "sx_intr out\n"));
|
|
}
|
|
|
|
/*
|
|
* sx_start()
|
|
* Handle transmit and state-change stuff.
|
|
*
|
|
* Description:
|
|
* This is part of the line discipline processing; at various points in
|
|
* the line discipline he calls ttstart() which calls the oproc routine,
|
|
* which is this function. We're called by the line discipline to start
|
|
* data transmission and to change signal states (for RTS flow control).
|
|
* We're also called by this driver to perform line-breaks and to actually
|
|
* do the data transmission.
|
|
|
|
* We can only fill the FIFO from interrupt since the card only makes it
|
|
* available to us during a service request such as TXRDY; this only
|
|
* happens at interrupt.
|
|
*
|
|
* All paths through this code call ttwwakeup().
|
|
*/
|
|
static void
|
|
sx_start(
|
|
struct tty *tp)
|
|
{
|
|
struct sx_softc *sc;
|
|
struct sx_port *pp;
|
|
struct clist *qp;
|
|
int s;
|
|
int count = CD1865_TFIFOSZ;
|
|
|
|
s = spltty();
|
|
pp = TP2PP(tp);
|
|
qp = &tp->t_outq;
|
|
DPRINT((pp, DBG_ENTRY|DBG_START,
|
|
"sx_start %x st %x sp %x cc %d\n",
|
|
tp, tp->t_state, pp->sp_state, qp->c_cc));
|
|
|
|
/*
|
|
* If we're stopped, just wake up sleepers and get out.
|
|
*/
|
|
if (tp->t_state & (TS_TIMEOUT|TS_TTSTOP)) {
|
|
ttwwakeup(tp);
|
|
splx(s);
|
|
DPRINT((pp, DBG_EXIT|DBG_START, "sx_start out\n", tp->t_state));
|
|
return;
|
|
}
|
|
sc = TP2SC(tp);
|
|
/*
|
|
* If we're not transmitting, we may have been called to crank up the
|
|
* transmitter and start things rolling or we may have been called to
|
|
* get a bit of tty state. If the latter, handle it. Either way, if
|
|
* we have data to transmit, turn on the transmit-ready interrupt,
|
|
* set the XMIT flag and we're done. As soon as we allow interrupts
|
|
* the card will interrupt for the first chunk of data. Note that
|
|
* we don't mark the tty as busy until we are actually sending data
|
|
* and then only if we have more than will fill the FIFO. If there's
|
|
* no data to transmit, just handle the tty state.
|
|
*/
|
|
if (!SX_XMITTING(pp)) {
|
|
/*
|
|
* If we were flow-controlled and input is no longer blocked,
|
|
* raise RTS if we can.
|
|
*/
|
|
if (SX_ISTOP(pp) && !(tp->t_state & TS_TBLOCK)) {
|
|
if (!SX_DTRPIN(pp) && SX_IFLOW(pp))
|
|
(void)sx_modem(sc, pp, BIS, TIOCM_RTS);
|
|
pp->sp_state &= ~SX_SS_ISTOP;
|
|
}
|
|
/*
|
|
* If input is blocked, drop RTS if we can and set our flag.
|
|
*/
|
|
if (tp->t_state & TS_TBLOCK) {
|
|
if (!SX_DTRPIN(pp) && SX_IFLOW(pp))
|
|
(void)sx_modem(sc, pp, BIC, TIOCM_RTS);
|
|
pp->sp_state |= SX_SS_ISTOP;
|
|
}
|
|
if ((qp->c_cc > 0 && !SX_OSTOP(pp)) || SX_DOBRK(pp)) {
|
|
disable_intr();
|
|
sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan);
|
|
sx_cd1865_bis(sc, CD1865_IER|SX_EI, CD1865_IER_TXRDY);
|
|
enable_intr();
|
|
pp->sp_state |= SX_SS_XMIT;
|
|
}
|
|
ttwwakeup(tp);
|
|
splx(s);
|
|
DPRINT((pp, DBG_EXIT|DBG_START,
|
|
"sx_start out B st %x sp %x cc %d\n",
|
|
tp->t_state, pp->sp_state, qp->c_cc));
|
|
return;
|
|
}
|
|
/*
|
|
* If we weren't called from an interrupt or it wasn't a transmit
|
|
* interrupt, we've done all we need to do. Everything else is done
|
|
* in the transmit interrupt.
|
|
*/
|
|
if (!SX_INTR(pp) || !SX_IXMIT(pp)) {
|
|
ttwwakeup(tp);
|
|
splx(s);
|
|
DPRINT((pp, DBG_EXIT|DBG_START, "sx_start out X\n"));
|
|
return;
|
|
}
|
|
/*
|
|
* We're transmitting. If the clist is empty and we don't have a break
|
|
* to send, turn off transmit-ready interrupts, and clear the XMIT
|
|
* flag. Mark the tty as no longer busy, in case we haven't done
|
|
* that yet. A future call to sxwrite() with more characters will
|
|
* start up the process once more.
|
|
*/
|
|
if (qp->c_cc == 0 && !SX_DOBRK(pp)) {
|
|
disable_intr();
|
|
/* sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan);*/
|
|
sx_cd1865_bic(sc, CD1865_IER|SX_EI, CD1865_IER_TXRDY);
|
|
enable_intr();
|
|
pp->sp_state &= ~SX_SS_XMIT;
|
|
tp->t_state &= ~TS_BUSY;
|
|
ttwwakeup(tp);
|
|
splx(s);
|
|
DPRINT((pp, DBG_EXIT|DBG_START,
|
|
"sx_start out E st %x sp %x\n",
|
|
tp->t_state, pp->sp_state));
|
|
return;
|
|
}
|
|
disable_intr();
|
|
/*
|
|
* If we have a BREAK state-change pending, handle it. If we aren't
|
|
* sending a break, start one. If we are, turn it off.
|
|
*/
|
|
if (SX_DOBRK(pp)) {
|
|
count -= 2; /* Account for escape chars in FIFO. */
|
|
if (SX_BREAK(pp)) { /* Doing break, stop it. */
|
|
sx_cd1865_out(sc, CD1865_TDR, CD1865_C_ESC);
|
|
sx_cd1865_out(sc, CD1865_TDR, CD1865_C_EBRK);
|
|
sx_cd1865_etcmode(sc, SX_EI, pp->sp_chan, 0);
|
|
pp->sp_state &= ~SX_SS_BREAK;
|
|
}
|
|
else { /* Start doing break. */
|
|
sx_cd1865_etcmode(sc, SX_EI, pp->sp_chan, 1);
|
|
sx_cd1865_out(sc, CD1865_TDR, CD1865_C_ESC);
|
|
sx_cd1865_out(sc, CD1865_TDR, CD1865_C_SBRK);
|
|
pp->sp_state |= SX_SS_BREAK;
|
|
}
|
|
pp->sp_state &= ~SX_SS_DOBRK;
|
|
}
|
|
/*
|
|
* We've still got data in the clist, fill the channel's FIFO. The
|
|
* CD1865 only gives us access to the FIFO during a transmit ready
|
|
* request [interrupt] for this channel.
|
|
*/
|
|
while (qp->c_cc > 0 && count-- >= 0) {
|
|
register unsigned char ch, *cp;
|
|
int nch;
|
|
|
|
ch = (char)getc(qp);
|
|
/*
|
|
* If we're doing a break we're in ETC mode, so we need to
|
|
* double any NULs in the stream.
|
|
*/
|
|
if (SX_BREAK(pp)) { /* Doing break, in ETC mode. */
|
|
if (ch == '\0') { /* NUL? Double it. */
|
|
sx_cd1865_out(sc, CD1865_TDR, ch);
|
|
count--;
|
|
}
|
|
/*
|
|
* Peek the next character; if it's a NUL, we need
|
|
* to escape it, but we can't if we're out of FIFO.
|
|
* We'll do it on the next pass and leave the FIFO
|
|
* incompletely filled.
|
|
*/
|
|
if (qp->c_cc > 0) {
|
|
cp = qp->c_cf;
|
|
cp = nextc(qp, cp, &nch);
|
|
if (nch == '\0' && count < 1)
|
|
count = -1;
|
|
}
|
|
}
|
|
sx_cd1865_out(sc, CD1865_TDR, ch);
|
|
}
|
|
enable_intr();
|
|
/*
|
|
* If we still have data to transmit, mark the tty busy for the
|
|
* line discipline.
|
|
*/
|
|
if (qp->c_cc > 0)
|
|
tp->t_state |= TS_BUSY;
|
|
else
|
|
tp->t_state &= ~TS_BUSY;
|
|
/* Wake up sleepers if necessary. */
|
|
ttwwakeup(tp);
|
|
splx(s);
|
|
DPRINT((pp, DBG_EXIT|DBG_START,
|
|
"sx_start out R %d/%d\n",
|
|
count, qp->c_cc));
|
|
}
|
|
|
|
/*
|
|
* Stop output on a line. called at spltty();
|
|
*/
|
|
void
|
|
sx_stop(
|
|
struct tty *tp,
|
|
int rw)
|
|
{
|
|
struct sx_softc *sc;
|
|
struct sx_port *pp;
|
|
int s;
|
|
|
|
sc = TP2SC(tp);
|
|
pp = TP2PP(tp);
|
|
DPRINT((TP2PP(tp), DBG_ENTRY|DBG_STOP, "sx_stop(%x,%x)\n", tp, rw));
|
|
|
|
s = spltty();
|
|
/* XXX: must check (rw & FWRITE | FREAD) etc flushing... */
|
|
if (rw & FWRITE) {
|
|
disable_intr();
|
|
sx_cd1865_out(sc, CD1865_CAR|SX_EI, pp->sp_chan);
|
|
sx_cd1865_bic(sc, CD1865_IER|SX_EI, CD1865_IER_TXRDY);
|
|
sx_cd1865_wait_CCR(sc, SX_EI); /* Wait for CCR to go idle. */
|
|
sx_cd1865_out(sc, CD1865_CCR|SX_EI, CD1865_CCR_TXDIS);
|
|
sx_cd1865_wait_CCR(sc, SX_EI);
|
|
enable_intr();
|
|
/* what level are we meant to be flushing anyway? */
|
|
if (tp->t_state & TS_BUSY) {
|
|
if ((tp->t_state & TS_TTSTOP) == 0)
|
|
tp->t_state |= TS_FLUSH;
|
|
tp->t_state &= ~TS_BUSY;
|
|
ttwwakeup(tp);
|
|
}
|
|
}
|
|
/*
|
|
* Nothing to do for FREAD.
|
|
*/
|
|
splx(s);
|
|
}
|
|
|
|
#ifdef SX_DEBUG
|
|
|
|
void
|
|
sx_dprintf(
|
|
struct sx_port *pp,
|
|
int flags,
|
|
const char *fmt, ...)
|
|
{
|
|
static char *logbuf = NULL;
|
|
static char *linebuf = NULL;
|
|
static char *logptr;
|
|
char *lbuf;
|
|
int n, m;
|
|
va_list ap;
|
|
|
|
if (logbuf == NULL) {
|
|
logbuf = (char *)malloc(1024*1024, M_DEVBUF, M_WAITOK);
|
|
linebuf = (char *)malloc(256, M_DEVBUF, M_WAITOK);
|
|
logptr = logbuf;
|
|
}
|
|
lbuf = linebuf;
|
|
n = 0;
|
|
if ((pp == NULL && (sx_debug&flags)) ||
|
|
(pp != NULL && ((pp->sp_debug&flags) || (sx_debug&flags)))) {
|
|
if (pp != NULL &&
|
|
pp->sp_tty != NULL &&
|
|
pp->sp_tty->t_dev != NULL) {
|
|
n = snprintf(linebuf, 256, "%cx%d(%d): ", 's',
|
|
(int)SX_MINOR2CARD(minor(pp->sp_tty->t_dev)),
|
|
(int)SX_MINOR2CHAN(minor(pp->sp_tty->t_dev)));
|
|
if (n > 256)
|
|
n = 256;
|
|
lbuf += n;
|
|
}
|
|
m = n;
|
|
va_start(ap, fmt);
|
|
n = vsnprintf(lbuf, 256 - m, fmt, ap);
|
|
va_end(ap);
|
|
if (n > 256 - m)
|
|
n = 256 - m;
|
|
n += m;
|
|
if (logptr + n + 1 > logbuf + (1024 * 1024)) {
|
|
bzero(logptr, logbuf + (1024 * 1024) - logptr);
|
|
logptr = logbuf;
|
|
}
|
|
bcopy(linebuf, logptr, n);
|
|
logptr += n;
|
|
*logptr = '\0';
|
|
if (sx_debug & DBG_PRINTF)
|
|
printf("%s", linebuf);
|
|
}
|
|
}
|
|
|
|
static char *
|
|
sx_mctl2str(enum sx_mctl cmd)
|
|
{
|
|
switch (cmd) {
|
|
case GET:
|
|
return("GET");
|
|
case SET:
|
|
return("SET");
|
|
case BIS:
|
|
return("BIS");
|
|
case BIC:
|
|
return("BIC");
|
|
}
|
|
return("BAD");
|
|
}
|
|
|
|
#endif /* DEBUG */
|