/*	Copyright (c) 1990 UNIX System Laboratories, Inc.	*/
/*	Copyright (c) 1984, 1986, 1987, 1988, 1989, 1990 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF     	*/
/*	UNIX System Laboratories, Inc.                     	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

/*	Copyright (c) 1985, 1986, 1987, 1989, 1990  Intel Corporation	*/
/*	All Rights Reserved	*/

/*	INTEL CORPORATION PROPRIETARY INFORMATION	*/

/*	This software is supplied to AT & T under the terms of a license   */ 
/*	agreement with Intel Corporation and may not be copied nor         */
/*	disclosed except in accordance with the terms of that agreement.   */	

#ident	"@(#)mbus:uts/i386/io/enetdrv/enetrdwri.c	1.3.2.1"

#ident "@(#)enetrdwri.c  $SV_enet SV-Eval01 - 06/25/90$"

/*
 *   enetrdwri.c: put and service routines for enet driver.  No direct
 *                board access is made in this file.
 */
/*
 *  Modifications:
 *	I000	9/22/87		DF	Intel
 *		Moved MAX_PEND to space.c.
 *	I001	9/24/87		DF	Intel
 *		Added code to throttle wsrv() when rbs is running out.
 *	I002	04/13/88	RJS/SLH		Intel
 *		Made DATAGRAM_SIZE configurable by user.
 *	I003	04/13/88	RJS/SLH		Intel
 *		Made U_DEFAULTS configurable by user.
 *	I004	07/05/88	RJS		Intel
 *		Rewrote enetrsrv adding code for servicing queued disconnect
 *		indications.
 *	I005	09/18/88	RJS		Intel
 *		No reason for wakeup call.
 *	I006	07/11/89	rjf		Intel
 *		Made lint fixes.
 *	I007	09/13/89	rjf		Intel
 *		Added C_DISCON check.
 */

#define DEBUG 1
#include "sys/enet.h"

#ifdef NS
void	ns_req();
#endif
void	nmf_req();

extern endpoint		enet_endpoints[];
extern int		enet_n_endpoints;
extern struct enetboard	enet_boards[];
extern int		enet_max_data_rq;
extern int		enet_datagram_size;		/* I002 */
extern opts		enet_u_defaults;		/* I003 */
extern int		enet_max_pend; 		/* I000 */
extern int		enet_rbs_used;
extern int		enet_rb_hiwat;
extern int		enet_rb_hi;

extern int		enet_debug;

/*
 * TPI state transition matrix, defined in the STREAMS system
 * module timod
 */
extern char ti_statetbl[TE_NOEVENTS][TS_NOSTATES];

extern void enetreset();

/* Routine to pick up input processing */

extern	void	enet_buf_post();
extern	void	enet_data_ind_complete();
extern	void	enet_discon_ind_continue();


/*
 * Put functions that process the TPI primitives generated by the
 * transport user.
 *
 *
 * Each of these functions have the same three arguments:
 *
 *	(ep, mptr, tptr)
 *
 *		ep:	pointer to the endpoint data structure
 *		mptr:	pointer to the message to be processed, which
 *				will always be of type M_PROTO or
 *				M_PCPROTO or M_DATA
 *		tptr:	pointer to the TLI structure to be processed
 *
 * Each routine returns 0 or a TLI error code.
 *
 * If a TLI error code is returned, the state will be updated via 
 * TE_ERROR_ACK and an error_ack sent with the appropriate error code.
 *
 * On success, it is the responsibility of each function to:
 *   update the state of the endpoint, according to the state transition 
 *     table ti_statetbl[][] and/or the definition of TPI.
 *   ensure that any messages which require it are ack'd appropriately
 *   enqueue any messages which need flow control
 */
int p_conn_req(), p_conn_res(), p_discon_req(), p_data_req(),
	   p_exdata_req(), p_unitdata_req(),
	   p_unsupported(), p_info_req(), p_bind_req(), p_unbind_req(),
	   p_optmgmt_req();

/*
 * This function is indexed by PRIM_type field (T_xxx value), as given
 * in the sys/tihdr.h file.
 */
int (*ti_p_fcntbl[])() = {
		p_conn_req, p_conn_res, p_discon_req, p_data_req,
		p_exdata_req, p_info_req, p_bind_req, p_unbind_req,
		p_unitdata_req, p_optmgmt_req, p_unsupported,
		};

/*
 * Service functions that process the TPI primitives generated by the
 * transport user.
 *
 *
 * Each of these functions have the same two arguments:
 *
 *	(ep, mptr)
 *
 *		ep:	pointer to the endpoint data structure
 *		mptr:	pointer to the message to be processed, which
 *				will always be of type M_PROTO or
 *				M_PCPROTO or M_DATA
 *
 * If the function returns a non-zero value, the connection is assumed
 * to have broken, and no more messages will be allowed on this stream.
 *
 * Regardless of the function return value, it is the responsibility of
 *	the function itself to update the state of the endpoint,
 *	according to the state transition table ti_statetbl[][] and/or
 *	the definition of TPI.
 */
bool s_data_req(), s_discon_req(), s_unitdata_req();

/*
 * This function is indexed by PRIM_type field (T_xxx value), as given
 * in the sys/tihdr.h file.
 */
bool (*ti_s_fcntbl[])() = {
		NULL, NULL, s_discon_req, s_data_req,
		NULL, NULL, NULL, NULL, s_unitdata_req, NULL, NULL,
		};

void check_msg();

/*
 * enetwput
 *				(user)
 * Write put procedure:	        ( vv )
 *				(enet)
 *
 * This function:
 *
 *	1. Performs basic syntax checking, to weed out bad messages.
 *	2. Performs basic semantic checking, to weed out illegal
 *		messages.
 *	3. Carries out certain functions that are purely TPI or
 *		STREAMS related.
 */
void
enetwput(wr_q, mptr)
queue_t *wr_q;
register mblk_t *mptr;
{
	register endpoint *ep;

	ep = (endpoint *)wr_q->q_ptr;
	switch(mptr->b_datap->db_type) {
	case M_FLUSH:
		/*
		 * Process these messages immediately.
		 */
		if(*mptr->b_rptr & FLUSHW) {
			flushq(wr_q, FLUSHDATA);
			if(ep->flow)
				freemsg(ep->flow);
			ep->flow = NULL;
		}
		if(*mptr->b_rptr & FLUSHR) {
			flushq(RD(wr_q), FLUSHDATA);
			*mptr->b_rptr &= ~FLUSHW;
			qreply(wr_q, mptr);
		}
		else
			freemsg(mptr);
		break;
	case M_DATA:
		if(ep == &enet_endpoints[0]) {
			if(!(enet_boards[ep->bnum].state & RESET)) {
				DEBUGP(DEB_ERROR,(CE_CONT, "enetwput: Admin download to non-RESET board\n"));
				enet_pferr(ep);
			}
			if(iNA961_codeblock(ep->bnum, mptr->b_rptr))
				enet_pferr(ep);
			freemsg(mptr);
			break;
		}
	case M_PROTO:
	case M_PCPROTO:
		if(ep->str_state & C_ERROR) {
			/*
			 * A M_ERROR was previously issued for this
			 * stream. Ignore all messages.
			 */
			DEBUGP(DEB_ERROR,(CE_CONT, "enetwput: stream is M_ERROR'd\n"));
			freemsg(mptr);
			break;
		}
		switch(mptr->b_datap->db_type) {
		case M_DATA:
			/*
			 * There must be at least one byte of data in
			 * the message.
			 *
			 * If not, simply ignore the message.
			 */
			if(msgdsize(mptr) == 0) {
				freemsg(mptr);
				break;
			}
			/*
			 * Check (and possibly process) the message.
			 */
			check_msg(ep, mptr, T_DATA_REQ);
			break;
		case M_PROTO:
		case M_PCPROTO:
			/*
			 * Check (and possibly process) the message.
			 */
			check_msg(ep, mptr, (int)((union T_primitives *)mptr->b_rptr)->type);
			break;
		}
		break;
	case M_IOCTL:
		ioctl_check(ep, mptr, wr_q);
		break;
	default:
		/*
		 * This is an unexpected STREAMS message:
		 * 	Throw it away.
		 */
		freemsg(mptr);
		break;
	}
}

/*
 * check_msg
 *
 * This function is called from the wput function, to filter out
 * bad messages, and dispatch to the appropriate put routine.
 */
void
check_msg(ep, mptr, type)
endpoint *ep;
mblk_t *mptr;
int type;
{
	int event, next, error;

	/*
	 * Step 1:  See if the message is legal in the current TLI state.
	 */
	switch(type) {
	case T_CONN_REQ:
		event = TE_CONN_REQ;		break;
	case T_CONN_RES:
		event = TE_CONN_RES;		break;
	case T_DISCON_REQ:
		event = TE_DISCON_REQ;		break;
	case T_DATA_REQ:
		event = TE_DATA_REQ;		break;
	case T_EXDATA_REQ:
		event = TE_EXDATA_REQ;		break;
	case T_INFO_REQ:
		p_info_req(ep, mptr);		return;
	case T_BIND_REQ:
		event = TE_BIND_REQ;		break;
	case T_UNBIND_REQ:
		event = TE_UNBIND_REQ;		break;
	case T_UNITDATA_REQ:
		event = TE_UNITDATA_REQ;	break;
	case T_OPTMGMT_REQ:
		event = TE_OPTMGMT_REQ;		break;
#ifdef NS
	case NS_ADD_NAME_REQ:
	case NS_DEL_NAME_REQ:
	case NS_GET_VAL_REQ:
	case NS_CHNG_VAL_REQ:
	case NS_DEL_PROP_REQ:
	case NS_GET_NAME_REQ:
	case NS_GET_SPOK_REQ:
	case NS_LIST_TAB_REQ:
		ns_req(ep, mptr);		return;
#endif
	case NMF_READ_O_REQ:
	case NMF_RDCLR_O_REQ:
	case NMF_SET_O_REQ:
		nmf_req(ep, mptr);		return;
	default:
		/*
		 * A message of unknown type.
		 */
		enet_pterr(ep, mptr, TSYSERR, EINVAL);
		return;
	}
	if((next = ti_statetbl[event][ep->tli_state]) == TS_INVALID) {
		if((event == TE_DATA_REQ) || (event == TE_EXDATA_REQ)) {
			/*
			 * Out-of-state data transfer errors are either
			 * ignored or are fatal.
			 */
			DEBUGP(DEB_ERROR,(CE_CONT, "check_msg: out-of-state M_DATA(%x, %x)\n",
					ep, mptr));
			DEBUGP(DEB_ERROR,(CE_CONT, "check_msg: current state = %x event = %x\n",
					ep->tli_state, event));
			freemsg(mptr);
			if(ep->tli_state != TS_IDLE)
				enet_pferr(ep);
			return;
		}
		else {
			/*
			 * We may have gotten (and passed to the user)
			 * a conn_ind, but he hasn't seen it yet (if he
			 * hasn't done a t_listen yet).  So optmgmt req's
			 * are really still legal
			 */
			if((event == TE_OPTMGMT_REQ) &&
				(ep->tli_state == TS_WRES_CIND))
				next = TS_WRES_CIND;
			else {
				DEBUGP(DEB_ERROR,(CE_CONT, "check_msg: out-of-state non-M_DATA(%x, %x)\n",
						ep, mptr));
				switch(type) {
				case T_BIND_REQ:
				case T_UNBIND_REQ:
				case T_OPTMGMT_REQ:
					enet_pterr(ep, mptr, TOUTSTATE, 0);
					break;
				case T_CONN_REQ:
				case T_CONN_RES:
				case T_DISCON_REQ:
					enet_pterr(ep, mptr,
						   TOUTSTATE|HIGH_PRI, 0);
					break;
				}
				return;
			}
		}
	}
	ep->tli_state = next;
	/*
	 * Step 2: Call the appropriate put procedure
	 */
	if(error = (*ti_p_fcntbl[type])(ep, mptr, mptr->b_rptr)) {
		ep->tli_state = ti_statetbl[TE_ERROR_ACK][ep->tli_state];
		switch(error&~HIGH_PRI) {
		case TSYSERR:
			enet_pterr(ep, mptr, error, ENOSR);
			break;
		case MIPERR:
			enet_pterr(ep, mptr, error, EIO);
			break;
		default:
			enet_pterr(ep, mptr, error, 0);
			break;
		}
	}
	return;
}

/*
 * p_unsupported
 *
 * A TLI message has come in that is not supported by this version of
 * enet. Currently, this applies to:
 *
 *	T_ORDREL_REQ	Orderly release
 */
/*ARGSUSED*/
int
p_unsupported(ep, mptr, body)
endpoint *ep;
mblk_t *mptr;
union T_primitives *body;
{
	DEBUGP(DEB_CALL,(CE_CONT, "p_unsupported()\n"));
	DEBUGP(DEB_CALL,(CE_CONT, "p_unsuppoted => TNOTSUPPORT\n"));
	return(TNOTSUPPORT);
}

/*
 * p_conn_req
 *
 * Transport User Connection Request: T_CONN_REQ
 *
 * Input State: TS_IDLE --> TS_WACK_CREQ
 *
 * Output state: TS_WCON_CREQ
 */
int
p_conn_req(ep, mptr, conn)
endpoint *ep;
mblk_t *mptr;
register struct T_conn_req *conn;
{
	register int error;

	DEBUGP(DEB_CALL,(CE_CONT, "p_conn_req()\n"));
	if(!(ep->options & OPT_COTS)) {
		DEBUGP(DEB_ERROR,(CE_CONT, "p_conn_req: not a connection service\n"));
		DEBUGP(DEB_CALL,(CE_CONT, "p_conn_req => TNOTSUPPORT\n"));
		return(TNOTSUPPORT|HIGH_PRI);
	}
	/*
	 * Make sure there is not too much options
	 */
	if(conn->OPT_length != 0) {
		cmn_err(CE_NOTE,
		  "Ethernet Driver: p_conn_req - ignoring options, bad options size %x",
							conn->OPT_length);
#ifdef DISALLOW_OPTIONS
		DEBUGP(DEB_ERROR,(CE_CONT, "p_conn_req: Bad options size\n"));
		DEBUGP(DEB_CALL,(CE_CONT, "p_conn_req => TBADOPT\n"));
		return(TBADOPT|HIGH_PRI);
#endif
	}
	/*
	 * Make sure there is only one data buffer, and it is not too long
	 */
	if((msgdsize(mptr) > CONN_BUF_LEN) ||
	   ((mptr->b_cont != NULL) && (mptr->b_cont->b_cont != NULL))) {
		DEBUGP(DEB_ERROR,(CE_CONT, "p_conn_req: Too much data (%d)\n",
				msgdsize(mptr)));
		DEBUGP(DEB_CALL,(CE_CONT, "p_conn_req => TBADDATA\n"));
		return(TBADDATA|HIGH_PRI);
	}
	/*
	 * Begin the connection establishment procedure, passing any data
	 * supplied by the user.
	 */
	if(error = enet_conn_req(ep, mptr, conn)) {
		DEBUGP(DEB_CALL,(CE_CONT, "p_conn_req => %x\n", error));
		return(error|HIGH_PRI);
	}
	DEBUGP(DEB_CALL,(CE_CONT, "p_conn_req => 0\n"));
	return(0);
}

/*
 * p_conn_res
 *
 * Transport User Connection Response: T_CONN_RES
 *
 * Input State: TS_WRES_CIND --> TS_WACK_CRES
 *
 * Output State: TS_IDLE, TS_DATA_XFER, or TS_WRES_CIND
 */
int
p_conn_res(listen_ep, mptr, cresp)
endpoint *listen_ep;
mblk_t *mptr;
register struct T_conn_res *cresp;
{
	register endpoint *accept_ep;
	register pend_list *pending;
	int error;

	DEBUGP(DEB_CALL,(CE_CONT, "p_conn_res()\n"));
	/*
	 * Make sure there is not too much options
	 */
	if(cresp->OPT_length != 0) {
		cmn_err(CE_NOTE,
		  "Ethernet Driver: p_conn_res - ignoring options, bad options size %x",
							cresp->OPT_length);
#ifdef DISALLOW_OPTIONS
		DEBUGP(DEB_CALL,(CE_CONT, "p_conn_res => TBADOPT\n"));
		return(TBADOPT|HIGH_PRI);
#endif
	}
	/*
	 * Make sure there is only one data buffer, and it is not too long
	 */
	if((msgdsize(mptr) > CONN_BUF_LEN) ||
	   ((mptr->b_cont != NULL) && (mptr->b_cont->b_cont != NULL))) {
		DEBUGP(DEB_CALL,(CE_CONT, "p_conn_res => TBADDATA\n"));
		return(TBADDATA|HIGH_PRI);
	}
	/*
	 * Make sure the connection sequence number is to an actual connection
	 * indication.
	 */
	for(pending = listen_ep->pend_connects; 
	    pending != NULL;
	    pending = (pend_list *)pending->next) {
		if(cresp->SEQ_number == pending->seqno)
			break;
	}
	if(pending == NULL) {
		DEBUGP(DEB_CALL,(CE_CONT, "p_conn_res => TBADSEQ(%x,%x)\n",
				listen_ep,cresp->SEQ_number));
		return(TBADSEQ|HIGH_PRI);
	}
	/*
	 * Is the STREAM on which the connection is being accepted valid?
	 */
	if(cresp->QUEUE_ptr != listen_ep->rd_q)  {
		/*
		 * The accepting STREAM is not the one which did
		 * the listening.
		 */
		DEBUGP(DEB_FULL,(CE_CONT, "p_conn_res: different streams\n"));
		DEBUGP(DEB_FULL,(CE_CONT, "p_conn_res: listen_ep %x listen_ep->rd_q %x\n",
						listen_ep, listen_ep->rd_q));
		for(accept_ep = &enet_endpoints[0];
		    accept_ep < &enet_endpoints[enet_n_endpoints];
		    accept_ep++)
			if((cresp->QUEUE_ptr == accept_ep->rd_q) &&  /* I007 */
			    (!(accept_ep->str_state & (C_ERROR|C_DISCON))) &&
			    (ti_statetbl[TE_PASS_CONN][accept_ep->tli_state] != 
			      TS_INVALID) )
				break;
		if(accept_ep >= &enet_endpoints[enet_n_endpoints]) {
			DEBUGP(DEB_CALL,(CE_CONT, "p_conn_res => TBADF\n"));
			return(TBADF|HIGH_PRI);
		}
		DEBUGP(DEB_FULL,(CE_CONT, "p_conn_res: accept_ep %x accept_ep->rd_q %x\n",
						accept_ep, accept_ep->rd_q));
	}
	else {
		/*
		 * The accepting STREAM and the listening STREAM
		 * are one and the same.
		 */
		DEBUGP(DEB_FULL,(CE_CONT, "p_conn_res: accept on listening stream\n"));
		if(listen_ep->nbr_pend > 1) {
			return(TBADF|HIGH_PRI);
		}
		accept_ep = listen_ep;
	}
	/*
	 * Notify the remote endpoint of the acceptance 
	 */
	if(error = enet_conn_res(pending, listen_ep, accept_ep, mptr)) {
		DEBUGP(DEB_CALL,(CE_CONT, "p_conn_res => %x\n", error));
		return(error|HIGH_PRI);
	}
	/* Now mark the service as connection oriented */
	accept_ep->options |= OPT_COTS;
	DEBUGP(DEB_CALL,(CE_CONT, "p_conn_res => 0\n"));
	return(0);
}

/*
 * p_discon_req
 *
 * Transport User Disconnect Request: T_DISCON_REQ
 *
 * Input State: TS_WCON_CREQ   --> TS_WACK_DREQ6
 *	        TS_WRES_CIND   --> TS_WACK_DREQ7
 *	        TS_DATA_XFER   --> TS_WACK_DREQ9
 *		TS_WIND_ORDREL --> TS_WACK_DREQ10
 *		TS_WREQ_ORDREL --> TS_WACK_DREQ11
 *
 * Output State: TS_IDLE or
 *		 back to original input state on error
 */
int
p_discon_req(ep, mptr, dis)
register endpoint *ep;
mblk_t *mptr;
register struct T_discon_req *dis;
{
	int error;
	pend_list *pending, *prev;

	DEBUGP(DEB_CALL,(CE_CONT, "p_discon_req()\n"));
	/*
	 * Make sure there is only one data buffer, and it is not too long
	 */
	if((msgdsize(mptr) > CLOSE_BUF_LEN) ||
	   ((mptr->b_cont != NULL) && (mptr->b_cont->b_cont != NULL))) {
		DEBUGP(DEB_ERROR,(CE_CONT, "p_discon_req: Too much data (%d)\n",
				msgdsize(mptr)));
		DEBUGP(DEB_CALL,(CE_CONT, "p_discon_req => TBADDATA\n"));
		return(TBADDATA|HIGH_PRI);
	}
	switch(ep->tli_state) {
	case TS_WACK_DREQ9:
	case TS_WACK_DREQ10:
	case TS_WACK_DREQ11:
		/*
		 * The endpoint has one (and only one) active connection,
		 * take down the connection.
		 *
		 * When the user has selected the "flush on disconnect"
		 * option:
		 *	1. Flush any output not yet processed.
		 *	2. Throw away any output being sent.
		 *	3. Make sure the write queue is available for
		 *		scheduling by STREAMS.
		 */
		if((ep->options & OPT_DISCON) || (ep->str_state & C_DISCON)) {
			DEBUGC('F');
			flushq(WR(ep->rd_q), FLUSHALL);
			if(ep->flow)
				freemsg(ep->flow);
			ep->flow = NULL;
			if(error = enet_discon_req(ep, ep->c_reference,
						   (pend_list *)NULL,
						   RSN_NORMAL, mptr)) {
				return(error|HIGH_PRI);
			}
			break;
		}
		/*
		 * Schedule the (rest of the) disconnect operation.
		 */
		DEBUGC('P');
		putq(WR(ep->rd_q), mptr);
		break;
	case TS_WACK_DREQ7:
		/*
		 * The endpoint is listening, drop a pending connection request.
		 */
		prev = NULL;
		for(pending = ep->pend_connects; 
		    pending != NULL;
		    pending = (pend_list *)pending->next) {
			if(dis->SEQ_number == pending->seqno)
				break;
			prev = pending;
		}
		if(pending == NULL) {
			DEBUGP(DEB_CALL,(CE_CONT, "p_discon_req => TBADSEQ\n"));
			return(TBADSEQ|HIGH_PRI);
		}
		/*
		 * Clear out the indication of the connection.
		 */
		ep->nbr_pend--;
		if(ep->pend_connects == pending)
			ep->pend_connects = (pend_list *)pending->next;
		else
			prev->next = pending->next;
		/*
		 * Notify the other endpoint of the disconnect.
		 */
		if(error = enet_discon_req(ep, pending->reference,
					   pending, RSN_NORMAL, mptr)) {
			return(error|HIGH_PRI);
		}
		break;
	default: /* WACK_DREQ6 */
		/*
		 * The endpoint is a client who got tired of waiting for
		 * the connect confirm.
		 */
		if(error = enet_discon_req(ep, ep->c_reference,
					   (pend_list *)NULL,
					   RSN_NORMAL, mptr)) {
			return(error|HIGH_PRI);
		}
		break;
	}
	/* Mark the service as unknown type again */
	/* ep->options &= ~(OPT_COTS|OPT_CLTS); */
	DEBUGP(DEB_CALL,(CE_CONT, "p_discon_req => 0\n"));
	return(0);
}

/*
 * p_data_req
 *
 * Transport User Data Request: T_DATA_REQ
 *
 * Input State: TS_DATA_XFER   --> TS_DATA_XFER
 *		TS_WREQ_ORDREL --> TS_WREQ_ORDREL
 *
 * Output State: no change
 */
/*ARGSUSED*/
int
p_data_req(ep, mptr, data)
endpoint *ep;
register mblk_t *mptr;
struct T_data_req *data;
{
	DEBUGP(DEB_CALL,(CE_CONT, "p_data_req()\n"));
	/*
	 * Just pass through flow control and out
	 */
	DEBUGC('-');
	putq(WR(ep->rd_q), mptr);
	DEBUGP(DEB_CALL,(CE_CONT, "p_data_req => 0\n"));
	return(0);
}

/*
 * p_unitdata_req
 *
 * Transport Unit Data Request: T_UNITDATA_REQ
 *
 * Input State: TS_IDLE --> TS_IDLE
 *
 * Output State: no change
 */
/*ARGSUSED*/
int
p_unitdata_req(ep, mptr, data)
endpoint *ep;
register mblk_t *mptr;
struct T_unitdata_req *data;
{
	DEBUGP(DEB_CALL,(CE_CONT, "p_unitdata_req()\n"));
	if(msgdsize(mptr) > enet_datagram_size)
		return(TBADDATA);
	/*
	 * Just pass through flow control and out
	 */
	DEBUGC('=');
	putq(WR(ep->rd_q), mptr);
	DEBUGP(DEB_CALL,(CE_CONT, "p_unitdata_req => 0\n"));
	return(0);
}

/*
 * p_exdata_req
 *
 * Transport User Data Request: T_DATA_REQ
 */
int
p_exdata_req(ep, mptr)
endpoint *ep;
register mblk_t *mptr;
{
	register mblk_t *body;
	register int more;

	DEBUGP(DEB_CALL,(CE_CONT, "p_exdata_req()\n"));
	/*
	 * Read the more flag from the M_PROTO message block in front
	 * of the user data and get rid of the block
	 */
	more = (((struct T_exdata_req *)mptr->b_rptr)->MORE_flag != 0);
	if(more)
		return(TNOTSUPPORT);
	if(msgdsize(mptr) > ETSDU_SIZE)
		return(TBADDATA);
	body = unlinkb(mptr);
	freemsg(mptr);
	DEBUGC('/');
	if(!(ep->options & OPT_EXPD)) {
		cmn_err(CE_NOTE, "Ethernet Driver: p_exdata_req - OPT_EXPD not set - data being sent anyway.");
	}
	if(enet_expedited_data_req(ep, body)) {
		enet_pferr(ep);
		return(0);
	}
	DEBUGP(DEB_CALL,(CE_CONT, "p_exdata_req => 0\n"));
	return(0);
}

/*
 * p_info_req
 *
 * Transport User Information Request: T_INFO_REQ
 *
 * Input State: Any
 *
 * Output State: no change
 */
int 
p_info_req(ep, mptr)
register endpoint *ep;
register mblk_t *mptr;
{
	long	serv_type;

	DEBUGP(DEB_CALL,(CE_CONT, "p_info_req()\n"));
	serv_type = 0;		/* No service type will work either way */
	if(ep->options & OPT_CLTS)
		serv_type = T_CLTS;
	if(ep->options & OPT_COTS)
		serv_type = T_COTS;
	enet_info_ack(ep, mptr, (long)TSDU_SIZE, (long)ETSDU_SIZE,
		      (long)CONN_BUF_LEN, (long)CLOSE_BUF_LEN, (long)MAX_NAME_LEN,
		      (long)COPT_LEN, (long)TIDU_SIZE, (long)ep->tli_state,
		      (long)serv_type);
	DEBUGP(DEB_CALL,(CE_CONT, "p_info_req => NULL\n"));
	return(0);
}

/*
 * p_bind_req
 *
 * Transport User Bind Request: T_BIND_REQ
 *
 * Input State: TS_UNBND --> TS_WACK_BREQ
 *
 * Output State: TS_IDLE
 */
int
p_bind_req(ep, mptr, bind)
endpoint *ep;
register mblk_t *mptr;
register struct T_bind_req *bind;
{
	int	error;

	DEBUGP(DEB_CALL,(CE_CONT, "p_bind_req()\n"));
	bind = (struct T_bind_req *)mptr->b_rptr;
	if((bind->ADDR_length > MAX_NET_ADDR_LEN) ||
	   (T_NA_LEN((char *)bind + bind->ADDR_offset) > MAX_NET_ADDR_LEN-3))
		return(TBADADDR);
	/*
	 * Check the value of "max connection count"
	 * A non_zero value indicates that the endpoint is permitted to 
	 * accept incoming connection requests.
	 */
	if(bind->CONIND_number > enet_max_pend)  	/* I000 */
		bind->CONIND_number = enet_max_pend;
	ep->max_pend = bind->CONIND_number;
	if(bind->ADDR_length != 0) {
		/*
		 * The user supplied an address
		 */
		DEBUGP(DEB_FULL,(CE_CONT, "p_bind_req: User supplied address\n"));
		ep->tsap = T_TSAP((char *)bind + bind->ADDR_offset);
		/* Note: tsap 0 is illegal, don't try to bind to it. */
		if((ep->tsap != 0) && (!enet_bind_req(ep, mptr, bind))) {
			/*
			 * User's address seemed to work fine
			 */
			DEBUGP(DEB_CALL,(CE_CONT, "p_bind_req => NULL\n"));
			return(0);
		}
	}
	/*
	 * No address was provided by the user, or the request with supplied
	 * address failed.
	 * Make up an address and try it.
	 */
	DEBUGP(DEB_FULL,(CE_CONT, "p_bind_req: Making address\n"));
	if((mptr = enet_make_addr(ep, mptr, bind)) == NULL)
		return(TSYSERR);
	bind = (struct T_bind_req *)mptr->b_rptr;
	ep->tsap = T_TSAP((char *)bind + bind->ADDR_offset);
	error = enet_bind_req(ep, mptr, bind);
	DEBUGP(DEB_CALL,(CE_CONT, "p_bind_req => %d\n", error));
	return(error);
}

/*
 * p_unbind_req
 *
 * Transport User Unbind Request: T_UNBIND_REQ
 *
 * Input State: TS_IDLE --> TS_WACK_UREQ
 *
 * Output State: TS_UNBND;
 */
/*ARGSUSED*/
int
p_unbind_req(ep, mptr, unbind)
endpoint *ep;
mblk_t *mptr;
struct T_unbind_req *unbind;
{
	int	error;

	DEBUGP(DEB_CALL,(CE_CONT, "p_unbind_req()\n"));
	/*
	 * Generate a M_FLUSH.
	 */
	enet_send_flush(ep);
	/*
	 * Tell the board we're gone
	 */
	if(error = enet_unbind_req(ep, mptr, unbind))
		return(error);
	/*
	 * Update the state of the virtual endpoint.
	 */
	ep->tli_state = ti_statetbl[TE_OK_ACK1][ep->tli_state];
	enet_ok_ack(ep, mptr, 0);
	DEBUGP(DEB_CALL,(CE_CONT, "p_unbind_req => 0\n"));
	return(0);
}

/*
 * Transport User Options Management Request: T_OPTMGMT_REQ
 *
 * Input State: TS_IDLE --> TS_WACK_OPTREQ
 *
 * Output State: TS_IDLE
 */
int
p_optmgmt_req(ep, mptr, opt_req)
endpoint *ep;
mblk_t *mptr;
register struct T_optmgmt_req *opt_req;
{
	int flags;
	register opts *options;

	DEBUGP(DEB_CALL,(CE_CONT, "p_optmgmt_req()\n"));
	switch(flags = opt_req->MGMT_flags) {
	case T_DEFAULT:
	case T_CHECK:
	case T_NEGOTIATE:
		break;
	default:
		return(TBADFLAG);
	}
	if(opt_req->OPT_length == 0) {
		/*
		 * The user did not provide any options.
		 */
		DEBUGP(DEB_FULL,(CE_CONT, "p_optmgmt_req: No user options given\n"));
		if(flags & T_NEGOTIATE)
			return(TBADOPT);
		if(flags == T_CHECK) {
			/*
			 * No options to check: Automatic success.
			 */
			enet_optmgmt_ack(ep, mptr, T_SUCCESS, 0, 0);
			ep->tli_state = OPTMGMT_NEXT_STATE(ep);
			DEBUGP(DEB_CALL,(CE_CONT, "p_optmgmt_req => 0\n"));
			return(0);
		}
		enet_optmgmt_ack(ep, mptr, flags, COPT_LEN, enet_u_defaults);
		ep->tli_state = OPTMGMT_NEXT_STATE(ep);
		DEBUGP(DEB_CALL,(CE_CONT, "p_optmgmt_req => 0\n"));
		return(0);
	}
	if(opt_req->OPT_length != COPT_LEN)
		return(TBADOPT);
	options = (opts *)(mptr->b_datap->db_base + opt_req->OPT_offset);
	DEBUGP(DEB_FULL,(CE_CONT, "p_optmgmt_req: User supplied %x\n", *options));
	switch(flags) {
	case T_DEFAULT:
		/*
		 * Return the default options.
		 */
		*options = enet_u_defaults;
		break;
	case T_CHECK:
		/*
		 * See if any unknown or illegal options were specified.
		 */
		if(*options & ~U_OPTIONS)
			return(TBADFLAG);
		opt_req->MGMT_flags |= enet_check_opts(*options, (opts *)NULL);
		break;
	case T_NEGOTIATE:
		/*
		 * See if any unknown or illegal options were specified.
		 *
		 * Select a good set, and install them.
		 */
		if(*options & ~U_OPTIONS)
			return(TBADFLAG);
		(void)enet_check_opts(*options, options);
		enet_set_opts(ep, *options);
		break;
	}
	DEBUGP(DEB_FULL,(CE_CONT, "p_optmgmt_req: Replying with %x\n", *options));
	/*
	 * Transform the T_OPTMGMT_REQ into a T_OPTMGMT_ACK, and send it back to
	 * the transport user.  It's not worth calling optmgmt_ack since it's
	 * all bundled already.
	 */
	opt_req->PRIM_type = T_OPTMGMT_ACK;
	putnext(ep->rd_q, mptr);
	ep->tli_state = OPTMGMT_NEXT_STATE(ep);
	DEBUGP(DEB_CALL,(CE_CONT, "p_optmgmt_req => 0\n"));
	return(0);
}

/*
 * enetrsrv
 *
 * Read Service Procedure
 *
 * Used to throttle and serialize input on streams.  The only thing 
 * that can be queued here is a vcrb.  Check if its OK to send to 
 * stream head, if not, return, if so, get item from queue, call 
 * enet_buf_post() if no buffer has yet been posted on data incations,
 * then resume processing with enet_data_ind_complete or
 * enet_discon_ind_continue.
 */
 enetrsrv(rd_q)
 queue_t	*rd_q;
 {
	mblk_t *mp;
	struct vcp *vcp;		/* begin I004 */

	while (canput(rd_q->q_next))
	{
		if ((mp = getq(rd_q)) != NULL)
		{
			vcp = (struct vcp *)(mp->b_rptr);
		 	if (vcp->type == T_DATA_IND)
			{
				if (!vcp->post)
					enet_buf_post(vcp->vp);
				enet_data_ind_complete(vcp->vp);
			}
			else
				enet_discon_ind_continue((struct req_blk *)vcp->vp);
			freemsg(mp);
		}
		else
			break;		/* end I004 */
	}
 }

/*
 * enetwsrv
 *				  (user)
 * Write service procedure:       ( vv )
 *				  (enet)
 *
 *
 * Note that all messages read from this STREAM have already been
 * "filtered" through the the put procedure. As a result, only two 
 * types of messages are ever seen here:
 *
 *	T_DATA_REQ	: User data, to be sent
 *	T_DISCON_REQ	: Disconnect
 *
 * The T_DISCON_REQ, when appearing here, is a "draining disconnect" -
 * it is not to take effect until all previously sent user data has
 * been written.
 */
enetwsrv(wr_q)
register queue_t *wr_q;
{
	register mblk_t *mptr;
	endpoint *ep;
	register int type;

	DEBUGP(DEB_CALL,(CE_CONT, "enetwsrv()\n"));
	if (enet_rb_hi)				/* I001 */
		return;
	ep = (endpoint *)wr_q->q_ptr;
	while (ep->nbr_datarq < enet_max_data_rq)
	{
		if ((mptr = getq(wr_q)) == NULL)
		{
			break;
		}
		if(mptr->b_datap->db_type == M_DATA)
			type = T_DATA_REQ;
		else
			type = ((union T_primitives *)mptr->b_rptr)->type;
		/*
		 * Process the message.
		 *
		 * This call can return in one of two ways:
		 *	1. Continue processing with the other
		 *		messages on this STREAM.
		 *	2. Stop all processing from the STREAM.
		 */
		if(ti_s_fcntbl[type]) {
			if((*ti_s_fcntbl[type])(ep, mptr)) {
				DEBUGP(DEB_CALL,(CE_CONT, "enetwsrv => NULL\n"));
				return;
			}
		}
		else {
			cmn_err(CE_WARN, "Ethernet Driver: bad jump reference in wsrv %x\n", type);
			enet_pferr(ep);
		}
		if (enet_rb_hi)				/* I001 */
			return;
	}
}

/*
 * s_data_req
 *
 * Transport User Data Request: T_DATA_REQ
 */
bool
s_data_req(ep, mptr)
endpoint *ep;
register mblk_t *mptr;
{
	register mblk_t *body;
	register int more;

	DEBUGP(DEB_CALL,(CE_CONT, "s_data_req()\n"));
	if(mptr->b_datap->db_type == M_DATA) {
		more = FALSE;
		body = mptr;
	}
	else {
		/*
		 * Read the more flag from the M_PROTO message block in front
		 * of the user data and get rid of the block
		 */
		more = (((struct T_data_req *)mptr->b_rptr)->MORE_flag != 0);
		body = unlinkb(mptr);
		freemsg(mptr);
	}
	if(more)
		DEBUGC('#');
	else
		DEBUGC('$');
	if(enet_data_req(ep, body, more)) {
		enet_pferr(ep);
		return(0);
	}
	DEBUGP(DEB_CALL,(CE_CONT, "s_data_req => 0\n"));
	return(0);
}

/*
 * s_unitdata_req
 *
 * Transport Unit Data Request: T_UNITDATA_REQ
 */
bool
s_unitdata_req(ep, mptr)
endpoint *ep;
register mblk_t *mptr;
{
	register mblk_t *body;
	struct T_unitdata_req *udata;

	DEBUGP(DEB_CALL,(CE_CONT, "s_unitdata_req()\n"));
	udata = (struct T_unitdata_req *)mptr->b_rptr;
	body = unlinkb(mptr);
	DEBUGP(DEB_FULL,(CE_CONT, "s_unitdata_req: ep=%x, boby=%x, udata=%x\n",
		 ep, body, udata));
	if(enet_unitdata_req(ep, body, udata)) {
		freemsg(mptr);
		enet_pferr(ep);
		return(0);
	}
	freemsg(mptr);
	DEBUGP(DEB_CALL,(CE_CONT, "s_unitdata_req => 0\n"));
	return(0);
}

/*
 * s_discon_req
 *
 * Transport User Disconnection Request: T_DISCON_REQ
 *
 * Take down the active connection.
 */
bool
s_discon_req(ep, mptr)
register endpoint *ep;
mblk_t *mptr;
{
	int	x;
	DEBUGP(DEB_CALL,(CE_CONT, "s_discon_req()\n"));
	DEBUGC('D');
	x = SPL();
	if(ep->req_seqno > ep->complete_seqno) {
		putbq(WR(ep->rd_q), mptr);
		splx(x);
		DEBUGC('B');
		return(1);
	}
	splx(x);
	noenable(WR(ep->rd_q));
	/* Call discon_req and let the interrupt routine do the rest */
	if(enet_discon_req(ep, ep->c_reference,
			   (pend_list *)NULL, RSN_NORMAL, mptr)) {
		enableok(WR(ep->rd_q));
		/* wakeup(&ep->str_state);		I005 */
		return(1);
	}
	/* Don't do anymore work on this channel until the disconnect
	 * returns.  The interrupt routine will restart the processing */
	DEBUGP(DEB_CALL,(CE_CONT, "s_discon_req => 1\n"));
	return(1);
}

#ifdef NS
void
ns_req(ep, mp)
endpoint *ep;
mblk_t *mp;
{
	struct nsrb *ns_rb;
	struct NS_req *nsreq;
	mblk_t *data;
	unchar *blen;
	ushort *wlen;
	unchar *addr;

	DEBUGP(DEB_CALL,(CE_CONT, "ns_req()\n"));
	nsreq = (struct NS_req *)mp->b_datap->db_base;
	/******
	cmn_err(CE_CONT, "ns_req: mp->b_datap->db_base = %x\n",
						mp->b_datap->db_base);
	cmn_err(CE_CONT, "ns_req: mp->b_datap->db_lim = %x\n",
						mp->b_datap->db_lim);
	cmn_err(CE_CONT, "ns_req: msgdsize = %x\n", msgdsize(mp));
	cmn_err(CE_CONT, "ns_req: mp->b_cont = %x\n", mp->b_cont);
	*******/
	data = mp->b_cont;
	/******
	cmn_err(CE_CONT, "ns_req: data->b_rptr = %x\n", data->b_rptr);
	cmn_err(CE_CONT, "ns_req: data->b_datap->db_lim = %x\n",
						data->b_datap->db_lim);
	*******/
	if ((ns_rb = (struct nsrb *)getrb(ep)) == NULL)
	{
		DEBUGP(DEB_ERROR,(CE_CONT, "ns_req: No rb for ns request\n"));
		freemsg(mp);
		return;
	}

	init_crbh(&ns_rb->ns_crbh);
	ns_rb->ns_crbh.c_resp	= 0;
	ns_rb->ns_crbh.c_len	= NSRB_SIZE;
	ns_rb->ns_crbh.c_subsys	= SUB_NS;
	ns_rb->ns_crbh.c_opcode	= (unchar)(nsreq->PRIM_type & 0x0F);
	ns_rb->ns_ep		= ep;
	ns_rb->ns_mp		= mp;

	ns_rb->ns_unflag	= (unchar)nsreq->UNIQUE_flag;
	ns_rb->ns_ptype[0]	= (unchar)(nsreq->PROPERTY_type&0xFF);
	ns_rb->ns_ptype[1]	= (unchar)((nsreq->PROPERTY_type>>8)&0xFF);
	ns_rb->ns_pvaltype	= (unchar)nsreq->PROPERTY_value;
	if (nsreq->NAME_length)
	{
		DEBUGP(DEB_FULL,(CE_CONT, "ns_req: ns_nabufp %x\n", data->b_rptr));
		DEBUGP(DEB_FULL,(CE_CONT, "ns_req: ns_nabufp containe %s\n",
							data->b_rptr+1));
		/* Don't allow size beyond end of mp. */
		blen = (unchar *)data->b_rptr;
		DEBUGP(DEB_FULL,(CE_CONT, "ns_req: ns_nabufp blen %x\n", *blen));
		if ((data->b_rptr + *blen + 1) > data->b_datap->db_lim)
			*blen = 0;
		*(int *)ns_rb->ns_nabufp = (int)kvtophys((caddr_t)data->b_rptr);
	}
	if (nsreq->VALUE_length)
	{
		DEBUGP(DEB_FULL,(CE_CONT, "ns_req: ns_vabufp %x\n",
					(data->b_rptr+nsreq->NAME_length)));
		/* Don't allow size beyond end of mp. */
		addr = (unchar *)(data->b_rptr + nsreq->NAME_length);
		wlen = (ushort *)addr;
		DEBUGP(DEB_FULL,(CE_CONT, "ns_req: ns_vabufp wlen = %x\n", *wlen));
		if ((int)(addr + *wlen + 2) > (int)data->b_datap->db_lim)
			*wlen = 0;
		*(int *)ns_rb->ns_vabufp =
		      (int)kvtophys((caddr_t)addr);
	}
	if (nsreq->EXTRA_length)
	{
		DEBUGP(DEB_FULL,(CE_CONT, "ns_req: ns_exbufp %x\n",
			(data->b_rptr+nsreq->NAME_length+nsreq->VALUE_length)));
		/* Don't allow size beyond end of mp */
		addr = (unchar *)(data->b_rptr + nsreq->NAME_length
						+ nsreq->VALUE_length);
		wlen = (ushort *)addr;
		DEBUGP(DEB_FULL,(CE_CONT, "ns_req: ns_exbufp wlen = %x\n", *wlen));
		if ((ns_rb->ns_crbh.c_opcode != 0x6) &&
			 ((int)(addr + *wlen + 2) > (int)data->b_datap->db_lim))
			*wlen = 0;
		*(int *)ns_rb->ns_exbufp = (int)kvtophys((caddr_t)addr);
		if ((ns_rb->ns_crbh.c_opcode != 0x6) &&
				((int)nsreq->EXTRA_length > (int)(*wlen + 2)))
			ns_rb->ns_exbuflen = *wlen + 2;
		else if ((ns_rb->ns_crbh.c_opcode == 0x6) &&
			 (nsreq->EXTRA_length > (data->b_datap->db_lim - addr)))
			ns_rb->ns_exbuflen = data->b_datap->db_lim - addr;
		else
			ns_rb->ns_exbuflen = (ushort)nsreq->EXTRA_length;
	}
	else
		ns_rb->ns_exbuflen = 0x0;
	if (mipsend(ep->bnum, PORT_960, ns_rb, ns_rb->ns_crbh.c_len) == FALSE)
	{
		DEBUGP(DEB_ERROR,(CE_CONT, "ns_req: NS request (mipsend) failed\n"));
		freemsg(ns_rb->ns_mp);
		relrb(ns_rb);
		return;
	}
	DEBUGP(DEB_CALL,(CE_CONT, "ns_req: NS request succeeded\n"));
	return;
}

ns_req_complete(ns_rb)
struct nsrb *ns_rb;
{
	struct NS_res *nsres;

	DEBUGP(DEB_CALL,(CE_CONT, "ns_req_complete()\n"));
	if(ns_rb->ns_ep->str_state & (C_ERROR|C_IDLE)) 
	{
		freemsg(ns_rb->ns_mp);
		relrb((struct req_blk *)ns_rb);
		return;
	}

	nsres = (struct NS_res *)ns_rb->ns_mp->b_datap->db_base;
	ns_rb->ns_mp->b_wptr = ns_rb->ns_mp->b_rptr + sizeof(struct NS_res);
	nsres->PRIM_type = ns_rb->ns_crbh.c_opcode | 0x60;
	DEBUGP(DEB_FULL,(CE_CONT, "ns_req_complete: c_resp %x\n",
						ns_rb->ns_crbh.c_resp));
	nsres->RESP_code = ns_rb->ns_crbh.c_resp;
	nsres->EXTRA_length = ns_rb->ns_exbuflen;
	putnext(ns_rb->ns_ep->rd_q, ns_rb->ns_mp);
	relrb((struct req_blk *)ns_rb);
	DEBUGP(DEB_CALL,(CE_CONT, "ns_req_complete: return\n"));
	return;
}
#endif

void
nmf_req(ep, mp)
endpoint *ep;
mblk_t *mp;
{
	struct nmfrb *nmf_rb;
	struct NMF_req *nmfreq;
	mblk_t *data;
	unchar *blen;
	ushort *wlen;
	unchar *addr;

	DEBUGP(DEB_CALL,(CE_CONT, "nmf_req()\n"));
	nmfreq = (struct NMF_req *)mp->b_datap->db_base;
	data = mp->b_cont;
	if ((nmf_rb = (struct nmfrb *)getrb(ep)) == NULL)
	{
		DEBUGP(DEB_ERROR,(CE_CONT, "nmf_req: No rb for nmf request\n"));
		freemsg(mp);
		return;
	}

	init_crbh(&nmf_rb->nmf_crbh);
	nmf_rb->nmf_crbh.c_resp	= 0;
	nmf_rb->nmf_crbh.c_len	= NMFRB_SIZE;
	nmf_rb->nmf_crbh.c_subsys	= SUB_NMF;
	nmf_rb->nmf_crbh.c_opcode	= (unchar)(nmfreq->PRIM_type & 0x0F);
	nmf_rb->nmf_ep		= ep;
	nmf_rb->nmf_mp		= mp;

	nmf_rb->nmf_reference	= 0;
	nmf_rb->nmf_filledlen	= 0;

	if (nmfreq->CMD_length)
	{
		if ((data->b_rptr + nmfreq->CMD_length) > data->b_datap->db_lim)
			nmf_rb->nmf_cmdbuflen = 0;
		else
		{
			nmf_rb->nmf_cmdbuflen	= nmfreq->CMD_length;
			*(int *)nmf_rb->nmf_cmdbufp =
					(int)kvtophys((caddr_t)data->b_rptr);
		}
	}
	else
		nmf_rb->nmf_cmdbuflen	= 0;
	if (nmfreq->RESP_length)
	{
		if ((data->b_rptr + nmf_rb->nmf_cmdbuflen + nmfreq->RESP_length)
						> data->b_datap->db_lim)
			nmf_rb->nmf_respbuflen = 0;
		else
		{
			nmf_rb->nmf_respbuflen = nmfreq->RESP_length;
			*(int *)nmf_rb->nmf_respbufp =
			     (int)kvtophys((caddr_t)((unchar *)data->b_rptr +
							nmf_rb->nmf_cmdbuflen));
		}
	}
	else
		nmf_rb->nmf_respbuflen	= 0;
	if (mipsend(ep->bnum, PORT_960, nmf_rb, nmf_rb->nmf_crbh.c_len) == FALSE)
	{
		DEBUGP(DEB_ERROR,(CE_CONT, "nmf_req: NMF request (mipsend) failed\n"));
		freemsg(nmf_rb->nmf_mp);
		relrb(nmf_rb);
		return;
	}
	DEBUGP(DEB_CALL,(CE_CONT, "nmf_req: NMF request succeeded\n"));
	return;
}

nmf_req_complete(nmf_rb)
struct nmfrb *nmf_rb;
{
	struct NMF_res *nmfres;

	DEBUGP(DEB_CALL,(CE_CONT, "nmf_req_complete()\n"));
	if(nmf_rb->nmf_ep->str_state & (C_ERROR|C_IDLE)) 
	{
		freemsg(nmf_rb->nmf_mp);
		relrb((struct req_blk *)nmf_rb);
		return;
	}

	nmfres = (struct NMF_res *)nmf_rb->nmf_mp->b_datap->db_base;
	nmf_rb->nmf_mp->b_wptr = nmf_rb->nmf_mp->b_rptr + sizeof(struct NMF_res);
	nmfres->PRIM_type = nmf_rb->nmf_crbh.c_opcode | 0x80;
	DEBUGP(DEB_FULL,(CE_CONT, "nmf_req_complete: c_resp %x\n",
						nmf_rb->nmf_crbh.c_resp));
	nmfres->RESP_code = nmf_rb->nmf_crbh.c_resp;
	nmfres->RESP_length = nmf_rb->nmf_filledlen;
	putnext(nmf_rb->nmf_ep->rd_q, nmf_rb->nmf_mp);
	relrb((struct req_blk *)nmf_rb);
	DEBUGP(DEB_CALL,(CE_CONT, "nmf_req_complete: return\n"));
	return;
}

