+/*-
+ * Copyright (c) 1982, 1986, 1990, 1991 The Regents of the University of
+ * California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * the University of Utah and William Jolitz.
+ *
+ * %sccs.include.redist.c%
+ *
+ * @(#)com.c 7.1 (Berkeley) %G%
+ */
+
+#include "dca.h"
+#if NDCA > 0
+/*
+ * COM driver, from hp300 dca.c 98626/98644/internal serial interface
+ */
+#include "sys/param.h"
+#include "sys/systm.h"
+#include "sys/ioctl.h"
+#include "sys/tty.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 "i386/isa/isa_device.h"
+#include "i386/isa/comreg.h"
+#include "i386/isa/ic/ns16450.h"
+
+int comprobe(), comattach(), comintr(), comstart(), comparam();
+
+struct isa_driver comdriver = {
+ comprobe, comattach, "com"
+};
+
+int comsoftCAR;
+int com_active;
+int ncom = NDCA;
+int comconsole = -1;
+int comdefaultrate = TTYDEF_SPEED;
+short com_addr[NDCA];
+struct tty com_tty[NDCA];
+
+struct speedtab comspeedtab[] = {
+ 0, 0,
+ 50, COMBRD(50),
+ 75, COMBRD(75),
+ 110, COMBRD(110),
+ 134, COMBRD(134),
+ 150, COMBRD(150),
+ 200, COMBRD(200),
+ 300, COMBRD(300),
+ 600, COMBRD(600),
+ 1200, COMBRD(1200),
+ 1800, COMBRD(1800),
+ 2400, COMBRD(2400),
+ 4800, COMBRD(4800),
+ 9600, COMBRD(9600),
+ 19200, COMBRD(19200),
+ 38400, COMBRD(38400),
+ 57600, COMBRD(57600),
+ -1, -1
+};
+
+extern struct tty *constty;
+#ifdef KGDB
+extern int kgdb_dev;
+extern int kgdb_rate;
+extern int kgdb_debug_init;
+#endif
+
+#define UNIT(x) minor(x)
+
+comprobe(dev)
+struct isa_device *dev;
+{
+ if ((inb(dev->id_iobase+com_iir) & 0xf8) == 0)
+ return(1);
+ return(0);
+
+}
+
+
+int
+comattach(isdp)
+struct isa_device *isdp;
+{
+ struct tty *tp;
+ u_char unit;
+ int port = isdp->id_iobase;
+
+ if (unit == comconsole)
+ DELAY(100000);
+ unit = isdp->id_unit;
+ com_addr[unit] = port;
+ com_active |= 1 << unit;
+ /* comsoftCAR = isdp->id_flags; */
+ outb(port+com_ier, 0);
+ outb(port+com_mcr, 0 | MCR_IENABLE);
+#ifdef KGDB
+ if (kgdb_dev == makedev(1, unit)) {
+ if (comconsole == unit)
+ kgdb_dev = -1; /* can't debug over console port */
+ else {
+ (void) cominit(unit);
+ comconsole = -2; /* XXX */
+ if (kgdb_debug_init) {
+ printf("com%d: kgdb waiting...", unit);
+ /* trap into kgdb */
+ asm("trap #15;");
+ printf("connected.\n");
+ } else
+ printf("com%d: kgdb enabled\n", unit);
+ }
+ }
+#endif
+ /*
+ * Need to reset baud rate, etc. of next print so reset comconsole.
+ * Also make sure console is always "hardwired"
+ */
+ if (unit == comconsole) {
+ comconsole = -1;
+ comsoftCAR |= (1 << unit);
+ }
+comsoftCAR |= (1 << unit);
+ return (1);
+}
+
+comopen(dev, flag)
+ dev_t dev;
+{
+ register struct tty *tp;
+ register int unit;
+ int error = 0;
+
+ unit = UNIT(dev);
+ if (unit >= NDCA || (com_active & (1 << unit)) == 0)
+ return (ENXIO);
+ tp = &com_tty[unit];
+ tp->t_oproc = comstart;
+ tp->t_param = comparam;
+ tp->t_dev = dev;
+ if ((tp->t_state & TS_ISOPEN) == 0) {
+ tp->t_state |= TS_WOPEN;
+ ttychars(tp);
+ 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 = comdefaultrate;
+ comparam(tp, &tp->t_termios);
+ ttsetwater(tp);
+ } else if (tp->t_state&TS_XCLUDE && u.u_uid != 0)
+ return (EBUSY);
+ (void) spltty();
+ (void) commctl(dev, MCR_DTR | MCR_RTS, DMSET);
+ if ((comsoftCAR & (1 << unit)) || (commctl(dev, 0, DMGET) & MSR_DCD))
+ tp->t_state |= TS_CARR_ON;
+ while ((flag&O_NONBLOCK) == 0 && (tp->t_cflag&CLOCAL) == 0 &&
+ (tp->t_state & TS_CARR_ON) == 0) {
+ tp->t_state |= TS_WOPEN;
+ if (error = ttysleep(tp, (caddr_t)&tp->t_rawq, TTIPRI | PCATCH,
+ ttopen, 0))
+ break;
+ }
+ (void) spl0();
+ if (error == 0)
+ error = (*linesw[tp->t_line].l_open)(dev, tp);
+ return (error);
+}
+
+/*ARGSUSED*/
+comclose(dev, flag)
+ dev_t dev;
+{
+ register struct tty *tp;
+ register com;
+ register int unit;
+
+ unit = UNIT(dev);
+ com = com_addr[unit];
+ tp = &com_tty[unit];
+ (*linesw[tp->t_line].l_close)(tp);
+ outb(com+com_cfcr, inb(com+com_cfcr) & ~CFCR_SBREAK);
+#ifdef KGDB
+ /* do not disable interrupts if debugging */
+ if (kgdb_dev != makedev(1, unit))
+#endif
+ outb(com+com_ier, 0);
+ if (tp->t_cflag&HUPCL || tp->t_state&TS_WOPEN ||
+ (tp->t_state&TS_ISOPEN) == 0)
+ (void) commctl(dev, 0, DMSET);
+ ttyclose(tp);
+ return(0);
+}
+
+comread(dev, uio, flag)
+ dev_t dev;
+ struct uio *uio;
+{
+ register struct tty *tp = &com_tty[UNIT(dev)];
+
+ return ((*linesw[tp->t_line].l_read)(tp, uio, flag));
+}
+
+comwrite(dev, uio, flag)
+ dev_t dev;
+ struct uio *uio;
+{
+ int unit = UNIT(dev);
+ register struct tty *tp = &com_tty[unit];
+
+#ifdef notyet
+ /*
+ * (XXX) We disallow virtual consoles if the physical console is
+ * a serial port. This is in case there is a display attached that
+ * is not the console. In that situation we don't need/want the X
+ * server taking over the console.
+ */
+ if (constty && unit == comconsole)
+ constty = NULL;
+#endif
+ return ((*linesw[tp->t_line].l_write)(tp, uio, flag));
+}
+
+comintr(unit)
+ register int unit;
+{
+ register com;
+ register u_char code;
+ register struct tty *tp;
+
+ com = com_addr[unit];
+ while (1) {
+ code = inb(com+com_iir);
+ switch (code) {
+ case IIR_NOPEND:
+ return (1);
+ case IIR_RXRDY:
+ /* do time-critical read in-line */
+ tp = &com_tty[unit];
+ code = inb(com+com_data);
+ if ((tp->t_state & TS_ISOPEN) == 0) {
+#ifdef KGDB
+ if (kgdb_dev == makedev(1, unit) &&
+ code == '!') {
+ printf("kgdb trap from com%d\n", unit);
+ /* trap into kgdb */
+ asm("trap #15;");
+ }
+#endif
+ } else
+ (*linesw[tp->t_line].l_rint)(code, tp);
+ break;
+ case IIR_TXRDY:
+ tp = &com_tty[unit];
+ tp->t_state &=~ (TS_BUSY|TS_FLUSH);
+ if (tp->t_line)
+ (*linesw[tp->t_line].l_start)(tp);
+ else
+ comstart(tp);
+ break;
+ case IIR_RLS:
+ comeint(unit, com);
+ break;
+ default:
+ if (code & IIR_NOPEND)
+ return (1);
+ log(LOG_WARNING, "com%d: weird interrupt: 0x%x\n",
+ unit, code);
+ /* fall through */
+ case IIR_MLSC:
+ commint(unit, com);
+ break;
+ }
+ }
+}
+
+comeint(unit, com)
+ register int unit;
+ register com;
+{
+ register struct tty *tp;
+ register int stat, c;
+
+ tp = &com_tty[unit];
+ stat = inb(com+com_lsr);
+ c = inb(com+com_data);
+ if ((tp->t_state & TS_ISOPEN) == 0) {
+#ifdef KGDB
+ /* we don't care about parity errors */
+ if (((stat & (LSR_BI|LSR_FE|LSR_PE)) == LSR_PE) &&
+ kgdb_dev == makedev(1, unit) && c == '!') {
+ printf("kgdb trap from com%d\n", unit);
+ /* trap into kgdb */
+ asm("trap #15;");
+ }
+#endif
+ return;
+ }
+ if (stat & (LSR_BI | LSR_FE))
+ c |= TTY_FE;
+ else if (stat & LSR_PE)
+ c |= TTY_PE;
+ else if (stat & LSR_OE)
+ log(LOG_WARNING, "com%d: silo overflow\n", unit);
+ (*linesw[tp->t_line].l_rint)(c, tp);
+}
+
+commint(unit, com)
+ register int unit;
+ register com;
+{
+ register struct tty *tp;
+ register int stat;
+
+ tp = &com_tty[unit];
+ stat = inb(com+com_msr);
+ if ((stat & MSR_DDCD) && (comsoftCAR & (1 << unit)) == 0) {
+ if (stat & MSR_DCD)
+ (void)(*linesw[tp->t_line].l_modem)(tp, 1);
+ else if ((*linesw[tp->t_line].l_modem)(tp, 0) == 0)
+ outb(com+com_mcr,
+ inb(com+com_mcr) & ~(MCR_DTR | MCR_RTS) | MCR_IENABLE);
+ } else if ((stat & MSR_DCTS) && (tp->t_state & TS_ISOPEN) &&
+ (tp->t_flags & CRTSCTS)) {
+ /* the line is up and we want to do rts/cts flow control */
+ if (stat & MSR_CTS) {
+ tp->t_state &=~ TS_TTSTOP;
+ ttstart(tp);
+ } else
+ tp->t_state |= TS_TTSTOP;
+ }
+}
+
+comioctl(dev, cmd, data, flag)
+ dev_t dev;
+ caddr_t data;
+{
+ register struct tty *tp;
+ register int unit = UNIT(dev);
+ register com;
+ register int error;
+
+ tp = &com_tty[unit];
+ error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag);
+ if (error >= 0)
+ return (error);
+ error = ttioctl(tp, cmd, data, flag);
+ if (error >= 0)
+ return (error);
+
+ com = com_addr[unit];
+ switch (cmd) {
+
+ case TIOCSBRK:
+ outb(com+com_cfcr, inb(com+com_cfcr) | CFCR_SBREAK);
+ break;
+
+ case TIOCCBRK:
+ outb(com+com_cfcr, inb(com+com_cfcr) & ~CFCR_SBREAK);
+ break;
+
+ case TIOCSDTR:
+ (void) commctl(dev, MCR_DTR | MCR_RTS, DMBIS);
+ break;
+
+ case TIOCCDTR:
+ (void) commctl(dev, MCR_DTR | MCR_RTS, DMBIC);
+ break;
+
+ case TIOCMSET:
+ (void) commctl(dev, *(int *)data, DMSET);
+ break;
+
+ case TIOCMBIS:
+ (void) commctl(dev, *(int *)data, DMBIS);
+ break;
+
+ case TIOCMBIC:
+ (void) commctl(dev, *(int *)data, DMBIC);
+ break;
+
+ case TIOCMGET:
+ *(int *)data = commctl(dev, 0, DMGET);
+ break;
+
+ default:
+ return (ENOTTY);
+ }
+ return (0);
+}
+
+comparam(tp, t)
+ register struct tty *tp;
+ register struct termios *t;
+{
+ register com;
+ register int cfcr, cflag = t->c_cflag;
+ int unit = UNIT(tp->t_dev);
+ int ospeed = ttspeedtab(t->c_ospeed, comspeedtab);
+
+ /* check requested parameters */
+ if (ospeed < 0 || (t->c_ispeed && t->c_ispeed != t->c_ospeed))
+ return(EINVAL);
+ /* and copy to tty */
+ tp->t_ispeed = t->c_ispeed;
+ tp->t_ospeed = t->c_ospeed;
+ tp->t_cflag = cflag;
+
+ com = com_addr[unit];
+ outb(com+com_ier, IER_ERXRDY | IER_ETXRDY | IER_ERLS /*| IER_EMSC*/);
+ if (ospeed == 0) {
+ (void) commctl(unit, 0, DMSET); /* hang up line */
+ return(0);
+ }
+ outb(com+com_cfcr, inb(com+com_cfcr) | CFCR_DLAB);
+ outb(com+com_data, ospeed & 0xFF);
+ outb(com+com_ier, ospeed >> 8);
+ switch (cflag&CSIZE) {
+ case CS5:
+ cfcr = CFCR_5BITS; break;
+ case CS6:
+ cfcr = CFCR_6BITS; break;
+ case CS7:
+ cfcr = CFCR_7BITS; break;
+ case CS8:
+ cfcr = CFCR_8BITS; break;
+ }
+ if (cflag&PARENB) {
+ cfcr |= CFCR_PENAB;
+ if ((cflag&PARODD) == 0)
+ cfcr |= CFCR_PEVEN;
+ }
+ if (cflag&CSTOPB)
+ cfcr |= CFCR_STOPB;
+ outb(com+com_cfcr, cfcr);
+ return(0);
+}
+
+comstart(tp)
+ register struct tty *tp;
+{
+ register com;
+ int s, unit, c;
+
+ unit = UNIT(tp->t_dev);
+ com = com_addr[unit];
+ s = spltty();
+ if (tp->t_state & (TS_TIMEOUT|TS_TTSTOP))
+ goto out;
+ 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);
+ }
+ if (tp->t_wsel) {
+ selwakeup(tp->t_wsel, tp->t_state & TS_WCOLL);
+ tp->t_wsel = 0;
+ tp->t_state &= ~TS_WCOLL;
+ }
+ }
+ if (tp->t_outq.c_cc == 0)
+ goto out;
+ if (inb(com+com_lsr) & LSR_TXRDY) {
+ c = getc(&tp->t_outq);
+ tp->t_state |= TS_BUSY;
+ outb(com+com_data, c);
+ }
+out:
+ splx(s);
+}
+
+/*
+ * Stop output on a line.
+ */
+/*ARGSUSED*/
+comstop(tp, flag)
+ register struct tty *tp;
+{
+ register int s;
+
+ s = spltty();
+ if (tp->t_state & TS_BUSY) {
+ if ((tp->t_state&TS_TTSTOP)==0)
+ tp->t_state |= TS_FLUSH;
+ }
+ splx(s);
+}
+
+commctl(dev, bits, how)
+ dev_t dev;
+ int bits, how;
+{
+ register com;
+ register int unit;
+ int s;
+
+ unit = UNIT(dev);
+ com = com_addr[unit];
+ s = spltty();
+ switch (how) {
+
+ case DMSET:
+ outb(com+com_mcr, bits | MCR_IENABLE);
+ break;
+
+ case DMBIS:
+ outb(com+com_mcr, inb(com+com_mcr) | bits | MCR_IENABLE);
+ break;
+
+ case DMBIC:
+ outb(com+com_mcr, inb(com+com_mcr) & ~bits | MCR_IENABLE);
+ break;
+
+ case DMGET:
+ bits = inb(com+com_msr);
+ break;
+ }
+ (void) splx(s);
+ return(bits);
+}
+#endif