/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident "$Header: sc.c 1.76 90/11/07 $"

/*
 * sc.c
 *     SSM console driver software  (STREAMS version)
 */

/* $Log:	sc.c,v $
 */

#include "sys/sysmacros.h"
#include "sys/debug.h"
#include "sys/types.h"
#include "sys/mutex.h"
#include "sys/devsw.h"
#include "sys/param.h"
#include "sys/signal.h"
#include "sys/errno.h"
#include "sys/systm.h"
#include "sys/conf.h"
#include "sys/termio.h"
#include "sys/ioctl.h"
#include "sys/file.h"
#include "sys/timer.h"
#include "sys/user.h"
#include "sys/proc.h"
#include "sys/vmmac.h"
#include "sys/vmmeter.h"
#include "sys/vmsystm.h"
#include "sys/strid.h"
#include "sys/session.h"
#include "sys/stream.h"
#include "sys/stropts.h"
#include "sys/cmn_err.h"
#include "sys/uio.h"
#include "sys/slic.h"
#include "sys/mc_vmparam.h"
#include "sys/pte.h"
#include "sys/plocal.h"
#include "sys/intctl.h"
#include "sys/ssm_misc.h"
#include "sys/ssm.h"
#include "sys/cfg.h"
#include "sys/ssm_cb.h"
#include "sys/sc.h"
#include "sys/ssm_device.h"
#include "sys/autoconf.h"

int scstart();
void scboot(), scintr();

extern int sccflags;
extern uint bufcall();
extern unchar scprintalocfail;
extern int scflow;
extern ulong scrxtime;
extern int sc_c_major, ss_c_major;
extern dev_t smem_devnum, cons_devnum;
extern dev_t altcons_id, cons_id;
extern struct firmwaresw ssm_interface;
extern void ssm_init_powerst();
 
extern int scaddwait;
extern int putnextctl2();
extern putbqx();

static int scmaxdev;
static int sc_base_vector;
static struct sc_info **sc_info;
int	sc_cons_devs_added = 0;

static ulong sc_unit_flags[] = {
	/* There should be SC_PER_SSM entries */
	CFG_SSM_LOCAL,
	CFG_SSM_REMOTE,
};
static struct module_info minfo = {
/*  module ID     module name     min pkt size    max pkt siz   hiwat  lowat */
      STRID_SC,      "sc",            0,            INFPSZ,     150,    50
};

/* Table of ms-per-byte serial output rate (indexed by baud B0-EXTA) */
static unchar scbaud_time[] = {
	0, 200, 143, 91, 77, 67, 50, 33, 17, 8, 6, 4, 2, 1, 1 };

int sc_draintime(), sc_delaytime();
static int scopen(), scclose(), scrsrv(), scwsrv();

static struct qinit rinit = {
	NULL,  scrsrv,  scopen,  scclose,  NULL,  &minfo,  NULL
};

static struct qinit winit = {
	putq,  scwsrv,  NULL,  NULL,  NULL,  &minfo,  NULL
};

struct streamtab scinfo = { &rinit, &winit,  NULL,  NULL };

extern struct devops ssdevops;
extern struct devops scdevops;

/*
 * scboot - allocate data structures, etc at beginning of time.
 *
 * Called by ssm_init with the ssm driver record. We allocate the necessary
 * structures and fill them.
 */
void
scboot(driver)
	struct driver_module *driver;
{
	register struct ssm_device *devp =
		(struct ssm_device *) driver->devices;
	struct sc_info	*infop;
	int dev;
	unchar vector;
	struct dev *dp;
	sc_base_vector = ivecpeek( sc_bin );
	
	/* Allocate an array of pointer to info structures */
	sc_info = (struct sc_info **)calloc(SC_PER_SSM * driver->numdev
					    * sizeof(struct sc_info *));
	/* If no memory is available, report error */
	if (!sc_info) {
		CPRINTF("sc: no memory for state info pointers\n");
		return;
	}

	/* Set up info structure for each SSM device in system */
	for (dev = 0, devp--; dev < SC_PER_SSM * driver->numdev; dev++) {
		if ( !(dev%SC_PER_SSM) )
			devp++;

		/* Always need to allocate vector, even if device dead. */
		vector = ivecall( sc_bin );
		(void) ivecall(sc_bin);	  /* dual ints */
		if (!devp->driver)	  /* alive test for ssm */
			continue;

		/* Short-circuit if NOT the boot console */
		if( !SSM_desc[dev/SC_PER_SSM].ssm_is_cons )
			continue;

		if ( SSM_desc[dev/SC_PER_SSM].ssm_diag_flags &
		      sc_unit_flags[dev%SC_PER_SSM] )
			continue;	  /* serial line cfg alive test */

		sc_info[dev] = infop = (struct sc_info *)
			ssm_alloc(sizeof(struct sc_info),
				  SSM_ALIGN_BASE, SSM_BAD_BOUND);
		
		/* If no memory for info struct, report error */
		if (!infop) {
			cmn_err( CE_WARN,
				"sc: no memory for info structure\n");
			/*
			 *+ The kernel ran out of memory
			 *+ while initializing the SSM serial line.
			 */
			return;
		}
		infop->alive = 1;
		infop->ssm = SSM_desc + dev/SC_PER_SSM;
		infop->devno = dev%SC_PER_SSM;
		infop->unit = dev;
		infop->parity_errs = 0;
		infop->frame_errs = 0;
		infop->overruns = 0;
		infop->os_busy_flag = 0;
		
		/*
		 * If this port is the front-panel-selected console,
		 * load the appropriate device numbers into cons_id 
		 * and altcons_id for the console pseudo-driver.
		 * Set cons and smem pseudo driver major numbers,
		 * and set the pointer to the firmware dispatch
		 * table.
		 */
		if (infop->devno == CCB_LOCAL 
		&&  infop->ssm->ssm_is_cons == CDSC_LOCAL
		||  infop->devno == CCB_REMOTE 
		&&  infop->ssm->ssm_is_cons == CDSC_REMOTE) {
			firmwaresw = &ssm_interface;
			switch (infop->ssm->ssm_is_cons) {
			case CDSC_LOCAL:
				cons_id = cons_devnum = 
					makedev(sc_c_major, dev);
				altcons_id = makedev(sc_c_major, dev + 1);
				break;
			
			case CDSC_REMOTE:
				cons_id = cons_devnum =
					makedev(sc_c_major, dev);
				altcons_id = makedev(sc_c_major, dev - 1);
			}
			smem_devnum = makedev(ss_c_major, 64);
			/*
			 * initialize the devs for these devices
			 */
			if (!sc_cons_devs_added) {
				sc_cons_devs_added++;
				dp = (struct dev *)calloc(sizeof(struct dev));
				dp->d_dev = makedev(ss_c_major, 64);
				smem_devnum = dp->d_dev;
				dp->d_ops = &ssdevops;
				if (devsw_add(dp) != 0) {
					cmn_err(CE_WARN,
		"sc: failure to add smem pseudo-driver dev to devsw\n");
					/*
					 *+ The kernel encountered a problem
					 *+ when it attempted to
					 *+ add an smem
					 *+ dev to the devsw data
					 *+ structure.  This is
					 *+ due to a driver that
					 *+ is adding a dev to
					 *+ devsw; the major/minor number for
					 *+ the dev already belongs to another dev.
					 */
				}
			}
			/* Initialize the powerfail interrupt */
			ssm_init_powerst();
		}
		dp = (struct dev *)calloc(sizeof(struct dev));
		dp->d_dev = makedev(sc_c_major, infop->unit);
		dp->d_ops = &scdevops;
		infop->sc_dev = dp->d_dev;
		if (devsw_add(dp) != 0) {
			cmn_err(CE_WARN,
			"sc%d: failure to add SSM console dev to devsw\n",
				infop->unit);
			/*
			 *+ The kernel encountered a problem when it 
			 *+ attempted to add an SSM
			 *+ console device to the system's devsw data 
			 *+ structure.  This is due to a driver that 
			 *+ is adding a dev to devsw; the major/minor number
			 *+ for the dev already belongs to another dev.
			 */
		}
		ivecinit( sc_bin, vector, scintr);
		ivecinit( sc_bin, vector+1, scintr);
		init_ssm_cons_dev(infop->ssm->ssm_slicaddr,
				  infop->ssm->ssm_cons_cbs,
				  sc_bin, vector,
				  infop->devno);

		init_lock(&infop->sc_lock);
		init_sema(&infop->os_carrier_wait, 0);
		init_sema(&infop->os_busy_wait, 0);
	}
	scmaxdev = driver->numdev - 1;
}

/*
 * scopen - open an SSM console port
 */
static
scopen(rd_q, dev, flag, sflag)
	queue_t  *rd_q;
	dev_t	dev;
	int flag, sflag;
{
	spl_t tplock;
	int unit = minor(dev);
	unchar firstopen;
	register struct sc_info *ip;

	/* Check for non-driver open */
	if (sflag != DRVROPEN)
		return (OPENFAIL);
		
	if (unit > scmaxdev)
		return (OPENFAIL);

	if ((ip = sc_info[unit]) == NULL)
		return (OPENFAIL);

	if (!ip->alive)
		return (OPENFAIL);

	/*
	 * Lock the device.
	 */
	tplock = p_lock(&ip->sc_lock, SPLSTR);

	REQ_CTTY();	/* inform stream head that we can be a ctty */
	firstopen = 0;

	/*
	 *  If the device is not already open, initialize it.
	 */
	if ((ip->sc_state & ISOPEN) == 0) {
		firstopen = 1;
		if (rd_q->q_ptr != NULL) {
			cmn_err(CE_WARN, "sc: first open found struct ptr");
			/*
			 *+ The console open routine has been passed a
			 *+ pointer to a stream that is already in use
			 *+ by another port or that was not cleaned up
			 *+ correctly.
			 */
			v_lock(&ip->sc_lock, tplock);
			return(OPENFAIL);
		}
		ip->msg = NULL;
		ip->qptr = WR(rd_q);
	 	ip->dev	 = dev;
	 	ip->bufcallid = 0;
		rd_q->q_ptr = (char *) ip; 
		WR(rd_q)->q_ptr = (char *) ip; 
		ip->sc_cflags = sccflags;
		ip->sc_state |= ISOPEN;
		ip->sc_modem = CCM_RTS | CCM_DTR;
		ip->sc_ld0flow = CCF_XOFF;
		ip->sc_ld0xon  = CSTART;
		ip->sc_ld0xoff = CSTOP;
		
		/*
		 * Send a message to the SSM to establish line
		 * settings and baud rate of this console port.
		 */
		if (sc_param(ip)) {
			ip->sc_state = 0;
			ip->qptr = NULL;
			rd_q->q_ptr = NULL;
			WR(rd_q)->q_ptr = NULL;
			v_lock(&ip->sc_lock, tplock);
			return (OPENFAIL);
		}

		sc_abort_read(ip);

		if (ip->sc_cflags & CREAD) {
			ip->restart_read = 1;
			sc_start_read(ip);
		} else
			ip->restart_read = 0;
	} 
	
	if (ip->sc_cflags & CLOCAL)
		ip->sc_state |= CARR_ON;
	else
		ip->sc_state &= ~CARR_ON;
	
	/*
	 *  Wait for carrier, if required. 
	 */

 	if (!(flag & FNDELAY)) {
		sc_get_modes(ip);	
		if (ip->sc_modem & CCM_DCD)
			ip->sc_state |= CARR_ON;
		while ((ip->sc_state & CARR_ON) == 0) {
			ip->sc_state |= WOPEN;
			if (p_sema_v_lock(&ip->os_carrier_wait, (PZERO+1) | PCATCH, 
			      		  &ip->sc_lock) == PCATCH) {
				if (firstopen) {
					tplock = p_lock(&ip->sc_lock, SPLSTR);
					ip->sc_state = 0;
					ip->qptr = NULL;
					rd_q->q_ptr = NULL;
					WR(rd_q)->q_ptr = NULL;
					ip->restart_read = 0;
					v_lock(&ip->sc_lock, tplock);
				}
				return(OPENFAIL);
			}
			tplock = p_lock(&ip->sc_lock, SPLSTR);
		}
		ip->sc_state &= ~WOPEN;
	}
	v_lock(&ip->sc_lock, tplock);
	return(OPENOK);
}

/*
 * scclose - close an SSM console port
 *
 * If the HUPCL flag is set in the sc_cflags word,
 * the RTS and DTR lines are turned off after the SSM tx buffer is 
 * flushed. 
 */
static
scclose(rd_q, flag)
	queue_t *rd_q;
	int flag;
{
	register struct sc_info *ip = (struct sc_info *) rd_q->q_ptr;
	spl_t	tplock;
	short wait, numq;
	uint	bid;
	int	drainid;

	tplock = p_lock(&ip->sc_lock, SPLSTR);
	ip->restart_read = 0;
	/*
	 * If the carrier is still active, and if the NDELAY flag is not set,
	 *  try to process all queued output
	 */
	if ((ip->sc_state & CARR_ON) && ((flag & FNDELAY) == 0)) {
		if ((numq = qsize(WR(rd_q))) || (ip->sc_state & BUSY)){
			ip->sc_state |= WCLOSE;
			ip->sc_flags &= ~DRAINTIME;
			wait = ((((numq * SC_AVGCNT) + ip->tx_count) *
				scbaud_time[(ip->sc_cflags & CBAUD)])/1000) +
				scaddwait;
			drainid = timeout(sc_draintime, (caddr_t)rd_q, wait * HZ);
			switch (p_sema_v_lock(&ip->os_busy_wait, PZERO+1 | PCATCH, &ip->sc_lock)) {
			case PCATCH:
				untimeout(drainid);
				tplock = p_lock(&ip->sc_lock, SPLSTR);	/* SIGNALLED */
				ip->sc_state &= ~WCLOSE;
				sc_abort_write(ip);
				break;
			default:
				untimeout(drainid);
				tplock = p_lock(&ip->sc_lock, SPLSTR);
				if (ip->sc_flags & DRAINTIME)
					sc_abort_write(ip);  /* DRAIN TIMEOUT */
			}

		} 
	} else
		/*
		 * If the carrier is off, or NDELAY is set, kill the 
		 * current output
		 */
		sc_abort_write(ip);

	/* in all close cases, release all allocated messages */
	flushq(WR(rd_q), FLUSHALL);
	freemsg(ip->msg);
	ip->msg = NULL;

	if ((ip->sc_cflags & HUPCL) == 0) {
		ip->sc_modem &= ~(CCM_DTR | CCM_RTS | CCM_BREAK);
		sc_set_modes(ip);
	}

	sc_abort_read(ip);
	bid=ip->bufcallid;
	ip->bufcallid = 1;	/* prevent further buffcalls */
	ip->sc_state &= ~ISOPEN;
	v_lock(&ip->sc_lock, tplock);
	if (bid) {
		unbufcall(bid);
	}

	disable_procs(rd_q);

	tplock = p_lock(&ip->sc_lock, SPLSTR);
	ip->sc_state = 0;
	freemsg(ip->msg);
	ip->msg = NULL;
	rd_q->q_ptr = NULL;
	WR(rd_q)->q_ptr = NULL;
	ip->qptr = NULL;
	v_lock(&ip->sc_lock, tplock);
}


/*
 * screcover - bufcall recovery interface.
 */
screcover(ip)
	struct sc_info *ip;
{
	mblk_t	*bp;
	int count;
	spl_t tplock;
	struct cons_rcb *rptr;

	tplock = p_lock(&ip->sc_lock, SPLSTR);
	rptr = (struct cons_rcb *)
		CONS_BASE_CB(ip->ssm->ssm_cons_cbs, ip->devno) + CCB_RECV_CB;
	count = SCRXSIZE - rptr->rcb_count;
	/*
	 * Try to get a streams buffer for the input
	 * data.  If we *still* cannot get a buffer, the only thing to do
	 * is bufcall again.  Otherwise, load the buffer with input data and
	 * send it upstream.
	 */
	if ((bp = allocb(count, BPRI_MED)) == NULL)
		ip->bufcallid = bufcall((uint) count, BPRI_MED, screcover, ip);
	else {
		ip->bufcallid = 0;
		bcopy((char *) &ip->rx_buf[0], (char *) bp->b_wptr, (unsigned) count);
		bp->b_wptr += count;
		if (canputnext(RD(ip->qptr))) {
			putnext(RD(ip->qptr), bp);
			ip->restart_read = 1;
			sc_start_read(ip);
		}
		else
			putq(RD(ip->qptr), bp);
	}
	v_lock(&ip->sc_lock, tplock);
}

/*
 * scintr - SSM interrupt service routine
 */
void
scintr(vector)
	int vector;
{
	register int count;
	struct cons_rcb *rptr;
	struct cons_xcb *xptr;
	int unit = (vector -= sc_base_vector) >> CCB_INTRSHFT;
	int status, func = vector & 1;
	struct sc_info *ip;
	mblk_t  *bp;
	spl_t tplock;

	if (unit < 0 || unit > scmaxdev) {
		cmn_err(CE_NOTE,"sc: stray interrupt, unit out of range %x\n",unit);
		/*
		 *+ An interrupt was received from an unknown SSM port.  The interrupt
		 *+ is discarded.  
		 */
		return;
	}
	ip = sc_info[unit];
	if (!ip->alive) {
		cmn_err(CE_NOTE,"sc: stray interrupt, dead unit %x\n", unit);
		/*
		 *+ At boot time, the kernel determined that the SSM was non-functional;
		 *+ however, an interrupt was received from the SSM.
		 *+ The interrupt is discarded. 
		 */
		return;
	}
	tplock = p_lock(&ip->sc_lock, SPLSTR);

	if ((ip->sc_state & ISOPEN) == 0) {
		v_lock(&ip->sc_lock, tplock);
		return;
	}
	
	/*
	 * Handle read-CB-done interrupts.
	 */
	if (func == CCB_RECV_CB) {
		l.cnt.v_rcvint++;
		rptr = (struct cons_rcb *)
			CONS_BASE_CB(ip->ssm->ssm_cons_cbs,     
				     ip->devno) + CCB_RECV_CB;
		
		switch (rptr->rcb_status) {
		case CCB_BUSY:
			/*
			 * Note:  The cmn_err call that used to be here was
			 * removed due to a funny with the ssm f/w handling
			 * of read flush.  Since the f/w doesn't re-read the
			 * current read CB when a flush command is received,
			 * the old INABLE/NOT IENABLE flag is used.  We don't
			 * want to be interrupted on a read flush, but the
			 * f/w does it anyway.  So, don't print anything here.
			 * Just ignore it.
			 */
			v_lock(&ip->sc_lock, tplock);
			return;

		case CCB_MS_CHG:
			l.cnt.v_mdmint++;
			/*
		  	 * If this port's DCD* line changed state,
		 	 *  send a message upstream.
			 */
			sc_get_modes(ip);
			if (!(ip->sc_modem & CCM_DCD)) {
				if (!(ip->sc_cflags & CLOCAL)) {
					ip->sc_state &= ~CARR_ON;
					if (!(ip->sc_state & WOPEN)) {
						if (ip->sc_dev == cons_id) 
							status = putnextctl1(RD(ip->qptr), M_SIG, SIGHUP);
						else 
							status = putnextctl(RD(ip->qptr), M_HANGUP);
						if (!status)
							cmn_err(CE_WARN,"sc: carrier drop report failed\n");
							/*
							    *+ A driver attempt to report a carrier drop
							    *+ upstream failed. 
				 	 	 	 */
					}
				}
			} else 
			        if ((ip->sc_state & CARR_ON) == 0) {
					ip->sc_state |= CARR_ON;
					vall_sema(&ip->os_carrier_wait);
				}
			break;
		
		case CCB_PERR:
			if (!putnextctl1(RD(ip->qptr), M_CTL, MC_PARITY_ERR))
				cmn_err(CE_WARN, "sc: parity error report failed\n");
				/*
				 *+ An attempt to send a M_CTL message 
				 *+ upstream to report a parity error
				 *+ on the SSM console port failed.
				 */
					
			ip->parity_errs++;
			break;	
		
		case CCB_OVERR:
			ip->overruns++;
			break;	
		
		case CCB_FERR:
		case CCB_BREAK:
			if (!putnextctl1(RD(ip->qptr), M_CTL, MC_FRAME_ERR))
				cmn_err(CE_WARN,"sc: break detect report failed\n");	
				/*
				  *+ A driver attempt to report a break
				  *+ detect upstream failed.  
		 		 */
			if (rptr->rcb_status == CCB_FERR)
				ip->frame_errs++;
			break;	
		
		case CCB_TIMEO:
		case CCB_OK:	
	                break;	
		}
		count = SCRXSIZE - rptr->rcb_count;
		/* Send the received chars upstream */
		if (count > 0) {
			l.cnt.v_rawch += count;

			if ((bp = allocb(count, BPRI_MED)) == NULL) {
				if (scprintalocfail)
					cmn_err(CE_WARN,"sc: unable to allocb %d bytes\n", count);
					/*
					  *+ A driver attempt to allocate a
					  *+ stream buffer for incoming data
					  *+ failed.  
				 	 */

				ip->restart_read = 0;
				if (ip->bufcallid == 0) {
					ip->bufcallid = bufcall((uint) count, BPRI_MED,
							screcover, ip);
				}
				(void) putnextctl1(RD(ip->qptr), M_CTL, MC_STOPI);
			} else {
				bcopy((char *) &ip->rx_buf[0], (char *) bp->b_wptr, (unsigned) count);
				bp->b_wptr += count;
				/*
				 *  Put the message upstream if possible.  Otherwise,
				 *  put the message on the local queue, block
				 *  interrupts on the correct line and set a flag
				 *  to indicate the we are blocked.  The message on
				 *  the local queue will be processed by the service
				 *  procedure, scrsrv().
				 */
				if (canputnext(RD(ip->qptr))) {
					putnext(RD(ip->qptr), bp);
				} else {
					putq(RD(ip->qptr), bp);
					ip->restart_read = 0;
					(void) putnextctl1(RD(ip->qptr), M_CTL, MC_STOPI);
				}
			}
		}
		if (ip->restart_read)
			sc_start_read(ip);

	} else {  
		/* Handle completed write operations */
		l.cnt.v_xmtint++;
		xptr = (struct cons_xcb *) 
			CONS_BASE_CB(ip->ssm->ssm_cons_cbs,
				     ip->devno) + CCB_XMIT_CB;
		
		/* If CB still busy, ignore spurious interrupt */	
		if (xptr->xcb_status == CCB_BUSY) {
			v_lock(&ip->sc_lock, tplock);
			return;
		}
		ip->sc_state &= ~BUSY;
		if ((bp = ip->msg) != NULL) {
			if (xptr->xcb_count != 0)
				bp->b_rptr += (ip->tx_count - xptr->xcb_count);
			else {
				bp = ip->msg->b_cont;
				ip->msg->b_cont = NULL;
				freeb(ip->msg);
				ip->msg = bp;
			}
			if (ip->msg != NULL) {
				ASSERT_DEBUG(!(ip->sc_state & TXDELAY), "scintr");
				scstart(ip, ip->msg);
			} else
				qenable(ip->qptr);
		}
	}
	v_lock(&ip->sc_lock, tplock);
}

/*
 * scrsrv - Read service procedure.  Takes a message
 * off the read queue and processes it, if the driver is able.
 */
static
scrsrv(rd_q)
        queue_t *rd_q;

{
	spl_t tplock;
	mblk_t *bp;
	struct sc_info *ip = (struct sc_info *) rd_q->q_ptr;

	/*
	 * Make sure there is something pointed to by
	 * rd_q.  If there isn't, return.
	 */
	if (rd_q == NULL) {
		cmn_err(CE_WARN, "scrsrv: bad rd_q passed to routine\n");
		/*
		 *+ The STREAMS system passed a bad read queue
		 *+ pointer to this service routine.
		 */
		return;
	}

	if (ip == NULL) {
		cmn_err(CE_WARN, "scrsrv: bad rd_q passed to routine: bad ip\n");
		/*
		 *+ The STREAMS system passed a bad read queue
		 *+ pointer to this service routine.
		 */
		return;
	}

	/*
	 *  Process the message on the queue.  There
	 *  should only be one, but we'll be safe and
	 *  use a while loop.
	 */
	while ((bp = getq(rd_q)) != NULL) {
		if (canputnext(rd_q)) {
			putnext(rd_q, bp);
		} else {
			putbq(rd_q, bp);
			return;
		}
	}

	/*
	 *  restart reads
	 */
	tplock = p_lock(&ip->sc_lock, SPLSTR);
	ip->restart_read = 1;
	sc_start_read(ip);
	v_lock(&ip->sc_lock, tplock);
}

/*
 * scmsg - write-side stream message handler
 *
 *  If the message heading downstream is of type M_FLUSH,
 *  flush the appropriate queues (read or write.)  If the
 *  flush message is NOT for the upstream path (read flush),
 *  it is released.  Otherwise, the message is sent upstream.
 * 
 *  scmsg() may be called when the console port's output side
 *  is busy.  It is up to the code to decide whether a message
 *  can be processed if the output side is busy.  
 */
scmsg(wr_q, mp)
	queue_t *wr_q;
	mblk_t  *mp;
{
	struct sc_info *ip = (struct sc_info *) wr_q->q_ptr;
	spl_t tplock;
	switch (mp->b_datap->db_type) {

	case M_PCCTL:
		if (mp->b_rptr != NULL) {
			switch (*mp->b_rptr) {
			case MC_STOP:		/* Suspend data to device */
				tplock = p_lock(&ip->sc_lock, SPLSTR);
				ip->sc_state |= SUSPOUT;
				if (ip->sc_state & BUSY)
					sc_suspend_out(ip);
				v_lock(&ip->sc_lock, tplock);
				break;

			case MC_START:		/* Unsuspend data to device */
				tplock = p_lock(&ip->sc_lock, SPLSTR);
				if (ip->sc_state & SUSPOUT) {
					ip->sc_state &= ~SUSPOUT;
					if (ip->sc_state & BUSY)
						sc_restart_out(ip);
				}
				v_lock(&ip->sc_lock, tplock);
				break;
	
			case MC_STOPI:		/* Suspend data from device */
				tplock = p_lock(&ip->sc_lock, SPLSTR);
				if (ip->sc_state & BUSY) {
					v_lock(&ip->sc_lock, tplock);
					return(PRIPUTBACK);
				}
				ip->sc_modem &= ~CCM_RTS;
				sc_set_modes(ip);
				sc_start_write(ip, 1, ++mp->b_rptr);
				v_lock(&ip->sc_lock, tplock);
				break;

			case MC_STARTI:		/* Unsuspend data from device */
				tplock = p_lock(&ip->sc_lock, SPLSTR);
				if (ip->sc_state & BUSY) {
					v_lock(&ip->sc_lock, tplock);
					return(PRIPUTBACK);
				}
				ip->sc_modem |= CCM_RTS;
				sc_set_modes(ip);
				sc_start_write(ip, 1, ++mp->b_rptr);
				v_lock(&ip->sc_lock, tplock);
				break;
			default:
				cmn_err(CE_WARN, "sc: M_PCCTL message received with bad subtype\n"); 
				/*
				 *+ A driver write put routine received a stream
				 *+ message of type M_PCCTL, but the message subtype
				 *+ is illegal.  
				 */
			}
		} else 
			cmn_err(CE_WARN, "sc: M_PCCTL message received with NULL data ptr\n");
			/*
			 *+ A driver write put routine received a stream
			 *+ message of type M_PCCTL, but the message did
			 *+ not include a data block containing a subcommand and
			 *+ its parameters.  
			 */
		freemsg(mp);
		break;

	case M_PCIOCTL:				/* IOCTL to be done *now* */
		tplock = p_lock(&ip->sc_lock, SPLSTR);
		if (ip->sc_state & BUSY) {
			v_lock(&ip->sc_lock, tplock);
			return(PRIPUTBACK);
		} else
			sc_doioctl(ip, mp);
		v_lock(&ip->sc_lock, tplock);
		break;

	case M_IOCTL:				/* non-priority IOCTL */
		tplock = p_lock(&ip->sc_lock, SPLSTR);
		if (ip->sc_state & BUSY) {
			v_lock(&ip->sc_lock, tplock);
			return(STDPUTBACK);
		} else
			sc_doioctl(ip, mp);
		v_lock(&ip->sc_lock, tplock);
		break;

	case M_FLUSH:				/* Flush data and SSM */
		if (mp->b_rptr != NULL) {
			if (*mp->b_rptr & FLUSHW) {
				tplock = p_lock(&ip->sc_lock, SPLSTR);
				flushq(wr_q, FLUSHDATA);
				sc_abort_write(ip);
				if (ip->msg) {
					freemsg(ip->msg);
					ip->msg = NULL;
				}
				ip->sc_state &= ~BUSY;
				v_lock(&ip->sc_lock, tplock);
			}
			if (*mp->b_rptr & FLUSHR) {
				tplock = p_lock(&ip->sc_lock, SPLSTR);
				flushq(RD(wr_q), FLUSHDATA);
				sc_abort_read(ip);
				if (ip->restart_read)
					sc_start_read(ip);
				v_lock(&ip->sc_lock, tplock);
				*mp->b_rptr &= ~FLUSHW;
				qreply(wr_q, mp);
				break;
			}
			else freemsg(mp);
		} else {
			cmn_err(CE_WARN, "sc: M_FLUSH message received with NULL data ptr\n");
			/*
			 *+ A driver write put routine received a stream
			 *+ message of type M_FLUSH, but the message did
			 *+ not include a data block indicating the
			 *+ type of flush to perform.  
			 */
			freemsg(mp);
		}
		break;

	case M_DELAY:
	case M_DATA:	                      /* Std. data or ioctl messages */
		tplock = p_lock(&ip->sc_lock, SPLSTR);
		if ((ip->sc_state & (BUSY | SUSPOUT | TXDELAY)) == 0) {
			scstart(ip, mp);
			v_lock(&ip->sc_lock, tplock);
		} else {
			v_lock(&ip->sc_lock, tplock);
			return(STDPUTBACK);
		}
		/*NOTREACHED*/
		break;
	default:
		freemsg(mp);
	}
	return(0);
}

/*
 * Service routine takes messages off write queue and
 * processes them, if the driver is able to.  If scmsg()
 * returns a non-zero status, put the message back on the
 * queue for later processing.
 */
static
scwsrv(wr_q)
queue_t *wr_q;
{
	spl_t	tplock;
	mblk_t *bp;
	int stat;
	struct sc_info *ip = (struct sc_info *) wr_q->q_ptr;

	while ((bp = getq(wr_q)) != NULL) {
		if ((stat = scmsg(wr_q, bp)) == STDPUTBACK) {
			putbq(wr_q, bp);
			return;
		} else if (stat == PRIPUTBACK) {
			putbqx(wr_q, bp);
			return;
		}
 	}
	tplock = p_lock(&ip->sc_lock, SPLSTR);
	if (ip->sc_state & WCLOSE)
		v_sema(&ip->os_busy_wait);
	v_lock(&ip->sc_lock, tplock);
}

/*
 * sc_doioctl - process M_IOCTL and M_PCIOCTL messages.
 * 
 * Assumes that the ip structure is locked.
 * Process message by calling sc_param() to set up hardware
 * parameters.  Acknowlege message and send it back upstream
 * if all goes well.
 *
 *>>>>>> NOTE: HARDWARE MUST NOT BE BUSY WHEN THIS ROUTINE IS CALLED!!
 */
sc_doioctl(ip, mp)
	struct sc_info *ip;
	mblk_t *mp;
{
	struct iocblk *iocp;
	struct termio *tptr;
	struct termios *tsptr;
	mblk_t *bp;
	queue_t *wr_q = (queue_t *) ip->qptr;
	int oldcflags;
	unchar oldflow, oldxon, oldxoff;

	ASSERT_DEBUG(!(ip->sc_state & BUSY), "sc_doioctl");

	if (mp->b_rptr == NULL) {
		cmn_err(CE_WARN, "sc: IOCTL message received with NULL data ptr\n");
		/*
		 *+ A driver write put routine received a stream
		 *+ message of type M_IOCTL or M_PCIOCTL, but the message did
		 *+ not include a data block containing subcommands and
		 *+ parameters.  
		 */
		mp->b_datap->db_type = M_IOCNAK;
		qreply(wr_q, mp);
		return;
	}
	iocp = (struct iocblk *) mp->b_rptr;
	switch (iocp->ioc_cmd) {
	case TCSETAF:			/* Set params with read flush and output drain */
		sc_abort_read(ip);
		tptr = (struct termio *) mp->b_cont->b_rptr;
		oldcflags = ip->sc_cflags;
		ip->sc_cflags = tptr->c_cflag;

		/*
		 * Look at the termio iflags to see what flow control
		 * is requested in this message.  Set up the appropriate
		 * flags for later transfer to the SSM f/w via sc_param().
		 */
		oldflow = ip->sc_ld0flow;
		if (tptr->c_iflag & IXON)
			ip->sc_ld0flow = CCF_XOFF;
		else if (tptr->c_iflag & IXANY)
			ip->sc_ld0flow = CCF_XANY;
		else 	ip->sc_ld0flow = CCF_NOFLOW;

		if ((iocp->ioc_error = sc_param(ip))) {
			ip->sc_cflags = oldcflags;
			ip->sc_ld0flow = oldflow;
			mp->b_datap->db_type = M_IOCNAK;
		} else {
			if (ip->sc_cflags & CLOCAL)
				ip->sc_state |= CARR_ON;
			mp->b_datap->db_type = M_IOCACK;
		}
		iocp->ioc_count = 0;
		if (!putnextctl1(RD(wr_q), M_FLUSH, FLUSHR)) {
			cmn_err(CE_WARN, "sc: TCSETAF flush msg failed\n");
			/*
			 *+ A driver attempt to send a read flush message upstream
			 *+ after processing a TCSETAF message failed.  
			 */
		}
		qreply(wr_q, mp);
		sc_start_read(ip);
		break;

	case TCSETPF:			/* Set params with read flush and output drain */
		sc_abort_read(ip);
		tsptr = (struct termios *) mp->b_cont->b_rptr;
		oldcflags = ip->sc_cflags;
		ip->sc_cflags = tsptr->c_cflag;

		/*
		 * Look at the termios iflags to see what flow control
		 * is requested in this message.  Set up the appropriate
		 * flags for later transfer to the SSM f/w via sc_param().
		 */
		oldflow = ip->sc_ld0flow;
		oldxon = ip->sc_ld0xon;
		oldxoff = ip->sc_ld0xoff;
		if (tsptr->c_iflag & IXON) {
			ip->sc_ld0flow = CCF_XOFF;
			ip->sc_ld0xon = tsptr->c_cc[VSTART];
			ip->sc_ld0xoff = tsptr->c_cc[VSTOP];
		} else if (tsptr->c_iflag & IXANY) {
			ip->sc_ld0flow = CCF_XANY;
			ip->sc_ld0xon = tsptr->c_cc[VSTART];
			ip->sc_ld0xoff = tsptr->c_cc[VSTOP];
		} else 	ip->sc_ld0flow = CCF_NOFLOW;

		if ((iocp->ioc_error = sc_param(ip))) {
			ip->sc_cflags = oldcflags;
			ip->sc_ld0flow = oldflow;
			ip->sc_ld0xon = oldxon;
			ip->sc_ld0xoff = oldxoff;
			mp->b_datap->db_type = M_IOCNAK;
		} else {
			if (ip->sc_cflags & CLOCAL)
				ip->sc_state |= CARR_ON;
			mp->b_datap->db_type = M_IOCACK;
		}
		iocp->ioc_count = 0;
		if (!putnextctl1(RD(wr_q), M_FLUSH, FLUSHR)) {
			cmn_err(CE_WARN, "sc: TCSETPF flush msg failed\n");
			/*
			 *+ A driver attempt to send a read flush message upstream
			 *+ after processing a TCSETPF message failed. 
			 */
		}
		qreply(wr_q, mp);
		sc_start_read(ip);
		break;

	case TCSBRK:
		if (*(int *) mp->b_cont->b_rptr == 0) {
			ip->sc_modem |= CCM_BREAK;
			sc_set_modes(ip);
			ip->sc_modem &= ~CCM_BREAK;
		}
		mp->b_datap->db_type = M_IOCACK;
		iocp->ioc_count = 0;
		qreply(wr_q, mp);
		break;

	case TCSETAW:
	case TCSETA:			/* Set parameters with output drain */
		tptr = (struct termio *) mp->b_cont->b_rptr;
		oldcflags = ip->sc_cflags;
		ip->sc_cflags = tptr->c_cflag;

		/*
		 * Look at the termio iflags to see what flow control
		 * is requested in this message.  Set up the appropriate
		 * flags for later transfer to the SSM f/w via sc_param().
		 */
		oldflow = ip->sc_ld0flow;
		if (tptr->c_iflag & IXON)
			ip->sc_ld0flow = CCF_XOFF;
		else if (tptr->c_iflag & IXANY)
			ip->sc_ld0flow = CCF_XANY;
		else 	ip->sc_ld0flow = CCF_NOFLOW;

		if ((iocp->ioc_error = sc_param(ip))) {
			ip->sc_cflags = oldcflags;
			ip->sc_ld0flow = oldflow;
			mp->b_datap->db_type = M_IOCNAK;
		} else {
			if (ip->sc_cflags & CLOCAL)
				ip->sc_state |= CARR_ON;
			mp->b_datap->db_type = M_IOCACK;
		}
		iocp->ioc_count = 0;
		qreply(wr_q, mp);
		break;

	case TCSETPW:
	case TCSETP:			/* Set parameters with output drain */
		tsptr = (struct termios *) mp->b_cont->b_rptr;
		oldcflags = ip->sc_cflags;
		ip->sc_cflags = tsptr->c_cflag;

		/*
		 * Look at the termios iflags to see what flow control
		 * is requested in this message.  Set up the appropriate
		 * flags for later transfer to the SSM f/w via sc_param().
		 */
		oldflow = ip->sc_ld0flow;
		oldxon = ip->sc_ld0xon;
		oldxoff = ip->sc_ld0xoff;
		if (tsptr->c_iflag & IXON) {
			ip->sc_ld0flow = CCF_XOFF;
			ip->sc_ld0xon = tsptr->c_cc[VSTART];
			ip->sc_ld0xoff = tsptr->c_cc[VSTOP];
		} else if (tsptr->c_iflag & IXANY) {
			ip->sc_ld0flow = CCF_XANY;
			ip->sc_ld0xon = tsptr->c_cc[VSTART];
			ip->sc_ld0xoff = tsptr->c_cc[VSTOP];
		} else 	ip->sc_ld0flow = CCF_NOFLOW;

		if ((iocp->ioc_error = sc_param(ip))) {
			ip->sc_cflags = oldcflags;
			ip->sc_ld0flow = oldflow;
			ip->sc_ld0xon = oldxon;
			ip->sc_ld0xoff = oldxoff;
			mp->b_datap->db_type = M_IOCNAK;
		} else {
			if (ip->sc_cflags & CLOCAL)
				ip->sc_state |= CARR_ON;
			mp->b_datap->db_type = M_IOCACK;
		}
		iocp->ioc_count = 0;
		qreply(wr_q, mp);
		break;

	case TCGETA:					/* Get parameters */
		if (mp->b_cont == NULL) {
			if ((bp = allocb(sizeof(struct termio), BPRI_MED)) == NULL) {
				cmn_err(CE_WARN, "sc: TCGETA data block allocb failed\n");
				/*
				 *+ An attempt to allocate a streams data buffer for
				 *+ TCGETA parameters failed.  
				 */
				 mp->b_datap->db_type = M_IOCNAK;
				 qreply(wr_q, mp);
				 break;
			}
			/* clear unset fields */ 
			bzero((char *) bp->b_rptr, sizeof(struct termio));
			mp->b_cont = bp;			/* Link data buffer */
		} else 
			if (mp->b_cont->b_datap->db_lim - 
			    mp->b_cont->b_datap->db_base < sizeof(struct termio)) {
				cmn_err(CE_WARN, "sc: TCGETA recvd data block bad size\n");
				/*
				 *+ A TCGETA ioctl with a preallocated buffer
				 *+ does not have enough allocated space to
				 *+ store the requested information. 
				 */
				 mp->b_datap->db_type = M_IOCNAK;
				 qreply(wr_q, mp);
				 break;
			}
		tptr = (struct termio *) mp->b_cont->b_wptr;    /* Cast data buffer */
		tptr->c_cflag = ip->sc_cflags;		/* Load cflags */
		mp->b_cont->b_wptr += sizeof(struct termio);
		iocp->ioc_count = sizeof(struct termio);
		mp->b_datap->db_type = M_IOCACK;
		qreply(wr_q, mp);
		break;

	case TCGETP:					/* Get parameters */
		if (mp->b_cont == NULL) {
			if ((bp = allocb(sizeof(struct termios), BPRI_MED)) == NULL) {
				cmn_err(CE_WARN, "sc: TCGETP data block allocb failed\n");
				/*
				 *+ An attempt to allocate streams data buffer for
				 *+ TCGETP parameters failed.  
				 */
				 mp->b_datap->db_type = M_IOCNAK;
				 qreply(wr_q, mp);
				 break;
			}
			/* clear unset fields */ 
			bzero((char *) bp->b_rptr, sizeof(struct termios));
			mp->b_cont = bp;			/* Link data buffer */
		} else 
			if (mp->b_cont->b_datap->db_lim - 
			    mp->b_cont->b_datap->db_base < sizeof(struct termios)) {
				cmn_err(CE_WARN, "sc: TCGETP recvd data block bad size\n");
				/*
				 *+ A TCGETP ioctl with a preallocated buffer
				 *+ does not have enough allocated space to
				 *+ store the requested information.  
				 */
				 mp->b_datap->db_type = M_IOCNAK;
				 qreply(wr_q, mp);
				 break;
			}
		tsptr = (struct termios *) mp->b_cont->b_wptr;    /* Cast data buffer */
		tsptr->c_cflag = ip->sc_cflags;		/* Load cflags */
		mp->b_cont->b_wptr += sizeof(struct termios);
		iocp->ioc_count = sizeof(struct termios);
		mp->b_datap->db_type = M_IOCACK;
		qreply(wr_q, mp);
		break;

	default:
		/* nak the ioctl */
		mp->b_datap->db_type = M_IOCNAK;
		qreply(wr_q, mp);
	}
}

/*
 * This table defines the SC command bits for setting asynchronous
 * baud rates.  '-1' is the signal to turn off the DTR* output.
 * '-2' indicates that a baud rate is not available on the console.
 */

#define BHANGUP		((unchar)0xff)		/* hangup */
#define BNOSUPP		((unchar)0xfe)		/* baud rate not supported */

unchar sc_mrates[] = {
	BHANGUP,	/* B0	  (hangup)	*/
	BNOSUPP,	/* B50	  (not avail.)	*/
	BNOSUPP,	/* B75	  (not avail.)	*/
	B110,		/* B110			*/
	BNOSUPP,	/* B134.5 (not avail.)	*/
	BNOSUPP,	/* B150	  (not avail.)	*/
	BNOSUPP,	/* B200	  (not avail.)	*/
	B300,		/* B300			*/
	BNOSUPP,	/* B600	  (not avail.)	*/
	B1200,		/* B1200		*/
	BNOSUPP,	/* B1800  (not avail.)	*/
	B2400,		/* B2400		*/
	B4800,		/* B4800		*/
	B9600,		/* B9600		*/
	B19200,		/* B19200		*/
	B38400		/* B38400		*/
};

/*
 * sc_param - set line parameters on the SSM console hardware.
 *
 * returns 0 for success, or errno on error
 *
 * Mutex: assumed called with locked tp to insure stable data
 * being read.
 */
sc_param(ip)
	register struct sc_info *ip;
{
	ushort cflags = ip->sc_cflags;
	unchar speed = sc_mrates[(cflags & CBAUD)];

	ASSERT_DEBUG(!(ip->sc_state & BUSY), "sc_param");

	if (speed == BNOSUPP) {
		cmn_err(CE_NOTE, "sc: illegal baud rate requested\n");
		/*
		 *+ The console does not support 1800, 600, 200, 150, 134.5,
		 *+ 110, 75, or 50 baud.
		 */
		return (EINVAL);
	}

	if (speed == BHANGUP) {
		ip->sc_modem &= ~(CCM_RTS | CCM_BREAK | CCM_DTR);
		sc_set_modes(ip);
		return (0);
	} else {
		ip->sc_parsiz &= ~CCP_CSMASK;		/* Clear char siz bits*/
		switch (cflags & CSIZE) {
		case CS8:
			ip->sc_parsiz |= CCP_CSIZ8;
			break;
		case CS7:
			ip->sc_parsiz |= CCP_CSIZ7;
			break;
		case CS6:
			ip->sc_parsiz |= CCP_CSIZ6;
			break;
		case CS5:
			ip->sc_parsiz |= CCP_CSIZ5;
			break;
		}

		ip->sc_parsiz &= ~CCP_PMASK;		/* Clear parity bits */
		if (cflags & PARENB)
			if (cflags & PARODD) 
				ip->sc_parsiz |= CCP_ODDP;
			else
				ip->sc_parsiz |= CCP_EVENP;
		
		ip->sc_baud = speed;

		ip->sc_parsiz &= ~CCP_STMASK;		/* Clear stop bit mask*/
		if (cflags & CSTOPB)
			ip->sc_parsiz |= CCP_ST2;
		else
			ip->sc_parsiz |= CCP_ST1;

	}

	/* Send the new console parameters to the SSM */
	sc_set_modes(ip);

	return (0);
}

/*
 * scstart - Send characters to the console output.
 *
 * Assumes that the ip structure is locked.  Routine must be called
 * when the output busy flag is not set.  This flag is not checked in the
 * routine and assumes that it if off.
 *
 *>>>>>> NOTE: HARDWARE MUST NOT BE BUSY WHEN THIS ROUTINE IS CALLED!!
 */
int
scstart(ip, bp)
	struct sc_info *ip;
	mblk_t *bp;
{
	int nch;

	ASSERT_DEBUG(!(ip->sc_state & BUSY), "scstart");
	switch (bp->b_datap->db_type) {
	case M_DELAY:
		if (bp->b_rptr != NULL) {
			ASSERT_DEBUG(ip->sc_delayid == 0, "scstart");
			ip->sc_state |= TXDELAY;
			ip->sc_delayid = timeout(sc_delaytime, 
						 (caddr_t) ip,
						 *(int *) bp->b_rptr);
			freemsg(bp);
			return;
		} else {
			cmn_err(CE_WARN, "sc: M_DELAY message received with NULL data ptr\n");
			/*
			 *+ A driver write put routine received a stream
			 *+ message of type M_DELAY, but the message did
			 *+ not include a data block containing the
			 *+ amount of time to delay.  
			 */
			freemsg(bp);
			return;
		}
		/*NOTREACHED*/
		break;

	case M_DATA:
		ip->msg = bp;
		if (bp->b_rptr >= bp->b_wptr) {
			bp = ip->msg->b_cont;
			ip->msg->b_cont = NULL;
			freeb(ip->msg);
			ip->msg = bp;
			if (ip->msg == NULL)
				return;
		}
		/* send block of data in msg to port */
		nch = bp->b_wptr - bp->b_rptr;
		l.cnt.v_outch += nch;
		sc_start_write(ip, nch, bp->b_rptr);
		ip->sc_state |= BUSY;
		break;
	}
}

/*
 * sc_ssm_send - send an SSM SLIC interrupt 
 */
sc_ssm_send(ip, func)
	struct sc_info *ip;
	int func;
{
	spl_t s = splhi();
	mIntr(ip->ssm->ssm_slicaddr, CONS_BIN,
	      (unchar)COVEC(ip->devno, func));
	splx(s);
}

/*
 * sc_start_read - Start an SSM read request and do not wait for the
 *                  request to complete.
 */
sc_start_read(ip)
	struct sc_info	*ip; 
{
	struct cons_rcb *rptr;
	
	rptr = (struct cons_rcb *)
		CONS_BASE_CB(ip->ssm->ssm_cons_cbs,
			     ip->devno) + CCB_RECV_CB;
	rptr->rcb_cmd = CCB_RECV | CCB_IENABLE | CCB_TERM_MS;
	rptr->rcb_status = CCB_BUSY;
	rptr->rcb_count = SCRXSIZE;
	rptr->rcb_addr = (ulong)ip->rx_buf;
	rptr->rcb_timeo = scrxtime;
	sc_ssm_send(ip, CCB_RECV_CB);
}

/*
 * sc_start_write - Start an SSM write request and do not wait for the
 *                   request to complete.
 */
sc_start_write(ip, nch, addr)
	struct sc_info *ip;
	register int nch;
	register unchar *addr;
{
	register struct cons_xcb *xptr;

	xptr = (struct cons_xcb *)
		CONS_BASE_CB(ip->ssm->ssm_cons_cbs,
			     ip->devno) + CCB_XMIT_CB;
	xptr->xcb_cmd = CCB_XMIT | CCB_IENABLE | CCB_TERM_MS;
	xptr->xcb_status = CCB_BUSY;
	xptr->xcb_addr = addr;
	xptr->xcb_count = ip->tx_count = (ushort)nch;
	sc_ssm_send(ip, CCB_XMIT_CB);
}

/*
 * sc_set_modes - Set console serial port settings.      
 *
 */
sc_set_modes(ip)
	struct sc_info *ip;
{
	struct cons_mcb *mptr;
	
	mptr = (struct cons_mcb *)
		CONS_BASE_CB(ip->ssm->ssm_cons_cbs,
			     ip->devno) + CCB_MSG_CB;
	mptr->mcb_cmd = CCB_STTY;
	mptr->mcb_status = CCB_BUSY;
	mptr->mcb_modem = ip->sc_modem;
	mptr->mcb_baud = ip->sc_baud;
	mptr->mcb_parsiz = ip->sc_parsiz; 
	mptr->mcb_flow = scflow;
	mptr->mcb_oflow = ip->sc_ld0flow;
	mptr->mcb_oxoff = ip->sc_ld0xoff;
	mptr->mcb_oxon  = ip->sc_ld0xon;
	sc_ssm_send(ip, CCB_MSG_CB);
	while (mptr->mcb_status == CCB_BUSY)
		;
}

/*
 * sc_get_modes - Get current console serial port settings.
 */
sc_get_modes(ip)
	struct sc_info *ip;
{
	struct cons_mcb *mptr;

	mptr = (struct cons_mcb *)
		CONS_BASE_CB(ip->ssm->ssm_cons_cbs,
			     ip->devno) + CCB_MSG_CB;
	mptr->mcb_cmd = CCB_GTTY;
	mptr->mcb_status = CCB_BUSY;
	sc_ssm_send(ip, CCB_MSG_CB);
	while (mptr->mcb_status == CCB_BUSY)
		;
	ip->sc_modem = mptr->mcb_modem;
	ip->sc_baud = mptr->mcb_baud;
	ip->sc_parsiz = mptr->mcb_parsiz;
}

/*
 * sc_abort_read - Abort current read on SSM.
 */
sc_abort_read(ip)
	struct sc_info *ip;
{
	struct cons_rcb *rptr;
	spl_t s;

	rptr = (struct cons_rcb *) 
	        CONS_BASE_CB(ip->ssm->ssm_cons_cbs,
			     ip->devno) + CCB_RECV_CB;
	rptr->rcb_status = CCB_BUSY;
	s = splhi();
	mIntr(ip->ssm->ssm_slicaddr, CONS_BIN,
	      CONS_FLUSH(ip->devno, CCB_RECV_CB));
	splx(s);
	while (rptr->rcb_status == CCB_BUSY)
		;
}


/*
 * sc_suspend_out - Suspend SSM data output
 */
sc_suspend_out(ip)
	struct sc_info *ip;
{
	struct cons_xcb *xptr;
	spl_t s;

	xptr = (struct cons_xcb *) 
	        CONS_BASE_CB(ip->ssm->ssm_cons_cbs,
			     ip->devno) + CCB_XMIT_CB;
	xptr->xcb_status = CCB_BUSY;
	s = splhi();
	mIntr(ip->ssm->ssm_slicaddr, CONS_BIN,
	      CONS_STOP(ip->devno));
	splx(s);
	while (xptr->xcb_status == CCB_BUSY)
		;
}


/*
 * sc_restart_out - Restart suspended SSM data output
 */
sc_restart_out(ip)
	struct sc_info *ip;
{
	struct cons_xcb *xptr;
	spl_t s;

	xptr = (struct cons_xcb *) 
	        CONS_BASE_CB(ip->ssm->ssm_cons_cbs,
			     ip->devno) + CCB_XMIT_CB;
	xptr->xcb_status = CCB_BUSY;
	s = splhi();
	mIntr(ip->ssm->ssm_slicaddr, CONS_BIN,
	      (unchar) COVEC(ip->devno, CCB_XMIT_CB));
	splx(s);
	while (xptr->xcb_status == CCB_BUSY)
		;
}

/* 
 * sc_abort_write - Abort current write on SSM.
 */
sc_abort_write(ip)
	struct sc_info *ip;
{
	struct cons_xcb *xptr;
	spl_t	s;
	/*
	 * Send a SLIC interrupt to the SSM so that the 
	 * SSM's tx buffer will be flushed.  Wait for the flush
	 * to complete.
	 */
	xptr = (struct cons_xcb *) 
	        CONS_BASE_CB(ip->ssm->ssm_cons_cbs,
			     ip->devno) + CCB_XMIT_CB;
	xptr->xcb_status = CCB_BUSY;
	s = splhi();
	mIntr(ip->ssm->ssm_slicaddr, CONS_BIN,
	      CONS_FLUSH(ip->devno, CCB_XMIT_CB));
	splx(s);
	while (xptr->xcb_status == CCB_BUSY)
		;
}

/*
 * init_ssm_cons_dev - initialize SSM console port 
 *
 * Initializes an SSM console port by sending a CCB_INIT 
 * message to the SSM.
 * Assumes that mIntr() retries messages until they succeed.
 */
void
init_ssm_cons_dev(slic, cons_cbs, bin, basevec, unit)
	unchar	slic;
	struct cons_cb 	*cons_cbs;
	unchar bin, basevec;
	unchar unit;
{
	struct cons_icb *iptr;
	
	iptr = (struct cons_icb *) (CONS_BASE_CB(cons_cbs, unit) + CCB_MSG_CB);
	iptr->icb_cmd = CCB_INIT;
	iptr->icb_dest = SL_GROUP | TMPOS_GROUP;
	iptr->icb_basevec = basevec;
	iptr->icb_scmd = SL_MINTR | bin;
	iptr->icb_status = CCB_BUSY;

	mIntr(slic, CONS_BIN, (unchar)COVEC(unit, CCB_MSG_CB));
	while (iptr->icb_status == CCB_BUSY)
		;
}

/*
 * Called when timer expires waiting on output to drain during close.
 * Set appropriate flags and wakeup sleeping process which is waiting to
 * close.
 */
sc_draintime(rd_q)
	queue_t *rd_q;
{
	spl_t s;
	struct sc_info *ip = (struct sc_info *) rd_q->q_ptr;

	s = p_lock(&ip->sc_lock, SPLSTR);
	if (ip->sc_state & WCLOSE) {
		if (qsize(WR(rd_q)))
			ip->sc_flags |= DRAINTIME;
		v_sema(&ip->os_busy_wait);
	}
	v_lock(&ip->sc_lock, s);
}

/*
 * Called when timer expires after number of machine ticks as specified in
 * an M_DELAY message.
 */
sc_delaytime(ip)
	struct sc_info *ip;
{
	spl_t s;

	s = p_lock(&ip->sc_lock, SPLSTR);
	ip->sc_state &= ~TXDELAY;
	ip->sc_delayid = 0;
	qenable(ip->qptr);
	v_lock(&ip->sc_lock, s);
}
