X-Git-Url: https://git.subgeniuskitty.com/unix-history/.git/blobdiff_plain/8e67d3271cc54ff24aea65bfa3892408180fc7c4..45fab98cfcead1a207c8a74804480af7ff26f089:/sys/i386/isa/sio.c diff --git a/sys/i386/isa/sio.c b/sys/i386/isa/sio.c index fd3efc82d2..a54b7bb2c3 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.18 1993/12/11 00:36:14 ache Exp $ + * $Id: sio.c,v 1.52 1994/05/31 18:18:46 ache Exp $ */ #include "sio.h" @@ -49,17 +49,19 @@ #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/icu.h" /* XXX */ #include "i386/isa/isa.h" #include "i386/isa/isa_device.h" #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,25 +69,27 @@ #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 -#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) +#define COM_QUIET(dev) ((dev)->id_flags & 0x80) + #define com_scr 7 /* scratch register for 16450-16550 (R/W) */ -#define schedsoftcom() (ipending |= 1 << 4) /* XXX */ /* * Input buffer watermarks. @@ -111,17 +115,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 @@ -135,25 +141,24 @@ static char *error_desc[] = { /* types. XXX - should be elsewhere */ typedef u_int Port_t; /* hardware port */ -typedef int Bool_t; /* promoted boolean */ 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 @@ -164,6 +169,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 */ @@ -182,6 +188,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]; @@ -197,48 +216,45 @@ struct com_s { }; /* - * 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)); -/* - * Also, most of the device switch functions are still declared old-style - * so they take a Dev_t arg and shorten it to a dev_t. It would be simpler - * and faster if dev_t's were always promoted (to ints or whatever) as - * early as possible. - */ - -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)); -#ifdef COM_MULTIPORT -static bool_t comintr1 __P((struct com_s *com)); -#endif /* COM_MULTIPORT */ -static int commctl __P((struct com_s *com, int bits, int how)); +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 compoll __P((void)); static void comstart __P((struct tty *tp)); static void comwakeup __P((caddr_t chan, int ticks)); static int tiocm_xxx2mcr __P((int tiocm_xxx)); @@ -247,7 +263,9 @@ static int tiocm_xxx2mcr __P((int tiocm_xxx)); static struct com_s *p_com_addr[NSIO]; #define com_addr(unit) (p_com_addr[unit]) -static struct com_s com_structs[NSIO]; +#ifdef TIOCTIMESTAMP +static struct timeval intr_timestamp; +#endif struct isa_driver siodriver = { sioprobe, sioattach, "sio" @@ -258,15 +276,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 */ +struct tty *sio_tty[NSIO]; +extern struct tty *constty; /* XXX */ #ifdef KGDB #include "machine/remote-sl.h" @@ -305,9 +319,13 @@ static int sioprobe(dev) struct isa_device *dev; { - static bool_t already_init=FALSE; + static bool_t already_init; Port_t *com_ptr; + bool_t failures[10]; + int fn; + struct isa_device *idev; Port_t iobase; + u_char mcr_image; int result; if (!already_init) { @@ -315,6 +333,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 @@ -323,8 +342,45 @@ 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 in the mcr. + * Otherwise, prepare to set MCR_IENABLE in the mcr. + * Point idev to the device struct giving the correct id_irq. + * This is the struct for the master device if there is one. + */ + idev = dev; + mcr_image = MCR_IENABLE; +#ifdef COM_MULTIPORT + if (COM_ISMULTIPORT(dev)) { + if (!COM_NOMASTER(dev)) { + idev = find_isadev(isa_devtab_tty, &siodriver, + COM_MPMASTER(dev)); + if (idev == NULL) { + printf("sio%d: master device %d not found\n", + dev->id_unit, COM_MPMASTER(dev)); + return (0); + } + if (idev->id_irq == 0) { + printf("sio%d: master device %d irq not configured\n", + dev->id_unit, COM_MPMASTER(dev)); + return (0); + } + outb(idev->id_iobase + com_scr, 0x80); + mcr_image = 0; + } + } + else +#endif /* COM_MULTIPORT */ + if (idev->id_irq == 0) { + printf("sio%d: irq not configured\n", dev->id_unit); + return (0); + } + + bzero(failures, sizeof failures); iobase = dev->id_iobase; - result = 1; /* * We don't want to get actual interrupts, just masked ones. @@ -335,52 +391,119 @@ sioprobe(dev) disable_intr(); /* - * Enable output interrupts (only) and check the following: + * XXX DELAY() reenables CPU interrupts. This is a problem for + * shared interrupts after the first device using one has been + * successfully probed - config_isadev() has enabled the interrupt + * in the ICU. + */ + outb(IO_ICU1 + 1, 0xff); + + /* + * 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. + */ + outb(iobase + com_mcr, mcr_image); + outb(iobase + com_ier, 0); + + /* + * 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); + + /* + * 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. + */ + 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); /* DLAB = 1 */ - outb(iobase + com_dlbl, COMBRD(9600) & 0xff); /* 9600bps */ - outb(iobase + com_dlbh, ((u_int) COMBRD(9600) >> 8) & 0xff); - 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(17000); /* wait for fifo drain on 9600 */ - 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 */ - || !isa_irq_pending(dev) -#endif - || (inb(iobase + com_iir) & IIR_IMASK) != IIR_TXRDY - || isa_irq_pending(dev) - || (inb(iobase + com_iir) & IIR_IMASK) != IIR_NOPEND) - result = 0; + failures[0] = inb(iobase + com_cfcr) - CFCR_8BITS; + failures[1] = inb(iobase + com_ier) - IER_ETXRDY; + failures[2] = inb(iobase + com_mcr) - mcr_image; + if (idev->id_irq != 0) + failures[3] = isa_irq_pending(idev) ? 0 : 1; + failures[4] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_TXRDY; + failures[5] = isa_irq_pending(idev) ? 1 : 0; + failures[6] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_NOPEND; /* * 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 */ - if ( inb(iobase + com_ier) != 0 - || isa_irq_pending(dev) - || (inb(iobase + com_iir) & IIR_IMASK) != IIR_NOPEND) - result = 0; + outb(iobase + com_cfcr, CFCR_8BITS); /* dummy to avoid bus echo */ + failures[7] = inb(iobase + com_ier); + failures[8] = isa_irq_pending(idev) ? 1 : 0; + failures[9] = (inb(iobase + com_iir) & IIR_IMASK) - IIR_NOPEND; + outb(IO_ICU1 + 1, imen); /* XXX */ enable_intr(); + + result = IO_COMSIZE; + for (fn = 0; fn < sizeof failures; ++fn) + if (failures[fn]) { + outb(iobase + com_mcr, 0); + result = 0; + if (!COM_QUIET(dev)) + printf("sio%d: probe test %d failed\n", + dev->id_unit, fn); + } return (result); } -static int /* XXX - should be void */ +static int sioattach(isdp) struct isa_device *isdp; { @@ -392,9 +515,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: @@ -403,15 +526,14 @@ 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 = 50; + com->dtr_wait = 3 * hz; + com->tx_fifo_size = 1; com->iptr = com->ibuf = com->ibuf1; com->ibufend = com->ibuf1 + RS_IBUFSIZE; com->ihighwater = com->ibuf1 + RS_IHIGHWATER; @@ -419,17 +541,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]; -#ifdef COM_BIDIR + /* - * if bidirectional ports possible, clear the bidir port info; + * 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->bidir = FALSE; - com->active = FALSE; - com->active_in = com->active_out = FALSE; -#endif /* COM_BIDIR */ + 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); @@ -448,25 +583,31 @@ sioattach(isdp) scr2 = inb(iobase + com_scr); outb(iobase + com_scr, scr); if (scr1 != 0xa5 || scr2 != 0x5a) { - printf(" <8250>"); + printf(" 8250"); goto determined_type; } } outb(iobase + com_fifo, FIFO_ENABLE | FIFO_TRIGGER_14); DELAY(100); - switch (inb(iobase + com_iir) & IIR_FIFO_MASK) { + switch (inb(com->int_id_port) & IIR_FIFO_MASK) { case FIFO_TRIGGER_1: - printf(" <16450>"); + printf(" 16450"); break; case FIFO_TRIGGER_4: - printf(" <16450?>"); + printf(" 16450?"); break; case FIFO_TRIGGER_8: - printf(" <16550?>"); + printf(" 16550?"); break; case FIFO_TRIGGER_14: - com->hasfifo = TRUE; - printf(" <16550A>"); + printf(" 16550A"); + if (COM_NOFIFO(isdp)) + printf(" fifo disabled"); + else { + com->hasfifo = TRUE; + com->ftl_init = FIFO_TRIGGER_14; + com->tx_fifo_size = 16; + } break; } outb(iobase + com_fifo, 0); @@ -474,32 +615,36 @@ determined_type: ; #ifdef COM_MULTIPORT if (COM_ISMULTIPORT(isdp)) { - struct isa_device *masterdev; - 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(iobase + com_mcr, 0); - - masterdev = find_isadev(isa_devtab_tty, &siodriver, - COM_MPMASTER(isdp)); - outb(masterdev->id_iobase+com_scr, 0x80); - } - else - com->multiport = FALSE; + printf(" (multiport"); + if (!COM_NOMASTER(isdp) && unit == COM_MPMASTER(isdp)) + printf(" master"); + printf(")"); + } #endif /* COM_MULTIPORT */ printf("\n"); #ifdef KGDB if (kgdb_dev == makedev(commajor, unit)) { - if (comconsole == unit) + if (unit == comconsole) 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, @@ -507,29 +652,19 @@ determined_type: ; */ printf("sio%d: ", unit); kgdb_connect(1); - } - else + } else printf("sio%d: kgdb enabled\n", unit); } } #endif - /* - * Need to reset baud rate, etc. of next print so reset comconsinit. - * Also make sure console is always "hardwired" - */ - 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); } @@ -542,206 +677,126 @@ sioopen(dev, flag, mode, p) struct proc *p; { struct com_s *com; - int error = 0; + int error; Port_t iobase; + int mynor; int s; struct tty *tp; - int unit = UNIT(dev); -#ifdef COM_BIDIR - bool_t callout = CALLOUT(dev); -#endif /* COM_BIDIR */ + int unit; + + 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 */ - if (callout && !(com->bidir)) - return (ENXIO); -#endif /* COM_BIDIR */ - - tp = com->tp; + if (mynor & CONTROL_MASK) + return (0); + tp = com->tp = sio_tty[unit] = ttymalloc(sio_tty[unit]); s = spltty(); - -#ifdef COM_BIDIR - -bidir_open_top: - /* 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 = tsleep((caddr_t)&com->active_out, - TTIPRI|PCATCH, - "siooth", - 0); - /* if there was an error, take off. */ - if (error != 0) { - splx(s); - return (error); - } - /* else take it from the top */ - goto bidir_open_top; - } - } else 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 */ - /* NOTE: cgd'sdriver used the ier register - * to enable/disable interrupts. This one - * uses both ier and IENABLE in the mcr. - */ - /* XXX - bring up RTS earlier? */ - (void) 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 DTR off */ - if (!com->active) - (void) 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; + error = EBUSY; + goto out; } + error = tsleep((caddr_t)&com->active_out, + TTIPRI | PCATCH, "siobi", 0); + if (error != 0) + goto out; + goto open_top; } } - } - - com->active = TRUE; -#endif /* COM_BIDIR */ - - 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. It's useful to have clocal on - * initially so that "stty changed-defaults t_iflag = 0; -#ifdef COMCONSOLE - tp->t_oflag = TTYDEF_OFLAG; -#else - tp->t_oflag = 0; -#endif - tp->t_cflag = CREAD | CS8 | CLOCAL; - tp->t_lflag = 0; - tp->t_ispeed = tp->t_ospeed = comdefaultrate; - } -#ifdef COM_BIDIR - if (com->bidir) { - if (callout) - tp->t_cflag |= CLOCAL; - else - tp->t_cflag &= ~CLOCAL; - tp->t_cflag |= HUPCL; + if (tp->t_state & TS_XCLUDE && p->p_ucred->cr_uid != 0) { + error = EBUSY; + goto out; } -#endif - (void) commctl(com, MCR_DTR | MCR_RTS, DMSET); + } else { + /* + * 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; + 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) { + /* Drain fifo. */ + outb(iobase + com_fifo, + FIFO_ENABLE | FIFO_RCV_RST | FIFO_XMT_RST + | com->ftl); + DELAY(100); + } disable_intr(); - if (com->hasfifo) - /* (re)enable and drain FIFO */ - outb(iobase + com_fifo, FIFO_ENABLE | FIFO_TRIGGER_14 - | FIFO_RCV_RST | FIFO_XMT_RST); (void) inb(com->line_status_port); (void) inb(com->data_port); - 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; } + 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); - if (error == 0) - error = (*linesw[tp->t_line].l_open)(dev, tp, 0); - -#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); } @@ -754,13 +809,21 @@ sioclose(dev, flag, mode, p) struct proc *p; { struct com_s *com; + int mynor; + int s; struct tty *tp; - 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); comhardclose(com); ttyclose(tp); + splx(s); return (0); } @@ -771,32 +834,45 @@ comhardclose(com) Port_t iobase; int s; 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); #ifdef KGDB - /* do not disable interrupts if debugging */ - if (kgdb_dev != makedev(commajor, com - &com_structs[0])) + /* do not disable interrupts or hang up if debugging */ + if (kgdb_dev != makedev(commajor, unit)) #endif + { outb(iobase + com_ier, 0); - tp = com->tp; - if (tp->t_cflag & HUPCL || tp->t_state & TS_WOPEN - || !(tp->t_state & TS_ISOPEN)) { - (void) commctl(com, MCR_RTS, DMSET); - if (com->dtr_wait != 0) - /* uninterruptible since we want to wait a fixed time */ - tsleep((caddr_t)&com->dtr_wait, TTIPRI, "sioclose", - com->dtr_wait); + tp = com->tp; + 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) { + 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); } @@ -806,8 +882,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)); } @@ -817,9 +898,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 @@ -831,78 +918,88 @@ 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(unit) + int unit; +{ + microtime(&intr_timestamp); + siointr(unit); +} +#endif + void siointr(unit) int unit; { - struct com_s *com; #ifndef COM_MULTIPORT - u_char line_status; - u_char modem_status; - u_char *ioptr; - u_char recv_data; - - com = com_addr(unit); + siointr1(com_addr(unit)); #else /* COM_MULTIPORT */ - int i; - bool_t donesomething; + struct com_s *com; + bool_t possibly_more_intrs; /* * Loop until there is no activity on any port. This is necessary * to get an interrupt edge more than to avoid another interrupt. * If the IRQ signal is just an OR of the IRQ signals from several * devices, then the edge from one may be lost because another is - * on, or it may cause a stray interrupt because another was on a - * short time before. - * - * XXX getting the status from comintr1() is not best and may be - * incorrect. It would be better to test the int_id's in a tight - * loop. If each is off when it is tested, then they all must - * have been off at the start. + * on. */ do { - donesomething = FALSE; - for(i=0;iint_id_port) & IIR_IMASK) + != IIR_NOPEND) { + siointr1(com); + possibly_more_intrs = TRUE; } } - } while (donesomething); - return; + } while (possibly_more_intrs); +#endif /* COM_MULTIPORT */ } -static bool_t -comintr1(com) +static void +siointr1(com) struct com_s *com; { - u_char line_status; - u_char modem_status; - u_char *ioptr; - u_char recv_data; - bool_t donesomething; - - donesomething = FALSE; -#endif /* COM_MULTIPORT */ - + 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); /* input event? (check first to help avoid overruns) */ while (line_status & LSR_RCV_MASK) { /* break/unnattached error bits or real input? */ -#ifdef COM_MULTIPORT - donesomething = TRUE; -#endif /* COM_MULTIPORT */ if (!(line_status & LSR_RXRDY)) recv_data = 0; 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 @@ -917,6 +1014,10 @@ comintr1(com) CE_RECORD(com, CE_INTERRUPT_BUF_OVERFLOW); else { ++com_events; +#if 0 /* for testing input latency vs efficiency */ +if (com->iptr - com->ibuf == 8) + setsofttty(); +#endif ioptr[0] = recv_data; ioptr[CE_INPUT_OFFSET] = line_status; com->iptr = ++ioptr; @@ -945,14 +1046,11 @@ comintr1(com) * UARTs mess them up, and it's easy to remember the * previous bits and calculate the delta. */ -#ifdef COM_MULTIPORT - donesomething = TRUE; -#endif /* COM_MULTIPORT */ com->last_modem_status = modem_status; if (!(com->state & CS_CHECKMSR)) { com_events += LOTS_OF_EVENTS; com->state |= CS_CHECKMSR; - schedsoftcom(); + setsofttty(); } /* handle CTS change immediately for crisp flow ctl */ @@ -967,36 +1065,43 @@ comintr1(com) /* output queued and everything ready? */ if (line_status & LSR_TXRDY && com->state >= (CS_ODEVREADY | CS_BUSY | CS_TTGO)) { -#ifdef COM_MULTIPORT - donesomething = TRUE; -#endif /* COM_MULTIPORT */ ioptr = com->optr; - outb(com->data_port, *ioptr); - ++com->bytes_out; - com->optr = ++ioptr; + if (com->tx_fifo_size > 1) { + u_int ocount; + + ocount = com->obufend - ioptr; + if (ocount > com->tx_fifo_size) + ocount = com->tx_fifo_size; + com->bytes_out += ocount; + do + outb(com->data_port, *ioptr++); + while (--ocount != 0); + } else { + outb(com->data_port, *ioptr++); + ++com->bytes_out; + } + com->optr = ioptr; if (ioptr >= com->obufend) { /* output just completed */ com_events += LOTS_OF_EVENTS; com->state ^= (CS_ODONE | CS_BUSY); - schedsoftcom(); /* handle at high level ASAP */ + setsofttty(); /* handle at high level ASAP */ } } /* finished? */ +#ifndef COM_MULTIPORT if ((inb(com->int_id_port) & IIR_IMASK) == IIR_NOPEND) -#ifdef COM_MULTIPORT - return (donesomething); -#else - return; #endif /* COM_MULTIPORT */ + return; } } static int tiocm_xxx2mcr(tiocm_xxx) - int tiocm_xxx; + int tiocm_xxx; { - int mcr; + int mcr; mcr = 0; if (tiocm_xxx & TIOCM_DTR) @@ -1019,19 +1124,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); if (error >= 0) return (error); - iobase = com->iobase; s = spltty(); switch (cmd) { @@ -1042,19 +1203,19 @@ sioioctl(dev, cmd, data, flag, p) outb(iobase + com_cfcr, com->cfcr_image &= ~CFCR_SBREAK); break; case TIOCSDTR: - (void) commctl(com, MCR_DTR, DMBIS); + commctl(com, MCR_DTR, DMBIS); break; case TIOCCDTR: - (void) commctl(com, MCR_DTR, DMBIC); + commctl(com, MCR_DTR, DMBIC); break; case TIOCMSET: - (void) commctl(com, tiocm_xxx2mcr(*(int *)data), DMSET); + commctl(com, tiocm_xxx2mcr(*(int *)data), DMSET); break; case TIOCMBIS: - (void) commctl(com, tiocm_xxx2mcr(*(int *)data), DMBIS); + commctl(com, tiocm_xxx2mcr(*(int *)data), DMBIS); break; case TIOCMBIC: - (void) commctl(com, tiocm_xxx2mcr(*(int *)data), DMBIC); + commctl(com, tiocm_xxx2mcr(*(int *)data), DMBIC); break; case TIOCMGET: tiocm_xxx = TIOCM_LE; /* XXX - always enabled while open */ @@ -1070,49 +1231,33 @@ sioioctl(dev, cmd, data, flag, p) tiocm_xxx |= TIOCM_CD; if (msr & MSR_DSR) tiocm_xxx |= TIOCM_DSR; - /* XXX - MSR_TERI is too volatile. */ + /* + * XXX - MSR_RI is naturally volatile, and we make MSR_TERI + * more volatile by reading the modem status a lot. Perhaps + * we should latch both bits until the status is read here. + */ if (msr & (MSR_RI | MSR_TERI)) tiocm_xxx |= TIOCM_RI; *(int *)data = tiocm_xxx; break; -#ifdef COM_BIDIR - case TIOCMSBIDIR: - /* must be root to set bidir. capability */ - if (p->p_ucred->cr_uid != 0) - return(EPERM); - - /* if it's the console, can't do it */ - if (UNIT(dev) == comconsole) - return(ENOTTY); - - /* 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) - return(EBUSY); */ - - com->bidir = *(int *)data; - break; - case TIOCMGBIDIR: - *(int *)data = com->bidir; - break; -#endif /* COM_BIDIR */ case TIOCMSDTRWAIT: - /* must be root (XXX why?) */ - if (p->p_ucred->cr_uid != 0) - return(EPERM); - - /* if it's the console, can't do it (XXX why?) */ - if (UNIT(dev) == comconsole) - return(ENOTTY); - - com->dtr_wait = *(int *)data; + /* 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); + } + com->dtr_wait = *(int *)data * 100 / hz; break; case TIOCMGDTRWAIT: *(int *)data = com->dtr_wait; break; +#ifdef TIOCTIMESTAMP + case TIOCTIMESTAMP: + com->do_timestamp = TRUE; + *(struct timeval *)data = com->timestamp; + break; +#endif default: splx(s); return (ENOTTY); @@ -1133,34 +1278,24 @@ comflush(com) com_events -= LOTS_OF_EVENTS; com->state &= ~(CS_ODONE | CS_BUSY); enable_intr(); - rbp = &com->tp->t_out; + rbp = com->tp->t_out; rbp->rb_hd += com->ocount; rbp->rb_hd = RB_ROLLOVER(rbp, rbp->rb_hd); com->ocount = 0; com->tp->t_state &= ~TS_BUSY; } -static void -compoll() +void +siopoll() { - static bool_t awake = FALSE; - struct com_s *com; - 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 = 0; + u_char *buf; + struct com_s *com; u_char *ibuf; int incc; struct tty *tp; @@ -1169,11 +1304,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)) + if (com->iptr == (ibuf = com->ibuf)) { + buf = NULL; /* not used, but compiler can't tell */ incc = 0; - else { + } 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; @@ -1191,7 +1340,15 @@ repeat: * of input, so enable RTS if it is now disabled and * there is room in the high-level buffer. */ - if (!(com->mcr_image & MCR_RTS) + /* + * 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)) outb(com->modem_ctl_port, com->mcr_image |= MCR_RTS); @@ -1209,40 +1366,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; - u_int delta_error_counts[CE_NTYPES]; - int errnum; - u_long total; + u_int delta; + int errnum; + u_long total; - disable_intr(); - bcopy(com->delta_error_counts, delta_error_counts, - sizeof delta_error_counts); - bzero(com->delta_error_counts, - sizeof delta_error_counts); - enable_intr(); for (errnum = 0; errnum < CE_NTYPES; ++errnum) { - delta = delta_error_counts[errnum]; - if (delta != 0) { - total = - com->error_counts[errnum] += delta; + disable_intr(); + delta = com->delta_error_counts[errnum]; + com->delta_error_counts[errnum] = 0; + 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]); } } } @@ -1257,7 +1419,7 @@ repeat: if (incc <= 0 || !(tp->t_state & TS_ISOPEN)) continue; if (com->state & CS_RTS_IFLOW - && RB_LEN(&tp->t_raw) + incc >= RB_I_HIGH_WATER + && RB_LEN(tp->t_raw) + incc >= RB_I_HIGH_WATER && !(tp->t_state & TS_RTS_IFLOW) /* * XXX - need RTS flow control for all line disciplines. @@ -1284,7 +1446,7 @@ repeat: tk_rawcc += incc; tp->t_rawcc += incc; com->delta_error_counts[CE_TTY_BUF_OVERFLOW] - += incc - rb_write(&tp->t_raw, (char *) buf, + += incc - rb_write(tp->t_raw, (char *) buf, incc); ttwakeup(tp); if (tp->t_state & TS_TTSTOP @@ -1294,8 +1456,7 @@ repeat: tp->t_lflag &= ~FLUSHO; ttstart(tp); } - } - else { + } else { do { u_char line_status; int recv_data; @@ -1321,8 +1482,6 @@ repeat: } if (com_events >= LOTS_OF_EVENTS) goto repeat; - splx(s); - awake = FALSE; } static int @@ -1341,19 +1500,20 @@ comparam(tp, t) /* check requested parameters */ divisor = ttspeedtab(t->c_ospeed, comspeedtab); - if (divisor < 0 || t->c_ispeed != 0 && t->c_ispeed != t->c_ospeed) + if (t->c_ispeed == 0) + t->c_ispeed = t->c_ospeed; + if (divisor < 0 || divisor > 0 && t->c_ispeed != t->c_ospeed) 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(); - if (divisor == 0) { - (void) commctl(com, MCR_RTS, DMSET); /* hang up line */ - splx(s); - return (0); - } + if (divisor == 0) + commctl(com, MCR_DTR, DMBIC); /* hang up line */ + else + commctl(com, MCR_DTR, DMBIS); cflag = t->c_cflag; switch (cflag & CSIZE) { case CS5: @@ -1377,12 +1537,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(); @@ -1391,8 +1567,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(); @@ -1408,17 +1584,19 @@ retry: /* * XXX - clearing CS_TTGO is not sufficient to stop further output, - * because compoll() calls comstart() which set it again because - * TS_TTSTOP is set. Clearing TS_TTSTOP would not be sufficient, - * for similar reasons. + * because siopoll() calls comstart() which usually sets it again + * because TS_TTSTOP is clear. Setting TS_TTSTOP would not be + * sufficient, for similar reasons. */ if ((inb(com->line_status_port) & (LSR_TSRE | LSR_TXRDY)) - != (LSR_TSRE | LSR_TXRDY)) + != (LSR_TSRE | LSR_TXRDY)) goto retry; - outb(iobase + com_cfcr, cfcr | CFCR_DLAB); - outb(iobase + com_dlbl, divisor & 0xFF); - outb(iobase + com_dlbh, (u_int) divisor >> 8); + if (divisor != 0) { + outb(iobase + com_cfcr, cfcr | CFCR_DLAB); + outb(iobase + com_dlbl, divisor & 0xFF); + outb(iobase + com_dlbh, (u_int) divisor >> 8); + } outb(iobase + com_cfcr, com->cfcr_image = cfcr); if (!(tp->t_state & TS_TTSTOP)) com->state |= CS_TTGO; @@ -1430,7 +1608,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; @@ -1440,7 +1618,14 @@ retry: com->state &= ~CS_ODEVREADY; } - siointr(unit); /* recover from fiddling with CS_TTGO */ + /* + * Recover from fiddling with CS_TTGO. We used to call siointr1() + * unconditionally, but that defeated the careful discarding of + * stale input in sioopen(). + */ + if (com->state >= (CS_BUSY | CS_TTGO)) + siointr1(com); + enable_intr(); splx(s); return (0); @@ -1454,7 +1639,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(); @@ -1465,38 +1650,32 @@ comstart(tp) if (tp->t_state & TS_RTS_IFLOW) { if (com->mcr_image & MCR_RTS && com->state & CS_RTS_IFLOW) outb(com->modem_ctl_port, com->mcr_image &= ~MCR_RTS); - } - else { + } 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(); - siointr(unit); + siointr1(com); enable_intr(); - } - else if (RB_LEN(&tp->t_out) != 0) { + } else if (RB_LEN(tp->t_out) != 0) { tp->t_state |= TS_BUSY; - com->ocount = RB_CONTIGGET(&tp->t_out); + 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; - siointr(unit); /* fake interrupt to start output */ + siointr1(com); /* fake interrupt to start output */ enable_intr(); } out: @@ -1510,7 +1689,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(); @@ -1525,22 +1704,28 @@ siostop(tp, rw) enable_intr(); } -static int +int +sioselect(dev, rw, p) + dev_t dev; + int rw; + struct proc *p; +{ + if (minor(dev) & CONTROL_MASK) + return (ENODEV); + return (ttselect(dev & ~MINOR_MAGIC_MASK, rw, p)); +} + +static void commctl(com, bits, how) struct com_s *com; int bits; int how; { -#ifdef COM_MULTIPORT - if (how != DMBIC && !com->multiport) -#else - if (how != DMBIC) -#endif - bits |= MCR_IENABLE; disable_intr(); switch (how) { case DMSET: - outb(com->modem_ctl_port, com->mcr_image = bits); + outb(com->modem_ctl_port, + com->mcr_image = bits | (com->mcr_image & MCR_IENABLE)); break; case DMBIS: outb(com->modem_ctl_port, com->mcr_image |= bits); @@ -1550,47 +1735,123 @@ commctl(com, bits, how) break; } enable_intr(); - return (bits); } static void comwakeup(chan, ticks) - caddr_t chan; - int ticks; + caddr_t chan; + int ticks; { - struct com_s *com; - int unit; + int unit; + + timeout(comwakeup, (caddr_t)NULL, hz / 100); - timeout(comwakeup, (caddr_t) NULL, 1); - if (com_events != 0) - /* schedule compoll() to run when the cpl allows */ - schedsoftcom(); + if (com_events != 0) { + int s; + + s = splsofttty(); + siopoll(); + splx(s); + } /* recover from lost output interrupts */ for (unit = 0; unit < NSIO; ++unit) { + struct com_s *com; + com = com_addr(unit); if (com != NULL && com->state >= (CS_BUSY | CS_TTGO)) { disable_intr(); - siointr(unit); + siointr1(com); enable_intr(); } } - return; -} - -void -softsio1() -{ - compoll(); } /* - * Following are all routines needed for COM to act as console - * XXX - not tested in this version - * XXX - check that the corresponding serial interrupts are never enabled + * Following are all routines needed for SIO to act as console */ #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; @@ -1598,20 +1859,19 @@ 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 = CONUNIT; - com_addr(unit) = &com_structs[unit]; - com_addr(unit)->iobase = CONADDR; + unit = DEV_TO_UNIT(CONUNIT); + siocniobase = CONADDR; /* make sure hardware exists? XXX */ /* initialize required fields */ cp->cn_dev = makedev(commajor, unit); - cp->cn_tp = &sio_tty[unit]; #ifdef COMCONSOLE cp->cn_pri = CN_REMOTE; /* Force a serial port console */ #else @@ -1623,39 +1883,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_data, rate & 0xFF); - outb(iobase + com_ier, rate >> 8); - outb(iobase + com_cfcr, CFCR_8BITS); - /* - * 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); - outb(iobase + com_fifo, - FIFO_ENABLE | FIFO_RCV_RST | FIFO_XMT_RST | FIFO_TRIGGER_14); - (void) inb(iobase + com_iir); - splx(s); + comconsole = DEV_TO_UNIT(cp->cn_dev); } int @@ -1665,13 +1897,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); } @@ -1681,80 +1915,15 @@ 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) { - (void) 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); - splx(s); -} - -/* - * 10 Feb 93 Jordan K. Hubbard Added select code - * 27 May 93 Rodney W. Grimes Stole the select code from com.c.pl5 - * - * XXX - the standard com.c never needed this, but we need it because - * ttselect() can't determine the tty struct because stuff is encoded in the - * high bit of the minor number. - */ - -int -sioselect(dev, rw, p) - dev_t dev; - int rw; - struct proc *p; -{ - struct tty *tp = &sio_tty[UNIT(dev)]; - int nread; - int s = spltty(); - struct proc *selp; - - switch (rw) { - - case FREAD: - nread = ttnread(tp); - if (nread > 0 || - ((tp->t_cflag&CLOCAL) == 0 && (tp->t_state&TS_CARR_ON) == 0)) - goto win; - if (tp->t_rsel && (selp = pfind(tp->t_rsel)) && selp->p_wchan == (caddr_t)&selwait) - tp->t_state |= TS_RCOLL; - else - tp->t_rsel = p->p_pid; - break; - - case FWRITE: - if (RB_LEN(&tp->t_out) <= tp->t_lowat) - goto win; - if (tp->t_wsel && (selp = pfind(tp->t_wsel)) && selp->p_wchan == (caddr_t)&selwait) - tp->t_state |= TS_WCOLL; - else - tp->t_wsel = p->p_pid; - break; - } - splx(s); - return (0); - win: + s = spltty(); + siocnopen(&sp); + siocntxwait(); + outb(siocniobase + com_data, c); + siocnclose(&sp); splx(s); - return (1); } #endif /* NSIO > 0 */