/* * cyclades cyclom-y serial driver * Andrew Herbert , 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 1994/03/08 12:21:16 mycroft 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 (!) */ #undef 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 */ #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? */ #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); void delay(int delay); extern unsigned int delaycount; /* calibrated 1 ms cpu-spin delay */ 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]; struct tty *cy_tty[NCY*PORTS_PER_CYCLOM]; 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 >= PORTS_PER_CYCLOM) return (ENXIO); infop = info[unit]; base = infop->base_addr; if (!cy_tty[unit]) infop->tty = cy_tty[unit] = ttymalloc(); 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(caddr_t 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 */ static void cytimeout(caddr_t 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, p); 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 if (cflag & CCTS_OFLOW) 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; } int cyselect(dev_t dev, int rw, struct proc *p) { struct tty *tp = info[UNIT(dev)]->tty; int s = spltty(); int nread; switch (rw) { case FREAD: nread = ttnread(tp); if (nread > 0 || ((tp->t_cflag&CLOCAL) == 0 && (tp->t_state&TS_CARR_ON) == 0)) goto win; selrecord(p, &tp->t_rsel); break; case FWRITE: if (tp->t_outq.c_cc <= tp->t_lowat) goto win; selrecord(p, &tp->t_wsel); break; } splx(s); return (0); win: splx(s); return (1); } /* end of cyselect() */ 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) { unsigned maxwait = delaycount * 5; /* 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", delaycount * 5); *(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 > PORTS_PER_CYCLOM) { printf("bad unit number %d\n", unit); return; } cy_tty[unit]->t_param = active ? cyparam : cyparam_dummy; } /* 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 */