* Copyright (c) 1991 The Regents of the University of California.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* from: @(#)com.c 7.5 (Berkeley) 5/16/91
* $Id: sio.c,v 1.43 1994/04/03 11:41:11 ache Exp $
* Serial driver, based on 386BSD-0.1 com driver.
* Mostly rewritten to use pseudo-DMA.
* Works for National Semiconductor NS8250-NS16550AF UARTs.
* COM driver, based on HP dca driver.
#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)
#define TTY_BI TTY_FE /* XXX */
#define TTY_OE TTY_PE /* XXX */
#define CALLOUT(x) (minor(x) & COM_CALLOUT_MASK)
#define COM_CALLOUT_MASK 0x80
#define COM_MINOR_MAGIC_MASK 0x80
#define COM_MINOR_MAGIC_MASK 0
#define UNIT(x) (minor(x) & ~COM_MINOR_MAGIC_MASK)
/* checks in flags for multiport and which is multiport "master chip"
#define COM_ISMULTIPORT(dev) ((dev)->id_flags & 0x01)
#define COM_MPMASTER(dev) (((dev)->id_flags >> 8) & 0x0ff)
#endif /* COM_MULTIPORT */
#define COM_NOFIFO(dev) ((dev)->id_flags & 0x02)
* 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
#define com_scr 7 /* scratch register for 16450-16550 (R/W) */
#define OLD_INTERRUPT_HANDLING /* XXX FreeBSD-1.1 and earlier */
#define setsofttty() (ipending |= 1 << 4) /* XXX requires owning IRQ4 */
extern u_int ipending
; /* XXX */
void softsio1
__P((void));
* Input buffer watermarks.
* The external device is asked to stop sending when the buffer exactly reaches
* high water, or when the high level requests it.
* The high level is notified immediately (rather than at a later clock tick)
* when this watermark is reached.
* The buffer size is chosen so the watermark should almost never be reached.
* The low watermark is invisibly 0 since the buffer is always emptied all at
#define RS_IHIGHWATER (3 * RS_IBUFSIZE / 4)
* (CS_BUSY | CS_TTGO) and (CS_BUSY | CS_TTGO | CS_ODEVREADY) must be higher
* than the other bits so that they can be tested as a group without masking
* The following com and tty flags correspond closely:
* TS_BUSY = CS_BUSY (maintained by comstart() and comflush())
* CS_TTGO = ~TS_TTSTOP (maintained by comstart() and siostop())
* CS_CTS_OFLOW = CCTS_OFLOW (maintained by comparam())
* CS_RTS_IFLOW = CRTS_IFLOW (maintained by comparam())
* Bug: I think TIOCSETA doesn't clear TS_TTSTOP when it clears IXON.
#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_ODONE 4 /* output completed */
#define CS_RTS_IFLOW 8 /* use RTS input flow control */
static char *error_desc
[] = {
#define CE_INTERRUPT_BUF_OVERFLOW 1
"interrupt-level buffer overflow",
#define CE_TTY_BUF_OVERFLOW 2
"tty-level buffer overflow",
#define CE_RECORD(com, errnum) (++(com)->delta_error_counts[errnum])
/* types. XXX - should be elsewhere */
typedef u_int Port_t
; /* hardware port */
typedef u_char bool_t
; /* boolean */
/* com device structure */
u_char state
; /* miscellaneous flag bits */
u_char cfcr_image
; /* copy of value written to CFCR */
bool_t hasfifo
; /* nonzero for 16550 UARTs */
u_char mcr_image
; /* copy of value written to MCR */
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? */
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) */
* The high level of the driver never reads status registers directly
* because there would be too many side effects to handle conveniently.
* Instead, it reads copies of the registers stored here by the
u_char last_modem_status
; /* last MSR read by intr handler */
u_char prev_modem_status
; /* last MSR handled by high level */
u_char
*ibuf
; /* start of input buffer */
u_char
*ibufend
; /* end of input buffer */
u_char
*ihighwater
; /* threshold in input buffer */
u_char
*iptr
; /* next free spot in input buffer */
u_char
*obufend
; /* end of output buffer */
int ocount
; /* original count for current output */
u_char
*optr
; /* next char to output */
Port_t data_port
; /* i/o ports */
Port_t modem_status_port
;
struct tty
*tp
; /* cross reference */
struct timeval timestamp
;
u_long bytes_in
; /* statistics */
u_int delta_error_counts
[CE_NTYPES
];
u_int error_counts
[CE_NTYPES
];
* Ping-pong input buffers. The extra factor of 2 in the sizes is
* to allow for an error byte for each input byte.
#define CE_INPUT_OFFSET RS_IBUFSIZE
u_char ibuf1
[2 * RS_IBUFSIZE
];
u_char ibuf2
[2 * RS_IBUFSIZE
];
* The public functions in the com module ought to be declared in a com-driver
#define Dev_t int /* promoted dev_t */
/* 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
,
int sioclose
__P((Dev_t dev
, int fflag
, int devtype
,
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
));
int sioselect
__P((Dev_t dev
, int rw
, struct proc
*p
));
#define siostrategy nostrategy
/* Console device entry points. */
int siocngetc
__P((Dev_t dev
));
void siocninit
__P((struct consdev
*cp
));
void siocnprobe
__P((struct consdev
*cp
));
void siocnputc
__P((Dev_t dev
, int c
));
static int sioattach
__P((struct isa_device
*dev
));
static void comflush
__P((struct com_s
*com
));
static void comhardclose
__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 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
;
struct isa_driver siodriver
= {
sioprobe
, sioattach
, "sio"
static int comconsole
= COMCONSOLE
;
static int comconsole
= -1;
static speed_t comdefaultrate
= TTYDEF_SPEED
;
static u_int com_events
; /* input chars + weighted output completions */
#define TB_OUT(tp) (&(tp)->t_out)
#define TB_RAW(tp) (&(tp)->t_raw)
struct tty sio_tty
[NSIO
];
#define TB_OUT(tp) ((tp)->t_out)
#define TB_RAW(tp) ((tp)->t_raw)
struct tty
*sio_tty
[NSIO
];
extern struct tty
*constty
;
extern int tk_nin
; /* XXX */
extern int tk_rawcc
; /* XXX */
#include "machine/remote-sl.h"
extern int kgdb_debug_init
;
static struct speedtab comspeedtab
[] = {
/* XXX - configure this list */
static Port_t likely_com_ports
[] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8, };
static bool_t already_init
;
* 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.
for (com_ptr
= likely_com_ports
;
com_ptr
< &likely_com_ports
[sizeof likely_com_ports
/ sizeof likely_com_ports
[0]];
outb(*com_ptr
+ com_mcr
, 0);
* We don't want to get actual interrupts, just masked ones.
* Interrupts from this line should already be masked in the ICU,
* but mask them in the processor as well in case there are some
* (misconfigured) shared interrupts.
* 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:
* 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_iir
) & IIR_IMASK
) != IIR_TXRDY
|| (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
* 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
|| (inb(iobase
+ com_iir
) & IIR_IMASK
) != IIR_NOPEND
)
outb(iobase
+ com_mcr
, 0);
static bool_t comwakeup_started
= FALSE
;
iobase
= isdp
->id_iobase
;
* sioprobe() has initialized the device registers as follows:
* It is most important that CFCR_DLAB is off, so that the
* data port is not hidden when we enable interrupts.
* Interrupts are only enabled when the line is open.
* Keeping MCR_DTR and MCR_RTS off might stop the external
* device from sending before we are ready.
com
= &com_structs
[unit
]; /* XXX malloc it */
com
->cfcr_image
= CFCR_8BITS
;
com
->mcr_image
= MCR_IENABLE
;
com
->iptr
= com
->ibuf
= com
->ibuf1
;
com
->ibufend
= com
->ibuf1
+ RS_IBUFSIZE
;
com
->ihighwater
= com
->ibuf1
+ RS_IHIGHWATER
;
com
->data_port
= iobase
+ com_data
;
com
->int_id_port
= iobase
+ com_iir
;
com
->modem_ctl_port
= iobase
+ com_mcr
;
com
->line_status_port
= iobase
+ com_lsr
;
com
->modem_status_port
= iobase
+ com_msr
;
com
->tp
= &sio_tty
[unit
];
/* attempt to determine UART type */
printf("sio%d: type", unit
);
if (!COM_ISMULTIPORT(isdp
))
scr
= inb(iobase
+ com_scr
);
outb(iobase
+ com_scr
, 0xa5);
scr1
= inb(iobase
+ com_scr
);
outb(iobase
+ com_scr
, 0x5a);
scr2
= inb(iobase
+ com_scr
);
outb(iobase
+ com_scr
, scr
);
if (scr1
!= 0xa5 || scr2
!= 0x5a) {
outb(iobase
+ com_fifo
, FIFO_ENABLE
| FIFO_TRIGGER_14
);
switch (inb(com
->int_id_port
) & IIR_FIFO_MASK
) {
printf(" fifo disabled");
outb(iobase
+ com_fifo
, 0);
if (COM_ISMULTIPORT(isdp
)) {
struct isa_device
*masterdev
;
/* 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
,
outb(masterdev
->id_iobase
+ com_scr
, 0x80);
#endif /* COM_MULTIPORT */
if (kgdb_dev
== makedev(commajor
, unit
)) {
kgdb_dev
= -1; /* can't debug over console port */
* 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
);
* Print prefix of device name,
* let kgdb_connect print the rest.
printf("sio%d: kgdb enabled\n", unit
);
if (!comwakeup_started
) {
comwakeup((caddr_t
) NULL
, 0);
comwakeup_started
= TRUE
;
sioopen(dev
, flag
, mode
, p
)
bool_t got_status
= FALSE
;
if ((u_int
) unit
>= NSIO
|| (com
= com_addr(unit
)) == NULL
)
/* if it's a callout device, and bidir not possible on that dev, die */
if (callout
&& !(com
->bidir
))
sio_tty
[unit
] = ttymalloc(sio_tty
[unit
]);
tp
= com
->tp
= sio_tty
[unit
];
/* if it's bidirectional, we've gotta deal with it... */
/* it's ours. lock it down, and set it up */
/* it's busy, outgoing. wait, if possible */
error
= tsleep((caddr_t
)&com
->active_out
,
/* if there was an error, take off. */
/* else take it from the top */
com
->last_modem_status
= inb(com
->modem_status_port
);
if (com
->prev_modem_status
& MSR_DCD
/* there's a carrier on the line; we win */
/* there is no carrier on the line */
/* can't wait; let it open */
/* XXX - bring up RTS earlier? */
commctl(com
, MCR_DTR
| MCR_RTS
, DMSET
);
outb(com
->iobase
+ com_ier
, IER_EMSC
);
error
= tsleep((caddr_t
)&com
->active_in
,
/* if not active, turn intrs and DTR off */
outb(com
->iobase
+ com_ier
, 0);
commctl(com
, MCR_DTR
, DMBIC
);
/* if there was an error, take off. */
/* else take it from the top */
if (!(tp
->t_state
& TS_ISOPEN
)) {
* We don't use all the flags from <sys/ttydefaults.h>
* 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
tp
->t_cflag
= CREAD
| CS8
;
if (com
->bidir
&& !callout
)
tp
->t_ispeed
= tp
->t_ospeed
= comdefaultrate
;
if (unit
== comconsole
) {
tp
->t_iflag
= TTYDEF_IFLAG
;
tp
->t_oflag
= TTYDEF_OFLAG
;
tp
->t_cflag
= TTYDEF_CFLAG
;
tp
->t_lflag
= TTYDEF_LFLAG
;
* XXX the full state after a first open() needs to be
* programmable and separate for callin and callout.
commctl(com
, MCR_DTR
| MCR_RTS
, DMSET
);
error
= comparam(tp
, &tp
->t_termios
);
/* (re)enable and drain FIFO */
outb(iobase
+ com_fifo
, FIFO_ENABLE
| FIFO_TRIGGER
| FIFO_RCV_RST
| FIFO_XMT_RST
);
(void) inb(com
->line_status_port
);
(void) inb(com
->data_port
);
com
->last_modem_status
= inb(com
->modem_status_port
);
outb(iobase
+ com_ier
, IER_ERXRDY
| IER_ETXRDY
| IER_ERLS
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) {
while (!(flag
& O_NONBLOCK
) && !(tp
->t_cflag
& CLOCAL
)
/* 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.
&& !(tp
->t_state
& TS_CARR_ON
)) {
error
= ttysleep(tp
, (caddr_t
)TB_RAW(tp
), TTIPRI
| PCATCH
,
error
= (*linesw
[tp
->t_line
].l_open
)(dev
, tp
, 0);
wakeup((caddr_t
) &com
->active_in
);
* 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
))
tp
->t_state
&= ~TS_WOPEN
;
sioclose(dev
, flag
, mode
, p
)
com
= com_addr(UNIT(dev
));
(*linesw
[tp
->t_line
].l_close
)(tp
, flag
);
siostop(tp
, FREAD
| FWRITE
);
unit
= com
- &com_structs
[0];
outb(iobase
+ com_cfcr
, com
->cfcr_image
&= ~CFCR_SBREAK
);
/* do not disable interrupts or hang up if debugging */
if (kgdb_dev
!= makedev(commajor
, unit
))
outb(iobase
+ com_ier
, 0);
if (tp
->t_cflag
& HUPCL
|| tp
->t_state
& TS_WOPEN
* 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. And we shouldn't look at DCD if
* CLOCAL is set (here or for the dialin device ...).
* When the termios state is reinitialized for initial
* opens, the correct CLOCAL bit will be
* ((the bit now) & (the initial bit)).
&& !(com
->prev_modem_status
& MSR_DCD
) && !FAKE_DCD(unit
)
|| !(tp
->t_state
& TS_ISOPEN
)) {
commctl(com
, MCR_RTS
, DMSET
);
* Uninterruptible sleep since we want to
* XXX - delay in open() (if necessary),
tsleep((caddr_t
)&com
->dtr_wait
, TTIPRI
,
"sioclose", com
->dtr_wait
);
com
->active
= com
->active_in
= com
->active_out
= FALSE
;
/* wakeup sleepers who are waiting for out to finish */
wakeup((caddr_t
) &com
->active_out
);
struct tty
*tp
= com_addr(UNIT(dev
))->tp
;
return ((*linesw
[tp
->t_line
].l_read
)(tp
, uio
, flag
));
struct tty
*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
* is not the console. In that situation we don't need/want the X
* server taking over the console.
if (constty
&& unit
== comconsole
)
return ((*linesw
[tp
->t_line
].l_write
)(tp
, uio
, flag
));
/* Interrupt routine for timekeeping purposes */
microtime(&intr_timestamp
);
siointr1(com_addr(unit
));
#else /* COM_MULTIPORT */
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
possibly_more_intrs
= FALSE
;
for (unit
= 0; unit
< NSIO
; ++unit
) {
&& (inb(com
->int_id_port
) & IIR_IMASK
)
possibly_more_intrs
= TRUE
;
} while (possibly_more_intrs
);
#endif /* COM_MULTIPORT */
/* XXX a little bloat here... */
com
->timestamp
= intr_timestamp
;
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? */
if (!(line_status
& LSR_RXRDY
))
recv_data
= inb(com
->data_port
);
/* XXX reduce SLIP input latency */
if (recv_data
== FRAME_END
)
/* trap into kgdb? (XXX - needs testing and optim) */
if (recv_data
== FRAME_END
&& !(com
->tp
->t_state
& TS_ISOPEN
)
&& kgdb_dev
== makedev(commajor
, unit
)) {
if (ioptr
>= com
->ibufend
)
CE_RECORD(com
, CE_INTERRUPT_BUF_OVERFLOW
);
#if 0 /* for testing input latency vs efficiency */
if (com
->iptr
- com
->ibuf
== 8)
ioptr
[CE_INPUT_OFFSET
] = line_status
;
if (ioptr
== com
->ihighwater
&& com
->state
& CS_RTS_IFLOW
)
outb(com
->modem_ctl_port
,
com
->mcr_image
&= ~MCR_RTS
);
/* XXX - move this out of isr */
if (line_status
& LSR_OE
)
CE_RECORD(com
, CE_OVERRUN
);
* "& 0x7F" is to avoid the gcc-1.40 generating a slow
* jump from the top of the loop to here
line_status
= inb(com
->line_status_port
) & 0x7F;
/* modem status change? (always check before doing output) */
modem_status
= inb(com
->modem_status_port
);
if (modem_status
!= com
->last_modem_status
) {
* Schedule high level to handle DCD changes. Note
* that we don't use the delta bits anywhere. Some
* UARTs mess them up, and it's easy to remember the
* previous bits and calculate the delta.
com
->last_modem_status
= modem_status
;
if (!(com
->state
& CS_CHECKMSR
)) {
com_events
+= LOTS_OF_EVENTS
;
com
->state
|= CS_CHECKMSR
;
/* handle CTS change immediately for crisp flow ctl */
if (com
->state
& CS_CTS_OFLOW
) {
if (modem_status
& MSR_CTS
)
com
->state
|= CS_ODEVREADY
;
com
->state
&= ~CS_ODEVREADY
;
/* output queued and everything ready? */
if (line_status
& LSR_TXRDY
&& com
->state
>= (CS_ODEVREADY
| CS_BUSY
| CS_TTGO
)) {
if (com
->tx_fifo_size
> 1) {
ocount
= com
->obufend
- ioptr
;
if (ocount
> com
->tx_fifo_size
)
ocount
= com
->tx_fifo_size
;
com
->bytes_out
+= ocount
;
outb(com
->data_port
, *ioptr
++);
outb(com
->data_port
, *ioptr
++);
if (ioptr
>= com
->obufend
) {
/* output just completed */
com_events
+= LOTS_OF_EVENTS
;
com
->state
^= (CS_ODONE
| CS_BUSY
);
setsofttty(); /* handle at high level ASAP */
if ((inb(com
->int_id_port
) & IIR_IMASK
) == IIR_NOPEND
)
#endif /* COM_MULTIPORT */
if (tiocm_xxx
& TIOCM_DTR
)
if (tiocm_xxx
& TIOCM_RTS
)
sioioctl(dev
, cmd
, data
, flag
, p
)
com
= com_addr(UNIT(dev
));
error
= (*linesw
[tp
->t_line
].l_ioctl
)(tp
, cmd
, data
, flag
);
error
= ttioctl(tp
, cmd
, data
, flag
);
/* XXX: plug security hole while sticky bits not yet implemented */
if (com
->bidir
&& com
->active_in
&& p
->p_ucred
->cr_uid
!= 0)
outb(iobase
+ com_cfcr
, com
->cfcr_image
|= CFCR_SBREAK
);
outb(iobase
+ com_cfcr
, com
->cfcr_image
&= ~CFCR_SBREAK
);
commctl(com
, MCR_DTR
, DMBIS
);
commctl(com
, MCR_DTR
, DMBIC
);
commctl(com
, tiocm_xxx2mcr(*(int *)data
), DMSET
);
commctl(com
, tiocm_xxx2mcr(*(int *)data
), DMBIS
);
commctl(com
, tiocm_xxx2mcr(*(int *)data
), DMBIC
);
tiocm_xxx
= TIOCM_LE
; /* XXX - always enabled while open */
msr
= com
->prev_modem_status
;
* 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
))
*(int *)data
= tiocm_xxx
;
/* must be root to set bidir. capability */
error
= suser(p
->p_ucred
, &p
->p_acflag
);
/* if it's the console, can't do it (XXX why?) */
if (UNIT(dev
) == comconsole
) {
/* 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 */
com
->bidir
= *(int *)data
;
*(int *)data
= com
->bidir
;
/* must be root since the wait applies to following logins */
error
= suser(p
->p_ucred
, &p
->p_acflag
);
/* if it's the console, can't do it (XXX why?) */
if (UNIT(dev
) == comconsole
) {
com
->dtr_wait
= *(int *)data
;
*(int *)data
= com
->dtr_wait
;
com
->do_timestamp
= TRUE
;
*(struct timeval
*)data
= com
->timestamp
;
/* cancel pending output */
if (com
->state
& CS_ODONE
)
com_events
-= LOTS_OF_EVENTS
;
com
->state
&= ~(CS_ODONE
| CS_BUSY
);
rbp
->rb_hd
+= com
->ocount
;
rbp
->rb_hd
= RB_ROLLOVER(rbp
, rbp
->rb_hd
);
com
->tp
->t_state
&= ~TS_BUSY
;
#ifdef OLD_INTERRUPT_HANDLING
static bool_t awake
= FALSE
;
#ifdef OLD_INTERRUPT_HANDLING
for (unit
= 0; unit
< NSIO
; ++unit
) {
/* switch the role of the low-level input buffers */
if (com
->iptr
== (ibuf
= com
->ibuf
)) {
buf
= NULL
; /* not used, but compiler can't tell */
com
->ibufend
= ibuf
+ RS_IBUFSIZE
;
com
->ihighwater
= ibuf
+ RS_IHIGHWATER
;
* There is now room for another low-level buffer full
* 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
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
);
if (com
->state
& CS_CHECKMSR
) {
u_char delta_modem_status
;
delta_modem_status
= com
->last_modem_status
^ com
->prev_modem_status
;
com
->prev_modem_status
= com
->last_modem_status
;
com_events
-= LOTS_OF_EVENTS
;
com
->state
&= ~CS_CHECKMSR
;
if (delta_modem_status
& MSR_DCD
&& !FAKE_DCD(unit
)) {
if (com
->prev_modem_status
& MSR_DCD
) {
(*linesw
[tp
->t_line
].l_modem
)(tp
, 1);
wakeup((caddr_t
) &com
->active_in
);
(*linesw
[tp
->t_line
].l_modem
)(tp
, 0);
for (errnum
= 0; errnum
< CE_NTYPES
; ++errnum
) {
delta
= com
->delta_error_counts
[errnum
];
com
->delta_error_counts
[errnum
] = 0;
com
->error_counts
[errnum
] += delta
;
"sio%d: %u more %s%s (total %lu)\n",
unit
, delta
, error_desc
[errnum
],
delta
== 1 ? "" : "s", total
);
if (com
->state
& CS_ODONE
) {
/* XXX - why isn't the table used for t_line == 0? */
(*linesw
[tp
->t_line
].l_start
)(tp
);
if (incc
<= 0 || !(tp
->t_state
& TS_ISOPEN
))
if (com
->state
& CS_RTS_IFLOW
&& RB_LEN(TB_RAW(tp
)) + incc
>= RB_I_HIGH_WATER
&& !(tp
->t_state
& TS_RTS_IFLOW
)
* XXX - need RTS flow control for all line disciplines.
* Only have it in standard one now.
&& linesw
[tp
->t_line
].l_rint
== ttyinput
) {
tp
->t_state
|= TS_RTS_IFLOW
;
* Avoid the grotesquely inefficient lineswitch routine
* (ttyinput) in "raw" mode. It usually takes about 450
* instructions (that's without canonical processing or echo!).
* slinput is reasonably fast (usually 40 instructions plus
if (!(tp
->t_iflag
& (ICRNL
| IGNCR
| IMAXBEL
| INLCR
| ISTRIP
&& !(tp
->t_lflag
& (ECHO
| ECHONL
| ICANON
| IEXTEN
| ISIG
&& !(tp
->t_state
& (TS_CNTTB
| TS_LNCH
))
&& linesw
[tp
->t_line
].l_rint
== ttyinput
) {
com
->delta_error_counts
[CE_TTY_BUF_OVERFLOW
]
+= incc
- rb_write(TB_RAW(tp
), (char *) buf
,
if (tp
->t_state
& TS_TTSTOP
|| tp
->t_cc
[VSTART
] == tp
->t_cc
[VSTOP
])) {
tp
->t_state
&= ~TS_TTSTOP
;
line_status
= (u_char
) buf
[CE_INPUT_OFFSET
];
recv_data
= (u_char
) *buf
++;
& (LSR_BI
| LSR_FE
| LSR_OE
| LSR_PE
)) {
if (line_status
& LSR_BI
)
if (line_status
& LSR_FE
)
if (line_status
& LSR_OE
)
if (line_status
& LSR_PE
)
(*linesw
[tp
->t_line
].l_rint
)(recv_data
, tp
);
if (com_events
>= LOTS_OF_EVENTS
)
#ifdef OLD_INTERRUPT_HANDLING
/* check requested parameters */
divisor
= ttspeedtab(t
->c_ospeed
, comspeedtab
);
t
->c_ispeed
= t
->c_ospeed
;
if (divisor
< 0 || t
->c_ispeed
!= t
->c_ospeed
)
/* parameters are OK, convert them to the com struct and the device */
commctl(com
, MCR_DTR
, DMBIC
); /* hang up line */
commctl(com
, MCR_DTR
, DMBIS
);
* 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 siointr1() might lose some receiver
* error bits, but that is acceptable here.
while ((inb(com
->line_status_port
) & (LSR_TSRE
| LSR_TXRDY
))
!= (LSR_TSRE
| LSR_TXRDY
)) {
error
= ttysleep(tp
, (caddr_t
)TB_RAW(tp
), TTIPRI
| PCATCH
,
if (error
!= 0 && error
!= EAGAIN
) {
if (!(tp
->t_state
& TS_TTSTOP
)) {
disable_intr(); /* very important while com_data is hidden */
* XXX - clearing CS_TTGO is not sufficient to stop further output,
* 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
))
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_RTS_IFLOW
; /* XXX - secondary changes? */
com
->state
&= ~CS_RTS_IFLOW
;
* 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.
com
->state
&= ~CS_CTS_OFLOW
;
com
->state
|= CS_ODEVREADY
;
if (cflag
& CCTS_OFLOW
) {
com
->state
|= CS_CTS_OFLOW
;
if (!(com
->last_modem_status
& MSR_CTS
))
com
->state
&= ~CS_ODEVREADY
;
* 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
if (com
->state
>= (CS_BUSY
| CS_TTGO
))
if (tp
->t_state
& TS_TTSTOP
)
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
);
* XXX don't raise MCR_RTS if CTS_RTS_IFLOW is off. Set it
* appropriately in comparam() if RTS-flow is being changed.
if (!(com
->mcr_image
& MCR_RTS
) && com
->iptr
< com
->ihighwater
)
outb(com
->modem_ctl_port
, com
->mcr_image
|= MCR_RTS
);
if (tp
->t_state
& (TS_TIMEOUT
| TS_TTSTOP
))
if (RB_LEN(TB_OUT(tp
)) <= tp
->t_lowat
) {
if (tp
->t_state
& TS_ASLEEP
) {
tp
->t_state
&= ~TS_ASLEEP
;
wakeup((caddr_t
)TB_OUT(tp
));
selwakeup(tp
->t_wsel
, tp
->t_state
& TS_WCOLL
);
tp
->t_state
&= ~TS_WCOLL
;
} else if (RB_LEN(TB_OUT(tp
)) != 0) {
com
->ocount
= RB_CONTIGGET(TB_OUT(tp
));
com
->obufend
= (com
->optr
= (u_char
*)TB_OUT(tp
)->rb_hd
)
siointr1(com
); /* fake interrupt to start output */
com
= com_addr(UNIT(tp
->t_dev
));
com_events
-= (com
->iptr
- com
->ibuf
);
if (tp
->t_state
& TS_TTSTOP
)
return (ttselect(dev
& ~COM_MINOR_MAGIC_MASK
, rw
, p
));
outb(com
->modem_ctl_port
,
com
->mcr_image
= bits
| (com
->mcr_image
& MCR_IENABLE
));
outb(com
->modem_ctl_port
, com
->mcr_image
|= bits
);
outb(com
->modem_ctl_port
, com
->mcr_image
&= ~bits
);
timeout(comwakeup
, (caddr_t
) NULL
, hz
/ 100);
#ifndef OLD_INTERRUPT_HANDLING
#ifndef OLD_INTERRUPT_HANDLING
/* recover from lost output interrupts */
for (unit
= 0; unit
< NSIO
; ++unit
) {
if (com
!= NULL
&& com
->state
>= (CS_BUSY
| CS_TTGO
)) {
#ifdef OLD_INTERRUPT_HANDLING
* Following are all routines needed for SIO to act as console
#include "i386/i386/cons.h"
static Port_t siocniobase
;
* Wait for any pending transmission to finish. Required to avoid
* the UART lockup bug when the speed is changed, and for normal
while ((inb(siocniobase
+ com_lsr
) & (LSR_TSRE
| LSR_TXRDY
))
!= (LSR_TSRE
| LSR_TXRDY
) && --timo
!= 0)
* 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.
sp
->ier
= inb(iobase
+ com_ier
);
outb(iobase
+ com_ier
, 0); /* spltty() doesn't stop siointr() */
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
);
* Restore the device control registers.
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 osicllations of MCR_DTR or MCR_RTS by not restoring them.
outb(iobase
+ com_mcr
, sp
->mcr
| MCR_DTR
| MCR_RTS
);
outb(iobase
+ com_ier
, sp
->ier
);
/* locate the major number */
/* XXX - should be elsewhere since KGDB uses it */
for (commajor
= 0; commajor
< nchrdev
; commajor
++)
if (cdevsw
[commajor
].d_open
== sioopen
)
/* make sure hardware exists? XXX */
/* initialize required fields */
cp
->cn_dev
= makedev(commajor
, unit
);
cp
->cn_pri
= CN_REMOTE
; /* Force a serial port console */
* XXX can delete more comconsole stuff now that i/o routines are
comconsole
= UNIT(cp
->cn_dev
);
while (!(inb(iobase
+ com_lsr
) & LSR_RXRDY
))
c
= inb(iobase
+ com_data
);
outb(siocniobase
+ com_data
, c
);