1676 lines
40 KiB
C
1676 lines
40 KiB
C
/*
|
|
* cyclades cyclom-y serial driver
|
|
* Andrew Herbert <andrew@werple.apana.org.au>, 17 August 1993
|
|
*
|
|
* Copyright (c) 1993 Andrew Herbert.
|
|
* 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. The name Andrew Herbert may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* 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 I 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.
|
|
*
|
|
* $Id: cy.c,v 1.6 1995/03/28 12:29:11 bde Exp $
|
|
*/
|
|
|
|
/*
|
|
* Device minor number encoding:
|
|
*
|
|
* c c x x u u u u - bits in the minor device number
|
|
*
|
|
* bits meaning
|
|
* ---- -------
|
|
* uuuu physical serial line (i.e. unit) to use
|
|
* 0-7 on a cyclom-8Y, 0-15 on a cyclom-16Y
|
|
* xx unused
|
|
* cc carrier control mode
|
|
* 00 complete hardware carrier control of the tty.
|
|
* DCD must be high for the open(2) to complete.
|
|
* 01 dialin pseudo-device (not yet implemented)
|
|
* 10 carrier ignored until a high->low transition
|
|
* 11 carrier completed ignored
|
|
*/
|
|
|
|
/*
|
|
* Known deficiencies:
|
|
*
|
|
* * no BREAK handling - breaks are ignored, and can't be sent either
|
|
* * no support for bad-char reporting, except via PARMRK
|
|
* * no support for dialin + dialout devices
|
|
*/
|
|
|
|
#include "cy.h"
|
|
#if NCY > 0
|
|
|
|
/* This disgusing hack because we actually have 16 units on one controller */
|
|
#if NCY < 2
|
|
#undef NCY
|
|
#define NCY (16)
|
|
#endif
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/tty.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/user.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/file.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/syslog.h>
|
|
|
|
#include <machine/cpu.h>
|
|
#ifdef NetBSD
|
|
#include <machine/pio.h>
|
|
#include <machine/cpufunc.h>
|
|
#else
|
|
#include <machine/clock.h>
|
|
#endif
|
|
|
|
#include <i386/isa/isa_device.h>
|
|
#include <i386/isa/ic/cd1400.h>
|
|
|
|
#define RxFifoThreshold 3 /* 3 characters (out of 12) in the receive
|
|
* FIFO before an interrupt is generated
|
|
*/
|
|
#define FastRawInput /* bypass the regular char-by-char canonical input
|
|
* processing whenever possible
|
|
*/
|
|
#define PollMode /* use polling-based irq service routine, not the
|
|
* hardware svcack lines. Must be defined for
|
|
* cyclom-16y boards.
|
|
*
|
|
* XXX cyclom-8y doesn't work without this defined
|
|
* either (!)
|
|
*/
|
|
#define LogOverruns /* log receive fifo overruns */
|
|
#undef TxBuffer /* buffer driver output, to be slightly more
|
|
* efficient
|
|
*
|
|
* XXX presently buggy
|
|
*/
|
|
#undef Smarts /* enable slightly more CD1400 intelligence. Mainly
|
|
* the output CR/LF processing, plus we can avoid a
|
|
* few checks usually done in ttyinput().
|
|
*
|
|
* XXX not yet implemented, and not particularly
|
|
* worthwhile either.
|
|
*/
|
|
#define CyDebug /* include debugging code (minimal effect on
|
|
* performance)
|
|
*/
|
|
|
|
#define CY_RX_BUFS 2 /* two receive buffers per port */
|
|
#define CY_RX_BUF_SIZE 256 /* bytes per receive buffer */
|
|
#define CY_TX_BUF_SIZE 512 /* bytes per transmit buffer */
|
|
|
|
/* #define CD1400s_PER_CYCLOM 1 */ /* cyclom-4y */
|
|
/* #define CD1400s_PER_CYCLOM 2 */ /* cyclom-8y */
|
|
#define CD1400s_PER_CYCLOM 4 /* cyclom-16y */
|
|
|
|
/* FreeBSD's getty doesn't know option for setting RTS/CTS handshake. Its
|
|
getty, like a lot of other old cruft, should be replaced with something
|
|
which used POSIX tty interfaces which at least allow enabling it. In the
|
|
meantime, use the force. */
|
|
#define ALWAYS_RTS_CTS 1
|
|
|
|
#if CD1400s_PER_CYCLOM < 4
|
|
#define CD1400_MEMSIZE 0x400 /* 4*256 bytes per chip: cyclom-[48]y */
|
|
#else
|
|
#define CD1400_MEMSIZE 0x100 /* 256 bytes per chip: cyclom-16y */
|
|
/* XXX or is it 0x400 like the rest? */
|
|
#define CYCLOM_16 1 /* This is a cyclom-16Y */
|
|
#endif
|
|
|
|
#define PORTS_PER_CYCLOM (CD1400_NO_OF_CHANNELS * CD1400s_PER_CYCLOM)
|
|
#define CYCLOM_RESET_16 0x1400 /* cyclom-16y reset */
|
|
#define CYCLOM_CLEAR_INTR 0x1800 /* intr ack address */
|
|
#define CYCLOM_CLOCK 25000000 /* baud rate clock */
|
|
|
|
#define CY_UNITMASK 0x0f
|
|
#define CY_CARRIERMASK 0xC0
|
|
#define CY_CARRIERSHIFT 6
|
|
|
|
#define UNIT(x) (minor(x) & CY_UNITMASK)
|
|
#define CARRIER_MODE(x) ((minor(x) & CY_CARRIERMASK) >> CY_CARRIERSHIFT)
|
|
|
|
typedef u_char * volatile cy_addr;
|
|
|
|
int cyprobe(struct isa_device *dev);
|
|
int cyattach(struct isa_device *isdp);
|
|
void cystart(struct tty *tp);
|
|
int cyparam(struct tty *tp, struct termios *t);
|
|
int cyspeed(int speed, int *prescaler_io);
|
|
static void cy_channel_init(dev_t dev, int reset);
|
|
static void cd1400_channel_cmd(cy_addr base, u_char cmd);
|
|
|
|
/* hsu@clinet.fi: sigh */
|
|
#ifdef __NetBSD__
|
|
#define DELAY(foo) delay(foo)
|
|
void delay(int delay);
|
|
#endif
|
|
|
|
/* Better get rid of this until the core people agree on kernel interfaces.
|
|
At least it will then compile on both WhichBSDs.
|
|
*/
|
|
#if 0
|
|
extern unsigned int delaycount; /* calibrated 1 ms cpu-spin delay */
|
|
#endif
|
|
|
|
struct isa_driver cydriver = {
|
|
cyprobe, cyattach, "cy"
|
|
};
|
|
|
|
/* low-level ping-pong buffer structure */
|
|
|
|
struct cy_buf {
|
|
u_char *next_char; /* location of next char to write */
|
|
u_int free; /* free chars remaining in buffer */
|
|
struct cy_buf *next_buf; /* circular, you know */
|
|
u_char buf[CY_RX_BUF_SIZE]; /* start of the buffer */
|
|
};
|
|
|
|
/* low-level ring buffer */
|
|
|
|
#ifdef TxBuffer
|
|
struct cy_ring {
|
|
u_char buf[CY_TX_BUF_SIZE];
|
|
u_char *head;
|
|
u_char *tail; /* next pos. to insert char */
|
|
u_char *endish; /* physical end of buf */
|
|
u_int used; /* no. of chars in queue */
|
|
};
|
|
#endif
|
|
|
|
|
|
/*
|
|
* define a structure to keep track of each serial line
|
|
*/
|
|
|
|
struct cy {
|
|
cy_addr base_addr; /* base address of this port's cd1400 */
|
|
struct tty *tty;
|
|
u_int dtrwait; /* time (in ticks) to hold dtr low after close */
|
|
u_int recv_exception; /* exception chars received */
|
|
u_int recv_normal; /* normal chars received */
|
|
u_int xmit; /* chars transmitted */
|
|
u_int mdm; /* modem signal changes */
|
|
#ifdef CyDebug
|
|
u_int start_count; /* no. of calls to cystart() */
|
|
u_int start_real; /* no. of calls that did something */
|
|
#endif
|
|
u_char carrier_mode; /* hardware carrier handling mode */
|
|
/*
|
|
* 0 = always use
|
|
* 1 = always use (dialin port)
|
|
* 2 = ignore during open, then use it
|
|
* 3 = ignore completely
|
|
*/
|
|
u_char carrier_delta; /* true if carrier has changed state */
|
|
u_char fifo_overrun; /* true if cd1400 receive fifo has... */
|
|
u_char rx_buf_overrun; /* true if low-level buf overflow */
|
|
u_char intr_enable; /* CD1400 SRER shadow */
|
|
u_char modem_sig; /* CD1400 modem signal shadow */
|
|
u_char channel_control;/* CD1400 CCR control command shadow */
|
|
u_char cor[3]; /* CD1400 COR1-3 shadows */
|
|
#ifdef Smarts
|
|
u_char spec_char[4]; /* CD1400 SCHR1-4 shadows */
|
|
#endif
|
|
struct cy_buf *rx_buf; /* current receive buffer */
|
|
struct cy_buf rx_buf_pool[CY_RX_BUFS];/* receive ping-pong buffers */
|
|
#ifdef TxBuffer
|
|
struct cy_ring tx_buf; /* transmit buffer */
|
|
#endif
|
|
};
|
|
|
|
int cydefaultrate = TTYDEF_SPEED;
|
|
cy_addr cyclom_base; /* base address of the card */
|
|
static struct cy *info[NCY*PORTS_PER_CYCLOM];
|
|
#ifdef __FreeBSD__ /* XXX actually only temporarily for 2.1-Development */
|
|
struct tty cy_tty[NCY*PORTS_PER_CYCLOM];
|
|
#else
|
|
struct tty *cy_tty[NCY*PORTS_PER_CYCLOM];
|
|
#endif
|
|
static volatile u_char timeout_scheduled = 0; /* true if a timeout has been scheduled */
|
|
|
|
#ifdef CyDebug
|
|
u_int cy_svrr_probes = 0; /* debugging */
|
|
u_int cy_timeouts = 0;
|
|
u_int cy_timeout_req = 0;
|
|
#endif
|
|
|
|
/**********************************************************************/
|
|
|
|
int
|
|
cyprobe(struct isa_device *dev)
|
|
{
|
|
int i, j;
|
|
u_char version = 0; /* firmware version */
|
|
|
|
/* Cyclom-16Y hardware reset (Cyclom-8Ys don't care) */
|
|
i = *(cy_addr)(dev->id_maddr + CYCLOM_RESET_16);
|
|
|
|
DELAY(500); /* wait for the board to get its act together (500 us) */
|
|
|
|
for (i = 0; i < CD1400s_PER_CYCLOM; i++) {
|
|
cy_addr base = dev->id_maddr + i * CD1400_MEMSIZE;
|
|
|
|
/* wait for chip to become ready for new command */
|
|
for (j = 0; j < 100; j += 50) {
|
|
DELAY(50); /* wait 50 us */
|
|
|
|
if (!*(base + CD1400_CCR))
|
|
break;
|
|
}
|
|
|
|
/* clear the GFRCR register */
|
|
*(base + CD1400_GFRCR) = 0;
|
|
|
|
/* issue a reset command */
|
|
*(base + CD1400_CCR) = CD1400_CMD_RESET;
|
|
|
|
/* wait for the CD1400 to initialise itself */
|
|
for (j = 0; j < 1000; j += 50) {
|
|
DELAY(50); /* wait 50 us */
|
|
|
|
/* retrieve firmware version */
|
|
version = *(base + CD1400_GFRCR);
|
|
if (version)
|
|
break;
|
|
}
|
|
|
|
/* anything in the 40-4f range is fine */
|
|
if ((version & 0xf0) != 0x40) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1; /* found */
|
|
}
|
|
|
|
|
|
int
|
|
cyattach(struct isa_device *isdp)
|
|
{
|
|
/* u_char unit = UNIT(isdp->id_unit); */
|
|
int i, j, k;
|
|
|
|
/* global variable used various routines */
|
|
cyclom_base = (cy_addr)isdp->id_maddr;
|
|
|
|
for (i = 0, k = 0; i < CD1400s_PER_CYCLOM; i++) {
|
|
cy_addr base = cyclom_base + i * CD1400_MEMSIZE;
|
|
|
|
/* setup a 1ms clock tick */
|
|
*(base + CD1400_PPR) = CD1400_CLOCK_25_1MS;
|
|
|
|
for (j = 0; j < CD1400_NO_OF_CHANNELS; j++, k++) {
|
|
struct cy *ip;
|
|
|
|
/*
|
|
* grab some space. it'd be more polite to do this in cyopen(),
|
|
* but hey.
|
|
*/
|
|
info[k] = ip = malloc(sizeof(struct cy), M_DEVBUF, M_WAITOK);
|
|
|
|
/* clear all sorts of junk */
|
|
bzero(ip, sizeof(struct cy));
|
|
|
|
ip->base_addr = base;
|
|
|
|
/* initialise the channel, without resetting it first */
|
|
cy_channel_init(k, 0);
|
|
}
|
|
}
|
|
|
|
/* clear interrupts */
|
|
*(cyclom_base + CYCLOM_CLEAR_INTR) = (u_char)0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
int
|
|
cyopen(dev_t dev, int flag, int mode, struct proc *p)
|
|
{
|
|
u_int unit = UNIT(dev);
|
|
struct cy *infop;
|
|
cy_addr base;
|
|
struct tty *tp;
|
|
int error = 0;
|
|
u_char carrier;
|
|
|
|
if (unit >= /* NCY * ? */ PORTS_PER_CYCLOM)
|
|
return (ENXIO);
|
|
|
|
infop = info[unit];
|
|
base = infop->base_addr;
|
|
#ifdef __FreeBSD__
|
|
infop->tty = &cy_tty[unit];
|
|
#else
|
|
if (!cy_tty[unit])
|
|
infop->tty = cy_tty[unit] = ttymalloc();
|
|
#endif
|
|
tp = infop->tty;
|
|
|
|
tp->t_oproc = cystart;
|
|
tp->t_param = cyparam;
|
|
tp->t_dev = dev;
|
|
if (!(tp->t_state & TS_ISOPEN)) {
|
|
tp->t_state |= TS_WOPEN;
|
|
ttychars(tp);
|
|
if (tp->t_ispeed == 0) {
|
|
tp->t_iflag = TTYDEF_IFLAG;
|
|
tp->t_oflag = TTYDEF_OFLAG;
|
|
tp->t_cflag = TTYDEF_CFLAG;
|
|
tp->t_lflag = TTYDEF_LFLAG;
|
|
tp->t_ispeed = tp->t_ospeed = cydefaultrate;
|
|
}
|
|
|
|
(void) spltty();
|
|
cy_channel_init(unit, 1); /* reset the hardware */
|
|
|
|
/*
|
|
* raise dtr and generally set things up correctly. this
|
|
* has the side-effect of selecting the appropriate cd1400
|
|
* channel, to help us with subsequent channel control stuff
|
|
*/
|
|
cyparam(tp, &tp->t_termios);
|
|
|
|
/* check carrier, and set t_state's TS_CARR_ON flag accordingly */
|
|
infop->modem_sig = *(base + CD1400_MSVR);
|
|
carrier = infop->modem_sig & CD1400_MSVR_CD;
|
|
|
|
if (carrier || (infop->carrier_mode >= 2))
|
|
tp->t_state |= TS_CARR_ON;
|
|
else
|
|
tp->t_state &=~ TS_CARR_ON;
|
|
|
|
/*
|
|
* enable modem & rx interrupts - relies on cyparam()
|
|
* having selected the appropriate cd1400 channel
|
|
*/
|
|
infop->intr_enable = (1 << 7) | (1 << 4);
|
|
*(base + CD1400_SRER) = infop->intr_enable;
|
|
|
|
ttsetwater(tp);
|
|
} else if (tp->t_state & TS_XCLUDE && p->p_ucred->cr_uid != 0)
|
|
return (EBUSY);
|
|
|
|
if (!(flag & O_NONBLOCK))
|
|
while (!(tp->t_cflag & CLOCAL) &&
|
|
!(tp->t_state & TS_CARR_ON) && !error)
|
|
error = ttysleep(tp, (caddr_t)&tp->t_rawq,
|
|
TTIPRI|PCATCH, ttopen, 0);
|
|
(void) spl0();
|
|
|
|
if (!error)
|
|
error = (*linesw[(u_char)tp->t_line].l_open)(dev, tp);
|
|
return (error);
|
|
} /* end of cyopen() */
|
|
|
|
|
|
void
|
|
cyclose_wakeup(void *arg)
|
|
{
|
|
wakeup(arg);
|
|
} /* end of cyclose_wakeup() */
|
|
|
|
|
|
int
|
|
cyclose(dev_t dev, int flag, int mode, struct proc *p)
|
|
{
|
|
u_int unit = UNIT(dev);
|
|
struct cy *infop = info[unit];
|
|
struct tty *tp = infop->tty;
|
|
cy_addr base = infop->base_addr;
|
|
int s;
|
|
|
|
(*linesw[(u_char)tp->t_line].l_close)(tp, flag);
|
|
|
|
s = spltty();
|
|
/* select the appropriate channel on the CD1400 */
|
|
*(base + CD1400_CAR) = (u_char)(unit & 0x03);
|
|
|
|
/* disable this channel and lower DTR */
|
|
infop->intr_enable = 0;
|
|
*(base + CD1400_SRER) = (u_char)0; /* no intrs */
|
|
*(base + CD1400_DTR) = (u_char)CD1400_DTR_CLEAR; /* no DTR */
|
|
infop->modem_sig &= ~CD1400_MSVR_DTR;
|
|
|
|
/* disable receiver (leave transmitter enabled) */
|
|
infop->channel_control = (1 << 4) | (1 << 3) | 1;
|
|
cd1400_channel_cmd(base, infop->channel_control);
|
|
splx(s);
|
|
|
|
ttyclose(tp);
|
|
#ifdef broken /* session holds a ref to the tty; can't deallocate */
|
|
ttyfree(tp);
|
|
infop->tty = cy_tty[unit] = (struct tty *)NULL;
|
|
#endif
|
|
|
|
if (infop->dtrwait) {
|
|
int error;
|
|
|
|
timeout(cyclose_wakeup, (caddr_t)&infop->dtrwait, infop->dtrwait);
|
|
do {
|
|
error = tsleep((caddr_t)&infop->dtrwait,
|
|
TTIPRI|PCATCH, "cyclose", 0);
|
|
} while (error == ERESTART);
|
|
}
|
|
|
|
return 0;
|
|
} /* end of cyclose() */
|
|
|
|
|
|
int
|
|
cyread(dev_t dev, struct uio *uio, int flag)
|
|
{
|
|
u_int unit = UNIT(dev);
|
|
struct tty *tp = info[unit]->tty;
|
|
|
|
return (*linesw[(u_char)tp->t_line].l_read)(tp, uio, flag);
|
|
} /* end of cyread() */
|
|
|
|
|
|
int
|
|
cywrite(dev_t dev, struct uio *uio, int flag)
|
|
{
|
|
u_int unit = UNIT(dev);
|
|
struct tty *tp = info[unit]->tty;
|
|
|
|
#ifdef Smarts
|
|
/* XXX duplicate ttwrite(), but without so much output processing on
|
|
* CR & LF chars. Hardly worth the effort, given that high-throughput
|
|
* sessions are raw anyhow.
|
|
*/
|
|
#else
|
|
return (*linesw[(u_char)tp->t_line].l_write)(tp, uio, flag);
|
|
#endif
|
|
} /* end of cywrite() */
|
|
|
|
|
|
#ifdef Smarts
|
|
/* standard line discipline input routine */
|
|
int
|
|
cyinput(int c, struct tty *tp)
|
|
{
|
|
/* XXX duplicate ttyinput(), but without the IXOFF/IXON/ISTRIP/IPARMRK
|
|
* bits, as they are done by the CD1400. Hardly worth the effort,
|
|
* given that high-throughput sessions are raw anyhow.
|
|
*/
|
|
} /* end of cyinput() */
|
|
#endif /* Smarts */
|
|
|
|
|
|
inline static void
|
|
service_upper_rx(int unit)
|
|
{
|
|
struct cy *ip = info[unit];
|
|
struct tty *tp = ip->tty;
|
|
struct cy_buf *buf;
|
|
int i;
|
|
u_char *ch;
|
|
|
|
buf = ip->rx_buf;
|
|
|
|
/* give service_rx() a new one */
|
|
disable_intr(); /* faster than spltty() */
|
|
ip->rx_buf = buf->next_buf;
|
|
enable_intr();
|
|
|
|
if (tp->t_state & TS_ISOPEN) {
|
|
ch = buf->buf;
|
|
i = buf->next_char - buf->buf;
|
|
|
|
#ifdef FastRawInput
|
|
/* try to avoid calling the line discipline stuff if we can */
|
|
if ((tp->t_line == 0) &&
|
|
!(tp->t_iflag & (ICRNL | IMAXBEL | INLCR)) &&
|
|
!(tp->t_lflag & (ECHO | ECHONL | ICANON | IEXTEN |
|
|
ISIG | PENDIN)) &&
|
|
!(tp->t_state & (TS_CNTTB | TS_LNCH))) {
|
|
|
|
i = b_to_q(ch, i, &tp->t_rawq);
|
|
if (i) {
|
|
/*
|
|
* we have no RTS flow control support on cy-8
|
|
* boards, so this is really just tough luck
|
|
*/
|
|
|
|
log(LOG_WARNING, "cy%d: tty input queue overflow\n",
|
|
unit);
|
|
}
|
|
|
|
ttwakeup(tp); /* notify any readers */
|
|
}
|
|
else
|
|
#endif /* FastRawInput */
|
|
{
|
|
while (i--)
|
|
(*linesw[(u_char)tp->t_line].l_rint)((int)*ch++, tp);
|
|
}
|
|
}
|
|
|
|
/* clear the buffer we've just processed */
|
|
buf->next_char = buf->buf;
|
|
buf->free = CY_RX_BUF_SIZE;
|
|
} /* end of service_upper_rx() */
|
|
|
|
|
|
#ifdef TxBuffer
|
|
static void
|
|
service_upper_tx(int unit)
|
|
{
|
|
struct cy *ip = info[unit];
|
|
struct tty *tp = ip->tty;
|
|
|
|
tp->t_state &=~ (TS_BUSY|TS_FLUSH);
|
|
|
|
if (tp->t_outq.c_cc <= tp->t_lowat) {
|
|
if (tp->t_state&TS_ASLEEP) {
|
|
tp->t_state &= ~TS_ASLEEP;
|
|
wakeup((caddr_t)&tp->t_outq);
|
|
}
|
|
selwakeup(&tp->t_wsel);
|
|
}
|
|
|
|
if (tp->t_outq.c_cc > 0) {
|
|
struct cy_ring *txq = &ip->tx_buf;
|
|
int free_count = CY_TX_BUF_SIZE - ip->tx_buf.used;
|
|
u_char *cp = txq->tail;
|
|
int count;
|
|
int chars_done;
|
|
|
|
tp->t_state |= TS_BUSY;
|
|
|
|
/* find the largest contig. copy we can do */
|
|
count = ((txq->endish - cp) > free_count) ?
|
|
free_count : txq->endish - cp;
|
|
|
|
count = ((cp + free_count) > txq->endish) ?
|
|
txq->endish - cp : free_count;
|
|
|
|
/* copy the first slab */
|
|
chars_done = q_to_b(&tp->t_outq, cp, count);
|
|
|
|
/* check for wrap-around time */
|
|
cp += chars_done;
|
|
if (cp == txq->endish)
|
|
cp = txq->buf; /* back to the start */
|
|
|
|
/* copy anything else, after we've wrapped around */
|
|
if ((chars_done == count) && (count != free_count)) {
|
|
/* copy the second slab */
|
|
count = q_to_b(&tp->t_outq, cp, free_count - count);
|
|
cp += count;
|
|
chars_done += count;
|
|
}
|
|
|
|
/*
|
|
* update queue, protecting ourselves from any rampant
|
|
* lower-layers
|
|
*/
|
|
disable_intr();
|
|
txq->tail = cp;
|
|
txq->used += chars_done;
|
|
enable_intr();
|
|
}
|
|
|
|
if (!tp->t_outq.c_cc)
|
|
tp->t_state &=~ TS_BUSY;
|
|
} /* end of service_upper_tx() */
|
|
#endif /* TxBuffer */
|
|
|
|
|
|
inline static void
|
|
service_upper_mdm(int unit)
|
|
{
|
|
struct cy *ip = info[unit];
|
|
|
|
if (ip->carrier_delta) {
|
|
int carrier = ip->modem_sig & CD1400_MSVR_CD;
|
|
struct tty *tp = ip->tty;
|
|
|
|
if (!(*linesw[(u_char)tp->t_line].l_modem)(tp, carrier)) {
|
|
cy_addr base = ip->base_addr;
|
|
|
|
/* clear DTR */
|
|
disable_intr();
|
|
*(base + CD1400_CAR) = (u_char)(unit & 0x03);
|
|
*(base + CD1400_DTR) = (u_char)CD1400_DTR_CLEAR;
|
|
ip->modem_sig &= ~CD1400_MSVR_DTR;
|
|
ip->carrier_delta = 0;
|
|
enable_intr();
|
|
}
|
|
else {
|
|
disable_intr();
|
|
ip->carrier_delta = 0;
|
|
enable_intr();
|
|
}
|
|
}
|
|
} /* end of service_upper_mdm() */
|
|
|
|
|
|
/* upper level character processing routine */
|
|
void
|
|
cytimeout(void *ptr)
|
|
{
|
|
int unit;
|
|
|
|
timeout_scheduled = 0;
|
|
|
|
#ifdef CyDebug
|
|
cy_timeouts++;
|
|
#endif
|
|
|
|
/* check each port in turn */
|
|
for (unit = 0; unit < NCY*PORTS_PER_CYCLOM; unit++) {
|
|
struct cy *ip = info[unit];
|
|
#ifndef TxBuffer
|
|
struct tty *tp = ip->tty;
|
|
#endif
|
|
|
|
/* ignore anything that is not open */
|
|
if (!ip->tty)
|
|
continue;
|
|
|
|
/*
|
|
* any received chars to handle? (doesn't matter if intr routine
|
|
* kicks in while we're testing this)
|
|
*/
|
|
if (ip->rx_buf->free != CY_RX_BUF_SIZE)
|
|
service_upper_rx(unit);
|
|
|
|
#ifdef TxBuffer
|
|
/* anything to add to the transmit buffer (low-water mark)? */
|
|
if (ip->tx_buf.used < CY_TX_BUF_SIZE/2)
|
|
service_upper_tx(unit);
|
|
#else
|
|
if (tp->t_outq.c_cc <= tp->t_lowat) {
|
|
if (tp->t_state&TS_ASLEEP) {
|
|
tp->t_state &= ~TS_ASLEEP;
|
|
wakeup((caddr_t)&tp->t_outq);
|
|
}
|
|
selwakeup(&tp->t_wsel);
|
|
}
|
|
#endif
|
|
|
|
/* anything modem signals altered? */
|
|
service_upper_mdm(unit);
|
|
|
|
/* any overruns to log? */
|
|
#ifdef LogOverruns
|
|
if (ip->fifo_overrun) {
|
|
/*
|
|
* turn off the alarm - not important enough to bother
|
|
* with interrupt protection.
|
|
*/
|
|
ip->fifo_overrun = 0;
|
|
|
|
log(LOG_WARNING, "cy%d: receive fifo overrun\n", unit);
|
|
}
|
|
#endif
|
|
if (ip->rx_buf_overrun) {
|
|
/*
|
|
* turn off the alarm - not important enough to bother
|
|
* with interrupt protection.
|
|
*/
|
|
ip->rx_buf_overrun = 0;
|
|
|
|
log(LOG_WARNING, "cy%d: receive buffer full\n", unit);
|
|
}
|
|
}
|
|
} /* cytimeout() */
|
|
|
|
|
|
inline static void
|
|
schedule_upper_service(void)
|
|
{
|
|
#ifdef CyDebug
|
|
cy_timeout_req++;
|
|
#endif
|
|
|
|
if (!timeout_scheduled) {
|
|
timeout(cytimeout, (caddr_t)0, 1); /* call next tick */
|
|
timeout_scheduled = 1;
|
|
}
|
|
} /* end of schedule_upper_service() */
|
|
|
|
|
|
/* initialise a channel on the cyclom board */
|
|
|
|
static void
|
|
cy_channel_init(dev_t dev, int reset)
|
|
{
|
|
u_int unit = UNIT(dev);
|
|
int carrier_mode = CARRIER_MODE(dev);
|
|
struct cy *ip = info[unit];
|
|
cy_addr base = ip->base_addr;
|
|
struct tty *tp = ip->tty;
|
|
struct cy_buf *buf, *next_buf;
|
|
int i;
|
|
#ifndef PollMode
|
|
u_char cd1400_unit;
|
|
#endif
|
|
|
|
/* clear the structure and refill it */
|
|
bzero(ip, sizeof(struct cy));
|
|
ip->base_addr = base;
|
|
ip->tty = tp;
|
|
ip->carrier_mode = carrier_mode;
|
|
|
|
/* select channel of the CD1400 */
|
|
*(base + CD1400_CAR) = (u_char)(unit & 0x03);
|
|
|
|
if (reset)
|
|
cd1400_channel_cmd(base, 0x80); /* reset the channel */
|
|
|
|
/* set LIVR to 0 - intr routines depend on this */
|
|
*(base + CD1400_LIVR) = 0;
|
|
|
|
#ifndef PollMode
|
|
/* set top four bits of {R,T,M}ICR to the cd1400
|
|
* number, cd1400_unit
|
|
*/
|
|
cd1400_unit = unit / CD1400_NO_OF_CHANNELS;
|
|
*(base + CD1400_RICR) = (u_char)(cd1400_unit << 4);
|
|
*(base + CD1400_TICR) = (u_char)(cd1400_unit << 4);
|
|
*(base + CD1400_MICR) = (u_char)(cd1400_unit << 4);
|
|
#endif
|
|
|
|
ip->dtrwait = hz/4; /* quarter of a second */
|
|
|
|
/* setup low-level buffers */
|
|
i = CY_RX_BUFS;
|
|
ip->rx_buf = next_buf = &ip->rx_buf_pool[0];
|
|
while (i--) {
|
|
buf = &ip->rx_buf_pool[i];
|
|
|
|
buf->next_char = buf->buf; /* first char to use */
|
|
buf->free = CY_RX_BUF_SIZE; /* i.e. empty */
|
|
buf->next_buf = next_buf; /* where to go next */
|
|
next_buf = buf;
|
|
}
|
|
|
|
#ifdef TxBuffer
|
|
ip->tx_buf.endish = ip->tx_buf.buf + CY_TX_BUF_SIZE;
|
|
|
|
/* clear the low-level tx buffer */
|
|
ip->tx_buf.head = ip->tx_buf.tail = ip->tx_buf.buf;
|
|
ip->tx_buf.used = 0;
|
|
#endif
|
|
|
|
/* clear the low-level rx buffer */
|
|
ip->rx_buf->next_char = ip->rx_buf->buf; /* first char to use */
|
|
ip->rx_buf->free = CY_RX_BUF_SIZE; /* completely empty */
|
|
} /* end of cy_channel_init() */
|
|
|
|
|
|
/* service a receive interrupt */
|
|
inline static void
|
|
service_rx(int cd, caddr_t base)
|
|
{
|
|
struct cy *infop;
|
|
unsigned count;
|
|
int ch;
|
|
u_char serv_type, channel;
|
|
#ifdef PollMode
|
|
u_char save_rir, save_car;
|
|
#endif
|
|
|
|
/* setup */
|
|
#ifdef PollMode
|
|
save_rir = *(base + CD1400_RIR);
|
|
channel = cd * CD1400_NO_OF_CHANNELS + (save_rir & 0x3);
|
|
save_car = *(base + CD1400_CAR);
|
|
*(base + CD1400_CAR) = save_rir; /* enter modem service */
|
|
serv_type = *(base + CD1400_RIVR);
|
|
#else
|
|
serv_type = *(base + CD1400_SVCACKR); /* ack receive service */
|
|
channel = ((u_char)*(base + CD1400_RICR)) >> 2; /* get cyclom channel # */
|
|
|
|
#ifdef CyDebug
|
|
if (channel >= PORTS_PER_CYCLOM) {
|
|
printf("cy: service_rx - channel %02x\n", channel);
|
|
panic("cy: service_rx - bad channel");
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
infop = info[channel];
|
|
|
|
/* read those chars */
|
|
if (serv_type & CD1400_RIVR_EXCEPTION) {
|
|
/* read the exception status */
|
|
u_char status = *(base + CD1400_RDSR);
|
|
|
|
/* XXX is it a break? Do something if it is! */
|
|
|
|
/* XXX is IGNPAR not set? Store a null in the buffer. */
|
|
|
|
#ifdef LogOverruns
|
|
if (status & CD1400_RDSR_OVERRUN) {
|
|
#if 0
|
|
ch |= TTY_PE; /* for SLIP */
|
|
#endif
|
|
infop->fifo_overrun++;
|
|
}
|
|
#endif
|
|
infop->recv_exception++;
|
|
}
|
|
else {
|
|
struct cy_buf *buf = infop->rx_buf;
|
|
|
|
count = (u_char)*(base + CD1400_RDCR); /* how many to read? */
|
|
infop->recv_normal += count;
|
|
if (buf->free < count) {
|
|
infop->rx_buf_overrun += count;
|
|
|
|
/* read & discard everything */
|
|
while (count--)
|
|
ch = (u_char)*(base + CD1400_RDSR);
|
|
}
|
|
else {
|
|
/* slurp it into our low-level buffer */
|
|
buf->free -= count;
|
|
while (count--) {
|
|
ch = (u_char)*(base + CD1400_RDSR); /* read the char */
|
|
*(buf->next_char++) = ch;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef PollMode
|
|
*(base + CD1400_RIR) = (u_char)(save_rir & 0x3f); /* terminate service context */
|
|
#else
|
|
*(base + CD1400_EOSRR) = (u_char)0; /* terminate service context */
|
|
#endif
|
|
} /* end of service_rx */
|
|
|
|
|
|
/* service a transmit interrupt */
|
|
inline static void
|
|
service_tx(int cd, caddr_t base)
|
|
{
|
|
struct cy *ip;
|
|
#ifdef TxBuffer
|
|
struct cy_ring *txq;
|
|
#else
|
|
struct tty *tp;
|
|
#endif
|
|
u_char channel;
|
|
#ifdef PollMode
|
|
u_char save_tir, save_car;
|
|
#else
|
|
u_char vector;
|
|
#endif
|
|
|
|
/* setup */
|
|
#ifdef PollMode
|
|
save_tir = *(base + CD1400_TIR);
|
|
channel = cd * CD1400_NO_OF_CHANNELS + (save_tir & 0x3);
|
|
save_car = *(base + CD1400_CAR);
|
|
*(base + CD1400_CAR) = save_tir; /* enter tx service */
|
|
#else
|
|
vector = *(base + CD1400_SVCACKT); /* ack transmit service */
|
|
channel = ((u_char)*(base + CD1400_TICR)) >> 2; /* get cyclom channel # */
|
|
|
|
#ifdef CyDebug
|
|
if (channel >= PORTS_PER_CYCLOM) {
|
|
printf("cy: service_tx - channel %02x\n", channel);
|
|
panic("cy: service_tx - bad channel");
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
ip = info[channel];
|
|
#ifdef TxBuffer
|
|
txq = &ip->tx_buf;
|
|
|
|
if (txq->used > 0) {
|
|
cy_addr base = ip->base_addr;
|
|
int count = min(CD1400_FIFOSIZE, txq->used);
|
|
int chars_done = count;
|
|
u_char *cp = txq->head;
|
|
u_char *buf_end = txq->endish;
|
|
|
|
/* ip->state |= CY_BUSY; */
|
|
while (count--) {
|
|
*(base + CD1400_TDR) = *cp++;
|
|
if (cp >= buf_end)
|
|
cp = txq->buf;
|
|
};
|
|
txq->head = cp;
|
|
txq->used -= chars_done; /* important that this is atomic */
|
|
ip->xmit += chars_done;
|
|
}
|
|
|
|
/*
|
|
* disable tx intrs if no more chars to send. we re-enable
|
|
* them in cystart()
|
|
*/
|
|
if (!txq->used) {
|
|
ip->intr_enable &=~ (1 << 2);
|
|
*(base + CD1400_SRER) = ip->intr_enable;
|
|
/* ip->state &= ~CY_BUSY; */
|
|
}
|
|
#else
|
|
tp = ip->tty;
|
|
|
|
if (!(tp->t_state & TS_TTSTOP) && (tp->t_outq.c_cc > 0)) {
|
|
cy_addr base = ip->base_addr;
|
|
int count = min(CD1400_FIFOSIZE, tp->t_outq.c_cc);
|
|
|
|
ip->xmit += count;
|
|
tp->t_state |= TS_BUSY;
|
|
while (count--)
|
|
*(base + CD1400_TDR) = getc(&tp->t_outq);
|
|
}
|
|
|
|
/*
|
|
* disable tx intrs if no more chars to send. we re-enable them
|
|
* in cystart()
|
|
*/
|
|
if (!tp->t_outq.c_cc) {
|
|
ip->intr_enable &=~ (1 << 2);
|
|
*(base + CD1400_SRER) = ip->intr_enable;
|
|
tp->t_state &= ~TS_BUSY;
|
|
}
|
|
#endif
|
|
|
|
#ifdef PollMode
|
|
*(base + CD1400_TIR) = (u_char)(save_tir & 0x3f); /* terminate service context */
|
|
#else
|
|
*(base + CD1400_EOSRR) = (u_char)0; /* terminate service context */
|
|
#endif
|
|
} /* end of service_tx */
|
|
|
|
|
|
/* service a modem status interrupt */
|
|
inline static void
|
|
service_mdm(int cd, caddr_t base)
|
|
{
|
|
struct cy *infop;
|
|
u_char channel, deltas;
|
|
#ifdef PollMode
|
|
u_char save_mir, save_car;
|
|
#else
|
|
u_char vector;
|
|
#endif
|
|
|
|
/* setup */
|
|
#ifdef PollMode
|
|
save_mir = *(base + CD1400_MIR);
|
|
channel = cd * CD1400_NO_OF_CHANNELS + (save_mir & 0x3);
|
|
save_car = *(base + CD1400_CAR);
|
|
*(base + CD1400_CAR) = save_mir; /* enter modem service */
|
|
#else
|
|
vector = *(base + CD1400_SVCACKM); /* ack modem service */
|
|
channel = ((u_char)*(base + CD1400_MICR)) >> 2; /* get cyclom channel # */
|
|
|
|
#ifdef CyDebug
|
|
if (channel >= PORTS_PER_CYCLOM) {
|
|
printf("cy: service_mdm - channel %02x\n", channel);
|
|
panic("cy: service_mdm - bad channel");
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
infop = info[channel];
|
|
|
|
/* read the siggies and see what's changed */
|
|
infop->modem_sig = (u_char)*(base + CD1400_MSVR);
|
|
deltas = (u_char)*(base + CD1400_MISR);
|
|
|
|
if ((infop->carrier_mode <= 2) && (deltas & CD1400_MISR_CDd))
|
|
/* something for the upper layer to deal with */
|
|
infop->carrier_delta = 1;
|
|
|
|
infop->mdm++;
|
|
|
|
/* terminate service context */
|
|
#ifdef PollMode
|
|
*(base + CD1400_MIR) = (u_char)(save_mir & 0x3f);
|
|
#else
|
|
*(base + CD1400_EOSRR) = (u_char)0;
|
|
#endif
|
|
} /* end of service_mdm */
|
|
|
|
|
|
int
|
|
cyintr(int unit)
|
|
{
|
|
int cd;
|
|
u_char status;
|
|
|
|
/* check each CD1400 in turn */
|
|
for (cd = 0; cd < CD1400s_PER_CYCLOM; cd++) {
|
|
cy_addr base = cyclom_base + cd*CD1400_MEMSIZE;
|
|
|
|
/* poll to see if it has any work */
|
|
while (status = (u_char)*(base + CD1400_SVRR)) {
|
|
#ifdef CyDebug
|
|
cy_svrr_probes++;
|
|
#endif
|
|
/* service requests as appropriate, giving priority to RX */
|
|
if (status & CD1400_SVRR_RX)
|
|
service_rx(cd, base);
|
|
if (status & CD1400_SVRR_TX)
|
|
service_tx(cd, base);
|
|
if (status & CD1400_SVRR_MDM)
|
|
service_mdm(cd, base);
|
|
}
|
|
}
|
|
|
|
/* request upper level service to deal with whatever happened */
|
|
schedule_upper_service();
|
|
|
|
/* re-enable interrupts on the cyclom */
|
|
*(cyclom_base + CYCLOM_CLEAR_INTR) = (u_char)0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
int
|
|
cyioctl(dev_t dev, int cmd, caddr_t data, int flag, struct proc *p)
|
|
{
|
|
int unit = UNIT(dev);
|
|
struct cy *infop = info[unit];
|
|
struct tty *tp = infop->tty;
|
|
int error;
|
|
|
|
error = (*linesw[(u_char)tp->t_line].l_ioctl)(tp, cmd, data, flag, p);
|
|
if (error >= 0)
|
|
return (error);
|
|
error = ttioctl(tp, cmd, data, flag
|
|
#ifdef NetBSD
|
|
, p
|
|
#endif
|
|
);
|
|
if (error >= 0)
|
|
return (error);
|
|
|
|
switch (cmd) {
|
|
#ifdef notyet /* sigh - more junk to do XXX */
|
|
case TIOCSBRK:
|
|
break;
|
|
case TIOCCBRK:
|
|
break;
|
|
case TIOCSDTR:
|
|
break;
|
|
case TIOCCDTR:
|
|
break;
|
|
|
|
case TIOCMSET:
|
|
break;
|
|
case TIOCMBIS:
|
|
break;
|
|
case TIOCMBIC:
|
|
break;
|
|
#endif /* notyet */
|
|
|
|
case TIOCMGET: {
|
|
int bits = 0;
|
|
u_char status = infop->modem_sig;
|
|
|
|
if (status & CD1400_MSVR_DTR) bits |= TIOCM_DTR | TIOCM_RTS;
|
|
if (status & CD1400_MSVR_CD) bits |= TIOCM_CD;
|
|
if (status & CD1400_MSVR_CTS) bits |= TIOCM_CTS;
|
|
if (status & CD1400_MSVR_DSR) bits |= TIOCM_DSR;
|
|
#ifdef CYCLOM_16
|
|
if (status & CD1400_MSVR_RI) bits |= TIOCM_RI;
|
|
#endif
|
|
if (infop->channel_control & 0x02) bits |= TIOCM_LE;
|
|
*(int *)data = bits;
|
|
break;
|
|
}
|
|
|
|
#ifdef TIOCMSBIDIR
|
|
case TIOCMSBIDIR:
|
|
return (ENOTTY);
|
|
#endif /* TIOCMSBIDIR */
|
|
|
|
#ifdef TIOCMGBIDIR
|
|
case TIOCMGBIDIR:
|
|
return (ENOTTY);
|
|
#endif /* TIOCMGBIDIR */
|
|
|
|
#ifdef TIOCMSDTRWAIT
|
|
case TIOCMSDTRWAIT:
|
|
/* must be root to set dtr delay */
|
|
if (p->p_ucred->cr_uid != 0)
|
|
return(EPERM);
|
|
|
|
infop->dtrwait = *(u_int *)data;
|
|
break;
|
|
#endif /* TIOCMSDTRWAIT */
|
|
|
|
#ifdef TIOCMGDTRWAIT
|
|
case TIOCMGDTRWAIT:
|
|
*(u_int *)data = infop->dtrwait;
|
|
break;
|
|
#endif /* TIOCMGDTRWAIT */
|
|
|
|
default:
|
|
return (ENOTTY);
|
|
}
|
|
|
|
return 0;
|
|
} /* end of cyioctl() */
|
|
|
|
|
|
int
|
|
cyparam(struct tty *tp, struct termios *t)
|
|
{
|
|
u_char unit = UNIT(tp->t_dev);
|
|
struct cy *infop = info[unit];
|
|
cy_addr base = infop->base_addr;
|
|
int cflag = t->c_cflag;
|
|
int iflag = t->c_iflag;
|
|
int ispeed, ospeed;
|
|
int itimeout;
|
|
int iprescaler, oprescaler;
|
|
int s;
|
|
u_char cor_change = 0;
|
|
u_char opt;
|
|
|
|
if (!t->c_ispeed)
|
|
t->c_ispeed = t->c_ospeed;
|
|
|
|
s = spltty();
|
|
|
|
/* select the appropriate channel on the CD1400 */
|
|
*(base + CD1400_CAR) = unit & 0x03;
|
|
|
|
/* handle DTR drop on speed == 0 trick */
|
|
if (t->c_ospeed == 0) {
|
|
*(base + CD1400_DTR) = CD1400_DTR_CLEAR;
|
|
infop->modem_sig &= ~CD1400_MSVR_DTR;
|
|
}
|
|
else {
|
|
*(base + CD1400_DTR) = CD1400_DTR_SET;
|
|
infop->modem_sig |= CD1400_MSVR_DTR;
|
|
}
|
|
|
|
/* set baud rates if they've changed from last time */
|
|
|
|
if ((ospeed = cyspeed(t->c_ospeed, &oprescaler)) < 0)
|
|
return EINVAL;
|
|
*(base + CD1400_TBPR) = (u_char)ospeed;
|
|
*(base + CD1400_TCOR) = (u_char)oprescaler;
|
|
|
|
if ((ispeed = cyspeed(t->c_ispeed, &iprescaler)) < 0)
|
|
return EINVAL;
|
|
*(base + CD1400_RBPR) = (u_char)ispeed;
|
|
*(base + CD1400_RCOR) = (u_char)iprescaler;
|
|
|
|
/*
|
|
* set receive time-out period
|
|
* generate a rx interrupt if no new chars are received in
|
|
* this many ticks
|
|
* don't bother comparing old & new VMIN, VTIME and ispeed - it
|
|
* can't be much worse just to calculate and set it each time!
|
|
* certainly less hassle. :-)
|
|
*/
|
|
|
|
/*
|
|
* calculate minimum timeout period:
|
|
* 5 ms or the time it takes to receive 1 char, rounded up to the
|
|
* next ms, whichever is greater
|
|
*/
|
|
if (t->c_ispeed > 0) {
|
|
itimeout = (t->c_ispeed > 2200) ? 5 : (10000/t->c_ispeed + 1);
|
|
|
|
/* if we're using VTIME as an inter-char timeout, and it is set to
|
|
* be longer than the minimum calculated above, go for it
|
|
*/
|
|
if (t->c_cc[VMIN] && t->c_cc[VTIME] && t->c_cc[VTIME]*10 > itimeout)
|
|
itimeout = t->c_cc[VTIME]*10;
|
|
|
|
/* store it, taking care not to overflow the byte-sized register */
|
|
*(base + CD1400_RTPR) = (u_char)((itimeout <= 255) ? itimeout : 255);
|
|
}
|
|
|
|
|
|
/*
|
|
* channel control
|
|
* receiver enable
|
|
* transmitter enable (always set)
|
|
*/
|
|
opt = (1 << 4) | (1 << 3) | ((cflag & CREAD) ? (1 << 1) : 1);
|
|
if (opt != infop->channel_control) {
|
|
infop->channel_control = opt;
|
|
cd1400_channel_cmd(base, opt);
|
|
}
|
|
|
|
#ifdef Smarts
|
|
/* set special chars */
|
|
if (t->c_cc[VSTOP] != _POSIX_VDISABLE &&
|
|
(t->c_cc[VSTOP] != infop->spec_char[0])) {
|
|
*(base + CD1400_SCHR1) = infop->spec_char[0] = t->c_cc[VSTOP];
|
|
}
|
|
if (t->c_cc[VSTART] != _POSIX_VDISABLE &&
|
|
(t->c_cc[VSTART] != infop->spec_char[1])) {
|
|
*(base + CD1400_SCHR2) = infop->spec_char[0] = t->c_cc[VSTART];
|
|
}
|
|
if (t->c_cc[VINTR] != _POSIX_VDISABLE &&
|
|
(t->c_cc[VINTR] != infop->spec_char[2])) {
|
|
*(base + CD1400_SCHR3) = infop->spec_char[0] = t->c_cc[VINTR];
|
|
}
|
|
if (t->c_cc[VSUSP] != _POSIX_VDISABLE &&
|
|
(t->c_cc[VSUSP] != infop->spec_char[3])) {
|
|
*(base + CD1400_SCHR4) = infop->spec_char[0] = t->c_cc[VSUSP];
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* set channel option register 1 -
|
|
* parity mode
|
|
* stop bits
|
|
* char length
|
|
*/
|
|
opt = 0;
|
|
/* parity */
|
|
if (cflag & PARENB) {
|
|
if (cflag & PARODD)
|
|
opt |= 1 << 7;
|
|
opt |= 2 << 5; /* normal parity mode */
|
|
}
|
|
if (!(iflag & INPCK))
|
|
opt |= 1 << 4; /* ignore parity */
|
|
/* stop bits */
|
|
if (cflag & CSTOPB)
|
|
opt |= 2 << 2;
|
|
/* char length */
|
|
opt |= (cflag & CSIZE) >> 8; /* nasty, but fast */
|
|
if (opt != infop->cor[0]) {
|
|
cor_change |= 1 << 1;
|
|
*(base + CD1400_COR1) = opt;
|
|
}
|
|
|
|
/*
|
|
* set channel option register 2 -
|
|
* flow control
|
|
*/
|
|
opt = 0;
|
|
#ifdef Smarts
|
|
if (iflag & IXANY)
|
|
opt |= 1 << 7; /* auto output restart on any char after XOFF */
|
|
if (iflag & IXOFF)
|
|
opt |= 1 << 6; /* auto XOFF output flow-control */
|
|
#endif
|
|
#ifndef ALWAYS_RTS_CTS
|
|
if (cflag & CCTS_OFLOW)
|
|
#endif
|
|
opt |= 1 << 1; /* auto CTS flow-control */
|
|
|
|
if (opt != infop->cor[1]) {
|
|
cor_change |= 1 << 2;
|
|
*(base + CD1400_COR2) = opt;
|
|
}
|
|
|
|
/*
|
|
* set channel option register 3 -
|
|
* receiver FIFO interrupt threshold
|
|
* flow control
|
|
*/
|
|
opt = RxFifoThreshold; /* rx fifo threshold */
|
|
#ifdef Smarts
|
|
if (t->c_lflag & ICANON)
|
|
opt |= 1 << 6; /* detect INTR & SUSP chars */
|
|
if (iflag & IXOFF)
|
|
opt |= (1 << 5) | (1 << 4); /* transparent in-band flow control */
|
|
#endif
|
|
if (opt != infop->cor[2]) {
|
|
cor_change |= 1 << 3;
|
|
*(base + CD1400_COR3) = opt;
|
|
}
|
|
|
|
|
|
/* notify the CD1400 if COR1-3 have changed */
|
|
if (cor_change) {
|
|
cor_change |= 1 << 6; /* COR change flag */
|
|
cd1400_channel_cmd(base, cor_change);
|
|
}
|
|
|
|
/*
|
|
* set channel option register 4 -
|
|
* CR/NL processing
|
|
* break processing
|
|
* received exception processing
|
|
*/
|
|
opt = 0;
|
|
if (iflag & IGNCR)
|
|
opt |= 1 << 7;
|
|
#ifdef Smarts
|
|
/*
|
|
* we need a new ttyinput() for this, as we don't want to
|
|
* have ICRNL && INLCR being done in both layers, or to have
|
|
* synchronisation problems
|
|
*/
|
|
if (iflag & ICRNL)
|
|
opt |= 1 << 6;
|
|
if (iflag & INLCR)
|
|
opt |= 1 << 5;
|
|
#endif
|
|
if (iflag & IGNBRK)
|
|
opt |= 1 << 4;
|
|
if (!(iflag & BRKINT))
|
|
opt |= 1 << 3;
|
|
if (iflag & IGNPAR)
|
|
#ifdef LogOverruns
|
|
opt |= 0; /* broken chars cause receive exceptions */
|
|
#else
|
|
opt |= 2; /* discard broken chars */
|
|
#endif
|
|
else {
|
|
if (iflag & PARMRK)
|
|
opt |= 4; /* precede broken chars with 0xff 0x0 */
|
|
else
|
|
#ifdef LogOverruns
|
|
opt |= 0; /* broken chars cause receive exceptions */
|
|
#else
|
|
opt |= 3; /* convert framing/parity errs to nulls */
|
|
#endif
|
|
}
|
|
*(base + CD1400_COR4) = opt;
|
|
|
|
/*
|
|
* set channel option register 5 -
|
|
*/
|
|
opt = 0;
|
|
if (iflag & ISTRIP)
|
|
opt |= 1 << 7;
|
|
if (t->c_iflag & IEXTEN) {
|
|
opt |= 1 << 6; /* enable LNEXT (e.g. ctrl-v quoting) handling */
|
|
}
|
|
#ifdef Smarts
|
|
if (t->c_oflag & ONLCR)
|
|
opt |= 1 << 1;
|
|
if (t->c_oflag & OCRNL)
|
|
opt |= 1;
|
|
#endif
|
|
*(base + CD1400_COR5) = opt;
|
|
|
|
/*
|
|
* set modem change option register 1
|
|
* generate modem interrupts on which 1 -> 0 input transitions
|
|
* also controls auto-DTR output flow-control, which we don't use
|
|
*/
|
|
opt = (cflag & CLOCAL) ? 0 : 1 << 4; /* CD */
|
|
*(base + CD1400_MCOR1) = opt;
|
|
|
|
/*
|
|
* set modem change option register 2
|
|
* generate modem interrupts on specific 0 -> 1 input transitions
|
|
*/
|
|
opt = (cflag & CLOCAL) ? 0 : 1 << 4; /* CD */
|
|
*(base + CD1400_MCOR2) = opt;
|
|
|
|
splx(s);
|
|
|
|
return 0;
|
|
} /* end of cyparam */
|
|
|
|
|
|
void
|
|
cystart(struct tty *tp)
|
|
{
|
|
u_char unit = UNIT(tp->t_dev);
|
|
struct cy *infop = info[unit];
|
|
cy_addr base = infop->base_addr;
|
|
int s;
|
|
|
|
#ifdef CyDebug
|
|
infop->start_count++;
|
|
#endif
|
|
|
|
/* check the flow-control situation */
|
|
if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP))
|
|
return;
|
|
|
|
if (tp->t_outq.c_cc <= tp->t_lowat) {
|
|
if (tp->t_state&TS_ASLEEP) {
|
|
tp->t_state &= ~TS_ASLEEP;
|
|
wakeup((caddr_t)&tp->t_outq);
|
|
}
|
|
selwakeup(&tp->t_wsel);
|
|
}
|
|
|
|
#ifdef TxBuffer
|
|
service_upper_tx(unit); /* feed the monster */
|
|
#endif
|
|
|
|
s = spltty();
|
|
|
|
if (!(infop->intr_enable & (1 << 2))) {
|
|
/* select the channel */
|
|
*(base + CD1400_CAR) = unit & (u_char)3;
|
|
|
|
/* (re)enable interrupts to set things in motion */
|
|
infop->intr_enable |= (1 << 2);
|
|
*(base + CD1400_SRER) = infop->intr_enable;
|
|
|
|
infop->start_real++;
|
|
}
|
|
|
|
splx(s);
|
|
} /* end of cystart() */
|
|
|
|
|
|
int
|
|
cystop(struct tty *tp, int flag)
|
|
{
|
|
u_char unit = UNIT(tp->t_dev);
|
|
struct cy *ip = info[unit];
|
|
cy_addr base = ip->base_addr;
|
|
int s;
|
|
|
|
s = spltty();
|
|
|
|
/* select the channel */
|
|
*(base + CD1400_CAR) = unit & 3;
|
|
|
|
/* halt output by disabling transmit interrupts */
|
|
ip->intr_enable &=~ (1 << 2);
|
|
*(base + CD1400_SRER) = ip->intr_enable;
|
|
|
|
splx(s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct tty *
|
|
cydevtotty(dev_t dev)
|
|
{
|
|
u_char unit = UNIT(dev);
|
|
|
|
if (unit >= /* NCY * ? */ PORTS_PER_CYCLOM)
|
|
return NULL;
|
|
|
|
return info[unit]->tty;
|
|
}
|
|
|
|
int
|
|
cyspeed(int speed, int *prescaler_io)
|
|
{
|
|
int actual;
|
|
int error;
|
|
int divider;
|
|
int prescaler;
|
|
int prescaler_unit;
|
|
|
|
if (speed == 0)
|
|
return 0;
|
|
|
|
if (speed < 0 || speed > 150000)
|
|
return -1;
|
|
|
|
/* determine which prescaler to use */
|
|
for (prescaler_unit = 4, prescaler = 2048; prescaler_unit;
|
|
prescaler_unit--, prescaler >>= 2) {
|
|
if (CYCLOM_CLOCK/prescaler/speed > 63)
|
|
break;
|
|
}
|
|
|
|
divider = (CYCLOM_CLOCK/prescaler*2/speed + 1)/2; /* round off */
|
|
if (divider > 255)
|
|
divider = 255;
|
|
actual = CYCLOM_CLOCK/prescaler/divider;
|
|
error = ((actual-speed)*2000/speed +1)/2; /* percentage */
|
|
|
|
/* 3.0% max error tolerance */
|
|
if (error < -30 || error > 30)
|
|
return -1;
|
|
|
|
#if 0
|
|
printf("prescaler = %d (%d)\n", prescaler, prescaler_unit);
|
|
printf("divider = %d (%x)\n", divider, divider);
|
|
printf("actual = %d\n", actual);
|
|
printf("error = %d\n", error);
|
|
#endif
|
|
|
|
*prescaler_io = prescaler_unit;
|
|
return divider;
|
|
} /* end of cyspeed() */
|
|
|
|
|
|
static void
|
|
cd1400_channel_cmd(cy_addr base, u_char cmd)
|
|
{
|
|
/* XXX hsu@clinet.fi: This is always more dependent on ISA bus speed,
|
|
as the card is probed every round? Replaced delaycount with 8k.
|
|
Either delaycount has to be implemented in FreeBSD or more sensible
|
|
way of doing these should be implemented. DELAY isn't enough here.
|
|
*/
|
|
unsigned maxwait = 5 * 8 * 1024; /* approx. 5 ms */
|
|
|
|
/* wait for processing of previous command to complete */
|
|
while (*(base + CD1400_CCR) && maxwait--)
|
|
;
|
|
|
|
if (!maxwait)
|
|
log(LOG_ERR, "cy: channel command timeout (%d loops) - arrgh\n",
|
|
5 * 8 * 1024);
|
|
|
|
*(base + CD1400_CCR) = cmd;
|
|
} /* end of cd1400_channel_cmd() */
|
|
|
|
|
|
#ifdef CyDebug
|
|
/* useful in ddb */
|
|
void
|
|
cyclear(void)
|
|
{
|
|
/* clear the timeout request */
|
|
disable_intr();
|
|
timeout_scheduled = 0;
|
|
enable_intr();
|
|
}
|
|
|
|
void
|
|
cyclearintr(void)
|
|
{
|
|
/* clear interrupts */
|
|
*(cyclom_base + CYCLOM_CLEAR_INTR) = (u_char)0;
|
|
}
|
|
|
|
int
|
|
cyparam_dummy(struct tty *tp, struct termios *t)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
cyset(int unit, int active)
|
|
{
|
|
if (unit < 0 || unit >= /* NCY *? */ PORTS_PER_CYCLOM) {
|
|
printf("bad unit number %d\n", unit);
|
|
return;
|
|
}
|
|
#ifdef __FreeBSD__
|
|
cy_tty[unit].t_param = active ? cyparam : cyparam_dummy;
|
|
#else
|
|
cy_tty[unit]->t_param = active ? cyparam : cyparam_dummy;
|
|
#endif
|
|
}
|
|
|
|
|
|
/* useful in ddb */
|
|
void
|
|
cystatus(int unit)
|
|
{
|
|
struct cy *infop = info[unit];
|
|
struct tty *tp = infop->tty;
|
|
cy_addr base = infop->base_addr;
|
|
|
|
printf("info for channel %d\n", unit);
|
|
printf("------------------\n");
|
|
|
|
printf("cd1400 base address:\t0x%x\n", (int)infop->base_addr);
|
|
|
|
/* select the port */
|
|
*(base + CD1400_CAR) = (u_char)unit;
|
|
|
|
printf("saved channel_control:\t%02x\n", infop->channel_control);
|
|
printf("saved cor1:\t\t%02x\n", infop->cor[0]);
|
|
printf("service request enable reg:\t%02x (%02x cached)\n",
|
|
(u_char)*(base + CD1400_SRER), infop->intr_enable);
|
|
printf("service request register:\t%02x\n",
|
|
(u_char)*(base + CD1400_SVRR));
|
|
printf("\n");
|
|
printf("modem status:\t\t\t%02x (%02x cached)\n",
|
|
(u_char)*(base + CD1400_MSVR), infop->modem_sig);
|
|
printf("rx/tx/mdm interrupt registers:\t%02x %02x %02x\n",
|
|
(u_char)*(base + CD1400_RIR), (u_char)*(base + CD1400_TIR),
|
|
(u_char)*(base + CD1400_MIR));
|
|
printf("\n");
|
|
if (tp) {
|
|
printf("tty state:\t\t\t%04x\n", tp->t_state);
|
|
printf("upper layer queue lengths:\t%d raw, %d canon, %d output\n",
|
|
tp->t_rawq.c_cc, tp->t_canq.c_cc, tp->t_outq.c_cc);
|
|
}
|
|
else
|
|
printf("tty state:\t\t\tclosed\n");
|
|
printf("\n");
|
|
|
|
printf("calls to cystart():\t\t%d (%d useful)\n",
|
|
infop->start_count, infop->start_real);
|
|
printf("\n");
|
|
printf("total cyclom service probes:\t%d\n", cy_svrr_probes);
|
|
printf("calls to upper layer:\t\t%d\n", cy_timeouts);
|
|
printf("rx buffer chars free:\t\t%d\n", infop->rx_buf->free);
|
|
#ifdef TxBuffer
|
|
printf("tx buffer chars used:\t\t%d\n", infop->tx_buf.used);
|
|
#endif
|
|
printf("received chars:\t\t\t%d good, %d exception\n",
|
|
infop->recv_normal, infop->recv_exception);
|
|
printf("transmitted chars:\t\t%d\n", infop->xmit);
|
|
printf("modem signal deltas:\t\t%d\n", infop->mdm);
|
|
printf("\n");
|
|
} /* end of cystatus() */
|
|
#endif
|
|
#endif /* NCY > 0 */
|