su_driver.c revision 7cb42c7ebab29418f23e6bc16bd40cea6f303d10
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/* Copyright (c) 1990, 1991 UNIX System Laboratories, Inc. */
/* Copyright (c) 1984, 1986, 1987, 1988, 1989, 1990 AT&T */
/* All Rights Reserved */
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Serial I/O driver for 82510/8250/16450/16550AF chips.
*/
#define SU_REGISTER_FILE_NO 0
#define SU_REGOFFSET 0
#define SU_REGISTER_LEN 8
#ifdef DEBUG
#endif
#define ASY_INIT 1
#define ASY_NOINIT 0
#ifdef DEBUG
#define ASY_DEBUG_INIT 0x001
#define ASY_DEBUG_INPUT 0x002
#define ASY_DEBUG_EOT 0x004
#define ASY_DEBUG_CLOSE 0x008
#define ASY_DEBUG_HFLOW 0x010
#define ASY_DEBUG_PROCS 0x020
#define ASY_DEBUG_STATE 0x040
#define ASY_DEBUG_INTR 0x080
static int asydebug = 0;
#endif
static int su_log = 0;
static struct ppsclockev asy_ppsev;
static int max_asy_instance = -1;
static void *su_asycom; /* soft state asycom pointer */
static void *su_asyncline; /* soft state asyncline pointer */
/* The async interrupt entry points */
static void async_reioctl(void *);
static void async_restart(void *);
static int asytodm(int, int);
static int dmtoasy(int);
extern kcondvar_t lbolt_cv;
/*
*/
0, /* 0 baud rate */
0x900, /* 50 baud rate */
0x600, /* 75 baud rate */
0x417, /* 110 baud rate (%0.026) */
0x359, /* 134 baud rate (%0.058) */
0x300, /* 150 baud rate */
0x240, /* 200 baud rate */
0x180, /* 300 baud rate */
0x0c0, /* 600 baud rate */
0x060, /* 1200 baud rate */
0x040, /* 1800 baud rate */
0x030, /* 2400 baud rate */
0x018, /* 4800 baud rate */
0x00c, /* 9600 baud rate */
0x006, /* 19200 baud rate */
0x003, /* 38400 baud rate */
0x002, /* 57600 baud rate */
0, /* 76800 baud rate - not supported */
0x001, /* 115200 baud rate */
0, /* 153600 baud rate - not supported */
0x8002, /* 230400 baud rate - supported on specific platforms */
0, /* 307200 baud rate - not supported */
0x8001 /* 460800 baud rate - supported on specific platforms */
};
/*
* Number of speeds supported is the number of entries in
* the above table.
*/
/*
* Human-readable baud rate table.
*/
int baudtable[] = {
0, /* 0 baud rate */
50, /* 50 baud rate */
75, /* 75 baud rate */
110, /* 110 baud rate */
134, /* 134 baud rate */
150, /* 150 baud rate */
200, /* 200 baud rate */
300, /* 300 baud rate */
600, /* 600 baud rate */
1200, /* 1200 baud rate */
1800, /* 1800 baud rate */
2400, /* 2400 baud rate */
4800, /* 4800 baud rate */
9600, /* 9600 baud rate */
19200, /* 19200 baud rate */
38400, /* 38400 baud rate */
57600, /* 57600 baud rate */
76800, /* 76800 baud rate */
115200, /* 115200 baud rate */
153600, /* 153600 baud rate */
230400, /* 230400 baud rate */
307200, /* 307200 baud rate */
460800 /* 460800 baud rate */
};
struct module_info asy_info = {
0,
"su",
0,
32*4096,
4096
};
putq,
(int (*)())asyrsrv,
NULL,
&asy_info,
};
(int (*)())asywput,
NULL,
NULL,
NULL,
NULL,
&asy_info,
};
struct streamtab asy_str_info = {
&asy_rint,
&asy_wint,
NULL,
};
void **result);
static int asyprobe(dev_info_t *);
static struct cb_ops cb_asy_ops = {
nodev, /* cb_open */
nodev, /* cb_close */
nodev, /* cb_strategy */
nodev, /* cb_print */
nodev, /* cb_dump */
nodev, /* cb_read */
nodev, /* cb_write */
nodev, /* cb_ioctl */
nodev, /* cb_devmap */
nodev, /* cb_mmap */
nodev, /* cb_segmap */
nochpoll, /* cb_chpoll */
ddi_prop_op, /* cb_prop_op */
&asy_str_info, /* cb_stream */
D_MP /* cb_flag */
};
DEVO_REV, /* devo_rev */
0, /* devo_refcnt */
asyinfo, /* devo_getinfo */
nulldev, /* devo_identify */
asyprobe, /* devo_probe */
asyattach, /* devo_attach */
asydetach, /* devo_detach */
nodev, /* devo_reset */
&cb_asy_ops, /* devo_cb_ops */
};
/*
* Module linkage information for the kernel.
*/
&mod_driverops, /* Type of module. This one is a driver */
"su driver %I%",
&asy_ops, /* driver ops */
};
static struct modlinkage modlinkage = {
&modldrv,
};
int
_init(void)
{
int status;
if (status != 0)
return (status);
if (status != 0) {
return (status);
}
}
return (status);
}
int
_fini(void)
{
int i;
i = mod_remove(&modlinkage);
if (i == 0) {
}
return (i);
}
int
{
}
static int
{
int instance;
return (DDI_PROBE_FAILURE);
}
#ifdef DEBUG
if (asydebug)
#endif
/*
* Probe for the device:
* Ser. int. uses bits 0,1,2; FIFO uses 3,6,7; 4,5 wired low.
* If bit 4 or 5 appears on inb() ISR, board is not there.
*/
return (DDI_PROBE_FAILURE);
if (max_asy_instance < instance)
return (DDI_PROBE_SUCCESS); /* hw is present */
}
static int
{
register int instance;
char name[16];
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
/* grab both mutex locks */
return (DDI_SUCCESS);
}
/* Disable further interrupts */
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
#ifdef DEBUG
if (asydebug & ASY_DEBUG_INIT)
"8250");
#endif
/*
* Before removing interrupts it is always better to disable
* interrupts if the chip gives a provision to disable the
* serial port interrupts.
*/
/* remove minor device node(s) for this device */
return (DDI_SUCCESS);
}
static int
{
register int instance;
char name[40];
/* cannot attach a device that has not been probed first */
if (instance > max_asy_instance)
return (DDI_FAILURE);
if (cmd != DDI_RESUME) {
/* Allocate soft state space */
instance);
goto error;
}
}
goto error;
}
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME: {
/* grab both mutex locks */
return (DDI_SUCCESS);
}
/* re-setup all the registers and enable interrupts if needed */
return (DDI_SUCCESS);
}
default:
goto error;
}
goto error;
}
#ifdef DEBUG
if (asydebug)
#endif
/*
* Initialize the port with default settings.
*/
/*
* Check for baudrate generator's "baud-divisor-factor" property setup
* by OBP, since different UART chips might have different baudrate
* generator divisor. e.g., in case of NSPG's Sputnik platform, the
* baud-divisor-factor is 13, it uses dedicated 16552 "DUART" chip
* instead of SuperIO. Since the baud-divisor-factor must be a positive
* integer, the divisors will always be at least as large as the values
* in asyspdtab[]. Make the default factor 1.
*/
/* set speed cap */
/* check for ASY82510 chip */
/*
* Since most of the general operation of the 82510 chip
* can be done from BANK 0 (8250A/16450 compatable mode)
* we will default to BANK 0.
*/
asy->asy_trig_level = 0;
} else { /* Set the UART in FIFO mode if it has FIFO buffers */
else {
asy->asy_trig_level = 0;
}
}
/* Set the baud rate to 9600 */
/*
* Set up the other components of the asycom structure for this port.
*/
"Get iblock_cookie failed-Device interrupt%x\n", instance);
goto error;
}
instance);
goto error;
}
(void *)asy->asy_soft_iblock);
(void *)asy->asy_iblock);
/*
* Install interrupt handlers for this device.
*/
"Cannot set device interrupt for su driver\n");
goto error;
}
!= DDI_SUCCESS) {
goto error;
}
/* initialize the asyncline structure */
goto error;
}
}
/*
* If the device is configured as the 'rsc-console'
* create the minor device for this node.
*/
== DDI_FAILURE) {
"%s%d: Failed to create node rsc-console",
goto error;
}
asy->asy_lom_console = 0;
asy->asy_rsc_control = 0;
/*
* If the device is configured as the 'lom-console'
* create the minor device for this node.
* Do not create a dialout device.
* Use the same minor numbers as would be used for standard
* serial instances.
*/
"%s%d: Failed to create node lom-console",
goto error;
}
asy->asy_rsc_console = 0;
asy->asy_rsc_control = 0;
/*
* If the device is configured as the 'rsc-control'
* create the minor device for this node.
*/
== DDI_FAILURE) {
goto error;
}
asy->asy_lom_console = 0;
asy->asy_rsc_console = 0;
"keyboard", 0)) {
/*
* If the device is a keyboard, then create an internal
* pathname so that the dacf code will link the node into
* the keyboard console stream. See dacf.conf.
*/
goto error;
}
"mouse", 0)) {
/*
* If the device is a mouse, then create an internal
* pathname so that the dacf code will link the node into
* the mouse stream. See dacf.conf.
*/
instance) == DDI_FAILURE) {
goto error;
}
} else {
/*
* for this device
*/
/* serial-port */
goto error;
}
/* serial-port:dailout */
goto error;
}
/* Property for ignoring DCD */
"ignore-cd", 0)) {
} else {
/*
* if ignore-cd is not available it could be
* some old legacy platform, try to see
* whether the old legacy property exists
*/
DDI_PROP_DONTPASS, name, 0))
}
}
return (DDI_SUCCESS);
}
}
}
/* no action for EMPTY state */
return (DDI_FAILURE);
}
static int
void **result)
{
return (DDI_FAILURE);
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
error = DDI_FAILURE;
else {
error = DDI_SUCCESS;
}
break;
case DDI_INFO_DEVT2INSTANCE:
error = DDI_SUCCESS;
break;
default:
error = DDI_FAILURE;
}
return (error);
}
static int
{
int mcr;
int unit;
int len;
#ifdef DEBUG
if (asydebug & ASY_DEBUG_CLOSE)
printf("open\n");
#endif
if (unit > max_asy_instance)
return (ENXIO); /* unit not configured */
return (ENXIO);
return (ENXIO); /* device not found by autoconfig */
/*
* Block waiting for carrier to come up, unless this is a no-delay open.
*/
/*
* If this port is for a RSC console or control
* use the following termio info
*/
& CIBAUD);
} else if (asy->asy_lom_console) {
& CIBAUD);
} else {
/*
* Set the default termios settings (cflag).
* Others are set in ldterm. Release the spin
* mutex as we can block here, reaquire before
* calling asy_program.
*/
== DDI_PROP_SUCCESS &&
} else {
"su: couldn't get ttymodes property!");
}
}
async->async_wbufcid = 0;
secpolicy_excl_open(cr) != 0) {
return (EBUSY);
return (EBUSY);
}
/* Raise DTR on every open */
/*
* Check carrier.
*/
else
/*
* If FNDELAY and FNONBLOCK are clear, block until carrier up.
* Quit on interrupt.
*/
return (EINTR);
}
goto again;
}
return (EBUSY);
}
}
async->async_polltid = 0;
return (0);
}
static void
async_progress_check(void *arg)
{
/*
* We define "progress" as either waiting on a timed break or delay, or
* having had at least one transmitter interrupt. If none of these are
* true, then just terminate the output and wake up that close thread.
*/
async->async_ocnt = 0;
async->async_timer = 0;
/*
* Since this timer is running, we know that we're in exit(2).
* That means that the user can't possibly be waiting on any
* valid ioctl(2) completion anymore, and we should just flush
* everything.
*/
} else {
}
}
/*
* Close routine.
*/
static int
{
int nohupcl;
#ifdef DEBUG
if (asydebug & ASY_DEBUG_CLOSE)
printf("close\n");
#endif
/* get the nohupcl OBP property of this device */
"nohupcl", 0);
/*
* Turn off PPS handling early to avoid events occuring during
* close. Also reset the DCD edge monitoring bit.
*/
/*
* There are two flavors of break -- timed (M_BREAK or TCSBRK) and
* untimed (TIOCSBRK). For the timed case, these are enqueued on our
* write queue and there's a timer running, so we don't have to worry
* about them. For the untimed case, though, the user obviously made a
* mistake, because these are handled immediately. We'll terminate the
* break now and honor his implicit request by discarding the rest of
* the data.
*/
}
goto nodrain;
}
/*
* If the user told us not to delay the close ("non-blocking"), then
* don't bother trying to drain.
*
* If the user did M_STOP (ASYNC_STOPPED), there's no hope of ever
* getting an M_START (since these messages aren't enqueued), and the
* only other way to clear the stop condition is by loss of DCD, which
* would discard the queue data. Thus, we drop the output data if
* ASYNC_STOPPED is set.
*/
goto nodrain;
}
/*
* If there's any pending output, then we have to try to drain it.
* There are two main cases to be handled:
* - called by close(2): need to drain until done or until
* a signal is received. No timeout.
* - called by exit(2): need to drain while making progress
* or until a timeout occurs. No signals.
*
* If we can't rely on receiving a signal to get us out of a hung
* session, then we have to use a timer. In this case, we set a timer
* to check for progress in sending the output data -- all that we ask
* (at each interval) is that there's been some progress made. Since
* the interrupt routine grabs buffers from the write queue, we can't
* trust async_ocnt. Instead, we use a flag.
*
* Note that loss of carrier will cause the output queue to be flushed,
* and we'll wake up again and finish normally.
*/
if (!ddi_can_receive_sig() && su_drain_check != 0) {
}
while (async->async_ocnt > 0 ||
break;
}
if (async->async_timer != 0) {
async->async_timer = 0;
}
/* turn off the loopback mode */
}
async->async_ocnt = 0;
/*
* If the "nohupcl" OBP property is set for this device, do
* not turn off DTR and RTS no matter what. Otherwise, if the
* line has HUPCL set or is incompletely opened, turn off DTR
* and RTS to fix the modem line.
*/
/* turn off DTR, RTS but NOT interrupt to 386 */
/*
* Don't let an interrupt in the middle of close
* bounce us back to the top; just continue closing
* as if nothing had happened.
*/
goto out;
}
/*
* If nobody's using it now, turn off receiver interrupts.
*/
}
out:
/*
* Clear out device state.
*/
async->async_flags = 0;
/*
* Clear ASY_DOINGSOFT and ASY_NEEDSOFT in case we were in
* async_softint or an interrupt was pending when the process
* using the port exited.
*/
/*
* Cancel outstanding "bufcall" request.
*/
if (async->async_wbufcid) {
async->async_wbufcid = 0;
}
/*
* If inperim is true, it means the port is closing while there's
* a pending software interrupt. async_flags has been zeroed out,
* so this instance of leaveq() needs to be called before we call
* qprocsoff() to disable services on the q. If inperim is false,
* leaveq() has already been called or we're not in a perimeter.
*/
leaveq(q);
} else {
}
/* Note that qprocsoff can't be done until after interrupts are off */
qprocsoff(q);
return (0);
}
/*
* Checks to see if the serial port is still transmitting
* characters. It returns true when there are characters
* queued to transmit, when the holding register contains
* a byte, or when the shifting register still contains
* data to send.
*
*/
static boolean_t
{
#ifdef DEBUG
if (asydebug & ASY_DEBUG_EOT)
printf("isbusy\n");
#endif
return ((async->async_ocnt > 0) ||
}
/*
* Program the ASY port. Most of the async operation is based on the values
* of 'c_iflag' and 'c_cflag'.
*/
static int
{
int ocflags;
int error = 0;
#ifdef DEBUG
if (asydebug & ASY_DEBUG_PROCS)
printf("program\n");
#endif
baudrate += 16;
/* Limit baudrate so it can't index out of baudtable */
/*
* If baud rate requested is greater than the speed cap
* or is an unsupported baud rate then reset t_cflag baud
* to the last valid baud rate. If this is the initial
* pass through asy_program then set it to 9600.
*/
} else {
}
goto end;
}
/* set the baud rate */
} else {
}
}
}
/* Set line control */
case CS5:
break;
case CS6:
break;
case CS7:
break;
case CS8:
break;
}
/* set the baud rate when the rate is NOT B0 */
if (baudrate != 0) {
}
/* set the line control modes */
/*
* if transitioning from CREAD off to CREAD on,
* flush the FIFO buffer if we have one.
*/
}
}
/* remember the new cflags */
}
/* whether or not CLOCAL is set, modify the modem control lines */
if (baudrate == 0)
/* B0 has been issued, lower DTR */
else
/* raise DTR */
/*
* Call the modem status interrupt handler to check for the carrier
* in case CLOCAL was turned off after the carrier came on.
* (Note: Modem status interrupt is not enabled if CLOCAL is ON.)
*/
/* Set interrupt control */
/*
* direct-wired line ignores DCD, so we don't enable modem
* status interrupts.
*/
else
end:
return (error);
}
/*
* asyintr() is the High Level Interrupt Handler.
*
* There are four different interrupt types indexed by ISR register values:
* 0: modem
* 1: Tx holding register is empty, ready for next char
* 2: Rx register now holds a char to be picked up
* 3: error or break on line
* This routine checks the Bit 0 (interrupt-not-pending) to determine if
* the interrupt is from this port.
*/
{
int ret_status = DDI_INTR_UNCLAIMED;
if (interrupt_id & NOINTERRUPT) {
return (DDI_INTR_UNCLAIMED);
} else {
((abort_enable == KIOCABORTENABLE) &&
abort_sequence_enter((char *)NULL);
else {
/* reset line status */
/* discard any data */
/* reset modem status */
return (DDI_INTR_CLAIMED);
}
}
}
/*
* Spurious interrupts happen in this driver
* because of the transmission on serial port not handled
* properly.
*
* The reasons for Spurious interrupts are:
* 1. There is a path in async_nstart which transmits
* characters without going through interrupt services routine
* which causes spurious interrupts to happen.
* 2. In the async_txint more than one character is sent
* in one interrupt service.
* 3. In async_rxint more than one characters are received in
* in one interrupt service.
*
* Hence we have flags to indicate that such scenerio has happened.
* and claim only such interrupts and others we donot claim it
* as it could be a indicator of some hardware problem.
*
*/
if (interrupt_id & NOINTERRUPT) {
(asy->asy_out_of_band_xmit > 0) ||
asy->asy_xmit_count = 0;
asy->asy_out_of_band_xmit = 0;
asy->asy_rx_count = 0;
return (DDI_INTR_CLAIMED);
} else {
return (DDI_INTR_UNCLAIMED);
}
}
#ifdef DEBUG
if (asydebug & ASY_DEBUG_INTR)
prom_printf("l");
#endif
switch (interrupt_id) {
case RxRDY:
case RSTATUS:
case FFTMOUT:
/* receiver interrupt or receiver errors */
break;
case TxRDY:
/* transmit interrupt */
break;
case MSTATUS:
/* modem status interrupt */
break;
}
return (ret_status);
}
/*
* Transmitter interrupt service routine.
* If there is more data to transmit in the current pseudo-DMA block,
* send the next character if output is not stopped or draining.
* Otherwise, queue up a soft interrupt.
*
* XXX - Needs review for HW FIFOs.
*/
static void
{
int fifo_len;
int xmit_progress;
/*
* If ASYNC_BREAK has been set, return to asyintr()'s context to
* claim the interrupt without performing any action.
*/
return;
/*
* Check for flow control and do the needed action.
*/
if (asycheckflowcontrol_sw(asy)) {
return;
}
if (async->async_ocnt > 0 &&
xmit_progress = 0;
fifo_len--;
async->async_ocnt--;
}
/*
* Reading the lsr, (moved reading at the end of
* while loop) as already we have read once at
* the beginning of interrupt service
*/
}
if (xmit_progress > 0)
}
if (fifo_len == 0) {
return;
}
}
/*
* Receiver interrupt: RxRDY interrupt, FIFO timeout interrupt or receive
* error interrupt.
* Try to put the character into the circular buffer for this line; if it
* overflows, indicate a circular buffer overrun. If this port is always
* to be serviced immediately, or the character is a STOP character, or
* more than 15 characters have arrived, queue up a soft interrupt to
* drain the circular buffer.
* XXX - needs review for hw FIFOs support.
*/
static void
{
uchar_t c = 0;
register tty_common_t *tp;
}
return; /* line is not open for read? */
}
asy->asy_rx_count = 0;
c = 0;
s = 0;
asy->asy_rx_count++;
/*
* Even a single character is received
* we need Soft interrupt to pass it to
* higher layers.
*/
needsoft = 1;
}
/* Check for character break sequence */
if ((abort_enable == KIOCABORTALTERNATE) &&
if (abort_charseq_recognize(c))
abort_sequence_enter((char *)NULL);
}
/* Handle framing errors */
s |= PERROR;
}
s |= FRERROR;
s |= OVERRUN;
}
}
if (s == 0)
(c == 0377))
} else
else
else
else
if (s & FRERROR) { /* Handle framing errors */
if (c == 0) {
/* Look for break on kbd, stdin, or rconsdev */
(abort_enable !=
abort_sequence_enter((char *)0);
else
async->async_break++;
} else {
else
}
} else { /* Parity errors handled by ldterm */
else
}
}
#ifdef DEBUG
if (asydebug & ASY_DEBUG_HFLOW)
printf("asy%d: hardware flow stop input.\n",
#endif
}
}
/*
* Interrupt on port: handle PPS event. This function is only called
* for a port on which PPS event handling has been enabled.
*/
static void
{
/* Have seen leading edge, now look for and record drop */
/*
* Waiting for leading edge, look for rise; stamp event and
* calibrate kernel clock.
*/
/*
* This code captures a timestamp at the designated
* transition of the PPS signal (DCD asserted). The
* code provides a pointer to the timestamp, as well
* as the hardware counter value at the capture.
*
* Note: the kernel has nano based time values while
* NTP requires micro based, an in-line fast algorithm
* to convert nsec to usec is used here -- see hrt2ts()
*/
gethrestime(&ts);
/*
* Because the kernel keeps a high-resolution time,
* pass the current highres timestamp in tvp and zero
* in usec.
*/
ddi_hardpps(tvp, 0);
}
}
/*
* Modem status interrupt.
*
* (Note: It is assumed that the MSR hasn't been read by asyintr().)
*/
static void
{
int msr;
#ifdef DEBUG
if (asydebug & ASY_DEBUG_STATE) {
printf(" transition: %3s %3s %3s %3s\n"
"current state: %3s %3s %3s %3s\n",
}
#endif
#ifdef DEBUG
if (asydebug & ASY_DEBUG_HFLOW)
printf("asy%d: hflow start\n",
#endif
}
/* Handle PPS event */
}
/*
* Handle a second-stage interrupt.
*/
{
int rv;
int cc;
/*
* Test and clear soft interrupt.
*/
#ifdef DEBUG
if (asydebug & ASY_DEBUG_PROCS)
printf("softintr\n");
#endif
if (rv != 0)
asy->asysoftpend = 0;
if (rv) {
return (rv);
(void) async_softint(asy);
}
/*
* There are some instances where the softintr is not
* scheduled and hence not called. It so happened that makes
* the last few characters to be stuck in ringbuffer.
* Hence, call once again the handler so that the last few
* characters are cleared.
*/
if (cc > 0) {
(void) async_softint(asy);
}
}
return (rv);
}
/*
* Handle a software interrupt.
*/
static int
{
queue_t *q;
uchar_t c;
#ifdef DEBUG
if (asydebug & ASY_DEBUG_PROCS)
printf("process\n");
#endif
return (0);
}
if (q != NULL) {
enterq(q);
}
else
#ifdef DEBUG
if (asydebug & ASY_DEBUG_HFLOW)
printf("asy%d: hflow start\n",
#endif
if (async->async_ocnt > 0) {
} else {
}
}
}
/* check for carrier up */
/* carrier present */
(void) putctl(q, M_UNHANGUP);
}
} else {
int flushflag;
/*
* Carrier went away.
* Drop DTR, abort any output in
* progress, indicate that output is
* not stopped, and send a hangup
* notification upstream.
*
* If we're in the midst of close, then flush
* everything. Don't leave stale ioctls lying
* about.
*/
}
async->async_ocnt = 0;
}
}
}
}
}
/*
* If data has been added to the circular buffer, remove
* it from the buffer, and send it up the stream if there's
* somebody listening. Try to do it 16 bytes at a time. If we
* have more than 16 bytes to move, move 16 byte chunks and
* leave the rest for next time around (maybe it will grow).
*/
goto rv;
}
goto rv;
}
if (!canput(q)) {
#ifdef DEBUG
if (!(asydebug & ASY_DEBUG_HFLOW)) {
printf("asy%d: hflow stop input.\n",
if (canputnext(q))
printf("asy%d: next queue is "
"ready\n",
}
#endif
goto rv;
}
if (async->async_ringbuf_overflow) {
#ifdef DEBUG
if (asydebug & ASY_DEBUG_HFLOW)
printf("asy%d: hflow start input.\n",
#endif
async->async_ringbuf_overflow = 0;
goto rv;
}
}
#ifdef DEBUG
if (asydebug & ASY_DEBUG_INPUT)
printf("asy%d: %d char(s) in queue.\n",
#endif
/*
* Before you pull the characters from the RING BUF
* Check whether you can put into the queue again
*/
if ((!canputnext(q)) || (!canput(q))) {
}
goto rv;
}
if (async->async_queue_full) {
/*
* Last time the Stream queue didnot allow
* now it allows so, relax, the flow control
*/
async->async_queue_full = 0;
goto rv;
} else
async->async_queue_full = 0;
}
goto rv;
}
do {
break;
} else {
}
} while (--cc);
if (!canputnext(q)) {
if (!canput(q)) {
/*
* Even after taking all precautions that
* Still we are unable to queue, then we
* cannot do anything, just drop the block
*/
"su%d: local queue full\n",
if ((async->async_flags &
ASYNC_HW_IN_FLOW) == 0) {
async->async_flags |=
async->async_flowc =
}
} else {
}
} else {
}
} else {
}
/*
* If we have a parity error, then send
* up an M_BREAK with the "bad"
* character as an argument. Let ldterm
* figure out what to do with the error.
*/
if (cc)
rv:
/*
* If a transmission has finished, indicate that it's finished,
* and start that line up again.
*/
if (async->async_break) {
async->async_break = 0;
}
}
if (async->async_xmitblk)
}
/*
* We need to check for inperim and ISOPEN due to
* multi-threading implications; it's possible to close the
* port and nullify async_flags while completing the software
* interrupt. If the port is closed, leaveq() will have already
* been called. We don't want to call it twice.
*/
}
}
/*
* A note about these overrun bits: all they do is *tell* someone
* about an error- They do not track multiple errors. In fact,
* you could consider them latched register bits if you like.
* We are only interested in printing the error message once for
* any cluster of overrun errrors.
*/
if (async->async_hw_overrun) {
if (su_log > 0) {
}
}
async->async_hw_overrun = 0;
}
if (async->async_sw_overrun) {
if (su_log > 0) {
}
}
async->async_sw_overrun = 0;
}
if (q != NULL)
leaveq(q);
return (0);
}
/*
* Restart output on a line after a delay or break timer expired.
*/
static void
async_restart(void *arg)
{
queue_t *q;
/*
* If break timer expired, turn off the break bit.
*/
#ifdef DEBUG
if (asydebug & ASY_DEBUG_PROCS)
printf("restart\n");
#endif
unsigned int rate;
/*
* Go to sleep for the time it takes for at least one
* stop bit to be received by the device at the other
* end of the line as stated in the RS-232 specification.
* The wait period is equal to:
* 2 clock cycles * (1 MICROSEC / baud rate)
*/
rate += 16;
}
}
enterq(q);
}
if (q != NULL)
leaveq(q);
/* cleared break or delay flag; may have made some output progress */
}
static void
{
async_nstart(async, 0);
}
/*
* Start output on a line, unless it's busy, frozen, or otherwise.
*/
static void
{
register int cc;
register queue_t *q;
int fifo_len = 1;
int xmit_progress;
#ifdef DEBUG
if (asydebug & ASY_DEBUG_PROCS)
printf("start\n");
#endif
/*
* If the chip is busy (i.e., we're waiting for a break timeout
* to expire, or for the current transmission to finish, or for
* output to finish draining from chip), don't grab anything new.
*/
#ifdef DEBUG
printf("asy%d: start %s.\n",
? "break" : "busy");
#endif
return;
}
/*
* If we have a flow-control character to transmit, do it now.
*/
if (asycheckflowcontrol_sw(asy)) {
return;
}
/*
* If we're waiting for a delay timeout to expire, don't grab
* anything new.
*/
#ifdef DEBUG
printf("asy%d: start ASYNC_DELAY.\n",
#endif
return;
}
#ifdef DEBUG
printf("asy%d: start writeq is null.\n",
#endif
return; /* not attached to a stream */
}
for (;;) {
return; /* no data to transmit */
/*
* We have a message block to work on.
* Check whether it's a break, a delay, or an ioctl (the latter
* occurs if the ioctl in question was waiting for the output
* to drain). If it's one of those, process it immediately.
*/
case M_BREAK:
/*
* Set the break bit, and arrange for "async_restart"
* to be called in 1/4 second; it will turn the
* break bit off, and call "async_start" to grab
* the next message.
*/
return; /* wait for this to finish */
case M_DELAY:
/*
* Arrange for "async_restart" to be called when the
* delay expires; it will turn ASYNC_DELAY off,
* and call "async_start" to grab the next message.
*/
return; /* wait for this to finish */
case M_IOCTL:
/*
* This ioctl needs to wait for the output ahead of
* it to drain. Try to do it, and then either
* redo the ioctl at a later time or grab the next
* message after it.
*/
if (asy_isbusy(asy)) {
/*
* Get the divisor by calculating the rate
*/
unsigned int rate;
rate += 16;
}
/*
* We need to do a callback as the port will
* be set to drain
*/
/*
* Put the message we just processed back onto
* the end of the queue
*/
/*
* We need to delay until the TSR and THR
* have been exhausted. We base the delay on
* the amount of time it takes to transmit
* 2 chars at the current baud rate in
* microseconds.
*
* Therefore, the wait period is:
*
* (#TSR bits + #THR bits) *
* 1 MICROSEC / baud rate
*/
return;
}
continue;
}
}
break;
}
/*
* We have data to transmit. If output is stopped, put
* it back and try again later.
*/
#ifdef DEBUG
if (asydebug & ASY_DEBUG_HFLOW &&
printf("asy%d: output hflow in effect.\n",
#endif
/*
* We entered the routine owning the lock, we need to
* exit the routine owning the lock.
*/
return;
}
}
/*
* In 5-bit mode, the high order bits are used
* to indicate character sizes less than five,
* so we need to explicitly mask before transmitting
*/
register unsigned char *p = xmit_addr;
while (cnt--)
*p++ &= (unsigned char) 0x1f;
}
/*
* Set up this block for pseudo-DMA.
*/
/*
* If the transmitter is ready, shove some
* characters out.
*/
xmit_progress = 0;
async->async_ocnt--;
}
}
if (xmit_progress > 0)
}
/*
* Resume output by poking the transmitter.
*/
static void
{
#ifdef DEBUG
if (asydebug & ASY_DEBUG_PROCS)
printf("resume\n");
#endif
if (asycheckflowcontrol_sw(asy)) {
return;
} else if (async->async_ocnt > 0) {
async->async_ocnt--;
}
}
}
/*
* Process an "ioctl" message sent down to us.
* Note that we don't need to get any locks until we are ready to access
* the hardware. Nothing we access until then is going to be altered
* outside of the STREAMS framework, so we should be safe.
*/
static void
{
register unsigned datasize;
int error = 0;
#ifdef DEBUG
if (asydebug & ASY_DEBUG_PROCS)
printf("ioctl\n");
#endif
/*
* We were holding an "ioctl" response pending the
* availability of an "mblk" to hold data to be passed up;
* another "ioctl" came through, which means that "ioctl"
* must have timed out or been aborted.
*/
}
/*
* For TIOCMGET, TIOCMBIC, TIOCMBIS, TIOCMSET, and PPS, do NOT call
* ttycommon_ioctl() because this function frees up the message block
* (mp->b_cont) that contains the address of the user variable where
* we need to pass back the bit array.
*/
else
/*
* The only way in which "ttycommon_ioctl" can fail is if the "ioctl"
* requires a response containing data to be returned to the user,
* and no mblk could be allocated for the data.
* No such "ioctl" alters our state. Thus, we always go ahead and
* do any state-changes the "ioctl" calls for. If we couldn't allocate
* the data, "ttycommon_ioctl" has stashed the "ioctl" away safely, so
* we just call "bufcall" to request that we be called back when we
* stand a better chance of allocating the data.
*/
if (async->async_wbufcid)
async);
return;
}
if (error == 0) {
/*
* "ttycommon_ioctl" did most of the work; we just use the
* data it set up.
*/
case TCSETS:
asy->asy_lom_console)) {
}
break;
case TCSETSF:
case TCSETSW:
case TCSETA:
case TCSETAW:
case TCSETAF:
asy->asy_lom_console)) {
return;
}
}
break;
case TIOCSSOFTCAR:
/* Set the driver state appropriately */
else
break;
}
} else if (error < 0) {
/*
* "ttycommon_ioctl" didn't do anything; we process it here.
*/
error = 0;
case TIOCGPPS:
/*
*/
break;
}
else
break;
case TIOCSPPS:
/*
*/
if (error != 0)
break;
else
/* Reset edge sense */
break;
case TIOCGPPSEV: {
/*
* Get PPS event data.
*/
void *buf;
#ifdef _SYSCALL32_IMPL
struct ppsclockev32 p32;
#endif
struct ppsclockev ppsclockev;
}
break;
}
/* Protect from incomplete asy_ppsev */
#ifdef _SYSCALL32_IMPL
} else
#endif
{
buf = &ppsclockev;
}
break;
}
break;
}
case TCSBRK:
if (error != 0)
break;
/*
* Get the divisor by calculating the rate
*/
rate += 16;
/*
* To ensure that erroneous characters are
* not sent out when the break is set, SB
* recommends three steps:
*
* 1) pad the TSR with 0 bits
* 2) When the TSR is full, set break
* 3) When the TSR has been flushed, unset
* the break when transmission must be
* restored.
*
* We loop until the TSR is empty and then
* set the break. ASYNC_BREAK has been set
* to ensure that no characters are
* transmitted while the TSR is being
* flushed and SOUT is being used for the
* break signal.
*
* The wait period is equal to
* clock / (baud * 16) * 16 * 2.
*/
}
/*
* Set the break bit, and arrange for
* "async_restart" to be called in 1/4 second;
* it will turn the break bit off, and call
* "async_start" to grab the next message.
*/
} else {
#ifdef DEBUG
if (asydebug & ASY_DEBUG_CLOSE)
printf("asy%d: wait for flush.\n",
#endif
return;
}
#ifdef DEBUG
if (asydebug & ASY_DEBUG_CLOSE)
printf("asy%d: ldterm satisfied.\n",
#endif
}
break;
case TIOCSBRK:
return;
case TIOCCBRK:
return;
case TIOCMSET:
case TIOCMBIS:
case TIOCMBIC:
else {
if (error != 0)
break;
}
break;
case TIOCSILOOP:
/*
* If somebody misues this Ioctl when used for
* driving keyboard and mouse indicate not supported
*/
break;
}
/* should not use when we're the console */
break;
}
/*
* Disable the Modem Status Interrupt
* The reason for disabling is the status of
* modem signal are in the higher 4 bits instead of
* lower four bits when in loopback mode,
* so, donot worry about Modem interrupt when
* you are planning to set
* this in loopback mode until it is cleared by
* another ioctl to get out of the loopback mode
*/
break;
case TIOCMGET:
break;
}
} else {
}
break;
default: /* unexpected ioctl type */
/*
* If we don't understand it, it's an error. NAK it.
*/
break;
}
}
if (error != 0) {
}
}
static void
{
async->async_polltid = 0;
}
/*
* Put procedure for write queue.
* Respond to M_STOP, M_START, M_IOCTL, and M_FLUSH messages here;
* set the flow control character for M_STOPI and M_STARTI messages;
* queue up M_BREAK, M_DELAY, and M_DATA messages for processing
* by the start routine, and then call the start routine; discard
* everything else. Note that this driver does not incorporate any
* mechanism to negotiate to handle the canonicalization process.
* It expects that these functions are handled in upper module(s),
* as we do in ldterm.
*/
static void
{
int error;
case M_STOP:
/*
* Since we don't do real DMA, we can just let the
* chip coast to a stop after applying the brakes.
*/
break;
case M_START:
/*
* If an output operation is in progress,
* resume it. Otherwise, prod the start
* routine.
*/
if (async->async_ocnt > 0) {
} else {
}
}
break;
case M_IOCTL:
case TCSBRK:
if (error != 0) {
return;
}
#ifdef DEBUG
if (asydebug & ASY_DEBUG_CLOSE)
printf("asy%d: flush request.\n",
#endif
break;
}
/*FALLTHROUGH*/
case TCSETSW:
case TCSETSF:
case TCSETAW:
case TCSETAF:
/*
* The changes do not take effect until all
* output queued before them is drained.
* Put this message on the queue, so that
* "async_start" will see it when it's done
* with the output before it. Poke the
* start routine, just in case.
*/
break;
default:
/*
* Do it now.
*/
break;
}
break;
case M_FLUSH:
/*
* Abort any output in progress.
*/
async->async_ocnt = 0;
}
/* Flush FIFO buffers */
}
/*
* Flush our write queue.
*/
}
}
/* Flush FIFO buffers */
}
} else {
}
/*
* We must make sure we process messages that survive the
* write-side flush. Without this call, the close protocol
* with ldterm can hang forever. (ldterm will have sent us a
* TCSBRK ioctl that it expects a response to.)
*/
break;
case M_BREAK:
case M_DELAY:
case M_DATA:
/*
* Queue the message up to be transmitted,
* and poke the start routine.
*/
break;
case M_STOPI:
break;
case M_STARTI:
break;
case M_CTL:
} else {
/*
* These MC_SERVICE type messages are used by upper
* modules to tell this driver to send input up
* immediately, or that it can wait for normal
* processing that may or may not be done. Sun
* requires these for the mouse module.
* (XXX - for x86?)
*/
case MC_SERVICEIMM:
break;
case MC_SERVICEDEF:
break;
}
}
break;
case M_IOCDATA:
async_iocdata(q, mp);
break;
default:
break;
}
}
/*
* Retry an "ioctl", now that "bufcall" claims we may be able to allocate
* the buffer we need.
*/
static void
async_reioctl(void *arg)
{
queue_t *q;
/*
* The bufcall is no longer pending.
*/
async->async_wbufcid = 0;
return;
}
/* not pending any more */
/* not in STREAMS queue; we no longer know if we're in wput */
} else
}
static void
{
return;
}
case TIOCMSET:
case TIOCMBIS:
case TIOCMBIC:
break;
}
break;
case TIOCMGET:
}
break;
default:
break;
}
}
/*
* Set or get the modem control status.
*/
static int
{
/* Read Modem Control Registers */
switch (how) {
case TIOCMSET:
break;
case TIOCMBIS:
break;
case TIOCMBIC:
break;
case TIOCMGET:
/* Read Modem Status Registers */
else
}
return (mcr_r);
}
static int
{
register int b = 0;
/* MCR registers */
b |= TIOCM_RTS;
b |= TIOCM_DTR;
/* MSR registers */
b |= TIOCM_CAR;
b |= TIOCM_CTS;
b |= TIOCM_DSR;
b |= TIOCM_RNG;
return (b);
}
static int
{
register int b = 0;
#ifdef CAN_NOT_SET /* only DTR and RTS can be set */
b |= DCD;
b |= CTS;
b |= DSR;
b |= RI;
#endif
b |= RTS;
b |= DTR;
return (b);
}
static void
{
}
}
}
static boolean_t
{
/*
* If we get this far, then we know that flowc is non-zero and
* that there's transmit room available. We've "handled" the
* request now, so clear it. If the user didn't ask for IXOFF,
* then don't actually send anything, but wait for the next
* opportunity.
*/
}
}
return (rval);
}
/*
* Check for abort character sequence
*/
static boolean_t
{
static int state = 0;
#define CNTRL(c) ((c)&037)
state = 0;
return (B_TRUE);
}
} else {
}
return (B_FALSE);
}