/* $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.
 */

/*	Copyright (c) 1984 1988 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/


/* #ident	"@(#)kern-io:ld0.c	1.3" */
#ident	"$Header: ld0.c 1.72 90/11/08 $"

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

/* 
 * tty line discipline 0:
 * pushable streams module
 */


#include "sys/param.h"
#include "sys/signal.h"
#include "sys/types.h"
#include "sys/systm.h"
#include "sys/sysmacros.h"
#include "sys/timer.h"
#include "sys/user.h"
#include "sys/proc.h"
#include "sys/dir.h"
#include "sys/file.h"
#include "sys/mutex.h"
#include "sys/intctl.h"
#include "sys/session.h"
#include "sys/stream.h"
#include "sys/stropts.h"
#include "sys/termio.h"
#include "sys/eucioctl.h"
#include "sys/euc.h"
#include "sys/ld0.h"
#include "sys/conf.h"
#include "sys/errno.h"
#include "sys/strid.h"
/* turn on debug aids from debug.h */
#define STREAMS_DEBUG_ID	STRID_LD0
#include "sys/debug.h"
#include "sys/cmn_err.h"
#include "sys/vm.h"
#include "sys/pte.h"
#include "sys/plocal.h"
#include "sys/ttold.h"
#include "sys/strlog.h"

static void	wqrestart();	/* restart wq after buffcall */
static void	rqrestart();	/* restart rq after buffcall */
static int	ld_ioc();	/* process w-side ioctls */
static int	ldsig();	/* send a signal upstream */
static int	check_ixon();	/* send a XOFF/ON downstream */
static void	qaddchar();	/* insert char in message buffer */
void		qdelchar();	/* delete char from message buffer */
static void	do_defer();	/* process any deferred actions */
static mblk_t	*echoc();	/* allocate message and echo character */

extern struct ld ttyld[];
extern int ld_cnt;
extern ushort ld_ttybuf_latency;
extern char	maptab[];
extern char	partab[];
extern void	v_sema();
lock_t		ttyld_lock;
extern uint	bufcall();
extern dev_t	cons_devnum;

#define IS_SPECIAL(c,special)	\
		   ((c == (unchar) special) && (special != _POSIX_VDISABLE))

int ldopen(), ldclose(), ldin(), ldinsrv(), ldosrv(), ldout();
extern nulldev();
/*
 *  NOTE:  ldmiinfo and eld0miinfo high-water mark shows here as CANBSIZ.
 *	   This value is increased in the corresponding queue structure
 *	   (q_hiwat) by 'ld_ttybuf_latency' in ldopen().
 */
struct module_info ldmiinfo = { STRID_LD0, "ld", 0, INFPSZ, CANBSIZ, 100 };
struct module_info ldmoinfo = { STRID_LD0, "ld", 0, INFPSZ, 300, 200 };
struct qinit ldrinit = {ldin, ldinsrv, ldopen, ldclose, nulldev, &ldmiinfo, NULL};
struct qinit ldwinit = {ldout, ldosrv, ldopen, ldclose, nulldev, &ldmoinfo, NULL};
struct streamtab ldinfo = { &ldrinit, &ldwinit };

/*
 *  eld0 is an alias for ld0, hence the parallel definitions:
 */
struct module_info eld0miinfo = { STRID_ELD0, "eld0", 0, INFPSZ, CANBSIZ, 100 };
struct module_info eld0moinfo = { STRID_ELD0, "eld0", 0, INFPSZ, 300, 200 };
struct qinit eld0rinit = {ldin, ldinsrv, ldopen, ldclose, nulldev, &eld0miinfo, NULL};
struct qinit eld0winit = {ldout, ldosrv, ldopen, ldclose, nulldev, &eld0moinfo, NULL};
struct streamtab eld0info = { &eld0rinit, &eld0winit };

/*
 * Define the necessary padding size of the outconv() output buffer.
 */
static int ld0_outfudge = 24;

/*
 * Canonicalization macros - we inline the most common manipulations of
 * canonb[] for speed.  All of these macros assume that access has been
 * serialized through tp->ld_lock.
 */

/*
 * delete all characters in input.
 */
#define KILL_CANON(tp) \
		tp->ld_op = tp->ld_canonb; 	\
		tp->ld_current = -1;		\
		tp->ld_pline[0] = '\0'; 	\
		tp->ld_euc_num = '\0';

/*
 * delete last char in input.
 */
#define BACK_CANON(tp) \
		if (tp->ld_op > tp->ld_canonb) \
			tp->ld_op = &tp->ld_canonb[tp->ld_pline[tp->ld_current--]];

/*
 * putnextctl2(): like putnextctl1() but with two data bytes.
 * returns 0 if allocb failed.
 */


int
putnextctl2(q, type, param1, param2)
	queue_t *q;
	int param1, param2;	/* one byte parameters */
{
	mblk_t	*bp;

	if (!(bp = allocb(2, BPRI_HI)))
		return (0);
	bp->b_datap->db_type = type;
	*bp->b_wptr++ = param1;
	*bp->b_wptr++ = param2;
	putnext(q, bp);
	return (1);
}

/*
 * putnextctl1i(): like putnextctl1() but with one data int.
 * returns 0 if allocb failed.
 */

int
putnextctl1i(q, type, param1)
	queue_t *q;
	int param1;	/* one int parameter */
{
	mblk_t	*bp;
	int	*ip;

	if (!(bp = allocb(sizeof(int), BPRI_HI)))
		return (0);
	bp->b_datap->db_type = type;
	ip = (int *)bp->b_wptr;
	*ip++ = param1;
	bp->b_wptr = (unsigned char *)ip;
	putnext(q, bp);
	return (1);
}


/*
 * Called from main at boot time.
 */
void
ld_init()
{
	/*
	 * This lock protects the allocation of struct ld table "ttyld".
	 */
	init_lock(&ttyld_lock);
}


/*
 * line discipline open
 *
 * q the stream queue that is opened.
 * dev is the complete dev_t (zero for modules).
 * oflag is zero for modules.
 * sflag should be MODOPEN.
 */
/*ARGSUSED*/
static int
ldopen(q, dev, oflag, sflag)
	register queue_t *q;			/* read queue */
	dev_t dev;
	int oflag, sflag;
{
	register struct ld	*tp;
	spl_t			spl;
	mblk_t			*mp;
	struct stroptions 	*st;
	struct	termios		termios;	

	DPRINTF4 ("ld0: ldopen(q:0x%x, dev:0x%x, oflag:0x%x, sflag:0x%x\n",
		q, dev, oflag, sflag);
	
	if (sflag != MODOPEN) {
		u.u_error = EINVAL;
		return (OPENFAIL);
	}

	if ((tp=(struct ld *)q->q_ptr) != NULL) {		
		/* already attached */
		if (tp->ld_state & TTUSE) {
			DPRINTF4 ("ld0: ldopen: already attatched to tp=0x%x\n",
									tp);
			spl = p_lock(&tp->ld_lock, SPLSTR);
			tp->ld_state &= ~TTHUP;
			v_lock(&tp->ld_lock,spl);
			return (OPENOK);
		}
		DPRINTF1 ("ld0: ldopen: already attatched not in use ?????\n");
	}
	/*
	 * select an unused ttyld structure.
	 */
	spl = p_lock(&ttyld_lock, SPLSTR);
	/*
	 * by looking at ld_state unlocked we can only miss a freeing
	 * tp.
	 */
	for (tp = ttyld; tp->ld_state&TTUSE; tp++) {
		if (tp >= &ttyld[ld_cnt-1]) {
			cmn_err(CE_NOTE, "ld0: No ttyld structures. all %d used", ld_cnt);
			/*
			 *+ The ld0 tty stream line discipline could not allocate a ttyld structure.
			 *+ Each login line used will require 1 NTTYLD; other streams may use this
			 *+ module.  Corrective action:
			 *+ increase the value of NTTYLD in io/ld0/ld0_space.c.
			 */
			u.u_error = ENOSPC;
			v_lock(&ttyld_lock, spl);
			return (OPENFAIL);
		}
	}
					   
	/*
	 * initialise the table.
	 * ttyld_lock will protect us from concurrent usage.
	 */
	init_lock(&tp->ld_lock);		/* spin lock */
	init_sema(&tp->ld_sema, 0);		/* sleep sema */

	termios_init(&termios, dev);		/* get default termios */
	ld0_termios_set(tp, &termios);		/* set tp struct from default */

	/*
	 * initialise the window parameters
	 */
	tp->ld_wd.ws_row = 0;
	tp->ld_wd.ws_col = 0;
	tp->ld_wd.ws_xpixel = 0;
	tp->ld_wd.ws_ypixel = 0;

	/*
	 * initialize ld_eucwioc
	 */
	tp->ld_eucwioc.eucw[0] = '\001';	/* euc codeset 0 width */
	tp->ld_eucwioc.eucw[1] = '\001';	/* euc codeset 1 width */
	tp->ld_eucwioc.eucw[2] = '\0';		/* euc codeset 2 width */
	tp->ld_eucwioc.eucw[3] = '\0';		/* euc codeset 3 width */
	tp->ld_eucwioc.scrw[0] = '\001';	/* screen codeset 0 width */
	tp->ld_eucwioc.scrw[1] = '\001';	/* screen codeset 1 width */
	tp->ld_eucwioc.scrw[2] = '\0';		/* screen codeset 2 width */
	tp->ld_eucwioc.scrw[3] = '\0';		/* screen codeset 3 width */

	tp->ld_rbid  = NULL;
	tp->ld_wbid  = NULL;
	KILL_CANON(tp);
	tp->ld_col   = tp->ld_ign = 0;			   
	tp->ld_state = TTUSE;		   
	tp->ld_pflags = LD0_CANON|LD0_ECHO;
	tp->ld_flushr = NULL;
	q->q_ptr     = (caddr_t)tp;
	WR(q)->q_ptr = (caddr_t)tp;

	v_lock(&ttyld_lock, spl);

	/* alter queue parameters */
	q->q_hiwat  += ld_ttybuf_latency;

	/*
	 * Set our maximum-write side packet size to the write-side packet
	 * size of the STREAMS module/driver below us if it is smaller than
	 * our default maximum write-side packet size.  This allows us to
	 * simply invoke putnext() in outconv() when no output conversion
	 * is taking place.
	 */
	if (WR(q)->q_next->q_maxpsz != INFPSZ &&
	    (WR(q)->q_maxpsz == INFPSZ ||
	     WR(q)->q_maxpsz > WR(q)->q_next->q_maxpsz)) {
		WR(q)->q_maxpsz = WR(q)->q_next->q_maxpsz;
		ASSERT(WR(q)->q_maxpsz > ld0_outfudge,
			"STREAMS module below ld0 specifies too small maxpsz");
		/*
		 *+ The STREAMS module below ld0 has declared a maximum
		 *+ write-side packet size that is smaller than the smallest
		 *+ possible write-side output packet of the ld0 line
		 *+ discipline module.
		 */
	}

	/* allocate message to send to stream head */

	while ((mp = allocb(sizeof(struct stroptions), BPRI_LO)) == NULL) {
		if ((tp->ld_rbid = bufcall((uint)sizeof(struct stroptions), 
		      BPRI_LO, (int (*)())v_sema, (long)&tp->ld_sema)) == NULL) {
			tp->ld_state = 0;
			q->q_ptr = NULL;
			WR(q)->q_ptr = NULL;
			u.u_error = ENOSPC;
			return (OPENFAIL);
		}
		switch (p_sema(&tp->ld_sema, STOPRI|PCATCH)) {
		case PCATCH:
			(void)unbufcall(tp->ld_rbid);
			tp->ld_rbid = NULL;
			/* EINTR */
			tp->ld_state = 0;
			q->q_ptr = NULL;
			WR(q)->q_ptr = NULL;
			return (OPENFAIL);
		default:
			(void)unbufcall(tp->ld_rbid);
			/* FALL THROUGH... */
		case 0:
			tp->ld_rbid = NULL;
			break;
		}
	}

	/* Alter stream head parameters.
	 *
	 * Canonical mode:
	 *	Message non-discard read mode with
	 *	VMIN/VTIME effectively disabled.
	 * Non-canonical mode:
	 *	Byte-stream read mode with
	 *	VMIN/VTIME set per ioctl() data.
	 * TOSTOP set per ioctl() data.
	 */

	mp->b_datap->db_type = M_SETOPTS;
	st = (struct stroptions *)mp->b_rptr;
	mp->b_wptr += sizeof(struct stroptions);
	if ((tp->ld_pflags & LD0_CANON) && (tp->ld_lflags & ICANON)) {
		st->so_flags = SO_READOPT|SO_VMODEOFF|SO_TOSTOPON;
		st->so_readopt = RMSGN;
	} else {
		st->so_flags = SO_READOPT|SO_VMODEON|SO_TOSTOPON;
		st->so_readopt = RNORM;
		st->so_vmin = (unsigned char) tp->ld_eofc;
		st->so_vtime = (unsigned char) tp->ld_eol;
	}
	st->so_tostop = (tp->ld_lflags & TOSTOP) ? 1 : 0;
	putnext(q, mp);

	enable_procs(q);	/* ask STREAMS to enable our servers */

	DPRINTF4 ("ld0: ldopen: struct ld is at 0x%x\n", tp);
	STRACE(tp-ttyld, 0, "open");
	return (OPENOK);
}


/*
 * streams close routine.
 * wait for output to drain unless opened O_NDELAY.
 */
/*ARGSUSED*/
static
ldclose(q, flag)
	register queue_t *q;		/* read queue */
{
	register struct ld 	*tp;
	spl_t			spl;
	int			x;
	uint			rbid;
	uint			wbid;

	DPRINTF4 ("ld0: ldclose(q:0x%x, flag:0x%x)\n", q, flag);
	tp = (struct ld *)q->q_ptr;
	STRACE(tp-ttyld, 0, "close");
	spl = p_lock(&tp->ld_lock, SPLSTR);

	/*
	 * disable reading. Mark state. insure normal flow control.
	 */
	tp->ld_cflags &= ~CREAD;
	tp->ld_state |= TTDRAIN;

	if (tp->ld_state & TTBLOCK) {
		/*
		 * If blocked allow other side to drain its
		 * outgoing data (will be discarded).
		 */
#ifdef DEBUG
		if (tp->ld_state & TTSTOP)
			STRACE(tp-ttyld, 0, "close unblock but stopped!");
#endif
		v_lock(&tp->ld_lock, spl);
		if (putnextctl2(WR(q), M_PCCTL, MC_STARTI, 
						(int)tp->ld_startc)) {
			DPRINTF1 ("ldclose: IXOFF sent MC_STARTI\n");
			tp->ld_state &= ~TTBLOCK;
			STRACE(tp-ttyld, 0, "close unblock");
		}
		(void)p_lock(&tp->ld_lock, SPLSTR);
	}

	DPRINTF4 ("ld0: ldclose: tp =0x%x ld_state=0x%x\n", tp, tp->ld_state);
	if ((flag & FNDELAY) == 0) {
		/*
		 * wait for output to drain.
		 */
		while (qsize(WR(q))) {
			int to;
			DPRINTF2 ("ld0: waiting to drain\n");
			/*
			 * If output is currently stopped then
			 * start it up.
			 */
			if (tp->ld_state & TTSTOP) {
				v_lock(&tp->ld_lock, spl);
				if (putnextctl1(WR(q), M_PCCTL, MC_START)) {
					tp->ld_state &= ~TTSTOP;
					STRACE(tp-ttyld, 0, "close start output");
				}
				(void)p_lock(&tp->ld_lock, SPLSTR);
				if (qsize(WR(q)) == 0)
					break;
			}
			qenable(WR(q));
			to = timeout((int (*)())v_sema, (caddr_t)&tp->ld_sema, 15*HZ);
			x = p_sema_v_lock(&tp->ld_sema, STOPRI|PCATCH, 
							  &tp->ld_lock);
			(void)p_lock(&tp->ld_lock, SPLSTR);
			untimeout(to);
			if (x != STOPPED)
				break;
		}
		DPRINTF2 ("ld0: drain completed\n");
	}
	/* 
	 * flush queue in case drain was not performed. 
	 */
	flushq(WR(q), FLUSHDATA);

	rbid = tp->ld_rbid;
	wbid = tp->ld_wbid;
	tp->ld_rbid = 1;	/* prevent further bufcalls */
	tp->ld_wbid = 1;	/* prevent further bufcalls */
	tp->ld_defer = 0;	/* cancel deferred actions */

	v_lock(&tp->ld_lock, spl);
	/*
	 * cancel any pending buff calls.
	 * unbufcall() must not be called with ld_lock held.
	 */
	if (rbid != NULL) {
		(void)unbufcall(rbid);
	}
	if (wbid != NULL) {
		(void)unbufcall(wbid);
	}

	disable_procs(q);		/* ask STREAMS to disable our servers */
	(void)p_lock(&tp->ld_lock, SPLSTR);

	q->q_ptr = NULL;	/* safe to do after disable_procs() */
	WR(q)->q_ptr = NULL;	/* safe to do after disable_procs() */
	freemsg(tp->ld_flushr);	/* free delayed flush ack packet */
	tp->ld_defer = 0;	/* cancel deferred actions */
	tp->ld_state = 0;	/* free tp for subsequent reuse */
	tp->ld_flushr = NULL;	
	bzero(tp, sizeof(tp));
	v_lock(&tp->ld_lock, spl);
	DPRINTF4 ("ld0: ldclose: now close\n");
	return;
}


/*
 *  put procedure for input from driver end of stream (read queue)
 * Since the open does not sleep this put procedure will only be called after
 * an open but there is no protection from already being closed.
 * This put procedure can be called concurrently and asynchronously.
 *
 * Resource depletion strategy.
 * Since all buffers are requested at HI_PRI when we have run out of them
 * there is little the system can do. In the case of signal requests or
 * flush requests we defer them until later. In the case of echoing
 * the data is lost.
 */

static
ldin(q, mp)
	register queue_t *q;		/* read queue */
	register mblk_t *mp;
{
	register unsigned char	c;
	register struct ld	*tp;
	mblk_t 			*bp;		/* current message */
	mblk_t 			*eb;		/* echo buffer */
	unsigned char		*readp;
	int			echo;
	spl_t			spl;
	int			message_type;
	int			delim;
	int			lflags;		/* copy of tp_lflags */
	int			iflags;		/* copy of tp_iflags */
	int			canon;		/* canonicalize a char */
	int			chg_flg;	/* used for advancing pline */
	int			ebcnt;		/* count of BS's to emit */
	int			est;		/* guess at number of chars */
	struct	iocblk		*iocp;
	struct	termio		*termio;	/* system V termio ioctl */
	struct	termios		*termios;	/* POSIX termios "ioctls" */
	unsigned short		pflags;		/* private flags */

	DPRINTF4 ("ld0:ldin(q:0x%x, mp:0x%x)\n", q, mp);
#ifdef DEBUG
	colour(mp, STRID_LD0 + 0x10000);
#endif

	ASSERT_DEBUG((q->q_flag & QREADR), "ld0: ldin() wrong queue");
	delim = 0;
	tp = (struct ld *)q->q_ptr;

	if (tp->ld_defer != 0)
		do_defer(q, tp);

	message_type = mp->b_datap->db_type;
	switch(message_type) {

	case M_IOCACK:	
		/*
		 * Process the termio structure being returned.
		 * If a positive acknowledgment is seen, the parameter
		 * information provided by the driver and downstream
		 * modules is augmented and passed upstream.
		 */
		DPRINTF3 ("ldin: M_IOCACK\n");
		iocp = (struct iocblk *)mp->b_rptr;
		switch (iocp->ioc_cmd) {
		case TCGETA:
			DPRINTF3 ("ldin: TCGETA\n");
			/*
			 * fill in our information.
			 */
			spl = p_lock(&tp->ld_lock, SPLSTR);
			termio = (struct termio *)mp->b_cont->b_rptr;
			ASSERT_DEBUG(mp->b_cont->b_wptr >= (unsigned char *)&termio[1], "ld0: ldin() ioack not struct termio");
			ASSERT_DEBUG(iocp->ioc_count == sizeof(struct termio), "ld0: ldin()");
			termio->c_line = 0;
			termio->c_lflag = tp->ld_lflags;
			termio->c_oflag = tp->ld_oflags;
			termio->c_iflag = tp->ld_iflags;
			termio->c_cc[VINTR] = (unchar) tp->ld_intrc;
			termio->c_cc[VQUIT] = (unchar) tp->ld_quitc;
			termio->c_cc[VERASE] = (unchar) tp->ld_erase;
			termio->c_cc[VKILL] = (unchar) tp->ld_kill;
			termio->c_cc[VEOF] = (unchar) tp->ld_eofc;
			termio->c_cc[VEOL] = (unchar) tp->ld_eol;
			termio->c_cc[VEOL2] = (unchar) tp->ld_eol2;
			termio->c_cc[VSWTCH] = (unchar) tp->ld_swtchc;
			v_lock(&tp->ld_lock, spl);
			break;

		case TCGETP:
			/*
			 * fill in our information.
			 */
			DPRINTF3 ("ldin: TCGETP\n");
			spl = p_lock(&tp->ld_lock, SPLSTR);
			termios = (struct termios *)mp->b_cont->b_rptr;
			ASSERT_DEBUG(mp->b_cont->b_wptr >= (unsigned char *)&termios[1], "ld0: ldin() ioack not struct termios");
			ASSERT_DEBUG(iocp->ioc_count == sizeof(struct termios), "ld0: ldin()");
			/* 
			 * Fill in the termios structure from the ld structure.
			 */
			ld0_termios_get(tp, termios);
			v_lock(&tp->ld_lock, spl);
			if (tp->ld_ioctl == iocp->ioc_id) {
				termios_getp(termios,mp);
			}
			break;

		case TCSETP:
			if (tp->ld_ioctl == iocp->ioc_id) {
				iocp->ioc_cmd = TIOCSETP;
			}
			break;
		}
		tp->ld_ioctl = 0;
		putnext(q, mp);
		return;

	case M_FLUSH:
		/*
		 * If FLUSHR or FLUSHRW is specified, the read queue is
		 * flushed along with all the data collected in the current 
		 * line and the the message is passed upstream.
		 */
		DPRINTF1 ("ldin: M_FLUSH %d\n", *mp->b_rptr);
		if ((*mp->b_rptr == FLUSHR) || (*mp->b_rptr == FLUSHRW)) {
			flushq(q, FLUSHDATA);
			spl = p_lock(&tp->ld_lock, SPLSTR);
			KILL_CANON(tp);
			tp->ld_state &= ~TTESC;
			if (bp=tp->ld_flushr) {
				tp->ld_flushr = NULL;
			}
			v_lock(&tp->ld_lock, spl);
			qenable(q);	/* to trigger IXOFF/MC_STARTI */
			putnext(q, mp);
			if (bp) {
				DPRINTF1 ("sync ACK @0x%x\n", bp);
				putq(q, bp);   /*send delayed ACK from FLUSHR */
			}
			return;
		}
		putnext(q, mp);
		return;
	case M_HANGUP:
		DPRINTF1 ("ldin: M_HANGUP\n");
		spl = p_lock(&tp->ld_lock, SPLSTR);
		flushq(WR(q), FLUSHDATA);
		tp->ld_state |= TTHUP;
		v_lock(&tp->ld_lock, spl);
		/*
		 * wakeup servers as if a delimiter is seen
		 */
		qenable(q);
		qenable(WR(q));
		putnext(q, mp);
		return;
	case M_CTL:
		/*
		 * The first data byte of the control message indicates
		 * the type of operation. Additional data bytes may be supplied
		 * dependent on the operation.
		 */
		switch (*mp->b_rptr++) {
		case MC_FRAME_ERR:
			/*
			 * A framing error was detected. If the data value of 
			 * the character with framing error is known, it is
			 * provided in the remaining data bytes of the message
			 * If not supplied, the data value of the character is
			 * assumed to be zero. A framing error character
			 * whose data value is zero is interpreted as a break 
			 * condition. If CREAD is not set, the message is 
			 * discarded.
			 */
			DPRINTF1 ("ldin: MC_FRAME_ERR %d chars\n",
				mp->b_wptr - mp->b_rptr);
			if (!(tp->ld_cflags & CREAD)) {
				freemsg(mp);
				return;
			}
			if (ld_err(q, mp, MC_FRAME_ERR))
				return;
			break;

		case MC_PARITY_ERR:
			/*
			 * A parity error was detected. If the data value of 
			 * the character with parity error is known, it is
			 * provided in the remaining data bytes of the message.
			 * If not supplied, the data value of the character is
			 * assumed to be zero. If CREAD is not set, the 
			 * message is discarded.
			 */
			DPRINTF1 ("ldin: MC_PARITY_ERR %d chars\n",
				mp->b_wptr - mp->b_rptr);
			if (!(tp->ld_cflags & CREAD)) {
				freemsg(mp);
				return;
			}
			if (ld_err(q, mp, MC_PARITY_ERR))
				return;
			break;

		case MC_STOPI:
			/*
			 * The driver is requesting that we stop input
			 * if ixoff is set.
			 */
			if ((tp->ld_iflags&IXOFF)) {
				ASSERT_DEBUG((mp->b_wptr < mp->b_datap->db_lim),
							"ld0: MC_STOPI");
				*(--mp->b_rptr) = MC_STOPI;
				*mp->b_wptr++ = tp->ld_stopc;
				spl = p_lock(&tp->ld_lock, SPLSTR);
				tp->ld_state |= TTBLOCK;
				STRACE(tp-ttyld, 0, "ldin device blocks inputs");
				v_lock(&tp->ld_lock, spl);
				qreply(q, mp);
			} else {
				freemsg(mp);
			}
			return;
 		case MC_PFLAGS:
			/*
			 * Control ld_pflags for ECHO, & ICANON. The 
			 * pflags are set according to what's specified
			 * in the message. This flag controls both on/off
			 * for the pflags.
			 * MESSAGE NOT PASSED UPSTREAM.
			 */
			pflags = *((unsigned short *) mp->b_rptr);

			if (pflags & ECHO)
				tp->ld_pflags |= LD0_ECHO;
			else
				tp->ld_pflags &= ~LD0_ECHO;

			if (pflags & ICANON)
				tp->ld_pflags |= LD0_CANON;
			else
				tp->ld_pflags &= ~LD0_CANON;
			freemsg(mp);
			return;
		default: /* M_CTL */
			DPRINTF1 ("ldin: M_CTL type 0x%x\n", *(mp->b_rptr-1));
			putq(q, mp);
			return;
		}

		/* fall through... */

	case M_DATA:
		DPRINTF4 ("ldin: M_DATA %d bytes\n", mp->b_wptr - mp->b_rptr);
		/*
		 * if CREAD is set, several kinds of input processing are
		 * done according to termio(7). If CREAD is not set, the 
		 * message is discarded.
		 */
		if (!(tp->ld_cflags & CREAD)) {
		DPRINTF4 ("ldin: M_DATA CREAD not set\n");
			freemsg(mp);
			return;
		}
		break;

	case M_IOCNAK:
		/*
		 * For certain ioctls, we give the lower modules and driver
		 * a chance to accept, but if they NAK them then we pick
		 * them up.
		 */
		iocp = (struct iocblk *)mp->b_rptr;
		switch (iocp->ioc_cmd) {

		case TIOCGWINSZ:
		case TIOCSWINSZ:
			DPRINTF1 ("ldin: Catch M_IOCNAK TIOC[GS]WINSZ\n");
			/*
			 * No need to acquire ld_lock since can only
			 * be one ioctl transaction on a stream at a time.
			 */
			ASSERT_DEBUG((tp->ld_ioctl == 0),
				"ld0: ldin(): already in ioctl");
			winsizioctl(q, mp, &tp->ld_wd, q, (lock_t *)NULL,
				(spl_t)NULL, &tp->ld_ioctl);
			return;

		default:
			break;
		}

		/*  FALL THROUGH ... */


	default: /* message_type */
		/*
		 * All other messages are passed upstream.
		 */
		DPRINTF1 ("ldin: message type 0x%x\n", message_type);
#ifdef DEBUG
		colour(mp, STRID_LD0 + 0xc0000);
#endif
		if (pcmsg(message_type)) {
			putnext(q, mp);	/* pass on priority messages */
		} else {
			putq(q, mp);	/* queue others for the service procedure */
		}
		return;
	}

	spl = p_lock(&tp->ld_lock, SPLSTR);
	if (tp->ld_state & TTHUP) {
		DPRINTF1 ("ldin: TTHUP\n");
		v_lock(&tp->ld_lock, spl);
		freemsg(mp);
		return;
	}
	/*
	 * these flags are acquired under lock and applied to 
	 * all of the incoming message.
	 */
	lflags = tp->ld_lflags;
	if (!(tp->ld_pflags & LD0_CANON))
		lflags &= ~ICANON;
	if (!(tp->ld_pflags & LD0_ECHO))
		lflags &= ~ECHO;

	iflags = tp->ld_iflags;
	DPRINTF4 ("ldin: lflags:0x%x iflags:0x%x\n", lflags, iflags);

	/* 
	 * If IXOFF is set, the system will transmit START/STOP characters
	 * when the input queue is nearly empty/full.
	 */
	if ((iflags&IXOFF) && q->q_count >= ldmiinfo.mi_hiwat) {
		if (!(tp->ld_state&TTBLOCK)) {
			/*
			 * we are already locked from driver.
			 */
			v_lock(&tp->ld_lock, spl);
			if (putnextctl2(WR(q), M_PCCTL, MC_STOPI, 
					(int)tp->ld_stopc)) {
				DPRINTF1 ("ldin: IXOFF sent MC_STOPI\n");
				/*
				 * Only if we succeed in sending the stop do
				 * we change state. Otherwise the next 
				 * invocation of ldin() will then try and do 
				 * the same.
				 */
				(void)p_lock(&tp->ld_lock, SPLSTR);
				tp->ld_state |= TTBLOCK;
				STRACE(tp-ttyld, 0, "ldin: qfull blocks input");
				v_lock(&tp->ld_lock, spl);
			}
		} else
			v_lock(&tp->ld_lock, spl);
	} else
		v_lock(&tp->ld_lock, spl);


	/* nothing left but M_DATA or processed M_CTL */

	/*
	 * optimise the no-need to scan cases.
	 */
	eb = NULL;
	if ((iflags & (ISTRIP|IGNCR|INLCR|ICRNL|IUCLC|IXON|PARMRK)) ||
					(lflags & (ISIG|ICANON|ECHO))) {
		/* look at all input for special characters */
		for (bp = mp; bp; bp=bp->b_cont) {
			for (readp = bp->b_rptr; readp < bp->b_wptr; readp++) {
				c = *readp;
				echo = lflags & ECHO;

				if (message_type == M_DATA) {
					/*
					 * If ISTRIP is set, valid input characters are 
					 * stripped to 7-bits, otherwise all 8-bits are
					 * processed.
					 */
					if (iflags&ISTRIP) {
						c &= 0177;
						*readp = c;
					} else if ((c == (unsigned char)'\377') && 
							    (iflags & PARMRK)) {
						/*
						 * To avoid ambiguity in ... if ISTRIP
						 * is not set a valid character of
						 * '\377' is read as '\377', '\377'.
						 */
						if (lflags & ICANON) {
							spl = p_lock(&tp->ld_lock, SPLSTR);
							if (tp->ld_state & TTCANFULL) {
								if (send_canon(q, spl) == 0) {
									v_lock(&tp->ld_lock, spl);
									freemsg(mp);
									return;
								}
							}
							if (tp->ld_op >= &tp->ld_canonb[CANBSIZ]
							    && !tp->ld_euc_num) {
								KILL_CANON(tp);
								tp->ld_state &= ~TTESC;
								strlog(STRID_LD0, tp-ttyld, 0, SL_ERROR, "ttyhog");
								DPRINTF1("ld0: canon() ttyhog\n");
								v_lock(&tp->ld_lock, spl);
								continue;
							}
							*tp->ld_op++ = '\377';
							v_lock(&tp->ld_lock, spl);
						} else {
							qaddchar(&bp, &readp, '\377');
						}
					}
					/*
					 * If IXON is set, start/stop output control is 
					 * enabled.
					 */
					if ((iflags&IXON) && check_ixon(tp, WR(q), c)) {
						if (!(lflags & ICANON))
							qdelchar(bp, &readp);
						continue;
					}
				}

				if (lflags&ISIG) {
					/*
					 * If ISIG is set, generates an interrupt
					 * signal (SIGINT) which is sent to all
					 * processes in the foreground group
					 * associated with the terminal. ...
					 * If ISIG is set the character is
					 * discarded when processed.
					 */
					if (IS_SPECIAL(c,tp->ld_intrc)) {
						ldsig(q, bp, SIGINT, !(lflags&NOFLSH));
						if (!(lflags & ICANON))
							qdelchar(bp, &readp);
						continue;
					}
					/*
					 * If ISIG is set, generates a quit signal
					 * (SIGQUIT). Its treatment is identical
					 * to the interrupt signal  .....
					 * If ISIG is set the character is
					 * discarded when processed.
					 */
					if (IS_SPECIAL(c,tp->ld_quitc)) {
						ldsig(q, bp, SIGQUIT, !(lflags&NOFLSH));
						if (!(lflags & ICANON))
							qdelchar(bp, &readp);
						continue;
					}
					/*
					 * If ISIG is set, generates a
					 * terminal stop signal (SIGTSTP) which
					 * is sent to all processes in the
					 * foreground process group associated
					 * with the terminal. ....
					 * If ISIG is set the character is
					 * discarded when processed.
					 */
					if (IS_SPECIAL(c,tp->ld_suspc)) {
						ldsig(q, bp, SIGTSTP, !(lflags&NOFLSH));
						if (!(lflags & ICANON))
							qdelchar(bp, &readp);
						continue;
					}
				}

				if (c == '\r') {
					/*
					 * If IGNCR is set a received CR is
					 * ignored (not read).
					 */
					if (iflags&IGNCR) {
						if (!(lflags & ICANON))
							qdelchar(bp, &readp);
						continue;
					}

					/*
					 * If IGNCR is not set and ICRNL is set
					 * a received CR character is translated
					 * into a NL character.
					 */
					if (iflags&ICRNL) {
						c = '\n';
						*readp = c;
					}
				} else if (c == '\n' && iflags&INLCR) {
					/*
					 * If INLCR is set, a received NL
					 * character is translated into a CR
					 * character.
					 */
					c = '\r';
					*readp = c;
				}

				if (lflags & ICANON) {
					/*
					 * Need to see if ld_canonb[] is full,
					 * waiting for a buffer. If so, try
					 * sending it again.  If this fails,
					 * the char gets dropped.  Note: OK to
					 * check TTCANFULL without asserting lock
					 * since worst case will show a false-positive
					 * and an additional call to send_canon() with
					 * a (possibly) empty buffer.
					 */
					if (tp->ld_state & TTCANFULL) {
						spl = p_lock(&tp->ld_lock, SPLSTR);
						if (send_canon(q, spl) == 0) {
							v_lock(&tp->ld_lock, spl);
							freemsg(mp);
							return;
						} else
							v_lock(&tp->ld_lock, spl);
					}
					
					/*
					 * If ICANON is set EOF may generate an
					 * end-of-file from the terminal. When
					 * received, all the characters waiting to
					 * to be read are immediately passed to
					 * the program, without waiting for a
					 * new-line and the EOF is discarded.
					 * Note: OK to test for TTESC outside of
					 * lock.  Worst case is you get a false-
					 * positive due to race with asynch M_FLUSH,
					 * (ie M_FLUSH clears after this point).
					 * An escaped EOF char gets buffered as the
					 * user probably intended.
					 */
					if (IS_SPECIAL(c,tp->ld_eofc)) {
						if (!(tp->ld_state&TTESC)) {
							delim = 1;
							goto send;
						}
					} else if (c == '\n') {
						/*
						 * '\n' is the normal line delimiter
						 * recognised if ICANON is set. It
						 * can not be changed.
						 *  - queue line for server.
						 */
						delim = 1;
						/*
						 * If ECHONL is set the NL character
						 * will be echoed even if ECHO is not
						 * set.
						 */
						if (lflags&ECHONL)
							echo = 1;
					} else if (IS_SPECIAL(c,tp->ld_eol) ||
					           (IS_SPECIAL(c,tp->ld_eol2) &&
							(lflags & IEXTEN))) {
						/*
						 * EOL is an additional line delimiter.
						 * EOL2 is another line delimiter.
						 * queue line for server.
						 */
						delim = 1;
					}
				} 

				/*
				 * If IUCLC is set, a received upper-case character
				 * is translated into the corresponding lower-case
				 * character.
				 */
				if ((iflags&IUCLC) && (lflags&IEXTEN)
						   && (c >= 'A') && (c <= 'Z')) {
					c += 'a' - 'A';
					*readp = c;
				}

				if (lflags & ICANON) {
					canon = 1;
					(void)p_lock(&tp->ld_lock, SPLSTR);
					if (!(tp->ld_state & TTESC)) {
						if ((c == '\\') &&
						    (lflags & IEXTEN)) {
							tp->ld_state |= TTESC;
						}
						/*
						 * If ECHO and ECHOE are set, when the 
						 * ERASE character is processed  it is
						 * echoed as BS SP BS (times screen width) ....
						 * If ECHOE is set but ECHO is not set
						 * .... is echoed as SP BS (times screen width)
						 * If ECHO is set and if ECHOE is off
						 * BS times screen width.
						 */
						if (IS_SPECIAL(c,tp->ld_erase)) {
							int echoe = (lflags&ECHOE);
							int current =
								tp->ld_canonb[tp->ld_pline[tp->ld_current]];
							int empty = (tp->ld_op == tp->ld_canonb);

							BACK_CANON(tp);
							canon = 0;
							if (empty || (echo == 0 && echoe == 0)) {
								/*
								 * nothing to do
								 */
								v_lock(&tp->ld_lock, spl);
								continue;
							}
							
							if (ISASCII(current))
								ebcnt = 1;		/* US ASCII */
							else if (ISSET2(current))
								ebcnt = tp->ld_eucwioc.scrw[TTSET2];
							else if (ISSET3(current))
								ebcnt = tp->ld_eucwioc.scrw[TTSET3];
							else
								ebcnt = tp->ld_eucwioc.scrw[TTSET1];
							
							est = bp->b_wptr-readp + (ebcnt * 3);
							while (ebcnt-- > 0) {
								if (echo)
									eb=echoc(q, eb, '\b', est);
								if (echoe) {
									eb=echoc(q, eb, ' ', est);
									eb=echoc(q, eb, '\b', est);
								}
							}

							echo = 0;
						} else if (IS_SPECIAL(c,tp->ld_kill)) {
							KILL_CANON(tp);
							canon = 0;
							if (lflags & ECHOK) {
								/* 
								 * if ECHOK is set, the NL
								 * character will be echoed 
								 * after the kill 
								 * character ...
								 */
								if (echo) {
									eb=echoc(q, eb, c,
									     bp->b_wptr-readp);
								}
								c = '\n';
								echo = 1;
							}
						}
					} else { /* TTESC is set */
						DPRINTF2 ("ldin: esacped char(0x%x)\n", c);
						tp->ld_state &= ~TTESC;

						if (IS_SPECIAL(c,tp->ld_erase) ||
						    IS_SPECIAL(c,tp->ld_kill)  ||
						    IS_SPECIAL(c,tp->ld_eofc) ) {
							BACK_CANON(tp);			/* removes '\' */
						} else if (tp->ld_lflags & XCASE) {
							if ((c < 0200) && maptab[c]) {
								/*
								 * echo the unprocessed char
								 * but canonicalize the processed
								 * char.
								 */
								if (echo) {
									eb=echoc(q, eb, c,
										bp->b_wptr-readp);
								} 
								echo = 0;
								c = maptab[c];
								BACK_CANON(tp);		/* removes '\' */
							} else if (c == '\\')
								canon = 0;		/* keep just 1 '\' */
						} else if (c == '\\')
							tp->ld_state |= TTESC;
					}
					if (canon) {
						/*
						 * Determine if the current input byte
						 * begins a new EUC character (chg_flg == 1), or if
						 * it is in the middle of a multi-byte char.
						 */
						if ((int)tp->ld_euc_num > 0) {
							tp->ld_euc_num--;
							chg_flg = 0;
						} else {
							chg_flg = 1;
							if (ISASCII(c))	/* optimize US ASCII case */
								tp->ld_euc_num = 0;
							else if (ISSET3(c))
								tp->ld_euc_num =
									tp->ld_eucwioc.eucw[TTSET3];
							else if (ISSET2(c))
								tp->ld_euc_num =
									tp->ld_eucwioc.eucw[TTSET2];
							else		/* Must be codeset 1 */
								tp->ld_euc_num =
									tp->ld_eucwioc.eucw[TTSET1] - 1;
						}
						/*
						 * If chg_flg is on, it means a switch to a new code set.
						 * In this case, we have to update tp->ld_current and
						 * tp->ld_pline[].
						 */
						if (chg_flg) {
							tp->ld_current++;
							tp->ld_pline[tp->ld_current] =
								(tp->ld_op - tp->ld_canonb);
						}

						/*
						 * ttyhog - ld_canonb[] has slop at the end.
						 */
						if (tp->ld_op >= &tp->ld_canonb[CANBSIZ]
						    && !tp->ld_euc_num) {
							KILL_CANON(tp);
							tp->ld_state &= ~TTESC;
							strlog(STRID_LD0, tp-ttyld, 0, SL_ERROR, "ttyhog");
							DPRINTF1("ld0: canon() ttyhog\n");
							v_lock(&tp->ld_lock, spl);
							continue;
						}
						*tp->ld_op++ = c;
					}
					v_lock(&tp->ld_lock, spl);
				}
				/*
				 * If ECHO is set, characters are echoed as received.
				 */
				if (echo) {
					/*
					 * allocate a message buffer for the echoed
					 * characters.
					 */
					eb=echoc(q, eb, c, bp->b_wptr-readp);
				}
				/*
				 * If a line terminator was seen, enqueue the canon buffer
				 * for the server to process.
				 */
send:
				if (delim) {
					DPRINTF4 ("ld0: ldin() send_canon-ing\n");
					spl = p_lock(&tp->ld_lock, SPLSTR);
					(void)send_canon(q, spl);
					v_lock(&tp->ld_lock, spl);
					delim = 0;
				}
			}
		}
	} else {
		DPRINTF1 ("ldin: shortcut\n");
	}

	/*
	 * If not in ICANON mode, send the chars upstream immediately.
	 */
	if (!(lflags & ICANON)) {
		if (mp->b_rptr == mp->b_wptr) {
			/*
			 * ignore packets which we received empty 
			 * or which we've caused to become empty
			 */
			freemsg(mp);
			DPRINTF1 ("ld0: ldin() freeing (now) empty message\n");
		} else {
			/*
			 * always convert it into an M_DATA (may have been M_CTL).
			 */
			mp->b_datap->db_type = M_DATA;

			putq(q, mp);
		}
	} else {
		freemsg(mp);
	}
	/*
	 * pass on a partial echo buffer.
	 */
	if (eb) {
		putother(q, eb);
	}
	if (tp->ld_defer != 0)
		do_defer(q, tp);
	return;
}


/*
 * line discipline input server processing.
 * The put procedure gathers input into ld_canonb[]
 * and then queues it for the server to send it
 * upstream.
 */

static
ldinsrv(q)
	queue_t *q;			/* read queue */
{
	struct ld *tp;
	mblk_t *mp;
	spl_t	spl;

	DPRINTF1 ("ld0: ldinsrv(q:0x%x) read service procedure\n", q);

	ASSERT_DEBUG((q->q_flag & QREADR), "ld0: ldinsrv() wrong queue");
	tp = (struct ld *)q->q_ptr;

	if (tp->ld_defer != 0)
		do_defer(q, tp);

	while ((mp = getq(q)) != NULL) {
#ifdef DEBUG
		colour(mp, STRID_LD0 + 0x20000);
#endif
		switch (mp->b_datap->db_type) {
		case M_DATA:
			/*
			 * If queue is full give up the loop.
			 */
			if (!canputnext(q)) {
				putbq(q, mp);
				return;
			}
#ifdef DEBUG
			colour(mp, STRID_LD0 + 0x50000);
#endif
			putnext(q, mp);
			DPRINTF4 ("ld0: ldinsrv() putnext %d chars\n", mp->b_wptr - mp->b_rptr);
			continue;
		default:
			if (pcmsg(mp->b_datap->db_type) || canputnext(q)) {
				putnext(q, mp);	/* pass on priority messages */
			} else {
				putbq(q, mp);	/* queue others for the service procedure */
				return;
			}
			continue;
		}
	}


	if ((tp->ld_iflags&IXOFF) && q->q_count < ldmiinfo.mi_hiwat) {
		spl = p_lock(&tp->ld_lock, SPLSTR);
		if (tp->ld_state&TTBLOCK) {
#ifdef DEBUG
			if (tp->ld_state & TTSTOP)
				STRACE(tp-ttyld, 0, "ldinsrc: unblocking but stopped!");
#endif
			v_lock(&tp->ld_lock, spl);
			if (putnextctl2(WR(q), M_PCCTL, MC_STARTI, 
					               (int)tp->ld_startc)) {
				DPRINTF1 ("ldinsrv: IXOFF sent MC_STARTI\n");
				/*
				 * Only if we succeed in sending the start do
				 * we change state. Otherwise the next 
				 * invocation of ldinsrv() will then try and do 
				 * the same.
				 */
				(void)p_lock(&tp->ld_lock, SPLSTR);
				tp->ld_state &= ~TTBLOCK;
				STRACE(tp-ttyld, 0, "ldinsrv: queue not full unblocks input");

				v_lock(&tp->ld_lock, spl);
			}
		} else
			v_lock(&tp->ld_lock, spl);
	}
}


/*
 * Check if 'c' is a start or stop character.
 * If it is, attempt to tell the driver to start or start.
 * return 1 if the character is to be discarded.
 *
 * Called with ld_lock held.
 *
 * If the IXON flag is set [STOP] can be used to temporarily
 * suspend output. It is useful with CRT terminals to prevent output
 * from disappearing before it can be read. If the IXON flag is set, the
 * STOP character is discarded when processed. While output is suspended
 * STOP characters are ignored and discarded.
 *
 * If the IXON flag is set, [START] is used to resume output which has 
 * been suspended by a STOP character. If the IXON flag is set, the
 * START character is discarded when processed. while output is not
 * suspended, START characters are ignored and discarded.
 *
 * called with no locks
 */
static int
check_ixon(tp, wrq, c)
	struct	ld	*tp;
	queue_t		*wrq;		/* write queue */
	unsigned char	c;
{
	spl_t		spl;

	if (IS_SPECIAL(c,tp->ld_startc)) {
		spl = p_lock(&tp->ld_lock, SPLSTR);
		tp->ld_state &= ~TTSTOP;
		v_lock(&tp->ld_lock, spl);
		STRACE(tp-ttyld, 0, "check_ixon ^Q start output");
		qenable(wrq);
		/*
		 * tell tty driver to start output.
		 */
		if (!putnextctl1(wrq, M_PCCTL, MC_START)) {
			STRACE(tp-ttyld, 0, "check_ixon start output deferred");
			tp->ld_defer |= LD0_START;
		}
		return (1);
	} 
	spl = p_lock(&tp->ld_lock, SPLSTR);
	if ((tp->ld_state & TTSTOP) &&
	    (tp->ld_iflags & IXANY) && (tp->ld_lflags & IEXTEN)) {
		tp->ld_state &= ~TTSTOP;
		v_lock(&tp->ld_lock, spl);
		STRACE(tp-ttyld, 0, "check_ixon ixany start output");
		qenable(wrq);
		/*
		 * tell tty driver to start output.
		 */
		if (!putnextctl1(wrq, M_PCCTL, MC_START)) {
			tp->ld_defer |= LD0_START;
			STRACE(tp-ttyld, 0, "check_ixon start output deferred");
		}
		if (IS_SPECIAL(c, tp->ld_stopc))
			return (1);	/* discard if [STOP] used to start */
		return (0);		/* otherwise don't discard*/
	}

	if (IS_SPECIAL(c, tp->ld_stopc)) {
		tp->ld_state |= TTSTOP;
		v_lock(&tp->ld_lock, spl);
		STRACE(tp-ttyld, 0, "check_ixon ^S stop output");
		/*
		 * tell tty driver to stop output.
		 */
		if (!putnextctl1(wrq, M_PCCTL, MC_STOP)) {
			tp->ld_defer |= LD0_STOP;
			STRACE(tp-ttyld, 0, "check_ixon stop output deferred");
		}
		return (1);
	}
	v_lock(&tp->ld_lock, spl);
	/* not stop or start */
	return (0);
}

/*
 * re_send_canon - retry a failed send_canon()
 * send_canon() may fail due to buffer depletion.
 * This routine will retry it.
 * Called as a result of bufcall().
 */
static void
re_send_canon(q)
	register queue_t	*q;		/* read queue */
{
	struct ld	*tp;
	spl_t		spl;

	tp = (struct ld *)q->q_ptr;
	tp->ld_rbid = NULL;			/* allow further bufcalls */
	spl = p_lock(&tp->ld_lock, SPLSTR);
	(void)send_canon(q, spl);
	v_lock(&tp->ld_lock, spl);
}


/*
 * send_canon - enqueue ld_canonb for server.
 *
 * If I_CANON is set, ldin() will be assembling characters into ld_canonb[],
 * honoring line editing commands like erase and kill.  When a delimiter
 * is reached, this routine puts the contents of ld_canonb[] on the read queue.
 *
 * Assumes: tp->ld_lock is asserted by caller, though all callers must
 * be able to tolerate the lock being dropped for a window.
 */
static
send_canon(q, spl)
	register queue_t	*q;		/* read queue */
	spl_t		spl;
{
	struct ld	*tp;
	mblk_t		*bpt;
	int		n;

	tp = (struct ld *)q->q_ptr;
	n = tp->ld_op - tp->ld_canonb;

	/* 
	 * allocate a message buffer to put the
	 * canonicalised input in.
	 */
	if ((bpt = allocb(n, BPRI_MED)) == NULL) {
		DPRINTF1 ("!send_canon: out of blocks\n");
		if (tp->ld_rbid == NULL) {
			/*
			 * If the bufcall fails, the system is
			 * woefully underconfigured, bufcall() itself will
			 * complain.  We set TTCANFULL, which forces a
			 * retry on next ldin().  Worst case: nothing
			 * happens until next keystroke.
			 */
			tp->ld_rbid = bufcall((uint)n, BPRI_MED,
			       (int (*)())re_send_canon, (long)q);
		}
		tp->ld_state |= TTCANFULL;
		/*
		 * If IXOFF is set, stop character input from the driver.
		 * This could be done unconditionally, if our drivers had a
		 * flavor of MC_STOPI which doesn't send the ld_stopc char.
		 * Currently, if IXOFF isn't set, chars may drop.
		 */
		if (tp->ld_iflags & IXOFF) {
			if (!(tp->ld_state&TTBLOCK)) {

				/*
				 * drop the lock while sending the M_PCCTL,
				 * to prevent possible deadlock.
				 */
				v_lock(&tp->ld_lock, spl);
				if (putnextctl2(WR(q), M_PCCTL, MC_STOPI, 
						(int)tp->ld_stopc)) {
					DPRINTF1 ("send_canon: IXOFF sent MC_STOPI\n");
					/*
					 * Only if we succeed in sending the stop do
					 * we change state. Otherwise the next 
					 * invocation of ldin() will then try and do 
					 * the same.
					 */
					(void)p_lock(&tp->ld_lock, SPLSTR);
					tp->ld_state |= TTBLOCK;
					STRACE(tp-ttyld, 0, "send_canon: qfull blocks input");
				} else {
					(void)p_lock(&tp->ld_lock, SPLSTR);
				}
			}
		}

		return(0);
	}
	if (n != 0)
		bcopy((caddr_t)tp->ld_canonb, (caddr_t)bpt->b_wptr, (uint)n);
				
#ifdef DEBUG
	colour(bpt, STRID_LD0 + 0xb0000);
#endif
	bpt->b_wptr += n;
	putq(q, bpt);
	l.cnt.v_canch += n;	/* statistics */
	KILL_CANON(tp);
	tp->ld_state &= ~(TTESC|TTCANFULL);

	return(1);
}


/*
 * Line discipline output queue put procedure
 */
static
ldout(q, mp)
	register queue_t *q;		/* write queue */
	register mblk_t *mp;
{
	struct iocblk *iocp;

	DPRINTF3 ("ld0: ldout(q:0x%x, mp:0x%x) - write put procedure\n", q, mp);

	ASSERT_DEBUG(!(q->q_flag & QREADR), "ld0: ldout() wrong queue");

	switch (mp->b_datap->db_type) {

	case M_PCIOCTL:
		iocp = (struct iocblk *)mp->b_rptr;
		DPRINTF3 ("ld0: M_PCIOCTL cmd:0x%x\n", iocp->ioc_cmd);

		switch (iocp->ioc_cmd) {

		case TIOCSETP:
		case TCSETA:
		case TCSETP:
		case TCFLSH:
		case TCXONC:
			putq(q, mp);	/* done in service procedure */
			return;

		}

		/* handle any other M_PCIOCTLs as generic priority msgs */
		break;

	case M_IOCDATA:
		putq(q, mp);	/* done in service procedure */
		return;

	case M_FLUSH:
		DPRINTF1 ("ld0: M_FLUSH cmd:0x%x\n", *mp->b_rptr);
		if (*mp->b_rptr == FLUSHW || *mp->b_rptr == FLUSHRW)
			flushq(q, FLUSHDATA);
		putnext(q, mp);
		return;
	}

	if (pcmsg(mp->b_datap->db_type)) {
		DPRINTF4 ("ld0: ldout() putnext(q->q_next:0x%x, mp:0x%x)\n", q->q_next, mp);
		putnext(q, mp);	/* pass on priority messages */
	} else {
		DPRINTF4 ("ld0: ldout() putq(q:0x%x, mp:0x%x) q->q_flag:0o%o\n", q, mp, q->q_flag);
		putq(q, mp);	/* queue others for the service procedure */
	}
	return;
}


/*
 * line discipline output service procedure: delays, tab and CR/NL
 * mapping
 */
static
ldosrv(q)
	queue_t *q;		/* write queue */
{
	struct ld *tp;
	mblk_t *mp;
	spl_t	spl;

	DPRINTF1 ("ld0: ldosrv(q:0x%x): (write service)\n", q);

	ASSERT_DEBUG(!(q->q_flag & QREADR), "ld0: ldosrv() wrong queue");

	tp = (struct ld *)q->q_ptr;

	while ((mp = getq(q)) != NULL) {
#ifdef DEBUG
		colour(mp, STRID_LD0 + 0x30000);
#endif

		switch(mp->b_datap->db_type) {

		case M_DATA:
			if (!outconv(q, mp))
				return;		/* can't process now */
			break;
		case M_IOCTL:
		case M_PCIOCTL:
			if (!ld_ioc(q, mp))
				return;		/* can't process now */
			break;
		case M_IOCDATA:
		{
			struct	copyresp *resp;

			resp = (struct copyresp *)mp->b_rptr;

			switch (resp->cp_cmd) {

			case  TIOCSWINSZ:
			case  TIOCGWINSZ:
				/*
				 * Ensure that this IOCDATA is for us
				 * and not for a lower module that has
				 * decided to process this ioctl.
				 *
				 * No need to acquire ld_lock since 
				 * can only be one ioctl transaction 
				 * on a stream at a time.
				 */
				if (tp->ld_ioctl == resp->cp_id) {
					DPRINTF2 ("ld0: ldosrv: IOCDATA: TIOC[GS]WINSZ\n");
					winsizioctl(RD(q), mp, &tp->ld_wd,
						RD(q), (lock_t *)NULL,
						(spl_t)NULL, &tp->ld_ioctl);
				} else {
					/*
					 * IOCDATA belongs to lower module.
					 *
					 * We shouldn't be in the middle of
					 * processing another ioctl.
					 */
					ASSERT_DEBUG(!tp->ld_ioctl, "ld0: ldosrv()");
					goto passon;
				}
				break;
			default:
				/* All other IOCDATA get passed downstream */

				goto passon;
			}	
			break;
		}

		default:
passon:
			/* not an ld0 msg, deliver to module downstream */
			DPRINTF3 ("ld0: ldosrv(): type:0x%x\n", mp->b_datap->db_type);

			/* Non-priority msgs cannot be processed 
			 * if flow control prohibits it.
			 */
			spl = p_lock(&tp->ld_lock, SPLSTR);
			if (!pcmsg(mp->b_datap->db_type) &&
			   ((tp->ld_state & TTSTOP) || !canputnext(q))) {
				v_lock(&tp->ld_lock, spl);
				putbq(q, mp);
				return;		/* can't process now */
			}
			v_lock(&tp->ld_lock, spl);
#ifdef DEBUG
			colour(mp, STRID_LD0 + 0x50000);
#endif
			putnext(q, mp);
			break;
		}
	}
	/*
	 * wake anyone waiting to drain.
	 */
	spl = p_lock(&tp->ld_lock, SPLSTR);
	if (tp->ld_state & TTDRAIN) {
		v_sema(&tp->ld_sema);
	}
	v_lock(&tp->ld_lock, spl);
}


/*
 * Output processing for contents of M_DATA messages.
 * Called by output service routine.
 * 
 * returns 0 if message (imp) was not processed and returned to output queue (q)
 * returns 1 is message (imp) was correctly processed.
 */
static int
outconv(q, imp)
	register queue_t *q;		/* write queue */
	register mblk_t *imp;
{
	
	register struct ld *tp;
	mblk_t *omp = NULL;
	mblk_t *bp;
	int c;
	int ctype, save_col, cols;
	int fchars;		/* number of OFILL delays */
	int fticks;		/* number of tick delays */
	int wantbytes;		/* non-zero if need more room in omp */
	unsigned char *colp;
	spl_t	spl;

	DPRINTF4 ("ld0: outconv\n");
	ASSERT_DEBUG(imp != NULL, "ld0: outconv()");
	ASSERT_DEBUG(imp->b_datap->db_type == M_DATA, "ld0: outconv()");

	tp = (struct ld *)q->q_ptr;
	spl = p_lock(&tp->ld_lock, SPLSTR);

	if (tp->ld_state & TTHUP) {
		v_lock(&tp->ld_lock, spl);
		freemsg(imp);
		return (1);
	}

	/* flow control allow another msg? */

	if ((tp->ld_state & TTSTOP) || !canputnext(q)) {
		v_lock(&tp->ld_lock, spl);
		putbq(q, imp);
		return (0);
	}

	if (tp->ld_fticks && !putnextctl1i(q, M_DELAY, tp->ld_fticks)) { 
		if (tp->ld_wbid == NULL) {
			tp->ld_wbid = bufcall(1, BPRI_HI,
				 	      (int (*)())wqrestart, (long)q);
		}
		v_lock(&tp->ld_lock, spl);
		putbq(q, imp);
		return (0);
	} else 
		tp->ld_fticks = 0;

	if (((tp->ld_lflags 
	      & (XCASE|((tp->ld_pflags & LD0_CANON) ? ICANON : 0)|IEXTEN)) 
			!= (XCASE|ICANON|IEXTEN)) 
	    && !(tp->ld_oflags & OPOST)) {
		DPRINTF1 ("ld0:canon shortcut putnext\n");
#ifdef DEBUG
		colour(imp, STRID_LD0 + 0x60000);
#endif
		v_lock(&tp->ld_lock, spl);
		/*
		 * We have already checked canput.
		 * The earlier limitation upon open to keep our maximum
		 * write-side packet size to the minimum of the write-side
		 * packet size of the STREAMS module/driver below us and
		 * our own default maximum write-side packet size, means
		 * that we don't have to segment the message here.
		 */
		putnext(q, imp);
		return (1);
	}

	tp = (struct ld *)q->q_ptr;
	wantbytes = 0;

	while (imp != NULL) {
		while (imp->b_rptr < imp->b_wptr) {

			/*
			 * Make sure there's room for TWO more chars here;
			 * in case XCASE processing needs to add a '\'.
			 */

			if (wantbytes || (omp == NULL) ||
			    (omp->b_wptr + 1 >= omp->b_datap->db_lim)) {

				if (omp != NULL) {
					DPRINTF4 ("ld0: outconv() putnext\n");
#ifdef DEBUG
					colour(omp, STRID_LD0 + 0x70000);
#endif
					v_lock(&tp->ld_lock, spl);
					putnext(q, omp);
					(void)p_lock(&tp->ld_lock, SPLSTR);
				}

				/* flow control allow another msg? */

				if ((tp->ld_state & TTSTOP) ||
				    !canputnext(q)) {
					v_lock(&tp->ld_lock, spl);
					DPRINTF2 ("ld0: outconv() putbq\n");
					putbq(q, imp);
					return (0);
				}

				wantbytes = imp->b_wptr - imp->b_rptr
					+ ld0_outfudge;

#ifdef LATER
COULD USE THE FOLLOWING CODE TO ONLY APPLY FUDGE FACTOR WHEN NECESSARY?
REMEMBER THAT CODE THAT SETS wantbytes ASSUMES ENOUGH WILL BE ALLOCATED.

				wantbytes = imp->b_wptr - imp->b_rptr;
				if (((tp->ld_lflags & (XCASE|IEXTEN|((tp->ld_pflags & LD0_CANON) ? ICANON : 0)))
				    == (XCASE|IEXTEN|ICANON)) ||
				    ((tp->ld_oflags & OPOST) &&
				    (tp->ld_oflags & (ONLCR|OFILL))))
					wantbytes += ld0_outfudge;
#endif

				if (q->q_maxpsz != INFPSZ &&
				    wantbytes > q->q_maxpsz) {
					/*
					 * Make sure that we do not generate
					 * a message exceeding the maximum
					 * allowed packet size of the module
					 * below us (we set our q->maxpsz in
					 * consideration of the STREAMS
					 * module/driver below us at open time).
					 */
					wantbytes = q->q_maxpsz;
				}
				if (wantbytes>MAXBSZ)
					wantbytes = MAXBSZ;
				if ((omp=allocb(wantbytes, BPRI_MED)) == NULL) {
					if (tp->ld_wbid == NULL) {
						tp->ld_wbid = bufcall((uint)wantbytes, BPRI_MED, (int (*)())wqrestart, (long)q);
					}
					v_lock(&tp->ld_lock, spl);
					DPRINTF2 ("ld0: outconv() putbq\n");
					putbq(q, imp);
					return (0);
				}
				wantbytes = 0;
			}

			/*
			 * Get a character from the input msg block.
			 */
			
			c = *imp->b_rptr++;

			/*
			 * Check the 8th bit - EUC chars don't get output delay!
			 * However, we need to keep ld_col up-to-date.
			 * Therefore, update it on the FIRST byte of multi-byte
			 * chars by adding in the screen-width of the EUC
			 * character to ld_col.  On subsequent bytes of the
			 * EUC char, don't touch ld_col, because it's been
			 * updated on the first byte for the whole multi-byte
			 * sequence. The way we keep ld_col in synch is to
			 * calculate how many subsequent bytes are in the
			 * char and ignore them in ld_col calculations.
			 * Set "ld_ign" on the first byte, and count down
			 * for each subsequent byte.  When "ld_ign" goes
			 * zero, we have no more bytes to ignore.
			 */
			if (c & 0x80) {
				*omp->b_wptr++ = c;
				if (tp->ld_ign) {
					--tp->ld_ign;
					continue;
				}
				if (ISSET2(c)) {
					tp->ld_col += tp->ld_eucwioc.scrw[TTSET2];
					tp->ld_ign = tp->ld_eucwioc.eucw[TTSET2];
				}
				else if (ISSET3(c)) {
					tp->ld_col += tp->ld_eucwioc.scrw[TTSET3];
					tp->ld_ign = tp->ld_eucwioc.eucw[TTSET3];
				}
				else {
					tp->ld_col += tp->ld_eucwioc.scrw[TTSET1];
					tp->ld_ign =
						tp->ld_eucwioc.eucw[TTSET1] - 1;
				}
				continue;
			}

			/*
			 * Generate XCASE escapes.
			 *
			 * (Above code ensures that there is space for at least
			 *  two more bytes in output message.)
			 */

			if ((tp->ld_lflags & (XCASE|IEXTEN|((tp->ld_pflags & LD0_CANON) ? ICANON : 0)))
					  == (XCASE|IEXTEN|ICANON)) {
				colp = (unsigned char *)"({)}!|^~'`\\\\\0";
				while (*colp++)
					if (c == *colp++) {
						*omp->b_wptr++ = '\\';
						c = colp[-2];
						break;
					}
				if ('A' <= c && c <= 'Z')
					*omp->b_wptr++ = '\\';
			}

			/*
			 * If no other output processing is required, push
			 * the character into the block and get another.
			 */

			if (!(tp->ld_oflags & OPOST)) {
				*omp->b_wptr++ = c;
				continue;
			} 
			
			/* OPOST output flag is set */
									
			/*
			 * Map lower case to upper case if OLCUC flag is set.
			 */
			if ((tp->ld_oflags & OLCUC) && 'a' <= c && c <= 'z')
				c -= 'a' - 'A';

			/*
			 * Push characters into output buffer until character
			 * is found that needs further output processing.
			 */
			if ((c > 0177) || ((ctype = partab[c] & 077) == 0)) {
				tp->ld_col++;
				*omp->b_wptr++ = c;
				continue;
			}

			/*
			 * Expand '\t' to spaces if TAB3 flag is set.
			 */

			if ((c == '\t') && ((tp->ld_oflags & TABDLY)==TAB3)) {

				/* Ensure sufficient space in output buffer */

				if (omp->b_wptr + (7 - (tp->ld_col & 07)) >=
				    omp->b_datap->db_lim) {
					imp->b_rptr--;
					wantbytes = 1;
					continue;
				}
				for (;;) {
					*omp->b_wptr++ = ' ';
					tp->ld_col++;
					if ((tp->ld_col & 07) == 0)
						break;
				}
				continue;
			}


			/*
			 * Delay values and column position calculated here.
			 */

			fchars = 0;
			fticks = 0;
			save_col = tp->ld_col;

			switch (ctype) {

			case ORDINARY:	/* already handled above!?! */
				DPRINTF1 ("ld0-outconv: ordinary char too far\n");
				tp->ld_col++;
				break;

			case CONTROL:	/* Non-printing characters */
				break;

			case BACKSPACE:
				if (tp->ld_col)
					tp->ld_col--;
				if (tp->ld_oflags & BSDLY) {
					if (tp->ld_oflags & OFILL)
						fchars = 1;
					else
						fticks = (5*HZ)/100;
				}
				break;

			case NEWLINE:
				/*
				 * <NL> interpreted as <CR><NL> by device
				 * if ONLRET flag is set.
				 */

				if (tp->ld_oflags & ONLRET)
					goto cr;

				/*
				 * Map <NL> to <CR><NL> if ONLCR flag is set.
				 */

				if (tp->ld_oflags & ONLCR) {
					/*
					 * Need enough for extra CR plus
					 * any possible CR fill characters.
					 * 1 for CR
					 * 4 for max CR fill
					 */
					if (omp->b_wptr + 15 >=
					    omp->b_datap->db_lim) {
						imp->b_rptr--;
						wantbytes = 1;
						continue;
					}
					if (!((tp->ld_oflags & ONOCR) &&
					    tp->ld_col == 0)) {
						*omp->b_wptr++ = '\r';
					}
					goto cr;
				}

			nl:
				if ((tp->ld_oflags & NLDLY) == NL1) {
					if (tp->ld_oflags & OFILL)
						fchars = 2;
					else
						fticks = (10*HZ)/100;
				}

				break;

			case TAB:
				cols = 8 - (tp->ld_col & 07);
				tp->ld_col += cols;

				if (tp->ld_oflags & OFILL) {
					if (tp->ld_oflags & TABDLY)
						fchars = 2;
					break;
				}

				/* OFILL not set: */

				if ((tp->ld_oflags & TABDLY) == TAB1) {
					if (cols > 4)
						fticks = (cols*HZ)/100;
				}

				if ((tp->ld_oflags & TABDLY) == TAB2)
					fticks = (10*HZ)/100;

				/* TAB3 handled above */

				break;

			case VTAB:	/* vertical tab */
				if (tp->ld_oflags & VTDLY)
					fticks = (200*HZ)/100;
				break;

			case RETURN:	/* carriage return */
				/*
				 * Map <CR> to <NL> if OCRNL flag set.
				 */

				if (tp->ld_oflags & OCRNL) {
					c = '\n';
					goto nl;
				}

				/*
				 * Ignore <CR> in column 0 if ONOCR flag set.
				 */

				if ((tp->ld_oflags & ONOCR) &&
				    tp->ld_col == 0) {
					continue;
				}
			cr:
				switch (tp->ld_oflags & CRDLY) {

				case CR1:
					if (tp->ld_oflags & OFILL)
						fchars = 2;
					else
						fticks = ((tp->ld_col / 2)*HZ)/100;
					break;
				case CR2:
					if (tp->ld_oflags & OFILL)
						fchars = 4;
					else
						fticks = (10*HZ)/100;
					break;
				case CR3:
					if (tp->ld_oflags & OFILL)
						fchars = 0;
					else
						fticks = (15*HZ)/100;
					break;
				}
				tp->ld_col = 0;
				break;

			case FORMFEED:
				if (tp->ld_oflags & FFDLY)
					fticks = (2000*HZ)/100;
				break;
			}

			/*
			 * Push the character into the output message.
			 */

			*omp->b_wptr++ = c;

			/*
			 * Process any delays or fill characters.
			 */

			if (fchars) {
				/*
				 * Need enough for fill characters.
				 */
				if (omp->b_wptr + fchars - 1 >=
				    omp->b_datap->db_lim) {
					imp->b_rptr--;
					omp->b_wptr--; /* back out output char */
					tp->ld_col = save_col;
					wantbytes = 1;
					continue;
				}
				for (; fchars; fchars--) {
					if (tp->ld_oflags & OFDEL)
						*omp->b_wptr++ = 0177;
					else
						*omp->b_wptr++ = CNUL;
				}
			} else if (fticks) {
				DPRINTF4 ("ld0: outconv() putnext\n");
#ifdef DEBUG
				colour(omp, STRID_LD0 + 0x80000);
#endif
				v_lock(&tp->ld_lock, spl);
				putnext(q, omp);
				omp = NULL;
				/*
				 * ok heres the plan lets have an
				 * tp->ld_fticks
				 * if set ldinsrv() should do as below.
				 */
				if (!putnextctl1i(q, M_DELAY, fticks)) { 
					(void)p_lock(&tp->ld_lock, SPLSTR);
					tp->ld_fticks = fticks;
					if (tp->ld_wbid == NULL) {
						tp->ld_wbid = bufcall(1,
						 	 BPRI_HI,
						         (int (*)())wqrestart,
							 (long)q);
					}
					putbq(q, imp);
					v_lock(&tp->ld_lock, spl);
					return (0);
				}
				(void)p_lock(&tp->ld_lock, SPLSTR);
			}
		}

		/*
		 * Free the processed data block and go to next block of msg
		 */

		 bp = unlinkb(imp);
		 freeb(imp);
		 imp = bp;
	}

	v_lock(&tp->ld_lock, spl);

	if (omp != NULL) {
		DPRINTF4 ("ld0: outconv() putnext\n");
#ifdef DEBUG
		colour(omp, STRID_LD0 + 0xa0000);
#endif
		putnext(q, omp);
	}
	DPRINTF4 ("ld0: outconv() end\n");

	ASSERT_DEBUG(imp == NULL, "ld0: outconv()");
	return (1);
}


/*
 * Signal generated by the reader: M_SIGNAL and M_FLUSH messages sent.
 */

static 
ldsig(q, mp, sig, flush)
	queue_t *q;		/* read queue */
	mblk_t	*mp;		/* current message buffer */
	int     sig;
	int     flush;		/* non-zero if flush read/write */
{
	struct ld *tp = (struct ld *)q->q_ptr;

	DPRINTF1 ("ld0: ldsig(q:0x%x, sig:%d)\n", q, sig);
	ASSERT_DEBUG((q->q_flag & QREADR), "ld0: ldsig() wrong queue");

	if (flush) {
		/*
		 * flush the characters following the signal.
		 * ldin will then free the message.
		 */
		mp->b_wptr = mp->b_rptr;
		/*
		 * request stream end send FLUSHR up read side
		 */
		if (!putnextctl1(WR(q), M_FLUSH, FLUSHR)) {
			/*
			 * flush our own queue going to the server
			 * now try again.
			 */
			flushq(q, FLUSHDATA);
			if (!putnextctl1(WR(q), M_FLUSH, FLUSHR)) {
				tp->ld_defer |= LD0_FLUSHR;
				qenable(q);
			}
		}
		/*
		 * request stream head send FLUSHW down write side
		 */
		if (!putnextctl1(q, M_FLUSH, FLUSHW)) {
			/*
			 * flush our own queues
			 * now try again.
			 */
			flushq(q, FLUSHDATA);
			flushq(WR(q), FLUSHDATA);
			if (!putnextctl1(q, M_FLUSH, FLUSHW)) {
				tp->ld_defer |= LD0_FLUSHW;
				qenable(q);
			}
		}
	}
	if (!putnextctl1(q, M_PCSIG, sig)) {
		/*
		 * defer the action till later.
		 */
		DPRINTF1 ("ld0:ldin signal %d deferred\n", sig);
		switch (sig){
		case SIGINT:
			tp->ld_defer |= LD0_INT;
			break;
		case SIGQUIT:
			tp->ld_defer |= LD0_QUIT;
			break;
		case SIGTSTP:
			tp->ld_defer |= LD0_TSTP;
			break;
		}
		qenable(q);
	}
}


/*
 * Adjust stream head parameters for ICANON, VMIN, VTIME, and TOSTOP.
 * Called with ld_lock held, though all callers must be able to
 * tolerate the lock being dropped for a window.
 */

static int
adjstrhead(q, mp, spl,
	   old_lflags, old_vmin, old_vtime,
	   new_lflags, new_vmin, new_vtime)
	queue_t		*q;			/* write queue */
	mblk_t 		*mp;
	spl_t		spl;
	tcflag_t  	old_lflags;
	cc_t  		old_vmin;
	cc_t  		old_vtime;
	tcflag_t	new_lflags;
	cc_t 		new_vmin;
	cc_t 		new_vtime;
{
	register struct ld 	*tp;
	struct stroptions	*st;
	mblk_t			*bpt;
	int			n;

	tp = (struct ld *)q->q_ptr;

	/* If ICANON or TOSTOP flags are changing,
	 * or VMIN (VEOF) or VTIME (VEOL) are changing in non-canonical
	 * mode, then adjust stream head parameters.
	 */
	if (((old_lflags ^ new_lflags) & (ICANON | TOSTOP)) 
	     || (!(new_lflags & ICANON)
		  && ((old_vmin != new_vmin) || (old_vtime != new_vtime)))) {

		/* allocate message to send to stream head */

		bpt = allocb(sizeof(struct stroptions), BPRI_MED);
		if (bpt == NULL) {
			DPRINTF1 ("ld0: adjstrhead() - putbqx()\n");
			putbqx(q, mp);
			if (tp->ld_wbid == NULL)
				tp->ld_wbid = bufcall((uint)sizeof(struct stroptions),
						BPRI_MED, (int (*)())wqrestart,
						(long)q);
			return (0);
		}

		/* Alter stream head parameters.
		 *
		 * Canonical mode:
		 *	Message non-discard read mode with
		 *	VMIN/VTIME effectively disabled.
		 * Non-canonical mode:
		 *	Byte-stream read mode with
		 *	VMIN/VTIME set per ioctl() data.
		 * TOSTOP set per ioctl() data.
		 */

		bpt->b_datap->db_type = M_PCSETOPTS;
		st = (struct stroptions *) bpt->b_rptr;
		bpt->b_wptr += sizeof(struct stroptions);
		if (new_lflags & ICANON) {
			st->so_flags = SO_READOPT|SO_VMODEOFF|SO_TOSTOPON;
			st->so_readopt = RMSGN;
		} else {
			st->so_flags = SO_READOPT|SO_VMODEON|SO_TOSTOPON;
			st->so_readopt = RNORM;
			st->so_vmin = new_vmin;
			st->so_vtime = new_vtime;

			/*
			 * If we have switched from ICANON mode to
			 * "raw", we may need to flush input held in the canon queue.
			 * Lock is held here, so we need to release it
			 * since send_canon assumes it can assert it.
			 * Note that spl should not change in send_canon(), so
			 * assume that caller of adjstrhead() can use its
			 * saved spl in dropping the lock.
			 */
			n = tp->ld_op - tp->ld_canonb;
			if (n != 0) {
				(void)send_canon(RD(q), spl);
			}
		}
		st->so_tostop = (new_lflags & TOSTOP) ? 1 : 0;

		DPRINTF1 ("ld0: adjstrhead() - sending MC_PCSETOPTS\n");
		putnext(RD(q), bpt);
	}
	return (1);
}


/*
 * Process write-side ioctl.
 * Returns: 1 if successful.
 *          0 if failed (message already putbq()).
 */

static int
ld_ioc(q, mp)
	mblk_t *mp;
	queue_t *q;		/* write queue */
{
	struct ld 	*tp;
	struct termio  	*termio;
	struct termios 	*termios;
	struct iocblk 	*iocp;
#ifdef	NO_STRICT_RLMOD
	struct iocblk 	*riocp;
	mblk_t		*dmp, *dmp_cont;
#endif	/* !NO_STRICT_RLMOD */
	struct eucioc	*euciocp;
	spl_t		spl;

	ASSERT_DEBUG(!(q->q_flag & QREADR), "ld0: ld_ioc() wrong queue");
	ASSERT_DEBUG(((mp->b_datap->db_type == M_IOCTL) || (mp->b_datap->db_type == M_PCIOCTL)|| (mp->b_datap->db_type == M_IOCDATA)), "ld0: ld_ioc()");

	iocp = (struct iocblk *)mp->b_rptr;
	tp = (struct ld *)q->q_ptr;

	DPRINTF2 ("ld0: ld_ioc() ioccmd = 0x%x\n", iocp->ioc_cmd);
	switch (iocp->ioc_cmd) {

	/* System V: Set current parameters and special characters.
	 * Uses struct termio.
	 */
	case TCSETA:
	case TCSETAW:
	case TCSETAF:
		DPRINTF2 ("ld0: ld_ioc() - TCSETA[WF]\n");

		/* Non-priority msgs cannot be processed 
		 * if flow control prohibits it.
		 */
		spl = p_lock(&tp->ld_lock, SPLSTR);
		if (!pcmsg(mp->b_datap->db_type) &&
		   ((tp->ld_state & TTSTOP) || !canputnext(q))) {
			DPRINTF1 ("ld0: ld_ioc() - putbq()\n");
			v_lock(&tp->ld_lock, spl);
			putbq(q, mp);
			return (0);
		}
		ASSERT_DEBUG(iocp->ioc_count == sizeof(struct termio), "ld0: ld_ioc()");

		termio  = (struct termio *) mp->b_cont->b_rptr;

		ASSERT_DEBUG(mp->b_cont->b_wptr >= (unsigned char *)&termio[1],
			"ld0: ld_ioc() TCSETA not struct termio");

		if (termio->c_line != 0){
			v_lock(&tp->ld_lock, spl);
			mp->b_datap->db_type = M_IOCNAK;
			iocp->ioc_error = EINVAL;
			iocp->ioc_count = 0;
			qreply(q, mp);
			return (1);
		}
		/* adjust stream head parameters, if necessary */
		if (!adjstrhead(q, mp, spl,
		     (tcflag_t)tp->ld_lflags, 
		     (cc_t)tp->ld_eofc, (cc_t)tp->ld_eol,
		     (tcflag_t)termio->c_lflag, 
		     (cc_t)termio->c_cc[VEOF], (cc_t)termio->c_cc[VEOL])) {
			v_lock(&tp->ld_lock, spl);
			return (0);
		}

		ld0_termio_set(tp, termio);

		v_lock(&tp->ld_lock, spl);

		putnext(q, mp);		/* let the driver send the ACK */
		return (1);

	case TIOCSETP:
		DPRINTF2 ("ld0: ld_ioc() - TIOCSETP\n");
		/* convert message to a termios request with IOLD set*/
		if (sgtty_setp(q, mp, tp)){
			return (1);
		}
		tp->ld_ioctl = iocp->ioc_id;	/* remember */
		/* fall through */
	/* POSIX: Set current parameters and special characters.
	 * Uses struct termios.
	 */
	case TCSETP:
	case TCSETPW:
	case TCSETPF:
		DPRINTF2 ("ld0: ld_ioc() - TCSETP[WF]\n");
		/* Non-priority msgs cannot be processed 
		 * if flow control prohibits it.
		 */
		spl = p_lock(&tp->ld_lock, SPLSTR);
		if (!pcmsg(mp->b_datap->db_type) &&
		   ((tp->ld_state & TTSTOP) || !canputnext(q))) {
			DPRINTF1 ("ld0: ld_ioc() - putbq()\n");
			v_lock(&tp->ld_lock, spl);
			putbq(q, mp);
			return (0);
		}
		ASSERT_DEBUG(iocp->ioc_count == sizeof(struct termios), "ld0: ld_ioc()");

		termios = (struct termios *)mp->b_cont->b_rptr;

		ASSERT_DEBUG(mp->b_cont->b_wptr >= (unsigned char *)&termios[1],
			"ld0: ld_ioc() TCSETP not struct termios");

		/* adjust stream head parameters, if necessary */
		if (!adjstrhead(q, mp, spl,
		  	(tcflag_t)tp->ld_lflags, 
			(cc_t)tp->ld_eofc, (cc_t)tp->ld_eol,
		  	(tcflag_t)termios->c_lflag, 
			(cc_t)termios->c_cc[VEOF], (cc_t)termios->c_cc[VEOL])) {
			v_lock(&tp->ld_lock, spl);
			return (0);
		}

		ld0_termios_set(tp, termios);

		v_lock(&tp->ld_lock, spl);

		putnext(q, mp);		/* let the driver send the ACK */
		return (1);

	case TCFLSH:
		DPRINTF1 ("ld0: ld_ioc() - TCFLSH\n");
		/*
		 * A FLUSHR sent down the write side will get
		 * turned around at the stream end and sent down
		 * the entire read side.  Similarly for a FLUSHW
		 * sent down the read-side.
		 * delay the return of the IOCACK for read side flushing
		 * until the M_FLUSH packet has been seen on the read side.
		 */

		mp->b_datap->db_type = M_IOCACK;  /* assume success */
		iocp->ioc_count = 0;
		iocp->ioc_error = 0;

		switch (*(int *)mp->b_cont->b_rptr) {
		case 1:
			if (!putnextctl1(RD(q), M_FLUSH, FLUSHW)) {
				mp->b_datap->db_type = M_IOCNAK;
				iocp->ioc_error = ENOSR;
			}
			break;
		case 2:
			if (!putnextctl1(RD(q), M_FLUSH, FLUSHW)) {
				mp->b_datap->db_type = M_IOCNAK;
				iocp->ioc_error = ENOSR;
				break;
			}
			/* fall through */
		case 0:
			/*
			 * Have ldin send up the ACK after flushing its queue.
			 */
			spl = p_lock(&tp->ld_lock, SPLSTR);
			ASSERT_DEBUG(tp->ld_flushr==NULL, "concurrent FLUSHR");
			tp->ld_flushr = mp;
			v_lock(&tp->ld_lock, spl);

			if (putnextctl1(q, M_FLUSH, FLUSHR)) {
				DPRINTF1 ("delayed ACK @0x%x\n",mp);
				return (1);
			}

			/*
			 * Nak the ioctl cancel the delay flag
			 */
			spl = p_lock(&tp->ld_lock, SPLSTR);
			tp->ld_flushr = NULL;
			v_lock(&tp->ld_lock, spl);
			mp->b_datap->db_type = M_IOCNAK;
			iocp->ioc_error = ENOSR;
			break;
		default:
			DPRINTF1 ("ld0: ld_ioc() - TCFLSH bad arg = 0x%x\n",
					*(int *)mp->b_cont->b_rptr);
			mp->b_datap->db_type = M_IOCNAK;  /* unknown */
			iocp->ioc_error = EINVAL;
			break;
		}
		qreply(q, mp);
		return (1);

	case TCXONC:
		DPRINTF2 ("ld0: ld_ioc() - TCXONC\n");
		mp->b_datap->db_type = M_IOCACK;  /* assume success */

		spl = p_lock(&tp->ld_lock, SPLSTR);

		switch (*(int *)mp->b_cont->b_rptr) {
		case 0:
			if (!putnextctl1(q, M_PCCTL, MC_STOP)) {
				mp->b_datap->db_type = M_IOCNAK;
				iocp->ioc_error = ENOSR;
			} else {
				tp->ld_state |= TTSTOP;
				STRACE(tp-ttyld, 0, "TCXONC stop ouput");
			}
			break;
		case 1:
			if (!putnextctl1(q, M_PCCTL, MC_START)) {
				mp->b_datap->db_type = M_IOCNAK;
				iocp->ioc_error = ENOSR;
			} else {
				tp->ld_state &= ~TTSTOP;
				qenable(q);
				STRACE(tp-ttyld, 0, "TCXONC start ouput");
			}
			break;
		case 2:
			if (!putnextctl2(q, M_PCCTL, MC_STOPI, (int)tp->ld_stopc)) {
				mp->b_datap->db_type = M_IOCNAK;
				iocp->ioc_error = ENOSR;
			} else {
				tp->ld_state |= TTBLOCK;
				STRACE(tp-ttyld, 0, "TCXONC block input ^S sent");
			}
			break;
		case 3:
			if (!putnextctl2(q, M_PCCTL, MC_STARTI, (int)tp->ld_startc)) {
				mp->b_datap->db_type = M_IOCNAK;
				iocp->ioc_error = ENOSR;
			} else {
#ifdef DEBUG
				if (tp->ld_state & TTSTOP)
					STRACE(tp-ttyld, 0, "TCXONC unblock but stopped!");
#endif
				tp->ld_state &= ~TTBLOCK;
				STRACE(tp-ttyld, 0, "TCXONC unblock input ^Q sent");
			}
			qenable(RD(q));
			break;
		default:
			DPRINTF1 ("ld0: ld_ioc() - TCXONC bad arg = 0x%x\n",
					*(int *)mp->b_cont->b_rptr);
			mp->b_datap->db_type = M_IOCNAK;  /* unknown */
			iocp->ioc_error = EINVAL;
			break;
		}
		v_lock(&tp->ld_lock, spl);

		iocp->ioc_count = 0;
		qreply(q, mp);
		return (1);

	/*
	 * Trap the new code set information.
	 */
	case EUC_WSET:
		if ((euciocp = (struct eucioc *) mp->b_cont->b_rptr) == NULL) {
			iocp->ioc_count = 0;
			iocp->ioc_error = EPROTO;
			mp->b_datap->db_type = M_IOCNAK;
			qreply(q, mp);
			return(1);
		}
		spl = p_lock(&tp->ld_lock, SPLSTR);
		bcopy((caddr_t)euciocp,(caddr_t)&tp->ld_eucwioc,(uint)sizeof(struct eucioc));
		tp->ld_eucwioc.eucw[TTSET0] = '\001';
		tp->ld_eucwioc.scrw[TTSET0] = '\001';
		v_lock(&tp->ld_lock, spl);
#ifdef	NO_STRICT_RLMOD
		if ((dmp = allocb(sizeof(struct iocblk),BPRI_HI)) == NULL) {
			iocp->ioc_error = ENOSPC;
			mp->b_datap->db_type = M_IOCNAK;
		} else {
			if ((dmp_cont = allocb(sizeof(struct eucioc),BPRI_HI))
								      == NULL) {
				freemsg(dmp);
				dmp = NULL;
				mp->b_datap->db_type = M_IOCNAK;
				iocp->ioc_error = ENOSPC;
			} else {
				bcopy((caddr_t)mp->b_cont->b_rptr,
					(caddr_t)dmp_cont->b_wptr,(uint)sizeof(struct eucioc));
				dmp_cont->b_wptr += sizeof(struct eucioc);
				dmp->b_cont = dmp_cont;
				dmp->b_datap->db_type = M_CTL;
				dmp_cont->b_datap->db_type = M_DATA;
				riocp = (struct iocblk *)dmp->b_rptr;
				riocp->ioc_count = iocp->ioc_count;
				riocp->ioc_error = 0;
				riocp->ioc_cmd = EUC_WSET;
				putnext(q, dmp);
				mp->b_datap->db_type = M_IOCACK;
			}
		}
#else	/* !NO_STRICT_RLMOD */
		/*
		 * rlmod does not tolerate M_CTLs it does not understand.  It
		 * will HANGUP the line if it gets the EUC_WSET M_CTL.
		 * Until rlmod is fixed, the code to send it downstream is
		 * ifdeffed out.  Remove the NO_STRICT_RLMOD code when it's fixed.
		 */
		mp->b_datap->db_type = M_IOCACK;
#endif	/* NO_STRICT_RLMOD */
		qreply(q, mp);
		return(1);

	/*
	 * return the current code set information.
	 */
	case EUC_WGET:
		if ((euciocp = (struct eucioc *) mp->b_cont->b_rptr) == NULL) {
			iocp->ioc_count = 0;
			iocp->ioc_error = EPROTO;
			mp->b_datap->db_type = M_IOCNAK;
			qreply(q, mp);
		} else {
			bcopy((caddr_t)&tp->ld_eucwioc,(caddr_t)euciocp,
					(uint)sizeof(struct eucioc));
			iocp->ioc_count = sizeof(struct eucioc);
			iocp->ioc_error = 0;
			mp->b_datap->db_type = M_IOCACK;
			qreply(q, mp);
		}
		return(1);

	case TIOCGETP:
		DPRINTF2 ("ld0: ld_ioc() - TIOCGETP\n");
		iocp->ioc_cmd = TCGETP;
		tp->ld_ioctl = iocp->ioc_id;	/* remember */
		putnext(q, mp);
		return (1);

	case TCGETA:
	case TCGETP:
	case TCSBRK:
		DPRINTF2 ("ld0: ld_ioc() - TCGET[AP] or TCSBRK\n");
		/* Pass to driver and augment the M_IOCACK coming back */
		/*  FALL THROUGH... */
	default:
		/* not a tty ioctl, deliver to module downstream */

		/* Non-priority msgs cannot be processed 
		 * if flow control prohibits it.
		 */

		spl = p_lock(&tp->ld_lock, SPLSTR);
		if (!pcmsg(mp->b_datap->db_type) &&
		   ((tp->ld_state & TTSTOP) || !canputnext(q))) {
			DPRINTF1 ("ld0: ld_ioc() - putbq()\n");
			v_lock(&tp->ld_lock, spl);
			putbq(q, mp);
			return (0);
		}
		v_lock(&tp->ld_lock, spl);

		putnext(q, mp);
		return (1);

	}
}

/*
 * Like putbq() but does not enable the queue when a priority
 * message is put back.
 */

putbqx(q, mp)
	queue_t	*q;		/* read or write queue */
	mblk_t 	*mp;
{
	if (pcmsg(mp->b_datap->db_type))
		insq(q, q->q_first, mp);
	else
		putbq(q, mp);
}	


/*
 * wqrestart() - qenable the write queue
 * Re-enable a queue that has been disabled:
 * Schedule the queue for immediate processing.
 * called as a result of a bufcall().
 */
static void
wqrestart(q)
	register queue_t *q;		/* write queue */
{
	struct	ld	*tp;

	tp = (struct ld *)q->q_ptr;
	ASSERT_DEBUG(!(q->q_flag & QREADR), "ld0: wqrestart() wrong queue");
	if (tp) {
		tp->ld_wbid = NULL;
		qenable(q);
	}
}

/*
 * rqrestart() - qenable the read queue
 * Re-enable a queue that has been disabled:
 * Schedule the queue for immediate processing.
 * called as a result of a bufcall().
 */
static void
rqrestart(q)
	register queue_t *q;		/* read queue */
{
	struct	ld	*tp;

	tp = (struct ld *)q->q_ptr;
	ASSERT_DEBUG((q->q_flag & QREADR), "ld0: rqrestart() wrong queue");
	if (tp) {
		tp->ld_rbid = NULL;
		qenable(q);
	}
}


/*
 * delete a character from a streams message.
 * p must lie between mp->b_dptr->d_base and mp->b_dptr->d_limit.
 * adjust pointer to previous char.
 */
void
qdelchar(mp, p)
	mblk_t	*mp;
	unsigned char	**p;
{
	unsigned char	*s;

	for (s = (*p)+1; s <= mp->b_wptr; s++) {
		*(s-1) = *s;
	}
	(*p)--;
	/*
	 * message may have been zapped by ldsig().
	 */
	if (mp->b_wptr > mp->b_rptr)
		mp->b_wptr--;
	return;
}


/*
 * add a character to a streams message.
 * The character is logicaly inserted before the current character.
 * p must lie between mp->b_dptr->d_base and mp->b_dptr->d_limit.
 * if need be allocate and link in a new message.
 * return pointer to new char.
 * The next character read may be in the continue message.
 */
static void
qaddchar(mp, p, c)
	mblk_t	**mp;
	unsigned char	**p;
	unsigned char	c;
{
	mblk_t		*mpt;
	unsigned char	*s;
	int		n;

	mpt = *mp;
	if (mpt->b_datap->db_lim > mpt->b_wptr) {
		/*
		 * room to fit.
		 */
		for (s = mpt->b_wptr; s > *p; s--) {
			*s = *(s-1);
		}
		**p = c;
		(*p)++;
		mpt->b_wptr++;
		return;
	} 
	/*
	 * allocate a new buffer and insert character.
	 * copy the tail end of this message into the new buffer.
	 */
	n = mpt->b_wptr - *p;
	ASSERT_DEBUG(n, "ld0: qaddchar()");
	if ((mpt = allocb(n, BPRI_MED)) != NULL) {
		bcopy((caddr_t)*p, (caddr_t)mpt->b_wptr, (uint)n);
		mpt->b_wptr += n;
		**p = c;
		mpt->b_cont = (*mp)->b_cont;
		(*mp)->b_cont = mpt;
		*p = mpt->b_rptr;
		*mp = mpt;
	} else {
		cmn_err(CE_WARN, "qaddchar: ran out of buffers");
		/*
		 *+ A high priority streams buffer could not be allocated.
		 *+ Corrective action: configure more high priority buffers.
		 */
	}
}


/*
 * allocate a buffer for echo processing.
 * Try and allocate a buffer of "guess" size. If that fails
 * then try a single byte buffer. If that fails give up.
 */

static mblk_t *
echoc(q, eb, c, guess)
	queue_t	*q;		/* read queue */
	mblk_t	*eb;
	unsigned char	c;
	int	guess;
{
	if (eb == (mblk_t *)NULL) {
		if ((eb = allocb(guess, BPRI_MED)) == (mblk_t *)NULL) {
			if ((eb = allocb(1, BPRI_MED)) == (mblk_t *)NULL) {
				return ((mblk_t *)0);
			}
		}
	} else if (eb->b_wptr >= eb->b_datap->db_lim) {
		putother(q, eb);
		return (echoc(q, (mblk_t *)NULL, c, guess));
	}
	*eb->b_wptr++ = c;
	return (eb);
}


/*
 * Process a M_CTL block for errors according to "termio".
 * Return 1 if all processing is complete (no data).
 */
static int
ld_err(q, mp , type)
	queue_t *q;		/* read queue */
	mblk_t	*mp;
	int	type;
{
	struct ld	*tp;
	mblk_t		*bp;
	unsigned char	*readp;
	spl_t		spl;

	tp = (struct ld *)q->q_ptr;

	/*
	 * If no data, set character to a null.
	 */
	if (mp->b_rptr == mp->b_wptr) {
		ASSERT_DEBUG((mp->b_wptr < mp->b_datap->db_lim), "ld0: ld_err()");
		DPRINTF1 ("ld0: lderr() NULL added\n");
		*mp->b_wptr++ = '\0';
	}

	/*
	 * If INPCK is not set, do not do input parity checking.
	 * So treat input parity errors as if they were M_DATA.
	 */
	if ((type == MC_PARITY_ERR) && !(tp->ld_iflags & INPCK)) {
		mp->b_datap->db_type = M_DATA;
		DPRINTF1 ("ld0: lderr() !INPCK converted to data\n");
		return (0);
	}

	if ((type == MC_PARITY_ERR) && (tp->ld_iflags & IGNPAR)) {
		/* 
		 * If IGNPAR is set characters with framing
		 * and parity errors (other than break)
		 * are ignored.
		 */
		DPRINTF1 ("ld0: lderr() IGNPAR dropped\n");
		freemsg(mp);
		return (1);
	}

	spl = p_lock(&tp->ld_lock, SPLSTR);

	for (bp=mp; bp; bp=bp->b_cont) {
		for (readp=bp->b_rptr; readp<bp->b_wptr;readp++) {
			if ((type == MC_FRAME_ERR) && (*readp == '\0')) {
				if (tp->ld_iflags & IGNBRK) {
					/* 
					 * if IGNBRK is set the break 
					 * condition is ignored, that 
					 * is, not put on the input queue
					 */
					qdelchar(bp, &readp);
					DPRINTF1 ("ld0: lderr() c1\n");
				} else if (tp->ld_iflags&BRKINT) {
					/*
					 * If IGNBRK is not set and BRKINT
					 * is set, the break condition will
					 * generate an interrupt signal
					 * and flush both queues.
					 */
					DPRINTF1 ("ld0: lderr() c2\n");
					ldsig(q, bp, SIGINT, 1); 
				} else if (tp->ld_iflags & PARMRK) {
					qaddchar(&bp, &readp, '\377');
					qaddchar(&bp, &readp, '\0');
					DPRINTF1 ("ld0: lderr() c3\n");
				} else {
					DPRINTF1 ("ld0: lderr() c4\n");
				}
			} else if (tp->ld_iflags & IGNPAR) {
				/* 
				 * If IGNPAR is set characters with framing
				 * and parity errors (other than break)
				 * are ignored.
				 */
				DPRINTF1 ("ld0: lderr() c5\n");
				qdelchar(bp, &readp);
			} else if (tp->ld_iflags & PARMRK) {
				/*
				 * If PARMRK is set then a character
				 * with framing or parity error
				 * (other than a break) ... is read as
				 * the 3 char sequence '\377' '0' X.
				 * If PARMRK is not set .. is read as '\0'.
				 */
				qaddchar(&bp, &readp, '\377');
				qaddchar(&bp, &readp, '\0');
				DPRINTF1 ("ld0: lderr() c6\n");
			} else {
				*readp = '\0';
				DPRINTF1 ("ld0: lderr() c7\n");
			}
		}
	}
	v_lock(&tp->ld_lock, spl);
	/*
	 * If the above action has resulted in a message with no
	 * data then free the message.
	 */
	if (mp->b_rptr == mp->b_wptr) {
		freemsg(mp);
		return (1);
	}
	return (0);
}


/*
 * do_defer tries and resolved actions put off due to
 * lack of resources.
 * This is called from ldin and ldinsrv().
 * If there is still deferred action at the end of all this bufcall.
 * Since this is all BPRI_HI then the failures should be pursued too
 * much.
 */
static void
do_defer(q, tp)
	queue_t	  *q;		/* read queue */
	struct ld *tp;
{

	DPRINTF1 ("ld0: lddefer(q:0x%x, tp:0x%x) ld_defer=0x%x\n",
						q, tp, tp->ld_defer);
	if (tp->ld_defer & LD0_INT) {
		if (putnextctl1(q, M_PCSIG, SIGINT))
			tp->ld_defer &= ~LD0_INT;
	}
	if (tp->ld_defer & LD0_QUIT) {
		if (putnextctl1(q, M_PCSIG, SIGQUIT))
			tp->ld_defer &= ~LD0_QUIT;
	}
	if (tp->ld_defer & LD0_TSTP) {
		if (putnextctl1(q, M_PCSIG, SIGTSTP))
			tp->ld_defer &= ~LD0_TSTP;
	}
	if (tp->ld_defer & LD0_FLUSHW) {
		if (putnextctl1(q, M_FLUSH, FLUSHW))
			tp->ld_defer &= ~LD0_FLUSHW;
	}
	if (tp->ld_defer & LD0_FLUSHR) {
		if (putnextctl1(WR(q), M_FLUSH, FLUSHR))
			tp->ld_defer &= ~LD0_FLUSHR;
	}
	if (tp->ld_defer & LD0_START) {
		if (putnextctl1(WR(q), M_PCCTL, MC_START))
			tp->ld_defer &= ~(LD0_START|LD0_STOP);
	}
	if (tp->ld_defer & LD0_STOP) {
		if (putnextctl1(WR(q), M_PCCTL, MC_STOP))
			tp->ld_defer &= ~LD0_STOP;
	}
	/*
	 * arrange for the server to be called when a single byte
	 * buffer is freed.
	 */
	if (tp->ld_defer && (tp->ld_rbid == NULL)) {
		tp->ld_rbid = bufcall(1, BPRI_HI,
				       (int (*)())rqrestart, (long)q);
	}
}


/*
 * assign the values from a termio structure to the ld structure.
 */

ld0_termio_set(tp, termio)
register struct	ld	*tp;
register struct termio	*termio;
{
	/* Force IEXTEN on to allow System V extensions to POSIX */
	tp->ld_lflags = termio->c_lflag | IEXTEN;
	tp->ld_oflags = termio->c_oflag;
	tp->ld_iflags = termio->c_iflag;
	tp->ld_cflags = termio->c_cflag;

	tp->ld_intrc  = (ushort) termio->c_cc[VINTR];
	tp->ld_quitc  = (ushort) termio->c_cc[VQUIT];
	tp->ld_erase  = (ushort) termio->c_cc[VERASE];
	tp->ld_kill   = (ushort) termio->c_cc[VKILL];
	tp->ld_eofc   = (ushort) termio->c_cc[VEOF];
	tp->ld_eol    = (ushort) termio->c_cc[VEOL];
	tp->ld_eol2   = (ushort) termio->c_cc[VEOL2];
	tp->ld_swtchc = (ushort) termio->c_cc[VSWTCH];
}

/*
 * assign the values from a termios structure to the ld structure.
 */

ld0_termios_set(tp, termios)
register struct	ld	*tp;
register struct termios	*termios;
{
	tp->ld_lflags = termios->c_lflag;
	tp->ld_oflags = termios->c_oflag;
	tp->ld_iflags = termios->c_iflag;
	tp->ld_cflags = termios->c_cflag;

	tp->ld_intrc  = termios->c_cc[VINTR];
	tp->ld_quitc  = termios->c_cc[VQUIT];
	tp->ld_erase  = termios->c_cc[VERASE];
	tp->ld_kill   = termios->c_cc[VKILL];
	tp->ld_eofc   = termios->c_cc[VEOF];
	tp->ld_eol    = termios->c_cc[VEOL];
	tp->ld_eol2   = termios->c_cc[VEOL2];
	tp->ld_swtchc = termios->c_cc[VSWTCH];
	tp->ld_startc = termios->c_cc[VSTART];
	tp->ld_stopc  = termios->c_cc[VSTOP];
	tp->ld_suspc  = termios->c_cc[VSUSP];
}


/*
 * initialise the termios structure for device "dev".
 * if it is the console then keep it in a "sane" mode
 */

termios_init(termios, dev)
struct termios	*termios;
dev_t		dev;
{
	extern	char	cons_kill;
	extern	char	cons_erase;
	extern	char	cons_intr;

	termios->c_iflag = 0;
	termios->c_oflag = 0;		   
	termios->c_cflag = CREAD;		   
	termios->c_lflag = IEXTEN;

	termios->c_cc[VERASE] = CERASE;		  
	termios->c_cc[VKILL]  = CKILL;		   
	termios->c_cc[VINTR]  = CINTR;		   
	termios->c_cc[VQUIT]  = CQUIT;		   
	termios->c_cc[VSTART] = CSTART;
	termios->c_cc[VSTOP]  = CSTOP;
	termios->c_cc[VEOF]   = CEOF;		   
	termios->c_cc[VEOL]   = CEOL;		   
	termios->c_cc[VEOL2]  = CEOL2;		   
	termios->c_cc[VSUSP]  = CSUSP;
	termios->c_cc[VSWTCH] = CNSWTCH;

	if (dev == cons_devnum) {
		if (cons_kill)
			termios->c_cc[VKILL] = cons_kill;		  
		if (cons_erase)
			termios->c_cc[VERASE] = cons_erase;		  
		if (cons_intr)
			termios->c_cc[VINTR] = cons_intr;		  
		termios->c_iflag |= ICRNL | IXON | IXOFF | BRKINT | IGNPAR;
		termios->c_oflag |= OPOST | ONLCR | TAB3;
		termios->c_lflag |= ISIG | ICANON | ECHO | ECHOK;
	}
}


/*
 * Fill in a termios structure from the contents of the ld structure.
 * Callers of this routine should hold tp->ld_lock.
 */
ld0_termios_get(tp, termios)
register struct ld *tp;
register struct termios *termios;
{
	termios->c_lflag = tp->ld_lflags;
	termios->c_oflag = tp->ld_oflags;
	termios->c_iflag = tp->ld_iflags;
	/* the c_cflag field is set by the driver */
	termios->c_cc[VINTR] = tp->ld_intrc;
	termios->c_cc[VQUIT] = tp->ld_quitc;
	termios->c_cc[VERASE] = tp->ld_erase;
	termios->c_cc[VKILL] = tp->ld_kill;
	termios->c_cc[VEOF] = tp->ld_eofc;
	termios->c_cc[VEOL] = tp->ld_eol;
	termios->c_cc[VEOL2] = tp->ld_eol2;
	termios->c_cc[VSWTCH] = tp->ld_swtchc;
	termios->c_cc[VSTART] = tp->ld_startc;
	termios->c_cc[VSTOP] = tp->ld_stopc;
	termios->c_cc[VSUSP] = tp->ld_suspc;
}
