X-Git-Url: https://git.subgeniuskitty.com/unix-history/.git/blobdiff_plain/1c7454b03a7c62aa38831452fcc466f1957fc70f..8a86c29ccc56a8b1f878d3b17d0759a8fd868c97:/sys/i386/isa/sio.c diff --git a/sys/i386/isa/sio.c b/sys/i386/isa/sio.c index 603c9630ca..4cbe3f6a58 100644 --- a/sys/i386/isa/sio.c +++ b/sys/i386/isa/sio.c @@ -31,7 +31,7 @@ * SUCH DAMAGE. * * from: @(#)com.c 7.5 (Berkeley) 5/16/91 - * $Id: sio.c,v 1.41 1994/04/01 16:47:01 ache Exp $ + * $Id: sio.c,v 1.45 1994/04/23 02:11:43 davidg Exp $ */ #include "sio.h" @@ -49,9 +49,11 @@ #include "proc.h" #include "user.h" #include "conf.h" +#include "dkstat.h" #include "file.h" #include "uio.h" #include "kernel.h" +#include "malloc.h" #include "syslog.h" #include "i386/isa/isa.h" @@ -59,7 +61,6 @@ #include "i386/isa/comreg.h" #include "i386/isa/ic/ns16550.h" -#define FAKE_DCD(unit) ((unit) == comconsole) #define LOTS_OF_EVENTS 64 /* helps separate urgent events from input */ #define RB_I_HIGH_WATER (RBSZ - 2 * RS_IBUFSIZE) #define RB_I_LOW_WATER ((RBSZ - 2 * RS_IBUFSIZE) * 7 / 8) @@ -67,35 +68,24 @@ #define TTY_BI TTY_FE /* XXX */ #define TTY_OE TTY_PE /* XXX */ -#ifndef COM_BIDIR -#define UNIT(x) (minor(x)) /* XXX */ -#else /* COM_BIDIR */ -#define COM_UNITMASK 0x7f -#define COM_CALLOUTMASK 0x80 /* for both minor and dev */ -#define UNIT(x) (minor(x) & COM_UNITMASK) -#define CALLOUT(x) (minor(x) & COM_CALLOUTMASK) -#endif /* COM_BIDIR */ +#define CALLOUT_MASK 0x80 +#define CONTROL_MASK 0x60 +#define CONTROL_INIT_STATE 0x20 +#define CONTROL_LOCK_STATE 0x40 +#define DEV_TO_UNIT(dev) (MINOR_TO_UNIT(minor(dev))) +#define MINOR_MAGIC_MASK (CALLOUT_MASK | CONTROL_MASK) +#define MINOR_TO_UNIT(mynor) ((mynor) & ~MINOR_MAGIC_MASK) #ifdef COM_MULTIPORT /* checks in flags for multiport and which is multiport "master chip" * for a given card */ -#define COM_ISMULTIPORT(dev) ((dev)->id_flags & 0x01) -#define COM_MPMASTER(dev) (((dev)->id_flags >> 8) & 0x0ff) +#define COM_ISMULTIPORT(dev) ((dev)->id_flags & 0x01) +#define COM_MPMASTER(dev) (((dev)->id_flags >> 8) & 0x0ff) +#define COM_NOMASTER(dev) ((dev)->id_flags & 0x04) #endif /* COM_MULTIPORT */ -#define COM_NOFIFO(dev) ((dev)->id_flags & 0x02) -#ifndef FIFO_TRIGGER -/* - * This driver is fast enough to work with any value and for high values - * to be only slightly more efficient. Low values may be better because - * they give lower latency. - * TODO: always use low values for low speeds. Mouse movements are jerky - * if more than one packet arrives at once. The low speeds used for - * serial mice help avoid this, but not if (large) fifos are enabled. - */ -#define FIFO_TRIGGER FIFO_TRIGGER_14 -#endif +#define COM_NOFIFO(dev) ((dev)->id_flags & 0x02) #define com_scr 7 /* scratch register for 16450-16550 (R/W) */ @@ -123,17 +113,19 @@ * CS_CTS_OFLOW = CCTS_OFLOW (maintained by comparam()) * CS_RTS_IFLOW = CRTS_IFLOW (maintained by comparam()) * TS_FLUSH is not used. - * Bug: I think TIOCSETA doesn't clear TS_TTSTOP when it clears IXON. + * XXX I think TIOCSETA doesn't clear TS_TTSTOP when it clears IXON. + * XXX CS_*FLOW should be CF_*FLOW in com->flags (control flags not state). */ #define CS_BUSY 0x80 /* output in progress */ #define CS_TTGO 0x40 /* output not stopped by XOFF */ #define CS_ODEVREADY 0x20 /* external device h/w ready (CTS) */ #define CS_CHECKMSR 1 /* check of MSR scheduled */ #define CS_CTS_OFLOW 2 /* use CTS output flow control */ +#define CS_DTR_OFF 0x10 /* DTR held off */ #define CS_ODONE 4 /* output completed */ #define CS_RTS_IFLOW 8 /* use RTS input flow control */ -static char *error_desc[] = { +static char const * const error_desc[] = { #define CE_OVERRUN 0 "silo overflow", #define CE_INTERRUPT_BUF_OVERFLOW 1 @@ -152,20 +144,19 @@ typedef u_char bool_t; /* boolean */ /* com device structure */ struct com_s { u_char state; /* miscellaneous flag bits */ + bool_t active_out; /* nonzero if the callout device is open */ u_char cfcr_image; /* copy of value written to CFCR */ + u_char ftl; /* current rx fifo trigger level */ + u_char ftl_init; /* ftl_max for next open() */ + u_char ftl_max; /* maximum ftl for curent open() */ bool_t hasfifo; /* nonzero for 16550 UARTs */ u_char mcr_image; /* copy of value written to MCR */ -#ifdef COM_BIDIR - bool_t bidir; /* is this unit bidirectional? */ - bool_t active; /* is the port active _at all_? */ - bool_t active_in; /* is the incoming port in use? */ - bool_t active_out; /* is the outgoing port in use? */ -#endif /* COM_BIDIR */ #ifdef COM_MULTIPORT bool_t multiport; /* is this unit part of a multiport device? */ #endif /* COM_MULTIPORT */ - int dtr_wait; /* time to hold DTR down on close (* 1/HZ) */ + int dtr_wait; /* time to hold DTR down on close (* 1/hz) */ u_int tx_fifo_size; + u_int wopeners; /* # processes waiting for DCD in open() */ /* * The high level of the driver never reads status registers directly @@ -176,6 +167,7 @@ struct com_s { u_char last_modem_status; /* last MSR read by intr handler */ u_char prev_modem_status; /* last MSR handled by high level */ + u_char hotchar; /* ldisc-specific char to be handled ASAP */ u_char *ibuf; /* start of input buffer */ u_char *ibufend; /* end of input buffer */ u_char *ihighwater; /* threshold in input buffer */ @@ -194,6 +186,19 @@ struct com_s { struct tty *tp; /* cross reference */ + /* Initial state. */ + struct termios it_in; /* should be in struct tty */ + struct termios it_out; + + /* Lock state. */ + struct termios lt_in; /* should be in struct tty */ + struct termios lt_out; + +#ifdef TIOCTIMESTAMP + bool_t do_timestamp; + struct timeval timestamp; +#endif + u_long bytes_in; /* statistics */ u_long bytes_out; u_int delta_error_counts[CE_NTYPES]; @@ -206,55 +211,59 @@ struct com_s { #define CE_INPUT_OFFSET RS_IBUFSIZE u_char ibuf1[2 * RS_IBUFSIZE]; u_char ibuf2[2 * RS_IBUFSIZE]; - int do_timestamp; - struct timeval timestamp; }; /* - * These functions in the com module ought to be declared (with a prototype) - * in a com-driver system header. The void ones may need to be int to match - * ancient devswitch declarations, but they don't actually return anything. + * The public functions in the com module ought to be declared in a com-driver + * system header. */ #define Dev_t int /* promoted dev_t */ -struct consdev; +/* Interrupt handling entry points. */ +void siointr __P((int unit)); +void siopoll __P((void)); + +/* Device switch entry points. */ +int sioopen __P((Dev_t dev, int oflags, int devtype, + struct proc *p)); int sioclose __P((Dev_t dev, int fflag, int devtype, struct proc *p)); -void siointr __P((int unit)); +int sioread __P((Dev_t dev, struct uio *uio, int ioflag)); +int siowrite __P((Dev_t dev, struct uio *uio, int ioflag)); int sioioctl __P((Dev_t dev, int cmd, caddr_t data, int fflag, struct proc *p)); +void siostop __P((struct tty *tp, int rw)); +#define sioreset noreset +int sioselect __P((Dev_t dev, int rw, struct proc *p)); +#define siommap nommap +#define siostrategy nostrategy + +/* Console device entry points. */ int siocngetc __P((Dev_t dev)); +struct consdev; void siocninit __P((struct consdev *cp)); void siocnprobe __P((struct consdev *cp)); void siocnputc __P((Dev_t dev, int c)); -int sioopen __P((Dev_t dev, int oflags, int devtype, - struct proc *p)); -void siopoll __P((void)); -int sioread __P((Dev_t dev, struct uio *uio, int ioflag)); -int sioselect __P((Dev_t dev, int rw, struct proc *p)); -void siostop __P((struct tty *tp, int rw)); -int siowrite __P((Dev_t dev, struct uio *uio, int ioflag)); -void softsio1 __P((void)); static int sioattach __P((struct isa_device *dev)); +static void siodtrwakeup __P((caddr_t chan, int ticks)); static void comflush __P((struct com_s *com)); static void comhardclose __P((struct com_s *com)); -static void cominit __P((int unit, int rate)); -static void comintr1 __P((struct com_s *com)); +static void siointr1 __P((struct com_s *com)); static void commctl __P((struct com_s *com, int bits, int how)); static int comparam __P((struct tty *tp, struct termios *t)); static int sioprobe __P((struct isa_device *dev)); -static void comstart __P((struct tty *tp)); -static void comwakeup __P((caddr_t chan, int ticks)); +static void comstart __P((struct tty *tp)); +static void comwakeup __P((caddr_t chan, int ticks)); static int tiocm_xxx2mcr __P((int tiocm_xxx)); /* table and macro for fast conversion from a unit number to its com struct */ static struct com_s *p_com_addr[NSIO]; #define com_addr(unit) (p_com_addr[unit]) -static struct com_s com_structs[NSIO]; - -static struct timeval intr_timestamp; +#ifdef TIOCTIMESTAMP +static struct timeval intr_timestamp; +#endif struct isa_driver siodriver = { sioprobe, sioattach, "sio" @@ -265,15 +274,11 @@ static int comconsole = COMCONSOLE; #else static int comconsole = -1; #endif -static bool_t comconsinit; static speed_t comdefaultrate = TTYDEF_SPEED; static u_int com_events; /* input chars + weighted output completions */ static int commajor; struct tty *sio_tty[NSIO]; -extern struct tty *constty; -extern u_int ipending; /* XXX */ -extern int tk_nin; /* XXX */ -extern int tk_rawcc; /* XXX */ +extern struct tty *constty; /* XXX */ #ifdef KGDB #include "machine/remote-sl.h" @@ -315,6 +320,7 @@ sioprobe(dev) static bool_t already_init; Port_t *com_ptr; Port_t iobase; + u_char mcr_image; int result; if (!already_init) { @@ -322,6 +328,7 @@ sioprobe(dev) * Turn off MCR_IENABLE for all likely serial ports. An unused * port with its MCR_IENABLE gate open will inhibit interrupts * from any used port that shares the interrupt vector. + * XXX the gate enable is elsewhere for some multiports. */ for (com_ptr = likely_com_ports; com_ptr < &likely_com_ports[sizeof likely_com_ports @@ -330,6 +337,27 @@ sioprobe(dev) outb(*com_ptr + com_mcr, 0); already_init = TRUE; } + + /* + * If the port is on a multiport card and has a master port, + * initialize the common interrupt control register in the + * master and prepare to leave MCR_IENABLE clear. Otherwise, + * prepare to set MCR_IENABLE. + */ + mcr_image = MCR_IENABLE; +#ifdef COM_MULTIPORT + if (COM_ISMULTIPORT(dev) && !COM_NOMASTER(dev)) { + struct isa_device *mdev; + + mdev = find_isadev(isa_devtab_tty, &siodriver, + COM_MPMASTER(dev)); + if (mdev != NULL) { + outb(mdev->id_iobase + com_scr, 0x80); + mcr_image = 0; + } + } +#endif /* COM_MULTIPORT */ + iobase = dev->id_iobase; result = IO_COMSIZE; @@ -342,50 +370,93 @@ sioprobe(dev) disable_intr(); /* - * Initialize the speed so that any junk in the THR or output fifo will - * be transmitted in a known time. (There may be lots of junk after a - * soft reboot, and output interrupts don't work right after a master - * reset, at least for 16550s. (The speed is undefined after MR, but - * MR empties the THR and the TSR so it's not clear why this matters)). - * Enable output interrupts (only) and check the following: + * Initialize the speed and the word size and wait long enough to + * drain the maximum of 16 bytes of junk in device output queues. + * The speed is undefined after a master reset and must be set + * before relying on anything related to output. There may be + * junk after a (very fast) soft reboot and (apparently) after + * master reset. + * XXX what about the UART bug avoided by waiting in comparam()? + * We don't want to to wait long enough to drain at 2 bps. + */ + outb(iobase + com_cfcr, CFCR_DLAB); + outb(iobase + com_dlbl, COMBRD(9600) & 0xff); + outb(iobase + com_dlbh, (u_int) COMBRD(9600) >> 8); + outb(iobase + com_cfcr, CFCR_8BITS); + DELAY((16 + 1) * 9600 / 10); + + /* + * Enable the interrupt gate and disable device interupts. This + * should leave the device driving the interrupt line low and + * guarantee an edge trigger if an interrupt can be generated. + * Attempt to set loopback mode so that we can send a null byte + * without annoying any external device. + */ + outb(iobase + com_mcr, mcr_image | MCR_LOOPBACK); + outb(iobase + com_ier, 0); + + /* + * Attempt to generate an output interrupt. On 8250's, setting + * IER_ETXRDY generates an interrupt independent of the current + * setting and independent of whether the THR is empty. On 16450's, + * setting IER_ETXRDY generates an interrupt independent of the + * current setting. On 16550A's, setting IER_ETXRDY only + * generates an interrupt when IER_ETXRDY is not already set. + */ + outb(iobase + com_ier, IER_ETXRDY); + + /* + * On some 16x50 incompatibles, setting IER_ETXRDY doesn't generate + * an interrupt. They'd better generate one for actually doing + * output. Loopback may be broken on the same incompatibles but + * it's unlikely to do more than allow the null byte out. + */ + outb(iobase + com_data, 0); + DELAY((1 + 1) * 9600 / 10); + + /* + * Turn off loopback mode so that the interrupt gate works again + * (MCR_IENABLE was hidden). This should leave the device driving + * an interrupt line high. It doesn't matter if the interrupt + * line oscillates while we are not looking at it, since interrupts + * are disabled. + * XXX interrupts are NOT disabled for the multiport shared + * interrupt case! isa_configure() enables them after the probe + * of the first device on a shared interrupt succeeds. + */ + outb(iobase + com_mcr, mcr_image); + + /* + * Check that * o the CFCR, IER and MCR in UART hold the values written to them * (the values happen to be all distinct - this is good for * avoiding false positive tests from bus echoes). * o an output interrupt is generated and its vector is correct. * o the interrupt goes away when the IIR in the UART is read. */ - outb(iobase + com_cfcr, CFCR_DLAB); - outb(iobase + com_dlbl, COMBRD(9600) & 0xff); - outb(iobase + com_dlbh, (u_int) COMBRD(9600) >> 8); - outb(iobase + com_cfcr, CFCR_8BITS); /* ensure IER is addressed */ - outb(iobase + com_mcr, MCR_IENABLE); /* open gate early */ - outb(iobase + com_ier, 0); /* ensure edge on next intr */ - outb(iobase + com_ier, IER_ETXRDY); /* generate interrupt */ - DELAY((16 + 1) * 9600 / 10); /* enough to drain 16 bytes */ if ( inb(iobase + com_cfcr) != CFCR_8BITS || inb(iobase + com_ier) != IER_ETXRDY - || inb(iobase + com_mcr) != MCR_IENABLE -#ifndef COM_MULTIPORT /* XXX - need to do more to enable interrupts */ + || inb(iobase + com_mcr) != mcr_image || !isa_irq_pending(dev) -#endif || (inb(iobase + com_iir) & IIR_IMASK) != IIR_TXRDY || isa_irq_pending(dev) - || !(inb(iobase + com_iir) & IIR_NOPEND)) + || (inb(iobase + com_iir) & IIR_IMASK) != IIR_NOPEND) result = 0; /* * Turn off all device interrupts and check that they go off properly. - * Leave MCR_IENABLE set. It gates the OUT2 output of the UART to + * Leave MCR_IENABLE alone. For ports without a master port, it gates + * the OUT2 output of the UART to * the ICU input. Closing the gate would give a floating ICU input * (unless there is another device driving at) and spurious interrupts. * (On the system that this was first tested on, the input floats high * and gives a (masked) interrupt as soon as the gate is closed.) */ outb(iobase + com_ier, 0); - outb(iobase + com_mcr, MCR_IENABLE); /* dummy to avoid bus echo */ + outb(iobase + com_cfcr, CFCR_8BITS); /* dummy to avoid bus echo */ if ( inb(iobase + com_ier) != 0 || isa_irq_pending(dev) - || !(inb(iobase + com_iir) & IIR_NOPEND)) + || (inb(iobase + com_iir) & IIR_IMASK) != IIR_NOPEND) result = 0; if (result == 0) outb(iobase + com_mcr, 0); @@ -406,9 +477,9 @@ sioattach(isdp) iobase = isdp->id_iobase; unit = isdp->id_unit; - if (unit == comconsole) - DELAY(1000); /* XXX */ - s = spltty(); + com = malloc(sizeof *com, M_TTYS, M_NOWAIT); + if (com == NULL) + return (0); /* * sioprobe() has initialized the device registers as follows: @@ -417,14 +488,12 @@ sioattach(isdp) * data port is not hidden when we enable interrupts. * o ier = 0. * Interrupts are only enabled when the line is open. - * o mcr = MCR_IENABLE. + * o mcr = MCR_IENABLE, or 0 if the port has a master port. * Keeping MCR_DTR and MCR_RTS off might stop the external * device from sending before we are ready. */ - - com = &com_structs[unit]; + bzero(com, sizeof *com); com->cfcr_image = CFCR_8BITS; - com->mcr_image = MCR_IENABLE; com->dtr_wait = 3 * hz; com->tx_fifo_size = 1; com->iptr = com->ibuf = com->ibuf1; @@ -434,9 +503,30 @@ sioattach(isdp) com->data_port = iobase + com_data; com->int_id_port = iobase + com_iir; com->modem_ctl_port = iobase + com_mcr; + com->mcr_image = inb(com->modem_ctl_port); com->line_status_port = iobase + com_lsr; com->modem_status_port = iobase + com_msr; - com->tp = sio_tty[unit]; + + /* + * We don't use all the flags from since they + * are only relevant for logins. It's important to have echo off + * initially so that the line doesn't start blathering before the + * echo flag can be turned off. + */ + com->it_in.c_iflag = 0; + com->it_in.c_oflag = 0; + com->it_in.c_cflag = TTYDEF_CFLAG; + com->it_in.c_lflag = 0; + if (unit == comconsole) { + com->it_in.c_iflag = TTYDEF_IFLAG; + com->it_in.c_oflag = TTYDEF_OFLAG; + com->it_in.c_cflag = TTYDEF_CFLAG | CLOCAL; + com->it_in.c_lflag = TTYDEF_LFLAG; + com->lt_out.c_cflag = com->lt_in.c_cflag = CLOCAL; + } + termioschars(&com->it_in); + com->it_in.c_ispeed = com->it_in.c_ospeed = comdefaultrate; + com->it_out = com->it_in; /* attempt to determine UART type */ printf("sio%d: type", unit); @@ -473,10 +563,11 @@ sioattach(isdp) break; case FIFO_TRIGGER_14: printf(" 16550A"); - if (COM_NOFIFO(isdp)) { - printf(" fifo software disabled"); - } else { + if (COM_NOFIFO(isdp)) + printf(" fifo disabled"); + else { com->hasfifo = TRUE; + com->ftl_init = FIFO_TRIGGER_14; com->tx_fifo_size = 16; } break; @@ -486,22 +577,20 @@ determined_type: ; #ifdef COM_MULTIPORT if (COM_ISMULTIPORT(isdp)) { - struct isa_device *masterdev; + com->multiport = TRUE; + printf(" (multiport"); + if (!COM_NOMASTER(isdp)) { + struct isa_device *mdev; - com->multiport = TRUE; - printf(" (multiport)"); - - /* set the master's common-interrupt-enable reg., - * as appropriate. YYY See your manual - */ - /* enable only common interrupt for port */ - outb(com->modem_ctl_port, com->mcr_image = 0); - - masterdev = find_isadev(isa_devtab_tty, &siodriver, + mdev = find_isadev(isa_devtab_tty, &siodriver, COM_MPMASTER(isdp)); - outb(masterdev->id_iobase + com_scr, 0x80); - } else - com->multiport = FALSE; + if (mdev == NULL) + printf(" master not found"); + else if (COM_MPMASTER(isdp) == unit) + printf(" master"); + } + printf(")"); + } #endif /* COM_MULTIPORT */ printf("\n"); @@ -510,7 +599,22 @@ determined_type: ; if (comconsole == unit) kgdb_dev = -1; /* can't debug over console port */ else { - cominit(unit, kgdb_rate); + int divisor; + + /* + * XXX now unfinished and broken. Need to do + * something more like a full open(). There's no + * suitable interrupt handler so don't enable device + * interrupts. Watch out for null tp's. + */ + outb(iobase + com_cfcr, CFCR_DLAB); + divisor = ttspeedtab(kgdb_rate, comspeedtab); + outb(iobase + com_dlbl, divisor & 0xFF); + outb(iobase + com_dlbh, (u_int) divisor >> 8); + outb(iobase + com_cfcr, CFCR_8BITS); + outb(com->modem_status_port, + com->mcr_image |= MCR_DTR | MCR_RTS); + if (kgdb_debug_init) { /* * Print prefix of device name, @@ -524,16 +628,11 @@ determined_type: ; } #endif - /* - * Need to reset baud rate, etc. of next print so reset comconsinit. - */ - if (unit == comconsole) - comconsinit = FALSE; - + s = spltty(); com_addr(unit) = com; splx(s); if (!comwakeup_started) { - comwakeup((caddr_t) NULL, 0); + comwakeup((caddr_t)NULL, 0); comwakeup_started = TRUE; } return (1); @@ -547,222 +646,127 @@ sioopen(dev, flag, mode, p) int mode; struct proc *p; { -#ifdef COM_BIDIR - bool_t callout; -#endif /* COM_BIDIR */ - bool_t got_status = FALSE; struct com_s *com; - int error = 0; + int error; Port_t iobase; + int mynor; int s; struct tty *tp; int unit; - unit = UNIT(dev); + mynor = minor(dev); + unit = MINOR_TO_UNIT(mynor); if ((u_int) unit >= NSIO || (com = com_addr(unit)) == NULL) return (ENXIO); -#ifdef COM_BIDIR - /* if it's a callout device, and bidir not possible on that dev, die */ - callout = CALLOUT(dev); - if (callout && !(com->bidir)) - return (ENXIO); -#endif /* COM_BIDIR */ - - - sio_tty[unit] = ttymalloc(sio_tty[unit]); - tp = com->tp = sio_tty[unit]; + if (mynor & CONTROL_MASK) + return (0); + tp = com->tp = sio_tty[unit] = ttymalloc(sio_tty[unit]); s = spltty(); - -#ifdef COM_BIDIR - -bidir_open_top: - got_status = FALSE; - /* if it's bidirectional, we've gotta deal with it... */ - if (com->bidir) { - if (callout) { - if (com->active_in) { - /* it's busy. die */ - splx(s); - return (EBUSY); - } else { - /* it's ours. lock it down, and set it up */ - com->active_out = TRUE; + /* + * We jump to this label after all non-interrupted sleeps to pick + * up any changes of the device state. + */ +open_top: + while (com->state & CS_DTR_OFF) { + error = tsleep((caddr_t)&com->dtr_wait, TTIPRI | PCATCH, + "siodtr", 0); + if (error != 0) + goto out; + } + if (tp->t_state & TS_ISOPEN) { + /* + * The device is open, so everything has been initialized. + * Handle conflicts. + */ + if (mynor & CALLOUT_MASK) { + if (!com->active_out) { + error = EBUSY; + goto out; } } else { if (com->active_out) { - /* it's busy, outgoing. wait, if possible */ if (flag & O_NONBLOCK) { - /* can't wait; bail */ - splx(s); - return (EBUSY); - } else { - /* wait for it... */ + error = EBUSY; + goto out; + } error = tsleep((caddr_t)&com->active_out, - TTIPRI|PCATCH, - "siooth", - 0); - /* if there was an error, take off. */ - if (error != 0) { - splx(s); - return (error); + TTIPRI | PCATCH, "siobi", 0); + if (error != 0) + goto out; + goto open_top; } - /* else take it from the top */ - goto bidir_open_top; } + if (tp->t_state & TS_XCLUDE && p->p_ucred->cr_uid != 0) { + error = EBUSY; + goto out; } - disable_intr(); - com->last_modem_status = - com->prev_modem_status = inb(com->modem_status_port); - enable_intr(); - got_status = TRUE; - if (com->prev_modem_status & MSR_DCD - || FAKE_DCD(unit)) { - /* there's a carrier on the line; we win */ - com->active_in = TRUE; - } else { - /* there is no carrier on the line */ - if (flag & O_NONBLOCK) { - /* can't wait; let it open */ - com->active_in = TRUE; } else { - /* put DTR & RTS up */ - /* XXX - bring up RTS earlier? */ - commctl(com, MCR_DTR | MCR_RTS, DMSET); - outb(com->iobase + com_ier, IER_EMSC); - - /* wait for it... */ - error = tsleep((caddr_t)&com->active_in, - TTIPRI|PCATCH, - "siodcd", - 0); - - /* if not active, turn intrs and DTR off */ - if (!com->active) { - outb(com->iobase + com_ier, 0); - commctl(com, MCR_DTR, DMBIC); - } - - /* if there was an error, take off. */ - if (error != 0) { - splx(s); - return (error); - } - /* else take it from the top */ - goto bidir_open_top; - } - } - } - } - - com->active = TRUE; -#endif /* COM_BIDIR */ - + /* + * The device isn't open, so there are no conflicts. + * Initialize it. Initialization is done twice in many + * cases: to preempt sleeping callin opens if we are + * callout, and to complete a callin open after DCD rises. + */ tp->t_oproc = comstart; tp->t_param = comparam; tp->t_dev = dev; - if (!(tp->t_state & TS_ISOPEN)) { - tp->t_state |= TS_WOPEN; - ttychars(tp); - if (tp->t_ispeed == 0) { - /* - * We no longer use the flags from - * since those are only relevant for logins. It's - * important to have echo off initially so that the - * line doesn't start blathering before the echo flag - * can be turned off. - */ - tp->t_iflag = 0; - tp->t_oflag = 0; -#ifdef COMCONSOLE - if (unit == comconsole) - tp->t_oflag = TTYDEF_OFLAG; -#endif - tp->t_cflag = CREAD | CS8; -#ifdef COM_BIDIR - if (com->bidir && !callout) - tp->t_cflag |= HUPCL; -#endif - tp->t_lflag = 0; - tp->t_ispeed = tp->t_ospeed = comdefaultrate; - } - - /* - * XXX the full state after a first open() needs to be - * programmable and separate for callin and callout. - */ -#ifdef COM_BIDIR - if (com->bidir) { - if (callout) - tp->t_cflag |= CLOCAL; - else - tp->t_cflag &= ~CLOCAL; - } -#endif - + tp->t_termios = mynor & CALLOUT_MASK + ? com->it_out : com->it_in; commctl(com, MCR_DTR | MCR_RTS, DMSET); + com->ftl_max = com->ftl_init; + ++com->wopeners; error = comparam(tp, &tp->t_termios); + --com->wopeners; if (error != 0) goto out; + /* + * XXX we should goto open_top if comparam() slept. + */ ttsetwater(tp); iobase = com->iobase; if (com->hasfifo) { - /* (re)enable and drain FIFO */ - outb(iobase + com_fifo, FIFO_ENABLE | FIFO_TRIGGER - | FIFO_RCV_RST | FIFO_XMT_RST); + /* Drain fifo. */ + outb(iobase + com_fifo, + FIFO_ENABLE | FIFO_RCV_RST | FIFO_XMT_RST + | com->ftl); DELAY(100); } disable_intr(); (void) inb(com->line_status_port); (void) inb(com->data_port); - if (!got_status) - com->last_modem_status = - com->prev_modem_status = inb(com->modem_status_port); + com->prev_modem_status = + com->last_modem_status = inb(com->modem_status_port); outb(iobase + com_ier, IER_ERXRDY | IER_ETXRDY | IER_ERLS | IER_EMSC); enable_intr(); - if (com->prev_modem_status & MSR_DCD || FAKE_DCD(unit)) - tp->t_state |= TS_CARR_ON; - } else if (tp->t_state & TS_XCLUDE && p->p_ucred->cr_uid != 0) { - splx(s); - return (EBUSY); - } - while (!(flag & O_NONBLOCK) && !(tp->t_cflag & CLOCAL) -#ifdef COM_BIDIR - /* We went through a lot of trouble to open it, - * but it's certain we have a carrier now, so - * don't spend any time on it now. + /* + * Handle initial DCD. Callout devices get a fake initial + * DCD (trapdoor DCD). If we are callout, then any sleeping + * callin opens get woken up and resume sleeping on "siobi" + * instead of "siodcd". */ - && !(com->bidir) -#endif /* COM_BIDIR */ - && !(tp->t_state & TS_CARR_ON)) { - tp->t_state |= TS_WOPEN; - error = ttysleep(tp, (caddr_t)tp->t_raw, TTIPRI | PCATCH, - ttopen, 0); + if (com->prev_modem_status & MSR_DCD || mynor & CALLOUT_MASK) + (*linesw[tp->t_line].l_modem)(tp, 1); + } + /* + * Wait for DCD if necessary. + */ + if (!(tp->t_state & TS_CARR_ON) && !(mynor & CALLOUT_MASK) + && !(tp->t_cflag & CLOCAL) && !(flag & O_NONBLOCK)) { + ++com->wopeners; + error = tsleep(TSA_CARR_ON(tp), TTIPRI | PCATCH, "siodcd", 0); + --com->wopeners; if (error != 0) - break; + goto out; + goto open_top; } -out: - if (error == 0) error = (*linesw[tp->t_line].l_open)(dev, tp, 0); + if (tp->t_state & TS_ISOPEN && mynor & CALLOUT_MASK) + com->active_out = TRUE; +out: splx(s); - -#ifdef COM_BIDIR - /* wakeup sleepers */ - wakeup((caddr_t) &com->active_in); -#endif /* COM_BIDIR */ - - /* - * XXX - the next step was once not done, so interrupts, DTR and RTS - * remained hot if the process was killed while it was sleeping - * waiting for carrier. Now there is the opposite problem. If several - * processes are sleeping waiting for carrier on the same line and one - * is killed, interrupts are turned off so the other processes will - * never see the carrier rise. - */ - if (error != 0 && !(tp->t_state & TS_ISOPEN)) + if (!(tp->t_state & TS_ISOPEN) && com->wopeners == 0) comhardclose(com); - tp->t_state &= ~TS_WOPEN; - return (error); } @@ -775,14 +779,18 @@ sioclose(dev, flag, mode, p) struct proc *p; { struct com_s *com; + int mynor; + int s; struct tty *tp; - int s; - com = com_addr(UNIT(dev)); + mynor = minor(dev); + if (mynor & CONTROL_MASK) + return (0); + com = com_addr(MINOR_TO_UNIT(mynor)); tp = com->tp; s = spltty(); (*linesw[tp->t_line].l_close)(tp, flag); - siostop(tp, FREAD|FWRITE); + siostop(tp, FREAD | FWRITE); comhardclose(com); ttyclose(tp); splx(s); @@ -798,11 +806,13 @@ comhardclose(com) struct tty *tp; int unit; - s = spltty(); + unit = DEV_TO_UNIT(com->tp->t_dev); iobase = com->iobase; + s = spltty(); +#ifdef TIOCTIMESTAMP com->do_timestamp = 0; +#endif outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); - unit = com - &com_structs[0]; #ifdef KGDB /* do not disable interrupts or hang up if debugging */ if (kgdb_dev != makedev(commajor, unit)) @@ -810,32 +820,29 @@ comhardclose(com) { outb(iobase + com_ier, 0); tp = com->tp; - if (tp->t_cflag & HUPCL || tp->t_state & TS_WOPEN -#ifdef COM_BIDIR - || com->active_in - && !(com->prev_modem_status & MSR_DCD) && !FAKE_DCD(unit) -#endif + if (tp->t_cflag & HUPCL + /* + * XXX we will miss any carrier drop between here and the + * next open. Perhaps we should watch DCD even when the + * port is closed; it is not sufficient to check it at + * the next open because it might go up and down while + * we're not watching. + */ + || !com->active_out + && !(com->prev_modem_status & MSR_DCD) + && !(com->it_in.c_cflag & CLOCAL) || !(tp->t_state & TS_ISOPEN)) { commctl(com, MCR_RTS, DMSET); - if (com->dtr_wait != 0) - /* - * Uninterruptible sleep since we want to - * wait a fixed time. - * XXX - delay in open() (if necessary), - * not here (always). - */ - tsleep((caddr_t)&com->dtr_wait, TTIPRI, - "sioclose", com->dtr_wait); + if (com->dtr_wait != 0) { + timeout(siodtrwakeup, (caddr_t)com, + com->dtr_wait); + com->state |= CS_DTR_OFF; } } - -#ifdef COM_BIDIR - com->active = com->active_in = com->active_out = FALSE; - - /* wakeup sleepers who are waiting for out to finish */ - wakeup((caddr_t) &com->active_out); -#endif /* COM_BIDIR */ - + } + com->active_out = FALSE; + wakeup((caddr_t)&com->active_out); + wakeup(TSA_CARR_ON(tp)); /* restart any wopeners */ splx(s); } @@ -845,8 +852,13 @@ sioread(dev, uio, flag) struct uio *uio; int flag; { - struct tty *tp = com_addr(UNIT(dev))->tp; + int mynor; + struct tty *tp; + mynor = minor(dev); + if (mynor & CONTROL_MASK) + return (ENODEV); + tp = com_addr(MINOR_TO_UNIT(mynor))->tp; return ((*linesw[tp->t_line].l_read)(tp, uio, flag)); } @@ -856,9 +868,15 @@ siowrite(dev, uio, flag) struct uio *uio; int flag; { - int unit = UNIT(dev); - struct tty *tp = com_addr(unit)->tp; + int mynor; + struct tty *tp; + int unit; + mynor = minor(dev); + if (mynor & CONTROL_MASK) + return (ENODEV); + unit = MINOR_TO_UNIT(mynor); + tp = com_addr(unit)->tp; /* * (XXX) We disallow virtual consoles if the physical console is * a serial port. This is in case there is a display attached that @@ -870,25 +888,37 @@ siowrite(dev, uio, flag) return ((*linesw[tp->t_line].l_write)(tp, uio, flag)); } +static void +siodtrwakeup(chan, ticks) + caddr_t chan; + int ticks; +{ + struct com_s *com; + + com = (struct com_s *)chan; + com->state &= ~CS_DTR_OFF; + wakeup((caddr_t)&com->dtr_wait); +} + +#ifdef TIOCTIMESTAMP /* Interrupt routine for timekeeping purposes */ void -siointrts(int unit) +siointrts(unit) + int unit; { microtime(&intr_timestamp); siointr(unit); } +#endif void siointr(unit) int unit; { - struct com_s *com; - #ifndef COM_MULTIPORT - com = com_addr(unit); - if (com != NULL) - comintr1(com); + siointr1(com_addr(unit)); #else /* COM_MULTIPORT */ + struct com_s *com; bool_t possibly_more_intrs; /* @@ -903,31 +933,30 @@ siointr(unit) for (unit = 0; unit < NSIO; ++unit) { com = com_addr(unit); if (com != NULL - && !(inb(com->int_id_port) & IIR_NOPEND)) { - /* - * XXX call comintr1() instead of here from - * comwakeup(). The interrupt edge problem - * only exists for real interrupts. - */ - comintr1(com); + && (inb(com->int_id_port) & IIR_IMASK) + != IIR_NOPEND) { + siointr1(com); possibly_more_intrs = TRUE; } } } while (possibly_more_intrs); -#endif /* COM_MULTIPORT */ +#endif /* COM_MULTIPORT */ } static void -comintr1(com) +siointr1(com) struct com_s *com; { - u_char line_status; - u_char modem_status; - u_char *ioptr; - u_char recv_data; - - if(com->do_timestamp) + u_char line_status; + u_char modem_status; + u_char *ioptr; + u_char recv_data; + +#ifdef TIOCTIMESTAMP + if (com->do_timestamp) + /* XXX a little bloat here... */ com->timestamp = intr_timestamp; +#endif while (TRUE) { line_status = inb(com->line_status_port); @@ -939,6 +968,8 @@ comintr1(com) else recv_data = inb(com->data_port); ++com->bytes_in; + if (com->hotchar != 0 && recv_data == com->hotchar) + setsofttty(); #ifdef KGDB /* trap into kgdb? (XXX - needs testing and optim) */ if (recv_data == FRAME_END @@ -1017,7 +1048,7 @@ if (com->iptr - com->ibuf == 8) while (--ocount != 0); } else { outb(com->data_port, *ioptr++); - ++com->bytes_out; + ++com->bytes_out; } com->optr = ioptr; if (ioptr >= com->obufend) { @@ -1030,7 +1061,7 @@ if (com->iptr - com->ibuf == 8) /* finished? */ #ifndef COM_MULTIPORT - if (inb(com->int_id_port) & IIR_NOPEND) + if ((inb(com->int_id_port) & IIR_IMASK) == IIR_NOPEND) #endif /* COM_MULTIPORT */ return; } @@ -1038,9 +1069,9 @@ if (com->iptr - com->ibuf == 8) static int tiocm_xxx2mcr(tiocm_xxx) - int tiocm_xxx; + int tiocm_xxx; { - int mcr; + int mcr; mcr = 0; if (tiocm_xxx & TIOCM_DTR) @@ -1063,26 +1094,75 @@ sioioctl(dev, cmd, data, flag, p) Port_t iobase; int mcr; int msr; + int mynor; int s; int tiocm_xxx; struct tty *tp; - com = com_addr(UNIT(dev)); + mynor = minor(dev); + com = com_addr(MINOR_TO_UNIT(mynor)); + if (mynor & CONTROL_MASK) { + struct termios *ct; + + switch (mynor & CONTROL_MASK) { + case CONTROL_INIT_STATE: + ct = mynor & CALLOUT_MASK ? &com->it_out : &com->it_in; + break; + case CONTROL_LOCK_STATE: + ct = mynor & CALLOUT_MASK ? &com->lt_out : &com->lt_in; + break; + default: + return (ENODEV); /* /dev/nodev */ + } + switch (cmd) { + case TIOCSETA: + error = suser(p->p_ucred, &p->p_acflag); + if (error) + return (error); + *ct = *(struct termios *)data; + return (0); + case TIOCGETA: + *(struct termios *)data = *ct; + return (0); + case TIOCGETD: + *(int *)data = TTYDISC; + return (0); + case TIOCGWINSZ: + bzero(data, sizeof(struct winsize)); + return (0); + default: + return (ENOTTY); + } + } tp = com->tp; + if (cmd == TIOCSETA || cmd == TIOCSETAW || cmd == TIOCSETAF) { + int cc; + struct termios *dt = (struct termios *)data; + struct termios *lt = mynor & CALLOUT_MASK + ? &com->lt_out : &com->lt_in; + + dt->c_iflag = (tp->t_iflag & lt->c_iflag) + | (dt->c_iflag & ~lt->c_iflag); + dt->c_oflag = (tp->t_oflag & lt->c_oflag) + | (dt->c_oflag & ~lt->c_oflag); + dt->c_cflag = (tp->t_cflag & lt->c_cflag) + | (dt->c_cflag & ~lt->c_cflag); + dt->c_lflag = (tp->t_lflag & lt->c_lflag) + | (dt->c_lflag & ~lt->c_lflag); + for (cc = 0; cc < NCCS; ++cc) + if (lt->c_cc[cc] != 0) + dt->c_cc[cc] = tp->t_cc[cc]; + if (lt->c_ispeed != 0) + dt->c_ispeed = tp->t_ispeed; + if (lt->c_ospeed != 0) + dt->c_ospeed = tp->t_ospeed; + } error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag); if (error >= 0) return (error); error = ttioctl(tp, cmd, data, flag); - -#ifdef COM_BIDIR - /* XXX: plug security hole while sticky bits not yet implemented */ - if (com->bidir && com->active_in && p->p_ucred->cr_uid != 0) - tp->t_cflag &= ~CLOCAL; -#endif - if (error >= 0) return (error); - iobase = com->iobase; s = spltty(); switch (cmd) { @@ -1130,61 +1210,24 @@ sioioctl(dev, cmd, data, flag, p) tiocm_xxx |= TIOCM_RI; *(int *)data = tiocm_xxx; break; -#ifdef COM_BIDIR - case TIOCMSBIDIR: - /* must be root to set bidir. capability */ - error = suser(p->p_ucred, &p->p_acflag); - if (error != 0) { - splx(s); - return(EPERM); - } - - /* if it's the console, can't do it (XXX why?) */ - if (UNIT(dev) == comconsole) { - splx(s); - return(ENOTTY); - } - -#if 0 - /* XXX - can't do the next, for obvious reasons... - * but there are problems to be looked at... - */ - /* if the port is active, don't do it */ - if (com->active) { - splx(s); - return(EBUSY); - } -#endif - - com->bidir = *(int *)data; - break; - case TIOCMGBIDIR: - *(int *)data = com->bidir; - break; -#endif /* COM_BIDIR */ case TIOCMSDTRWAIT: /* must be root since the wait applies to following logins */ error = suser(p->p_ucred, &p->p_acflag); if (error != 0) { splx(s); - return(EPERM); + return (EPERM); } - - /* if it's the console, can't do it (XXX why?) */ - if (UNIT(dev) == comconsole) { - splx(s); - return(ENOTTY); - } - - com->dtr_wait = *(int *)data; + com->dtr_wait = *(int *)data * 100 / hz; break; case TIOCMGDTRWAIT: *(int *)data = com->dtr_wait; break; +#ifdef TIOCTIMESTAMP case TIOCTIMESTAMP: - com->do_timestamp = 1; + com->do_timestamp = TRUE; *(struct timeval *)data = com->timestamp; break; +#endif default: splx(s); return (ENOTTY); @@ -1215,24 +1258,14 @@ comflush(com) void siopoll() { - static bool_t awake = FALSE; - int s; int unit; if (com_events == 0) return; - disable_intr(); - if (awake) { - enable_intr(); - return; - } - awake = TRUE; - enable_intr(); - s = spltty(); repeat: for (unit = 0; unit < NSIO; ++unit) { - u_char *buf; - struct com_s *com; + u_char *buf; + struct com_s *com; u_char *ibuf; int incc; struct tty *tp; @@ -1241,12 +1274,25 @@ repeat: if (com == NULL) continue; tp = com->tp; + if (tp == NULL) + continue; /* switch the role of the low-level input buffers */ if (com->iptr == (ibuf = com->ibuf)) { buf = NULL; /* not used, but compiler can't tell */ incc = 0; } else { + /* + * Prepare to reduce input latency for packet + * discplines with a end of packet character. + * XXX should be elsewhere. + */ + if (tp->t_line == SLIPDISC) + com->hotchar = 0xc0; + else if (tp->t_line == PPPDISC) + com->hotchar = 0x7e; + else + com->hotchar = 0; buf = ibuf; disable_intr(); incc = com->iptr - buf; @@ -1264,6 +1310,13 @@ repeat: * of input, so enable RTS if it is now disabled and * there is room in the high-level buffer. */ + /* + * XXX this used not to look at CS_RTS_IFLOW. The + * change is to allow full control of MCR_RTS via + * ioctls after turning CS_RTS_IFLOW off. Check + * for races. We shouldn't allow the ioctls while + * CS_RTS_IFLOW is on. + */ if ((com->state & CS_RTS_IFLOW) && !(com->mcr_image & MCR_RTS) && !(tp->t_state & TS_RTS_IFLOW)) @@ -1283,35 +1336,45 @@ repeat: com_events -= LOTS_OF_EVENTS; com->state &= ~CS_CHECKMSR; enable_intr(); - if (delta_modem_status & MSR_DCD && !FAKE_DCD(unit)) { - if (com->prev_modem_status & MSR_DCD) { - (*linesw[tp->t_line].l_modem)(tp, 1); -#ifdef COM_BIDIR - wakeup((caddr_t) &com->active_in); -#endif /* COM_BIDIR */ - } else - (*linesw[tp->t_line].l_modem)(tp, 0); - } + if (delta_modem_status & MSR_DCD) + (*linesw[tp->t_line].l_modem) + (tp, com->prev_modem_status & MSR_DCD); } /* XXX */ if (TRUE) { - u_int delta; - int errnum; - u_long total; + u_int delta; + int errnum; + u_long total; for (errnum = 0; errnum < CE_NTYPES; ++errnum) { - disable_intr(); + disable_intr(); delta = com->delta_error_counts[errnum]; com->delta_error_counts[errnum] = 0; - enable_intr(); - if (delta != 0) { - total = - com->error_counts[errnum] += delta; + enable_intr(); + if (delta == 0 || !(tp->t_state & TS_ISOPEN)) + continue; + total = com->error_counts[errnum] += delta; log(LOG_WARNING, "sio%d: %u more %s%s (total %lu)\n", unit, delta, error_desc[errnum], delta == 1 ? "" : "s", total); + if (errnum == CE_OVERRUN && com->hasfifo + && com->ftl > FIFO_TRIGGER_1) { + static u_char ftl_in_bytes[] = + { 1, 4, 8, 14, }; + + com->ftl_init = FIFO_TRIGGER_8; +#define FIFO_TRIGGER_DELTA FIFO_TRIGGER_4 + com->ftl_max = + com->ftl -= FIFO_TRIGGER_DELTA; + outb(com->iobase + com_fifo, + FIFO_ENABLE | com->ftl); + log(LOG_WARNING, + "sio%d: reduced fifo trigger level to %d\n", + unit, + ftl_in_bytes[com->ftl + / FIFO_TRIGGER_DELTA]); } } } @@ -1389,8 +1452,6 @@ repeat: } if (com_events >= LOTS_OF_EVENTS) goto repeat; - splx(s); - awake = FALSE; } static int @@ -1415,7 +1476,7 @@ comparam(tp, t) return (EINVAL); /* parameters are OK, convert them to the com struct and the device */ - unit = UNIT(tp->t_dev); + unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); iobase = com->iobase; s = spltty(); @@ -1446,12 +1507,28 @@ comparam(tp, t) if (cflag & CSTOPB) cfcr |= CFCR_STOPB; + if (com->hasfifo) { + /* + * Use a fifo trigger level low enough so that the input + * latency from the fifo is less than about 16 msec and + * the total latency is less than about 30 msec. These + * latencies are reasonable for humans. Serial comms + * protocols shouldn't expect anything better since modem + * latencies are larger. + */ + com->ftl = t->c_ospeed <= 4800 + ? FIFO_TRIGGER_1 : FIFO_TRIGGER_14; + if (com->ftl > com->ftl_max) + com->ftl = com->ftl_max; + outb(iobase + com_fifo, FIFO_ENABLE | com->ftl); + } + /* * Some UARTs lock up if the divisor latch registers are selected * while the UART is doing output (they refuse to transmit anything * more until given a hard reset). Fix this by stopping filling * the device buffers and waiting for them to drain. Reading the - * line status port outside of siointr() might lose some receiver + * line status port outside of siointr1() might lose some receiver * error bits, but that is acceptable here. */ disable_intr(); @@ -1460,8 +1537,8 @@ retry: enable_intr(); while ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) != (LSR_TSRE | LSR_TXRDY)) { - error = ttysleep(tp, (caddr_t)tp->t_raw, TTIPRI | PCATCH, - "sioparam", 1); + error = ttysleep(tp, TSA_OCOMPLETE(tp), TTIPRI | PCATCH, + "siotx", hz / 100); if (error != 0 && error != EAGAIN) { if (!(tp->t_state & TS_TTSTOP)) { disable_intr(); @@ -1482,7 +1559,7 @@ retry: * sufficient, for similar reasons. */ if ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) - != (LSR_TSRE | LSR_TXRDY)) + != (LSR_TSRE | LSR_TXRDY)) goto retry; if (divisor != 0) { @@ -1501,7 +1578,7 @@ retry: /* * Set up state to handle output flow control. * XXX - worth handling MDMBUF (DCD) flow control at the lowest level? - * Now has 16+ msec latency, while CTS flow has 50- usec latency. + * Now has 10+ msec latency, while CTS flow has 50- usec latency. */ com->state &= ~CS_CTS_OFLOW; com->state |= CS_ODEVREADY; @@ -1512,15 +1589,12 @@ retry: } /* - * Recover from fiddling with CS_TTGO. We used to call siointr() + * Recover from fiddling with CS_TTGO. We used to call siointr1() * unconditionally, but that defeated the careful discarding of * stale input in sioopen(). - * - * XXX sioopen() is not careful waiting for carrier for the callout - * case. */ if (com->state >= (CS_BUSY | CS_TTGO)) - comintr1(com); + siointr1(com); enable_intr(); splx(s); @@ -1535,7 +1609,7 @@ comstart(tp) int s; int unit; - unit = UNIT(tp->t_dev); + unit = DEV_TO_UNIT(tp->t_dev); com = com_addr(unit); s = spltty(); disable_intr(); @@ -1547,35 +1621,31 @@ comstart(tp) if (com->mcr_image & MCR_RTS && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); } else { + /* + * XXX don't raise MCR_RTS if CTS_RTS_IFLOW is off. Set it + * appropriately in comparam() if RTS-flow is being changed. + * Check for races. + */ if (!(com->mcr_image & MCR_RTS) && com->iptr < com->ihighwater) outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); } enable_intr(); if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) goto out; - if (RB_LEN(tp->t_out) <= tp->t_lowat) { - if (tp->t_state & TS_ASLEEP) { - tp->t_state &= ~TS_ASLEEP; - wakeup((caddr_t)tp->t_out); - } - 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_state & (TS_SO_OCOMPLETE | TS_SO_OLOWAT) || tp->t_wsel) + ttwwakeup(tp); if (com->ocount != 0) { disable_intr(); - comintr1(com); + siointr1(com); enable_intr(); } else if (RB_LEN(tp->t_out) != 0) { tp->t_state |= TS_BUSY; com->ocount = RB_CONTIGGET(tp->t_out); disable_intr(); - com->obufend = (com->optr = (u_char *) tp->t_out->rb_hd) + com->obufend = (com->optr = (u_char *)tp->t_out->rb_hd) + com->ocount; com->state |= CS_BUSY; - comintr1(com); /* fake interrupt to start output */ + siointr1(com); /* fake interrupt to start output */ enable_intr(); } out: @@ -1589,7 +1659,7 @@ siostop(tp, rw) { struct com_s *com; - com = com_addr(UNIT(tp->t_dev)); + com = com_addr(DEV_TO_UNIT(tp->t_dev)); if (rw & FWRITE) comflush(com); disable_intr(); @@ -1610,11 +1680,9 @@ sioselect(dev, rw, p) int rw; struct proc *p; { -#ifdef COM_BIDIR - return ttselect(dev & ~COM_CALLOUTMASK, rw, p); -#else - return ttselect(dev, rw, p); -#endif + if (minor(dev) & CONTROL_MASK) + return (ENODEV); + return (ttselect(dev & ~MINOR_MAGIC_MASK, rw, p)); } static void @@ -1641,15 +1709,20 @@ commctl(com, bits, how) static void comwakeup(chan, ticks) - caddr_t chan; - int ticks; + caddr_t chan; + int ticks; { - int unit; + int unit; + + timeout(comwakeup, (caddr_t)NULL, hz / 100); - timeout(comwakeup, (caddr_t) NULL, hz / 100); - if (com_events != 0) - /* schedule siopoll() to run when the cpl allows */ - setsofttty(); + if (com_events != 0) { + int s; + + s = splsofttty(); + siopoll(); + splx(s); + } /* recover from lost output interrupts */ for (unit = 0; unit < NSIO; ++unit) { @@ -1658,27 +1731,97 @@ comwakeup(chan, ticks) com = com_addr(unit); if (com != NULL && com->state >= (CS_BUSY | CS_TTGO)) { disable_intr(); - comintr1(com); + siointr1(com); enable_intr(); } } - return; -} - -void -softsio1() -{ - siopoll(); } /* * Following are all routines needed for SIO to act as console - * XXX - not tested in this version - * XXX - i386/cons.c only knows about the com driver (NCOM and not NSIO) - * XXX - check that the corresponding serial interrupts are never enabled */ #include "i386/i386/cons.h" +struct siocnstate { + u_char dlbl; + u_char dlbh; + u_char ier; + u_char cfcr; + u_char mcr; +}; + +static Port_t siocniobase; + +static void siocnclose __P((struct siocnstate *sp)); +static void siocnopen __P((struct siocnstate *sp)); +static void siocntxwait __P((void)); + +static void +siocntxwait() +{ + int timo; + + /* + * Wait for any pending transmission to finish. Required to avoid + * the UART lockup bug when the speed is changed, and for normal + * transmits. + */ + timo = 100000; + while ((inb(siocniobase + com_lsr) & (LSR_TSRE | LSR_TXRDY)) + != (LSR_TSRE | LSR_TXRDY) && --timo != 0) + ; +} + +static void +siocnopen(sp) + struct siocnstate *sp; +{ + int divisor; + Port_t iobase; + + /* + * Save all the device control registers except the fifo register + * and set our default ones (cs8 -parenb speed=comdefaultrate). + * We can't save the fifo register since it is read-only. + */ + iobase = siocniobase; + sp->ier = inb(iobase + com_ier); + outb(iobase + com_ier, 0); /* spltty() doesn't stop siointr() */ + siocntxwait(); + sp->cfcr = inb(iobase + com_cfcr); + outb(iobase + com_cfcr, CFCR_DLAB); + sp->dlbl = inb(iobase + com_dlbl); + sp->dlbh = inb(iobase + com_dlbh); + divisor = ttspeedtab(comdefaultrate, comspeedtab); + outb(iobase + com_dlbl, divisor & 0xFF); + outb(iobase + com_dlbh, (u_int) divisor >> 8); + outb(iobase + com_cfcr, CFCR_8BITS); + sp->mcr = inb(iobase + com_mcr); + outb(iobase + com_mcr, MCR_DTR | MCR_RTS); +} + +static void +siocnclose(sp) + struct siocnstate *sp; +{ + Port_t iobase; + + /* + * Restore the device control registers. + */ + siocntxwait(); + iobase = siocniobase; + outb(iobase + com_cfcr, CFCR_DLAB); + outb(iobase + com_dlbl, sp->dlbl); + outb(iobase + com_dlbh, sp->dlbh); + outb(iobase + com_cfcr, sp->cfcr); + /* + * XXX damp oscllations of MCR_DTR and MCR_RTS by not restoring them. + */ + outb(iobase + com_mcr, sp->mcr | MCR_DTR | MCR_RTS); + outb(iobase + com_ier, sp->ier); +} + void siocnprobe(cp) struct consdev *cp; @@ -1686,14 +1829,14 @@ siocnprobe(cp) int unit; /* locate the major number */ + /* XXX - should be elsewhere since KGDB uses it */ for (commajor = 0; commajor < nchrdev; commajor++) if (cdevsw[commajor].d_open == sioopen) break; /* XXX: ick */ - unit = UNIT(CONUNIT); - com_addr(unit) = &com_structs[unit]; - com_addr(unit)->iobase = CONADDR; + unit = DEV_TO_UNIT(CONUNIT); + siocniobase = CONADDR; /* make sure hardware exists? XXX */ @@ -1710,43 +1853,11 @@ void siocninit(cp) struct consdev *cp; { - int unit; - - unit = UNIT(cp->cn_dev); - cominit(unit, comdefaultrate); - comconsole = unit; - comconsinit = TRUE; -} - -static void -cominit(unit, rate) - int unit; - int rate; -{ - Port_t iobase; - int s; - - iobase = com_addr(unit)->iobase; - s = splhigh(); - outb(iobase + com_cfcr, CFCR_DLAB); - rate = ttspeedtab(comdefaultrate, comspeedtab); - outb(iobase + com_dlbl, rate & 0xFF); - outb(iobase + com_dlbh, rate >> 8); - outb(iobase + com_cfcr, CFCR_8BITS); - outb(iobase + com_fifo, - FIFO_ENABLE | FIFO_TRIGGER | FIFO_RCV_RST | FIFO_XMT_RST); - DELAY(100); - (void) inb(iobase + com_lsr); - (void) inb(iobase + com_data); - (void) inb(iobase + com_msr); - /* - * XXX - fishy to enable interrupts and then poll. - * It shouldn't be necessary to ready the iir. + * XXX can delete more comconsole stuff now that i/o routines are + * fairly reentrant. */ - outb(iobase + com_ier, IER_ERXRDY | IER_ETXRDY | IER_ERLS | IER_EMSC); - (void) inb(iobase + com_iir); - splx(s); + comconsole = DEV_TO_UNIT(cp->cn_dev); } int @@ -1756,13 +1867,15 @@ siocngetc(dev) int c; Port_t iobase; int s; + struct siocnstate sp; - iobase = com_addr(UNIT(dev))->iobase; - s = splhigh(); + iobase = siocniobase; + s = spltty(); + siocnopen(&sp); while (!(inb(iobase + com_lsr) & LSR_RXRDY)) ; c = inb(iobase + com_data); - (void) inb(iobase + com_iir); + siocnclose(&sp); splx(s); return (c); } @@ -1772,30 +1885,14 @@ siocnputc(dev, c) dev_t dev; int c; { - Port_t iobase; int s; - int timo; + struct siocnstate sp; - iobase = com_addr(UNIT(dev))->iobase; - s = splhigh(); -#ifdef KGDB - if (dev != kgdb_dev) -#endif - if (!comconsinit) { - cominit(UNIT(dev), comdefaultrate); - comconsinit = TRUE; - } - /* wait for any pending transmission to finish */ - timo = 50000; - while (!(inb(iobase + com_lsr) & LSR_TXRDY) && --timo) - ; - outb(iobase + com_data, c); - /* wait for this transmission to complete */ - timo = 1500000; - while (!(inb(iobase + com_lsr) & LSR_TXRDY) && --timo) - ; - /* clear any interrupts generated by this transmission */ - (void) inb(iobase + com_iir); + s = spltty(); + siocnopen(&sp); + siocntxwait(); + outb(siocniobase + com_data, c); + siocnclose(&sp); splx(s); }