/* $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: co.c 1.81 90/10/22 $";

/*
 * co.c
 *	 SCSI/Ether console driver (STREAMS version)
 */

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

/*
 * There are two consoles per SEC card.  The SEC attached to the
 * front panel is distinguished.  The port selected as console on this
 * sec can be accessed through minor device 64 (0x40).  All ports can
 * be accessed through minor device numbers 0 through 2*n, for n sec
 * boards.  The minor device number 64 is an alias for one of these
 * devices.
 */

#include "sys/sysmacros.h"
#include "sys/debug.h"
#include "sys/types.h"
#include "sys/mutex.h"
#include "sys/devsw.h"
#include "sys/stropts.h"
#include "sys/param.h"
#include "sys/timer.h"
#include "sys/signal.h"
#include "sys/user.h"
#include "sys/proc.h"
#include "sys/conf.h"
#include "sys/ioctl.h"
#include "sys/systm.h"
#include "sys/file.h"
#include "sys/uio.h"
#include "sys/session.h"
#include "sys/stream.h"
#include "sys/cfg.h"
#include "sys/vmmac.h"
#include "sys/vmmeter.h"
#include "sys/vmsystm.h"
#include "sys/vmparam.h"
#include "sys/pte.h"
#include "sys/plocal.h"
#include "sys/intctl.h"
#include "sys/cmn_err.h"
#include "sys/termio.h"
#include "sys/sec.h"
#include "sys/consmemioctl.h"
#include "sys/sec_modes.h"
#include "sys/errno.h"
#include "sys/strid.h"
#include "sys/co.h"

extern int co_c_major, sm_c_major;
extern dev_t smem_devnum, cons_devnum;
extern char cons_kill, cons_erase, cons_intr;
extern struct firmwaresw sced_interface;
extern unchar coprintalocfail;
extern unchar coprintupstreamblkd;

#define	CO_FLAG	0x40

int co_draintime(), co_breaktime(), co_delaytime();
static int coopen(), coclose(), cowsrv();

static struct module_info com_info = {
/*  module ID    module name   min pkt size    max pkt siz  hiwat  lowat */
     STRID_CO,    "co",           0,            INFPSZ,    150,   50
};

static struct qinit corinit = {
	NULL, NULL, coopen,  coclose,  NULL,  &com_info,  NULL
};

static struct qinit cowinit = {
	putq,  cowsrv,  NULL,  NULL,  NULL,  &com_info,  NULL
};

struct streamtab coinfo = { &corinit,  &cowinit,  NULL, NULL };

int coboot();
void cointr();

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

int 	co_max_unit = -1;
unchar 	co_base_vec;
struct 	co_state *co_state;
int	cons_devs_added = 0;
extern	struct	devops	smdevops;
extern	struct	devops	codevops;

extern int cocflags;
extern uint bufcall();
extern int coaddwait;
extern putbqx();
struct sec_dev_prog * CO_GET_Q_TAIL();
struct sec_dev_prog * CO_GET_Q_HEAD();

extern dev_t cons_id;
extern dev_t altcons_id;


/*
 * coboot - allocate data structures, etc at beginning of time.
 *
 * Called by autoconfig with an array of configured devices and their
 * number.  We allocate the necessary soft descriptions and fill them
 * with the various fields from the devs[] array.  Due to the dual 
 * device-channel nature of SEC devices, each devs[] entry describes
 * input side or output side of a console device.
 */

coboot(ndevs, devs)
	struct sec_dev *devs;
{
	register int i;
	struct dev *dp;

	/* 
	 * find out the maximum potential unit number, and
	 * allocate array of soft states.
	 */

	for (i = 0; i < ndevs; ++i) {
		if (co_max_unit < 2*devs[i].sd_sec_idx + 1)
			co_max_unit = 2*devs[i].sd_sec_idx + 1;
	}

	co_state = (struct co_state *)calloc((co_max_unit + 1) *
			sizeof(struct co_state));
	
	co_base_vec = devs[0].sd_vector;

	/*
	 * Load scedmon special characters for ld0.
	 */
	cons_erase = CD_LOC->c_erase;
	cons_kill  = CD_LOC->c_kill;
	cons_intr  = CD_LOC->c_interrupt;


	/*
	 * now boot each configured device.
	 */

	for (i = 0; i < ndevs; ++i) {
		register struct sec_dev *devp;
		register int unit;

		devp = &devs[i];
		if (devp->sd_alive == 0)
			continue;

		unit = i/2;

		if (unit >= co_bin_config_count) {
			cmn_err(CE_WARN, 
				"co%d: console device not binary configured.\n",
				unit);
			/*
			 *+ The console device is not configured.  Corrective
			 *+ action:  edit the system.std file to include all 
			 *+ SCED console devices.
			 */
			continue;
		}

		co_boot_one(&co_state[unit], devp, unit);
		if (i % 2 == 0) {
			dp = (struct dev *)calloc(sizeof(struct dev));
			dp->d_dev = makedev(co_c_major, unit);
			co_state[unit].co_dev = dp->d_dev;
			dp->d_ops = &codevops;
			if (devsw_add(dp) != 0) {
				cmn_err(CE_WARN,
			     "co%d: failure to add SCED console dev to devsw\n",
				unit);
				/*
				 *+ The kernel encounted a problem when adding a SCED
				 *+ 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 is already assigned to
				 *+ another dev.
				 */
			}
		}
	}
}

/*
 * co_boot_one - combine binary config, autoconfig information into
 *		 softstate, for convenient access.
 *
 * Allocate device program, etc.  The device program queues were allocated
 * by autoconfig.
 *
 * status pointers for cibs point to data in the soft state.
 *
 * No locking needs to be done here, as we are still running config
 * code single processor.
 */
co_boot_one(softp, sd, unit)
	register struct co_state *softp;
	register struct sec_dev *sd;
	int unit;
{
	struct sec_cib *pu_cib;
	struct dev *dp;

#ifdef DEBUG
	if (co_debug)
		CPRINTF("B");
	else if (co_debug > 1)
		CPRINTF("B%d.\n", sd->sd_unit);
#endif /* DEBUG */

	if (!softp->ss_initted) {
		softp->ss_alive = 1;
		softp->ss_initted = 1;
		init_lock(&softp->lock);
		if (sd->sd_desc->sec_is_cons) {
			switch (sd->sd_desc->sec_is_cons) {
			case CDSC_LOCAL:
				if (sd->sd_chan == SDEV_CONSOLE0IN ||
					sd->sd_chan == SDEV_CONSOLE0OUT) {
					cons_devnum = cons_id = makedev(co_c_major, unit);
					altcons_id = makedev(co_c_major, unit + 1);
				}
				break;

			case CDSC_REMOTE:
				if (sd->sd_chan == SDEV_CONSOLE1IN ||
					sd->sd_chan == SDEV_CONSOLE1OUT) {
					cons_devnum = cons_id = makedev(co_c_major, unit);
					altcons_id = makedev(co_c_major, unit - 1);
				}
				break;

			default:
				cmn_err(CE_WARN, "Bad SCED console selected\n");
				/*
				 *+ A SCED console is neither the local or the
				 *+ remote port.  Corrective action:  check the front panel
				 *+ console section.
				 */
			}
			smem_devnum = makedev(sm_c_major, 64);
			firmwaresw = &sced_interface;
			/*
			 * initialize the devs for these devices
			 */
			if (!cons_devs_added) {
				cons_devs_added++;
				dp = (struct dev *)calloc(sizeof(struct dev));
				dp->d_dev = makedev(sm_c_major, 64);
				smem_devnum = dp->d_dev;
				dp->d_ops = &smdevops;
				if (devsw_add(dp) != 0) {
					cmn_err(CE_WARN,
		"co: failure to add smem pseudo-driver dev to devsw\n");
					/*
					 *+ The kernel encountered a problem 
					 *+ when it tried 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
					 *+ is already assigned to another dev.
					 */
				}
			}
			
		}

	}

	if (sd->sd_chan == SDEV_CONSOLE0IN || sd->sd_chan == SDEV_CONSOLE1IN) {
		ASSERT(!softp->is_initted, "co_boot_one: bad dev");
		/*
		 *+ A SCED console input port has already been initialized.
		 *+ This port has been passed to co_boot_one to be
		 *+ initialized again.
		 */

		softp->is_sd = sd;
		softp->is_ovc = 0;
		softp->is_parc = 0;

		softp->is_reqq.sq_size = sd->sd_req_size;
		softp->is_reqq.sq_progq = sd->sd_requestq;

		softp->is_doneq.sq_size = sd->sd_doneq_size;
		softp->is_doneq.sq_progq = sd->sd_doneq;

		SEC_fill_progq(softp->is_reqq.sq_progq,
			       (int)softp->is_reqq.sq_size,
			       (int)sizeof(struct sec_dev_prog));

		softp->is_initted = 1;

	} else 
	if (sd->sd_chan==SDEV_CONSOLE0OUT || sd->sd_chan==SDEV_CONSOLE1OUT) {
		ASSERT(!softp->os_initted, "co_boot_one: output initted");
		/*
		 *+ A SCED console output port has already been initialized.
		 *+ This port has been passed to co_boot_one to be
		 *+ initialized again.
		 */
		softp->os_sd = sd;

		softp->os_reqq.sq_size = sd->sd_req_size;
		softp->os_reqq.sq_progq = sd->sd_requestq;

		softp->os_doneq.sq_size = sd->sd_doneq_size;
		softp->os_doneq.sq_progq = sd->sd_doneq;

		SEC_fill_progq(softp->os_reqq.sq_progq,
				(int)softp->os_reqq.sq_size,
				(int)sizeof(struct sec_dev_prog));

		softp->os_busy_flag = 0;
		init_sema(&softp->rawqwait, 0);
		init_sema(&softp->os_busy_wait, 0);

		pu_cib = &softp->os_sd->sd_desc->sec_powerup->pu_cib;
#ifdef	DEBUG
		if (co_debug > 2){
			CPRINTF("coboot doing getmode\n");
		}
#endif	/* DEBUG */
		SEC_startio(SINST_GETMODE, &softp->os_smode.sm_status, 
			sd->sd_bin,
			sd->sd_chan,
			pu_cib,
			sd->sd_desc->sec_slicaddr);
		if (softp->os_smode.sm_status & (~SINST_INSDONE))
			CPRINTF("co: bad get_mode: %x\n", softp->os_smode.sm_status);
#ifdef	DEBUG
		if (co_debug > 2)
			CPRINTF("coboot getmode done flags %x, baud %x\n",
			softp->os_smode.CM_FLAGS, softp->os_smode.CM_BAUD);
#endif	/* DEBUG */

		softp->os_smode.CM_FLAGS &= ~(SCONS_DZMODE );
 
#ifdef	DEBUG
		if (co_debug > 2)
			CPRINTF("coboot doing setmode\n");
#endif	/* DEBUG */
		SEC_startio(SINST_SETMODE, &softp->os_smode.sm_status, 
			sd->sd_bin,
			sd->sd_chan,
			pu_cib,
			sd->sd_desc->sec_slicaddr);
		if (softp->os_smode.sm_status & (~SINST_INSDONE))
		CPRINTF("co: bad set_mode: %x\n", softp->os_smode.sm_status);
#ifdef	DEBUG
		if (co_debug > 2)
			CPRINTF("coboot setmode done flags %x, baud %x\n",
			softp->os_smode.CM_FLAGS, softp->os_smode.CM_BAUD);
#endif	/* DEBUG */

		softp->os_initted = 1;

	} else {
		CPRINTF("co %d: invalid device chan %d(0x%x) in boot routine\n",
			sd->sd_sec_idx, sd->sd_chan, sd->sd_chan);
	}
}

/*
 * coopen - Open a SCED console device
 */
static
coopen(rd_q, dev, flag, sflag)
	queue_t *rd_q; 				/* Read queue */
	dev_t dev;
	int flag, sflag;
{
	register struct co_state *softp;
	register unit = minor(dev);
	spl_t	tplock;
	unchar firstopen;

	/* Check if non-driver open */
	if (sflag != DRVROPEN)
		return (OPENFAIL);
		
	if (unit > co_max_unit)
		return (OPENFAIL);
	else
		softp = &co_state[unit];

	/* Verify that the device is alive */
	if (!softp->ss_alive)
		return (OPENFAIL);
	tplock = p_lock(&softp->lock, SPLSTR);

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

	/* If this is the first open of this device */
	if ((softp->state & CO_ISOPEN) == 0) {
		firstopen = 1;
		if (rd_q->q_ptr != NULL) {
			cmn_err(CE_WARN, "co: 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(&softp->lock, tplock);
			return(OPENFAIL);
		}
		softp->cflags = cocflags;	/* Initial value of cflags */
		if (dev == cons_id) {
			softp->cflags &= ~CBAUD; 	/* Clear baud rate */
			softp->cflags |= softp->os_smode.CM_BAUD;
		}
		
		softp->bufcallid = 0;
		softp->msg = NULL;
		softp->qptr = WR(rd_q);
		rd_q->q_ptr = (char *) softp;
		WR(rd_q)->q_ptr = (char *) softp;
		softp->state |= CO_ISOPEN;	/* Stream is in place */
		/* Turn on DTR and RTS */
		softp->os_smode.CM_FLAGS &= ~( SCONS_CLEAR_RTS | SCONS_CLEAR_DTR |
					       SCONS_IGN_CARRIER);
		co_param(softp);
		co_abort_read(softp);
		if (softp->cflags & CREAD) {
			softp->is_restart_read = 1;
			co_start_read(softp);
		} else
			softp->is_restart_read = 0;
	}
	
	if (softp->cflags & CLOCAL)
		softp->state |= CO_CARR_ON;
	else
		softp->state &= ~CO_CARR_ON;
	
	/* Wait for carrier, if required */
	if (!(flag & FNDELAY)) {
		co_get_modes(softp);
		if ((softp->os_smode.CM_FLAGS & SCONS_CARRIER_CLEAR) == 0)
			softp->state |= CO_CARR_ON;
		while ((softp->state & CO_CARR_ON) == 0)  {
			softp->state |= CO_WOPEN;
			if (p_sema_v_lock(&softp->rawqwait, (PZERO+1) | PCATCH, 
			      		  &softp->lock) == PCATCH) {
				if (firstopen) {
					softp->state = 0;
					softp->qptr = NULL;
					rd_q->q_ptr = NULL;
					WR(rd_q)->q_ptr = NULL;
					softp->is_restart_read = 0;
				}
			      return(OPENFAIL);	
			}
			tplock = p_lock(&softp->lock, SPLSTR);
		}
		softp->state &= ~CO_WOPEN;
	}
	v_lock(&softp->lock, tplock);
	return(OPENOK);
}
	
/*
 * coclose - Close a SCED console device 
 */
static
coclose(rd_q, flag)
	queue_t *rd_q;				/* Read queue */
	int flag;
{
	spl_t tplock;
	register struct co_state *softp = (struct co_state *) rd_q->q_ptr;
	short wait, numq;
	uint	bid;
	int	drainid;
	
	tplock = p_lock(&softp->lock, SPLSTR);
	softp->is_restart_read = 0;	/* No more rx */

	/* 
	 * If the carrier is still active and NDELAY is not set,
	 * try to process all queued output
	 */
	if ((softp->state & CO_CARR_ON) && ((flag & FNDELAY) == 0)) {
		/*
		 * If messages are still queued up on the write queue or
		 * if we are currently sending a message out, we must wait.
		 * The situation of no-current-message but messages
		 * enqueued on the write queue should never happen.  If it does,
		 * this test will handle it.
		 */
		if ((numq = qsize(WR(rd_q))) || (softp->state & CO_BUSY)){
			softp->state |= CO_WCLOSE;
			softp->flags &= ~DRAINTIME;
			wait = ((((numq * CO_AVGCNT) + softp->lasttxsize) *
				cobaud_time[(softp->cflags & CBAUD)])/1000) +
				coaddwait;
			drainid = timeout(co_draintime, (caddr_t)rd_q, wait * HZ);
			switch (p_sema_v_lock(&softp->rawqwait, PZERO+1 | PCATCH, &softp->lock)) {
			case PCATCH:
				untimeout(drainid);
				tplock = p_lock(&softp->lock, SPLSTR);	/* SIGNALLED */
				softp->state &= ~CO_WCLOSE;
				co_abort_write(softp);
				break;
			default:
				untimeout(drainid);
				tplock = p_lock(&softp->lock, SPLSTR);
				if (softp->flags & DRAINTIME)
					co_abort_write(softp);   	 /* DRAIN TIMEOUT */
			}

		} 
	} else
		/* 
		 * If the carrier is off or NDELAY is set, 
		 * kill the current output.
		 */
		co_abort_write(softp);

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

	if (softp->cflags & HUPCL) { 
		softp->os_smode.CM_FLAGS |= (SCONS_CLEAR_DTR | SCONS_CLEAR_RTS);
		softp->os_smode.CM_FLAGS &= ~SCONS_SET_BREAK;
		co_set_modes(softp);
	}

	bid = softp->bufcallid;
	softp->bufcallid = 1;	/* prevent any further buffcalls */
	softp->state &= ~CO_ISOPEN;
	v_lock(&softp->lock, tplock);
	if (bid) {
		unbufcall(bid);
	}
	/*
	 * wait for all put and server activity to finish
	 */
	disable_procs(rd_q);

	untimeout(softp->breakid);	
	untimeout(softp->delayid);

	(void)p_lock(&softp->lock, SPLSTR);
	softp->state = 0;
	freemsg(softp->msg);
	softp->msg = NULL;
	rd_q->q_ptr = NULL;
	WR(rd_q)->q_ptr = NULL;
	v_lock(&softp->lock, tplock);
}

/*
 * coresp - bufcall recovery handler
 */
coresp(softp)
	struct co_state *softp;
{
	mblk_t *bp;

	softp->bufcallid = 0;
	if ((bp = allocb(softp->bfc_count, BPRI_MED)) == NULL)
		softp->bufcallid = bufcall((uint) softp->bfc_count,
					   BPRI_MED, coresp, softp);
	else {
		bcopy(softp->is_buffer,
	         	(char *)(bp->b_wptr),
			(unsigned)softp->bfc_count);
		bp->b_wptr += softp->bfc_count;
		putnext(RD(softp->qptr), bp); 
		softp->is_restart_read = 1;
		co_start_read(softp);
	}
}		
		


/*
 *  cointr - SCED console interrupt handler
 */
void
cointr(vector)
	int vector;
{
	register int count;
	register int status;
	register struct co_state *softp;
	int unit = (vector - co_base_vec) >> CO_VEC_SHIFT;
	spl_t	tplock;
	int is_read = (((vector - co_base_vec) % 2) == 0);
	struct sec_dev_prog *completion;
	mblk_t *bp;
	static int coupstreamblkdcnt = 0;

	if (unit < 0 || unit > co_max_unit) {
		cmn_err(CE_NOTE,"co: stray interrupt, unit out of range %x\n",unit);
		/*
		 *+ An interrupt was received from an unknown SCED port.  The interrupt
		 *+ was discarded.  
		 */
		return;
	}
	softp = &co_state[unit];
	if (softp->ss_alive == 0) {
		cmn_err(CE_NOTE,"co: stray interrupt, dead unit %x\n", unit);
		/*
		 *+ At boot time, the kernel determined that the SCED was
		 *+ nonfunctional; however, an interrupt was received
		 *+ from the SCED.  The interrupt was discarded.  
		 */
		return;
	}

	tplock = p_lock(&softp->lock, SPLSTR);

	/*
	 * If this port is not open, its stream will have been dismantled.
	 * Therefore, we must discard all read data interrupts.  While we
	 * are at it, we will also discard all write-done interrupts.
	 */
	if ((softp->state & CO_ISOPEN) == 0) {
		if (is_read)
			while (!CO_TEST_Q_EMPTY(&softp->is_doneq)) {
				(void) CO_GET_Q_TAIL(&softp->is_doneq);
				CO_INCR_Q_TAIL(&softp->is_doneq);
			}
		else 
			while (!CO_TEST_Q_EMPTY(&softp->os_doneq)) {
				(void) CO_GET_Q_TAIL(&softp->os_doneq);
				CO_INCR_Q_TAIL(&softp->os_doneq);
			}
		v_lock(&softp->lock, tplock);
		return;
	}

	if (is_read) {
		l.cnt.v_rcvint++;
		while (!CO_TEST_Q_EMPTY(&softp->is_doneq)) {
			completion = CO_GET_Q_TAIL(&softp->is_doneq);
			status = (int)completion->dp_status1;
			if(status & SCONS_CARR_DET) {
				l.cnt.v_mdmint++;
				co_get_modes(softp);
				if (softp->os_smode.CM_FLAGS & SCONS_CARRIER_CLEAR) {
					if (!(softp->flags & CLOCAL)) {
						softp->state &= ~CO_CARR_ON;
						if (!(softp->state & CO_WOPEN)) {
							if (softp->co_dev == cons_id) {
								if (!putnextctl1(RD(softp->qptr), M_SIG, SIGHUP))
									cmn_err(CE_WARN,"co: system console carrier-drop report failed\n");
									/*
									    *+ A driver attempt to report a carrier drop
									    *+ upstream failed.  
				 		 	 		 */
							} else {
								if (!putnextctl(RD(softp->qptr), M_HANGUP))
									cmn_err(CE_WARN,"co: carrier-drop report failed\n");
									/*
									    *+ A driver attempt to report a carrier drop
									    *+ upstream failed. 
				 		 	 		 */
							}	
						}
					}
				} else {
					if ((softp->state & CO_CARR_ON) == 0) {
						softp->state |= CO_CARR_ON;
						vall_sema(&softp->rawqwait);
					}
				}
			}
			if (!(status & SCONS_FLUSHED)) {
				count = completion->dp_count;
				if (status & SCONS_OVRFLOW)
				 	softp->is_ovc++;	
				if (status & SCONS_PARITY_ERR) {
					softp->is_parc++;
					if (count > 0) {
						if ((bp = allocb(count + 1, BPRI_HI)) == NULL)
							cmn_err(CE_WARN, "co: parity error report failed\n");
							/*
							  *+ An attempt to send a M_CTL message 
							  *+ upstream to report a parity error
							  *+ on the SCED console port failed.
						 	 */
						else {
							bp->b_datap->db_type = M_CTL;
							*bp->b_wptr++ = MC_PARITY_ERR;
							bcopy(softp->is_buffer,
							  (char *)(bp->b_wptr),
							  (unsigned)count);
							bp->b_wptr += count;
							putnext(RD(softp->qptr), bp);
							count = 0;
						}
					}
				}
				if (status & SCONS_BREAK_DET) {
					if (count > 0) {
						if ((bp = allocb(count + 1, BPRI_HI)) == NULL)
							cmn_err(CE_WARN,"co: break detect report failed\n");	
							/*
							   *+ A driver attempt to report a break
							   *+ detect upstream failed.  
				 		 	 */
						else {
							bp->b_datap->db_type = M_CTL;
							*bp->b_wptr++ = MC_FRAME_ERR;
							bcopy(softp->is_buffer,
							   (char *)(bp->b_wptr),
							   (unsigned)count);
							bp->b_wptr += count;
							putnext(RD(softp->qptr), bp);
							count = 0;
						}
					} else {
						if (!putnextctl1(RD(softp->qptr), M_CTL, MC_FRAME_ERR))
							cmn_err(CE_WARN, "co: MC_FRAME_ERR msg failed\n");
							/*
							  *+ A driver attempt to send a frame error message upstream
							  *+ failed.
			 				 */
					}
				}
				if (count > 0) {
					l.cnt.v_rawch += count;
					if (!canputnext(RD(softp->qptr))) {
						if (coprintupstreamblkd)
							if (0 == coupstreamblkdcnt++ % 100)
								cmn_err(CE_WARN,"co: unable to deliver %d incoming bytes\n", count);
								/*
								 *+ A driver attempt to send incoming
								 *+ data upstream failed due to
								 *+ upstream flow control.
								 *+ The kernel should be tuned by
								 *+ increasing the value of TTYBUF_LATENCY
								 *+ in the ld0 config file.
								 */
					} else if ((bp = allocb(count, BPRI_MED)) == NULL) {
						if (coprintalocfail)
							cmn_err(CE_WARN,"co: unable to allocb %d bytes\n", count);
							/*
							   *+ A driver attempt to allocate a
							   *+ stream buffer for incoming data
							   *+ failed.  
					 	 	 */
						softp->is_restart_read = 0;
						softp->bfc_count = count;
						if (softp->bufcallid == 0) {
							softp->bufcallid = bufcall((uint) count, BPRI_MED, 
									   coresp, softp);
						}
						(void) putnextctl1(RD(softp->qptr), M_CTL, MC_STOPI);
					} else {
						bcopy(softp->is_buffer,
							(char *)(bp->b_wptr),
							(unsigned)count);
						bp->b_wptr += count;
						putnext(RD(softp->qptr), bp); 
					}
				}
			}
			CO_INCR_Q_TAIL(&softp->is_doneq);
		}
		if (softp->is_restart_read)
			co_start_read(softp);
	}   
	/* handle write-done interrupts */	
	else {  
		l.cnt.v_xmtint++;
		while (!CO_TEST_Q_EMPTY(&softp->os_doneq)) {
			completion = CO_GET_Q_TAIL(&softp->os_doneq);
			status = (int)completion->dp_status1;
			count = completion->dp_count;
			softp->state &= ~CO_BUSY;
			CO_INCR_Q_TAIL(&softp->os_doneq);
			/*
			 * softp->msg will be null if we were sending the character
			 * from a MC_STOPI or MC_STARTI message.  If this is the case,
			 * skip the message-processing code and try to start another
			 * output.
			 */
			if (softp->msg != NULL) {
				if (softp->state & CO_WFLUSH) {		/* Output drain */
					freemsg(softp->msg);
					softp->msg = NULL;
					softp->state &= ~CO_WFLUSH;
				} else {			/* Continue partial message */
					bp = softp->msg;
					bp->b_rptr += count;
					if (bp->b_rptr >= bp->b_wptr) {
						bp = softp->msg->b_cont;
						softp->msg->b_cont = NULL;
						freeb(softp->msg);	
						softp->msg = bp;
					}
				}
			}
			softp->lasttxsize = 0;
			/* 
			 * At this point, if we still have a partially unsent message, we
			 * will send more iff we are not suspended or delaying the transmit.
			 * If the message has been completely sent, we will qenable to get
			 * the service procedure to wake up.
			 */
			if ((softp->state & (CO_SUSPOUT | CO_TXDELAY)) == 0) {
				if (softp->msg != NULL) 
					costart(softp, softp->msg); 
				else
					qenable(softp->qptr);
			}
		}
	}
	v_lock(&softp->lock, tplock);
}

/*
 * comsg - Received message handler
 * 
 * If the message is a M_FLUSH message, flush the appropriate
 * queues.  If the message is a priority message, process it
 * immediately. If the message is of type M_DATA, enqueue
 * them onto our local message queue.  If we are not currently 
 * sending output to the port, start an output data transfer.
 * Attempt to do M_IOCTL messages now.  If they cannot be done,
 * return STD/PRIPUTBACK for return to the message queue.
 */
int
comsg(wr_q, mp)
	queue_t  *wr_q;
	mblk_t *mp;
{
	struct co_state *softp = (struct co_state *) 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(&softp->lock, SPLSTR);
				/*
				 * If we are in the middle of sending a break,
				 * do this function later.  (after the break.)
				 */
				if (softp->state & CO_TXBREAK) {
					v_lock(&softp->lock, tplock);
					return(PRIPUTBACK);
				}	
				/* 
				 * Normally, we would use the SCED suspend
				 * output command here.  This works just fine.
				 * But, flags are set in the SCED F/W so that
				 * when a control-q is received, the SCED
				 * reenables output and we never know what
				 * happens.  So, set the flag here to stop
				 * any additional output.  Note that the 
				 * current output, if any, will be allowed
				 * to continue.
				 */
				softp->state |= CO_SUSPOUT;
				v_lock(&softp->lock, tplock);
				break;

			case MC_START:		/* Unsuspend data to device */
				tplock = p_lock(&softp->lock, SPLSTR);
				/*
				 * If we are in the middle of sending a break,
				 * do this function later.  (after the break.)
				 */
				if (softp->state & CO_TXBREAK) {
					v_lock(&softp->lock, tplock);
					return(PRIPUTBACK);
				}	
				if (softp->state & CO_SUSPOUT) {
					softp->state &= ~CO_SUSPOUT;
					if (softp->msg != NULL) {
						if ((softp->state & (CO_BUSY | CO_TXDELAY)) == 0)
							costart(softp, softp->msg);
					} else
						qenable(softp->qptr);
				}	
				v_lock(&softp->lock, tplock);
				break;
	
			case MC_STOPI:		/* Suspend data from device */
				tplock = p_lock(&softp->lock, SPLSTR);
				/*
				 * If we are in the middle of sending a break,
				 * do this function later.  (after the break.)
				 */
				if (softp->state & CO_TXBREAK) {
					v_lock(&softp->lock, tplock);
					return(PRIPUTBACK);
				}	
				if (softp->state & CO_BUSY) {
					v_lock(&softp->lock, tplock);
					return(PRIPUTBACK);
				} else {
					softp->os_smode.CM_FLAGS |= SCONS_CLEAR_RTS;
					co_set_modes(softp);
					softp->state |= CO_BUSY;
					co_start_write(softp, 1, ++mp->b_rptr);
					v_lock(&softp->lock, tplock);
				}
				break;

			case MC_STARTI:		/* Unsuspend data from device */
				tplock = p_lock(&softp->lock, SPLSTR);
				/*
				 * If we are in the middle of sending a break,
				 * do this function later.  (after the break.)
				 */
				if (softp->state & CO_TXBREAK) {
					v_lock(&softp->lock, tplock);
					return(PRIPUTBACK);
				}	
				if (softp->state & CO_BUSY) {
					v_lock(&softp->lock, tplock);
					return(PRIPUTBACK);
				} else {
					softp->os_smode.CM_FLAGS &= ~SCONS_CLEAR_RTS;
					co_set_modes(softp);
					softp->state |= CO_BUSY;
					co_start_write(softp, 1, ++mp->b_rptr);
					v_lock(&softp->lock, tplock);
				}
				break;
			default:
				cmn_err(CE_WARN, "co: 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, "co: 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:				/* priority ioctl */
		tplock = p_lock(&softp->lock, SPLSTR);
		/*
		 * If we are in the middle of sending a break,
		 * do this function later.  (after the break.)
		 */
		if (softp->state & CO_TXBREAK) {
			v_lock(&softp->lock, tplock);
			return(PRIPUTBACK);
		}	
		if (co_doioctl(softp, mp)) {
			v_lock(&softp->lock, tplock);
			return(PRIPUTBACK);
		}
		v_lock(&softp->lock, tplock);
		break;

	case M_IOCTL:				/* non-priority ioctl */
		tplock = p_lock(&softp->lock, SPLSTR);
		/*
		 * If we are in the middle of sending a break,
		 * do this function later.  (after the break.)
		 */
		if (softp->state & CO_TXBREAK) {
			v_lock(&softp->lock, tplock);
			return(STDPUTBACK);
		}	
		if (co_doioctl(softp, mp)) {
			v_lock(&softp->lock, tplock);
			return(STDPUTBACK);
		}
		v_lock(&softp->lock, tplock);
		break;

	case M_FLUSH:				/* Flush data queue(s) */
		/*
		 * If we are in the middle of sending a break,
		 * do this function later.  (after the break.)
		 */
		tplock = p_lock(&softp->lock, SPLSTR);
		if (softp->state & CO_TXBREAK) {
			v_lock(&softp->lock, tplock);
			return(PRIPUTBACK);
		}	
		v_lock(&softp->lock, tplock);
		if (mp->b_rptr != NULL) {
			if (*mp->b_rptr & FLUSHW) {
				tplock = p_lock(&softp->lock, SPLSTR);
				flushq(wr_q, FLUSHDATA);
				co_abort_write(softp);
				if (softp->msg) {
					freemsg(softp->msg);
					softp->msg = NULL;
					softp->state &= ~CO_BUSY;
					softp->lasttxsize = 0;
				}
				v_lock(&softp->lock, tplock);
			}
			/*
			 * See co_abort_read() header for an explanation of
			 * why we need to hold the lock during FLUSHR operation.
			 */
			if (*mp->b_rptr & FLUSHR) {
				flushq(RD(wr_q), FLUSHDATA);
				tplock = p_lock(&softp->lock, SPLSTR);
				co_abort_read(softp);
				v_lock(&softp->lock, tplock);
				*mp->b_rptr &= ~FLUSHW;
				qreply(wr_q, mp);
				break;
			}
			else freemsg(mp);
		} else {
			cmn_err(CE_WARN, "co: 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(&softp->lock, SPLSTR);
		if ((softp->state & (CO_TXBREAK | CO_BUSY | CO_SUSPOUT | CO_TXDELAY)) == 0) {
			costart(softp, mp);
			v_lock(&softp->lock, tplock);
		} else {
			v_lock(&softp->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 comsg()
 * returns a non-zero status, put the message back on the
 * queue for later processing.
 */
static
cowsrv(wr_q)
queue_t *wr_q;
{
	spl_t	tplock;
	mblk_t *bp;
	struct co_state *softp = (struct co_state *) wr_q->q_ptr;
	int stat;

	while ((bp = getq(wr_q)) != NULL) {
		if ((stat = comsg(wr_q, bp)) == STDPUTBACK) {
			putbq(wr_q, bp);
			return;
		} else if (stat == PRIPUTBACK) {
			putbqx(wr_q, bp);
			return;
		}
 	}
	tplock = p_lock(&softp->lock, SPLSTR);
	if (softp->state & CO_WCLOSE)
		v_sema(&softp->rawqwait);
	v_lock(&softp->lock, tplock);
}

/*
 * co_doioctl - process M_IOCTL and M_PCIOCTL messages.
 * 
 * Returns 1 if ioctl message CANNOT be processed.  Else, returns zero.
 * Assumes that the softp structure is locked.
 * Process message by calling co_param() to set up hardware
 * parameters.  Acknowlege message and send it back upstream
 * if all goes well.
 *
 */
co_doioctl(softp, mp)
	struct co_state *softp;
	mblk_t *mp;
{
	struct iocblk *iocp;
	struct termio *tptr;
	struct termios *tsptr;
	mblk_t *bp;
	queue_t *wr_q = (queue_t *) softp->qptr;

	if (mp->b_rptr == NULL) {
		cmn_err(CE_WARN, "co: 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(0);
	}
	iocp = (struct iocblk *) mp->b_rptr;

	switch (iocp->ioc_cmd) {
	case TCSETAF:			/* Set params with read flush and output drain */
		if (softp->state & CO_BUSY)
			return(1);
		co_abort_read(softp);
		tptr = (struct termio *) mp->b_cont->b_rptr;
		softp->cflags = tptr->c_cflag;
		co_param(softp);
		if (softp->cflags & CLOCAL)
			softp->state |= CO_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, "co: 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);
		break;

	case TCSETPF:			/* Set params with read flush and output drain */
		if (softp->state & CO_BUSY)
			return(1);
		co_abort_read(softp);
		tsptr = (struct termios *) mp->b_cont->b_rptr;
		softp->cflags = tsptr->c_cflag;
		co_param(softp);
		if (softp->cflags & CLOCAL)
			softp->state |= CO_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, "co: 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);
		break;

	case TCSBRK:			/* Do 0.25 secs of BREAK after output drain */
		if (softp->state & CO_BUSY)
			return(1);
		if (*(int *) mp->b_cont->b_rptr == 0) {
			softp->os_smode.CM_FLAGS |= SCONS_SET_BREAK;
			co_set_modes(softp);
			softp->state |= CO_TXBREAK;
			softp->breakid = timeout(co_breaktime, (caddr_t) softp, HZ >> 2);
		}
		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 */
		if (softp->state & CO_BUSY)
			return(1);
		tptr = (struct termio *) mp->b_cont->b_rptr;
		softp->cflags = tptr->c_cflag;
		co_param(softp);
		if (softp->cflags & CLOCAL)
			softp->state |= CO_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 */
		if (softp->state & CO_BUSY)
			return(1);
		tsptr = (struct termios *) mp->b_cont->b_rptr;
		softp->cflags = tsptr->c_cflag;
		co_param(softp);
		if (softp->cflags & CLOCAL)
			softp->state |= CO_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, "co: 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, "co: 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 = softp->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_HI)) == NULL) {
				cmn_err(CE_WARN, "co: TCGETP data block allocb failed\n");
				/*
				 *+ An attempt to allocate a 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, "co: 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 = softp->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);
	}
	return(0);
}

/*
 * co_param - set line parameters on the hardware.
 * 
 *  Line setting are based on bits set or cleared in the
 *  "flags" field of the softp structure.  This field is loaded
 *  by co_doioctl() from the c_flag field of an incoming
 *  TCSETAx ioctl messages.  The flags are also initialized
 *  during the open to a state set in the configuration 
 *  file for this driver.
 *
 *>>>>>> NOTE: HARDWARE MUST NOT BE BUSY WHEN THIS ROUTINE IS CALLED!!
 */
co_param(softp)
	struct co_state *softp;
{
	ushort cflags = softp->cflags;	
	unchar par, len, stop;	

	ASSERT_DEBUG(!(softp->state & CO_BUSY), "co_param");

	if (!(cflags & CBAUD)) {		/* Check for B0 (hangup) */
		softp->os_smode.CM_FLAGS |= (SCONS_CLEAR_DTR | 
						SCONS_CLEAR_RTS);
		softp->os_smode.CM_FLAGS &= ~(SCONS_SET_BREAK);
		co_set_modes(softp);
		return;
	}
	if ((cflags & CS8) == CS8)	/* Set up bits per character */
		len = SCONS_DATA8;
	else 
		len = SCONS_DATA7;
	
	if (cflags & PARENB)			/* Set up parity, if any */
		par = (cflags & PARODD) ? SCONS_ODD_PARITY : SCONS_EVEN_PARITY;
	else {
		par = SCONS_NO_PARITY;
	}
	/* Setup number of stop bits */
	stop = (cflags & CSTOPB) ? SCONS_STOP2 : SCONS_STOP1;
	softp->os_smode.CM_FLAGS &= ~(SCONS_STOP1P5 | SCONS_STOP2 |
					SCONS_DATA7 |
					SCONS_EVEN_PARITY |
					SCONS_ODD_PARITY);
	softp->os_smode.CM_FLAGS |= len | par | stop;
	softp->os_smode.CM_BAUD = cflags & CBAUD;
	co_set_modes(softp);
	return;
}

/*
 * costart - Send a data block out the port.
 *
 * Assumes that the softp 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.
 * When the device program completes there will
 * be included in the completion a byte count which is what was transmitted.
 *
 *>>>>>> NOTE: HARDWARE MUST NOT BE BUSY WHEN THIS ROUTINE IS CALLED!!
 */
costart(softp, bp)
	struct co_state *softp;
	mblk_t *bp;
{
	int nch;

	switch (bp->b_datap->db_type) {
	case M_DELAY:
		if (bp->b_rptr != NULL) {
			ASSERT_DEBUG(softp->delayid == 0, "costart");
			softp->state |= CO_TXDELAY;
			softp->delayid = timeout(co_delaytime, 
						 (caddr_t) softp,
						 *(int *) bp->b_rptr);
			freemsg(bp);
			return;
		} else {
			cmn_err(CE_WARN, "co: 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:
		softp->msg = bp;
		if (bp->b_rptr >= bp->b_wptr) {
			bp = softp->msg->b_cont;
			softp->msg->b_cont = NULL;
			freeb(softp->msg);
			softp->msg = bp;
			if (softp->msg == NULL)
				return;
		}
		/* 
		 * If the number of characters left in this message block
		 * is greater than the max output count, only send this many.
		 * Pointers will be adjusted in the write-done interrupt
		 * handler code.  Normally, the sced could handle the entire
		 * data block.  But, in order to implement a quicker flow control,
		 * we will send the data out in small chunks so that an xoff will
		 * stop output a bit faster. 
		 */
		nch = bp->b_wptr - bp->b_rptr;
		if (nch > COMAXOUT)
			nch = COMAXOUT;
		co_start_write(softp, nch, bp->b_rptr);
		softp->state |= CO_BUSY;
		l.cnt.v_outch += nch;
		softp->lasttxsize = nch;
		break;
	}
}

/*
 * co_start_write   -queue a write request to the output channel.
 *
 * assume request queue and status are locked prior to entry
 * also assumes that the request queue is not full.
 *
 *>>>>>> NOTE: HARDWARE MUST NOT BE BUSY WHEN THIS ROUTINE IS CALLED!!
 */

#define	SCED_MAX_WINDOW_SIZE	4*1024*1024

co_start_write(softp, nch, addr)
	struct co_state *softp;
	int nch;
	unsigned char *addr;
{
	register struct sec_dev_prog *request;

	if ((int)addr > SCED_MAX_WINDOW_SIZE) {
		bcopy((char *) addr, (char *) softp->co_lobuf, (unsigned) nch);
		addr = softp->co_lobuf;
	}
	request = CO_GET_Q_HEAD(&softp->os_reqq);
	request->dp_status1 = 0;
	request->dp_count = 0;
	request->dp_un.dp_data = addr;
	request->dp_data_len = nch;
	request->dp_next = (struct sec_dev_prog*)0;
	CO_INCR_Q_HEAD(&softp->os_reqq);
	sec_startio(SINST_STARTIO, &softp->os_status, softp->os_sd);
	if (softp->os_status & (~SINST_INSDONE))
		cmn_err(CE_WARN,"co: bad start_write: %x\n", softp->os_status);
		/*
		 *+ The SCED failed to acknowledge a start-write command.  
		 */
}

/*
 * co_start_read   -queue a read request to the output channel.
 *
 * assume request queue and status are locked prior to entry
 * also assumes that the request queue is not full.
 */
co_start_read(softp)
	struct co_state *softp;
{
	register struct sec_dev_prog *request;

	request = CO_GET_Q_HEAD(&softp->is_reqq);
	request->dp_status1 = 0;
	request->dp_count = 0;
	request->dp_un.dp_data = (unchar *)softp->is_buffer;
	request->dp_data_len = CORXSIZE;
	request->dp_next = (struct sec_dev_prog*)0;
	CO_INCR_Q_HEAD(&softp->is_reqq);
	sec_startio(SINST_STARTIO, &softp->is_status, softp->is_sd);
	if (softp->is_status & (~SINST_INSDONE))
		cmn_err(CE_WARN,"co: bad start_read: %x\n", softp->is_status);
		/*
		 *+ The SCED failed to acknowledge a start-read command. 
		 */
}

/*
 * co_set_modes - set console modes with no waiting.
 *
 * This procedure assumes that access to the os_smode structure and
 * to the cib are locked.  
 */
co_set_modes(softp)
	struct co_state *softp;
{
	sec_startio(SINST_SETMODE, &softp->os_smode.sm_status, softp->os_sd);
	if (softp->os_smode.sm_status & (~SINST_INSDONE))
		cmn_err(CE_WARN,"co: bad set_mode: %x\n", softp->os_smode.sm_status);
		/*
		 *+ The SCED failed to acknowledge a set-mode command. 
		 */
}

/*
 * co_get_modes - get console modes.
 *
 * This procedure assumes that access to the os_smode structure and
 * to the cib are locked.  
 */
co_get_modes(softp)
	struct co_state *softp;
{
	sec_startio(SINST_GETMODE, &softp->os_smode.sm_status, softp->os_sd);
	if (softp->os_smode.sm_status & (~SINST_INSDONE))
		cmn_err(CE_WARN,"co: bad get_mode: %x\n", softp->os_smode.sm_status);
		/*
		 *+ The SCED failed to acknowledge a get-mode command.
		 */
}

/*
 * co_abort_read - abort any pending or current read device programs
 *
 * NOTE: V42 sced f/w and older handles read flushes in an odd way:  If a
 *       read is pending, the read will be cancelled and a completion interrupt
 *	 will be generated.  This means if the driver calls co_abort_read(),
 *	 it should allow cointr() to restart the read rather than doing it with
 *	 an explicit call to co_start_read().  A race condition between the
 *	 driver and f/w was uncovered, too.  If co_abort_read() is called 
 *	 without lock protection, the resulting interrupt (and resulting read
 *	 restart) will race with the SCED zeroing out its request queue.  Thus,
 *	 cointr's newly-restarted read request will be killed by co_abort_read,
 *	 causing a hung input path on a sced port.  Therefore, co_abort_read()
 *	 should be called under lock protection.
 *	 Also note that if no read is pending, the sced will not issue an
 *	 interrupt.
 */
co_abort_read(softp)
	struct co_state *softp;
{
	sec_startio(SINST_FLUSHQUEUE, &softp->is_status, softp->is_sd);
	if (softp->is_status & (~SINST_INSDONE))
		cmn_err(CE_WARN, "co: bad abort_read: %x\n", softp->is_status);
		/*
		 *+ The SCED failed to acknowledge an abort-read command. 
		 */
}

/*
 * Program queue management routines.  These may be candidates for
 * implementation as macros.  Or, they may be moved in line and
 * implemented more efficiently later.
 *
 * CO_TEST_Q_EMPTY  - return true if program queue is empty
 */

CO_TEST_Q_EMPTY(sq)
	struct sec_pq	*sq;
{
	struct sec_progq *pq;

	pq = sq->sq_progq;
	ASSERT(pq->pq_tail < sq->sq_size, "co_testq: bad tail");
	/*
	 *+ The SCED console driver queue manipulation routine 
	 *+ encountered a bad tail pointer.
	 */
	ASSERT(pq->pq_head < sq->sq_size, "co_testq: bad head");
	/*
	 *+ The SCED console driver queue manipulation routine 
	 *+ encountered a bad head pointer.
	 */

	return(pq->pq_head == pq->pq_tail);
}

/*
 * CO_GET_Q_HEAD  - return pointer to first available "empty" device
 *			program.
 */

struct sec_dev_prog *
CO_GET_Q_HEAD(sq)
	struct sec_pq	*sq;
{
	struct sec_progq *pq;

	pq = sq->sq_progq;
	ASSERT(pq->pq_tail < sq->sq_size, "co_getq: bad tail");
	/*
	 *+ The SCED console driver queue manipulation routine 
	 *+ encountered a bad tail pointer.
	 */
	ASSERT(pq->pq_head < sq->sq_size, "co_getq: bad head");
	/*
	 *+ The SCED console driver queue manipulation routine 
	 *+ encountered a bad head pointer.
	 */

	return(pq->pq_un.pq_progs[pq->pq_head]);
}

/*
 * CO_INCR_Q_HEAD  - increment the head of the queue, thus adding a
 *			device program to the request queue.
 */

CO_INCR_Q_HEAD(sq)
	struct sec_pq	*sq;
{
	struct sec_progq *pq;

	pq = sq->sq_progq;
	ASSERT(pq->pq_tail < sq->sq_size, "co_incrq: bad tail");
	/*
	 *+ The SCED console driver queue manipulation routine 
	 *+ encountered a bad tail pointer.
	 */
	ASSERT(pq->pq_head < sq->sq_size, "co_incrq: bad head");
	/*
	 *+ The SCED console driver queue manipulation routine 
	 *+ encountered a bad head pointer.
	 */

	pq->pq_head = (pq->pq_head + 1) % sq->sq_size;
}

/*
 * CO_GET_Q_TAIL  - return pointer to first completed device
 *			program.
 */

struct sec_dev_prog *
CO_GET_Q_TAIL(sq)
	struct sec_pq	*sq;
{
	struct sec_progq *pq;

	pq = sq->sq_progq;
	ASSERT(pq->pq_tail < sq->sq_size, "co_get_q_tail: bad tail");
	/*
	 *+ The SCED console driver queue manipulation routine 
	 *+ encountered a bad tail pointer.
	 */
	ASSERT(pq->pq_head < sq->sq_size, "co_get_q_tail: bad head");
	/*
	 *+ The SCED console driver queue manipulation routine 
	 *+ encountered a bad head pointer.
	 */

	return(pq->pq_un.pq_progs[pq->pq_tail]);
}

/*
 * CO_INCR_Q_TAIL  - increment the tail of the queue, thus deleting a
 *			device program to the completion queue.
 */

CO_INCR_Q_TAIL(sq)
	struct sec_pq	*sq;
{
	struct sec_progq *pq;

	pq = sq->sq_progq;
	ASSERT(pq->pq_tail < sq->sq_size, "co_incr_q_tail: bad tail");
	/*
	 *+ The SCED console driver queue manipulation routine 
	 *+ encountered a bad tail pointer.
	 */
	ASSERT(pq->pq_head < sq->sq_size, "co_incr_q_tail: bad head");
	/*
	 *+ The SCED console driver queue manipulation routine 
	 *+ encountered a bad head pointer.
	 */

	pq->pq_tail = (pq->pq_tail + 1) % sq->sq_size;
}

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

	s = p_lock(&softp->lock, SPLSTR);
	if (softp->state & CO_WCLOSE) {
		if (qsize(WR(rd_q)))
			softp->flags |= DRAINTIME;
		v_sema(&softp->rawqwait);
	}
	v_lock(&softp->lock, s);
}
/*
 * Called when timer expires when waiting 0.25sec for break output.
 */
co_breaktime(softp)
	struct co_state *softp;
{
	spl_t s;

	s = p_lock(&softp->lock, SPLSTR);
	softp->state &= ~CO_TXBREAK;
	softp->os_smode.CM_FLAGS &= ~SCONS_SET_BREAK; 	/* Clear break */
	co_set_modes(softp);
	softp->breakid = 0;
	qenable(softp->qptr);
	v_lock(&softp->lock, s);
}

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

	s = p_lock(&softp->lock, SPLSTR);
	softp->state &= ~CO_TXDELAY;
	softp->delayid = 0;
	qenable(softp->qptr);
	v_lock(&softp->lock, s);
}

/*
 * co_abort_write - abort any pending or current write device programs
 *
 * This procedure assumes that access to the output structures are locked.  
 */
co_abort_write(softp)
	register struct co_state *softp;
{
	sec_startio(SINST_FLUSHQUEUE, &softp->os_status, softp->os_sd);
	if (softp->os_status & (~SINST_INSDONE))
		cmn_err(CE_WARN, "co: bad abort_write: %x\n", softp->os_status);
		/*
		 *+ The SCED failed to acknowlege an abort-write command.  
		 */
}
