/* $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 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 "$Header: timod.c 1.10 89/08/07 $"

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

/*
 * Transport Interface Library cooperating module - issue 1
 */

#include "sys/param.h"
#include "sys/types.h"
#include "sys/sysmacros.h"
#include "sys/mutex.h"
#include "sys/intctl.h"
#include "sys/session.h"
#include "sys/stream.h"
#include "sys/stropts.h"
#include "sys/tihdr.h"
#include "sys/timod.h"
#include "sys/tiuser.h"
#include "sys/debug.h"
#include "sys/signal.h"
#include "sys/user.h"
#include "sys/errno.h"
#include "sys/strid.h"
#include "sys/tim.h"


extern struct tim_tim tim_tim[];
extern int tim_cnt;
extern lock_t tim_lock;
extern nulldev();


/* stream data structure definitions */
int timodopen(), timodclose(), timodrput(), timodwput();
static struct module_info timod_info = {STRID_TIMOD, "timod", 0, INFPSZ, 4096, 1024};
static struct qinit timodrinit = {timodrput, NULL, timodopen, timodclose, nulldev, &timod_info, NULL};
static struct qinit timodwinit = {timodwput, NULL, timodopen, timodclose, nulldev, &timod_info, NULL};
struct streamtab timodinfo = {&timodrinit, &timodwinit, NULL, NULL };



/*
 * state transition table for TI interface 
 */
#define nr	127		/* not reachable */

char ti_statetbl[TE_NOEVENTS][TS_NOSTATES] = {
				/* STATES */
/* 0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16 */

 { 1, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr},
 {nr, nr, nr,  2, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr},
 {nr, nr, nr,  4, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr},
 {nr,  3, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr},
 {nr, nr, nr, nr,  3, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr},
 {nr,  0,  3, nr,  3,  3, nr, nr,  7, nr, nr, nr,  6,  7,  9, 10, 11},
 {nr, nr,  0, nr, nr,  6, nr, nr, nr, nr, nr, nr,  3, nr,  3,  3,  3},
 {nr, nr, nr, nr, nr, nr, nr, nr,  9, nr, nr, nr, nr,  3, nr, nr, nr},
 {nr, nr, nr, nr, nr, nr, nr, nr,  3, nr, nr, nr, nr,  3, nr, nr, nr},
 {nr, nr, nr, nr, nr, nr, nr, nr,  7, nr, nr, nr, nr,  7, nr, nr, nr},
 {nr, nr, nr,  5, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr},
 {nr, nr, nr, nr, nr, nr, nr,  8, nr, nr, nr, nr, nr, nr, nr, nr, nr},
 {nr, nr, nr, nr, nr, nr, 12, 13, nr, 14, 15, 16, nr, nr, nr, nr, nr},
 {nr, nr, nr, nr, nr, nr, nr, nr, nr,  9, nr, 11, nr, nr, nr, nr, nr},
 {nr, nr, nr, nr, nr, nr, nr, nr, nr,  9, nr, 11, nr, nr, nr, nr, nr},
 {nr, nr, nr, nr, nr, nr, nr, nr, nr, 10, nr,  3, nr, nr, nr, nr, nr},
 {nr, nr, nr,  7, nr, nr, nr,  7, nr, nr, nr, nr, nr, nr, nr, nr, nr},
 {nr, nr, nr, nr, nr, nr,  9, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr},
 {nr, nr, nr, nr, nr, nr, nr, nr, nr,  9, 10, nr, nr, nr, nr, nr, nr},
 {nr, nr, nr, nr, nr, nr, nr, nr, nr,  9, 10, nr, nr, nr, nr, nr, nr},
 {nr, nr, nr, nr, nr, nr, nr, nr, nr, 11,  3, nr, nr, nr, nr, nr, nr},
 {nr, nr, nr, nr, nr, nr,  3, nr, nr,  3,  3,  3, nr, nr, nr, nr, nr},
 {nr, nr, nr, nr, nr, nr, nr,  3, nr, nr, nr, nr, nr, nr, nr, nr, nr},
 {nr, nr, nr, nr, nr, nr, nr,  7, nr, nr, nr, nr, nr, nr, nr, nr, nr},
 {nr, nr, nr,  9, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr},
 {nr, nr, nr,  3, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr},
 {nr, nr, nr,  3, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr},
 {nr, nr, nr,  3, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr, nr},
};


/*
 * Called from main at boot time.
 */
void
timod_init()
{
	/*
	 * This lock mutexes the tim_tim table.
	 */
	init_lock(&tim_lock);
}

/*
 * timodopen - open routine gets called when the
 *	       module gets pushed onto the stream.
 */

/*ARGSUSED*/
timodopen(q, dev, flag, sflag)
	register queue_t *q;
	dev_t	dev;
{
	register struct tim_tim *timptr;
	register spl_t spl;

#ifdef lint
	lint_ref_int((int)ti_statetbl);
	/* 
	 * This table is actually used in protocol providers
	 * which are layered products and hence lint cannot
	 * find them.
	 */
#endif /* lint */

	ASSERT_DEBUG(q != NULL, "timodopen: q is null");

	spl = p_lock(&tim_lock, TIM_SPL);

	/* If not first open then just return */
	if (q->q_ptr) {
		v_lock(&tim_lock, spl);
		return(1);
	}

	/* First open */
	for (timptr=tim_tim; timptr < &tim_tim[tim_cnt]; timptr++) {
		if (!(timptr->tim_flags & USED)) {
			break;
		}
	}
	if (timptr >= &tim_tim[tim_cnt]) {
		u.u_error = ENOSPC;
		v_lock(&tim_lock, spl);
		return(OPENFAIL);
	}

	timptr->tim_flags = USED;
	q->q_ptr = (caddr_t)timptr;
	v_lock(&tim_lock, spl);

	/* Since the procedures are still not enabled, the following is safe */

	timptr->tim_rdq = q;
	timptr->tim_iocsave = NULL;
	WR(q)->q_ptr = (caddr_t)timptr;
	init_lock(&timptr->tim_lock);

	/* And now we are all set to enable the procedures */
	enable_procs(q);
	return(1);
}


/*
 * timodclose - This routine gets called when the module
 *              gets popped off of the stream.
 */

timodclose(q)
	register queue_t *q;
{
	register struct tim_tim *timptr;
	register spl_t spl;
	register mblk_t *mp;

	ASSERT_DEBUG(q != NULL, "timodclose: q is null");

	/* Turn off all service/put procs. */
	disable_procs(q);

	timptr = (struct tim_tim *)q->q_ptr;
	ASSERT_DEBUG(timptr != NULL, "timodclose: timptr is null");
	mp = timptr->tim_iocsave;

	spl = p_lock(&tim_lock, TIM_SPL);
	timptr->tim_flags = 0;
	q->q_ptr = NULL;
	v_lock(&tim_lock, spl);

	if (mp) {
		freemsg(mp);
	}
}


/*
 * timodrput - Module read queue put procedure.
 *             This is called from the module or
 *	       driver downstream.
 */

timodrput(q, mp)
	register queue_t *q;
	register mblk_t *mp;
{
	register union T_primitives *pptr;
	register struct tim_tim *timptr;
	register struct iocblk *iocbp;
	register spl_t spl, splstr;
	register mblk_t *savemp;

	ASSERT_DEBUG(q != NULL, "timodrput: q is null");

	timptr = (struct tim_tim *)q->q_ptr;

	ASSERT_DEBUG(timptr != NULL, "timodrput: timptr is null");

	switch(mp->b_datap->db_type) {

	default:
		putnext(q, mp);
		return;

	case M_PROTO:
	case M_PCPROTO:
		/* asset checks if there is enough data to determine type */
		ASSERT_DEBUG((mp->b_wptr - mp->b_rptr) >= sizeof(long), "timodrput: M_PCPROTO msg size wrong");

		pptr = (union T_primitives *)mp->b_rptr;

		switch (pptr->type) {

		default:
			putnext(q, mp);
			return;

		case T_ERROR_ACK:
			ASSERT_DEBUG((mp->b_wptr - mp->b_rptr) == sizeof(struct T_error_ack),
				     "timodrput: T_ERROR_ACK msg size wrong");

			spl = p_lock(&timptr->tim_lock, TIM_SPL);
		        if (timptr->tim_flags & WAITIOCACK) {

				ASSERT_DEBUG(timptr->tim_iocsave != NULL, "timodrput: T_ERROR_ACK iocsave is null");
				if (pptr->error_ack.ERROR_prim != *(long *)timptr->tim_iocsave->b_cont->b_rptr) {
					v_lock(&timptr->tim_lock, spl);
					putnext(q, mp);
					return;
				}

				switch (pptr->error_ack.ERROR_prim) {

				case T_INFO_REQ:
				case T_OPTMGMT_REQ:
				case T_BIND_REQ:
				case T_UNBIND_REQ:
					/* get saved ioctl msg and set values */
					savemp = timptr->tim_iocsave;
					timptr->tim_iocsave = NULL;
					timptr->tim_flags &= ~WAITIOCACK;
					v_lock(&timptr->tim_lock, spl);

					iocbp = (struct iocblk *)savemp->b_rptr;
					iocbp->ioc_error = 0;
					iocbp->ioc_rval = pptr->error_ack.TLI_error;
					if (iocbp->ioc_rval == TSYSERR)
						iocbp->ioc_rval |= pptr->error_ack.UNIX_error << 8;
					savemp->b_datap->db_type = M_IOCACK;
					putnext(q, savemp);
					freemsg(mp);
					return;
				}
			} 
			v_lock(&timptr->tim_lock, spl);
			putnext(q, mp);
			return;

		case T_OK_ACK:
			spl = p_lock(&timptr->tim_lock, TIM_SPL);
			if (timptr->tim_flags & WAITIOCACK) {
				ASSERT_DEBUG(timptr->tim_iocsave != NULL, "timodrput: T_OK_ACK iocsave is null");
				if (pptr->ok_ack.CORRECT_prim != *(long *)timptr->tim_iocsave->b_cont->b_rptr) {
					v_lock(&timptr->tim_lock, spl);
					putnext(q, mp);
					return;
				}
				savemp = timptr->tim_iocsave;
				timptr->tim_iocsave = NULL;
				timptr->tim_flags &= ~WAITIOCACK;
				v_lock(&timptr->tim_lock, spl);
				goto out;
			}
			v_lock(&timptr->tim_lock, spl);
			putnext(q, mp);
			return;
		case T_BIND_ACK:
			spl = p_lock(&timptr->tim_lock, TIM_SPL);
			if (timptr->tim_flags & WAITIOCACK) {
				ASSERT_DEBUG(timptr->tim_iocsave != NULL, "timodrput: T_BIND_ACK iocsave is null");
				if (*(long *)timptr->tim_iocsave->b_cont->b_rptr != T_BIND_REQ) {
					v_lock(&timptr->tim_lock, spl);
					putnext(q, mp);
					return;
				}
				savemp = timptr->tim_iocsave;
				timptr->tim_iocsave = NULL;
				timptr->tim_flags &= ~WAITIOCACK;
				v_lock(&timptr->tim_lock, spl);
				goto out;
			}
			v_lock(&timptr->tim_lock, spl);
			putnext(q, mp);
			return;
		case T_OPTMGMT_ACK:
			spl = p_lock(&timptr->tim_lock, TIM_SPL);
			if (timptr->tim_flags & WAITIOCACK) {
				ASSERT_DEBUG(timptr->tim_iocsave != NULL, "timodrput: T_OPTMGT_ACK iocsave is null");
				if (*(long *)timptr->tim_iocsave->b_cont->b_rptr != T_OPTMGMT_REQ) {
					v_lock(&timptr->tim_lock, spl);
					putnext(q, mp);
					return;
				}
				savemp = timptr->tim_iocsave;
				timptr->tim_iocsave = NULL;
				timptr->tim_flags &= ~WAITIOCACK;
				v_lock(&timptr->tim_lock, spl);
				goto out;
			}
			v_lock(&timptr->tim_lock, spl);
			putnext(q, mp);
			return;
		case T_INFO_ACK:
			spl = p_lock(&timptr->tim_lock, TIM_SPL);
			if (timptr->tim_flags & WAITIOCACK) {
				ASSERT_DEBUG(timptr->tim_iocsave != NULL, "timodrput: T_INFO_ACK iocsave is null");
				if (*(long *)timptr->tim_iocsave->b_cont->b_rptr != T_INFO_REQ) {
					v_lock(&timptr->tim_lock, spl);
					putnext(q, mp);
					return;
				}
				/* 
				 * The max packet size of this queue is dynamically
				 * changed here.  Since this is accessed from its 
				 * neighbours, the stream lock needs to be acquired.
				 */
				splstr = p_lock(&q->q_str->sd_mutex, SPLSTR);
				q->q_maxpsz = pptr->info_ack.TIDU_size;
				OTHERQ(q)->q_maxpsz = pptr->info_ack.TIDU_size;
				v_lock(&q->q_str->sd_mutex, splstr);

				savemp = timptr->tim_iocsave;
				timptr->tim_iocsave = NULL;
				timptr->tim_flags &= ~WAITIOCACK;
				v_lock(&timptr->tim_lock, spl);
				goto out;
			}
			v_lock(&timptr->tim_lock, spl);
			putnext(q, mp);
			return;
					
out:			/* the lock has already been released */
			iocbp = (struct iocblk *)savemp->b_rptr;
			ASSERT_DEBUG(savemp->b_datap != NULL, "timodrput: savemp->b_datap is null");
			savemp->b_datap->db_type = M_IOCACK;
			mp->b_datap->db_type = M_DATA;
			freemsg(savemp->b_cont);
			savemp->b_cont = mp;
			iocbp->ioc_error = 0;
			iocbp->ioc_rval = 0;
			iocbp->ioc_count = mp->b_wptr - mp->b_rptr;
			putnext(q, savemp);
			return;
		}
	}
}


/*
 * timodwput - Module write queue put procedure.
 *             This is called from the module or
 *	       stream head upstream.
 */

timodwput(q, mp)
register queue_t *q;
register mblk_t *mp;
{
	register struct tim_tim *timptr;
 	struct iocblk *iocbp = (struct iocblk *)mp->b_rptr;
	register mblk_t *tmp;
	register spl_t spl;

	ASSERT_DEBUG(q != NULL, "timodwput: q is null");

	timptr = (struct tim_tim *)q->q_ptr;

	ASSERT_DEBUG(timptr != NULL, "timodwput: timptr is null");


	switch(mp->b_datap->db_type) {

	default:
		putnext(q, mp);
		return;
	
	case M_IOCTL:
		ASSERT_DEBUG((mp->b_wptr - mp->b_rptr) == sizeof(struct iocblk), 
			     "timodwput: M_IOCTL msg size is wrong");

		spl = p_lock(&timptr->tim_lock, TIM_SPL);
		if (timptr->tim_flags & WAITIOCACK) {
			v_lock(&timptr->tim_lock, spl);
			mp->b_datap->db_type = M_IOCNAK;
			iocbp->ioc_error = EPROTO;
			qreply(q, mp);
			return;
		}

		switch (iocbp->ioc_cmd) {

		default:
			v_lock(&timptr->tim_lock, spl);
			putnext(q, mp);
			return;

		case TI_BIND:
		case TI_UNBIND:
		case TI_GETINFO:
		case TI_OPTMGMT:

			if (mp->b_cont == NULL) {
				v_lock(&timptr->tim_lock, spl);
				mp->b_datap->db_type = M_IOCNAK;
				iocbp->ioc_error = EINVAL;
			 	qreply(q, mp);
				return;
			}
			if (!pullupmsg(mp->b_cont, -1)) {
				v_lock(&timptr->tim_lock, spl);
				mp->b_datap->db_type = M_IOCNAK;
				iocbp->ioc_error = EAGAIN;
			 	qreply(q, mp);
				return;
			}	

			if ((tmp = copymsg(mp->b_cont)) == NULL) {
				v_lock(&timptr->tim_lock, spl);
				mp->b_datap->db_type = M_IOCNAK;
				iocbp->ioc_error = EAGAIN;
			 	qreply(q, mp);
				return;
			}

			timptr->tim_iocsave = mp;
			timptr->tim_flags |= WAITIOCACK;
			v_lock(&timptr->tim_lock, spl);

			if (iocbp->ioc_cmd == TI_GETINFO)
				tmp->b_datap->db_type = M_PCPROTO;
			else
				tmp->b_datap->db_type = M_PROTO;
	
			putnext(q, tmp);
			return;
		}
	}
}
