/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Inter-Domain Network
 *
 * IDN Protocol functions to support domain link/unlink/reconfig.
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/machparam.h>
#include <sys/debug.h>
#include <sys/cpuvar.h>
#include <sys/kmem.h>
#include <sys/mutex.h>
#include <sys/rwlock.h>
#include <sys/systm.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/stropts.h>
#include <sys/sema_impl.h>
#include <sys/membar.h>
#include <sys/utsname.h>
#include <inet/common.h>
#include <inet/mi.h>
#include <netinet/ip6.h>
#include <inet/ip.h>
#include <netinet/in.h>
#include <sys/vm_machparam.h>
#include <sys/x_call.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/atomic.h>
#include <vm/as.h>		/* kas decl */

#include <sys/idn.h>
#include <sys/idn_xf.h>

#define	IDNBUG_CPUPERBOARD

extern pri_t		maxclsyspri;
extern u_longlong_t	gettick();

clock_t	idn_xmit_monitor_freq = 50;

static int	idn_connect(int domid);
static int	idn_disconnect(int domid, idn_fin_t fintype,
				idn_finarg_t finarg, idn_finsync_t finsync);
static void	idn_deconfig(int domid);
static void	idn_unlink_domainset(domainset_t domset, idn_fin_t fintype,
				idn_finarg_t finarg, idn_finopt_t finopt,
				boardset_t idnset);
static void	idn_retry_execute(void *arg);
static void	idn_retry_submit(void (*func)(uint_t token, void *arg),
				void *arg, uint_t token, clock_t ticks);
static void	idn_shutdown_datapath(domainset_t domset, int force);
static mblk_t	*idn_fill_buffer(caddr_t bufp, int size, mblk_t *mp,
				uchar_t **data_rptrp);
static ushort_t	idn_cksum(register ushort_t *hdrp, register int count);
static int	idn_mark_awol(int domid, clock_t *atime);

static void	idn_recv_proto(idn_protomsg_t *hp);
static void	idn_send_config(int domid, int phase);
static void	idn_recv_config(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static int	idn_send_master_config(int domid, int phase);
static int	idn_send_slave_config(int domid, int phase);
static uint_t	idn_check_master_config(int domid, uint_t *exp, uint_t *act);
static uint_t	idn_check_slave_config(int domid, uint_t *exp, uint_t *act);
static int	idn_recv_config_done(int domid);
static void	idn_nego_cleanup_check(int domid, int new_masterid,
				int new_cpuid);
static void	idn_recv_cmd(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static int	idn_recv_data(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static int	idn_send_data_loopback(idn_netaddr_t dst_netaddr,
				queue_t *wq, mblk_t *mp);
static void	idn_send_dataresp(int domid, idn_nack_t nacktype);
static int	idn_send_mboxdata(int domid, struct idn *sip, int channel,
				caddr_t bufp);
static int	idn_recv_mboxdata(int channel, caddr_t bufp);
static int	idn_program_hardware(int domid);
static int	idn_deprogram_hardware(int domid);

static void	idn_send_cmd_nackresp(int domid, idn_msgtype_t *mtp,
			idn_cmd_t cmdtype, idn_nack_t nacktype);
static void	idn_local_cmd(idn_cmd_t cmdtype, uint_t arg1,
				uint_t arg2, uint_t arg3);
static void	idn_terminate_cmd(int domid, int serrno);
static void	idn_mboxarea_init(idn_mboxtbl_t *mtp, register int ntbls);
static void	idn_mainmbox_activate(int domid);
static void	idn_mainmbox_deactivate(ushort_t domset);
static void	idn_mainmbox_chan_register(int domid,
				idn_mainmbox_t *send_mmp,
				idn_mainmbox_t *recv_mmp, int channel);
static int	idn_mainmbox_chan_unregister(ushort_t domset, int channel);
static int	idn_mainmbox_flush(int domid, idn_mainmbox_t *mmp);
static void	idn_mainmbox_reset(int domid, idn_mainmbox_t *cmp);
static int	idn_activate_channel(idn_chanset_t chanset,
				idn_chanop_t chanop);
static void	idn_deactivate_channel(idn_chanset_t chanset,
				idn_chanop_t chanop);
static int	idn_deactivate_channel_services(int channel,
				idn_chanop_t chanop);
static int	idn_activate_channel_services(int channel);
static void	idn_chan_server(idn_chansvr_t **cspp);
#if 0
static void	idn_chan_flush(idn_chansvr_t *csp);
#endif /* 0 */
static void	idn_chan_action(int channel, idn_chanaction_t chanaction,
				int wait);
static void	idn_chan_addmbox(int channel, ushort_t domset);
static void	idn_chan_delmbox(int channel, ushort_t domset);
static void	idn_submit_chanactivate_job(int channel);
static void	idn_exec_chanactivate(void *chn);

static void	idn_link_established(void *arg);
static void	idn_prealloc_slab(int nslabs);
static void	idn_recv_slaballoc_req(int domid, idn_msgtype_t *mtp,
				uint_t slab_size);
static void	idn_send_slaballoc_resp(int domid, idn_msgtype_t *mtp,
				uint_t slab_offset, uint_t slab_size,
				int serrno);
static void	idn_recv_slaballoc_resp(int domid, smr_offset_t slab_offset,
				uint_t slab_size, int serrno);
static void	idn_recv_slabreap_req(int domid, idn_msgtype_t *mtp,
				int nslabs);
static void	idn_recv_slabreap_resp(int domid, int nslabs, int serrno);
static void	idn_send_slabreap_resp(int domid, idn_msgtype_t *mtp,
				int nslabs, int serrno);
static void	idn_recv_slabfree_req(int domid, idn_msgtype_t *mtp,
				smr_offset_t slab_offset, uint_t slab_size);
static void	idn_recv_slabfree_resp(int domid, uint_t slab_offset,
				uint_t slab_size, int serrno);
static void	idn_send_slabfree_resp(int domid, idn_msgtype_t *mtp,
				uint_t slab_offset, uint_t slab_size,
				int serrno);
static void	idn_retry_nodename_req(void *arg);
static void	idn_send_nodename_req(int domid);
static void	idn_send_nodename_resp(int domid, idn_msgtype_t *mtp,
				uint_t bufoffset, int serrno);
static void	idn_recv_nodename_req(int domid, idn_msgtype_t *mtp,
				uint_t bufoffset);
static void	idn_recv_nodename_resp(int domid, uint_t bufoffset,
				int serrno);

static void	idn_protocol_server(int *id);
static void	idn_protocol_server_killall();
static void	idn_protojob_free(idn_protojob_t *jp);

static int	idn_xstate_transfunc(int domid, void *transarg);
static int	idn_xphase_transition(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static void	idn_sync_enter(int domid, idn_synccmd_t cmd,
				domainset_t xset, domainset_t rset,
				int (*transfunc)(), void *transarg);
static domainset_t
		idn_sync_register(int domid, idn_synccmd_t cmd,
				domainset_t ready_set, idn_syncreg_t regtype);
static void	idn_sync_register_awol(int domid);
static int	idn_verify_config_mbox(int domid);
static int	idn_select_master(int domid, int rmasterid, int rcpuid);

static int	valid_mtu(uint_t mtu);
static int	valid_bufsize(uint_t bufsize);
static int	valid_slabsize(int slabsize);
static int	valid_nwrsize(int nwrsize);

static int	idn_master_init();
static void	idn_master_deinit();

static void	idn_send_acknack(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);

static int	idn_send_nego(int domid, idn_msgtype_t *mtp,
				domainset_t conset);
static void	idn_retry_nego(uint_t token, void *arg);
static int	idn_check_nego(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static void	idn_action_nego_pend(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static void	idn_error_nego(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static void	idn_action_nego_sent(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static void	idn_action_nego_rcvd(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static void	idn_final_nego(int domid);
static void	idn_exit_nego(int domid, uint_t msgtype);

static int	idn_send_con(int domid, idn_msgtype_t *mtp,
				idn_con_t contype, domainset_t conset);
static void	idn_retry_con(uint_t token, void *arg);
static int	idn_check_con(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static void	idn_action_con_pend(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static void	idn_error_con(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static void	idn_action_con_sent(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static void	idn_action_con_rcvd(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static void	idn_final_con(int domid);
static void	idn_exit_con(int domid, uint_t msgtype);

static int	idn_send_fin(int domid, idn_msgtype_t *mtp, idn_fin_t fintype,
				idn_finarg_t finarg, idn_finopt_t finopt,
				domainset_t finset, uint_t finmaster);
static void	idn_retry_fin(uint_t token, void *arg);
static int	idn_check_fin_pend(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static void	idn_action_fin_pend(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static void	idn_error_fin_pend(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static int	idn_check_fin_sent(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static void	idn_action_fin_sent(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static void	idn_error_fin_sent(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static void	idn_action_fin_rcvd(int domid, idn_msgtype_t *mtp,
				idn_xdcargs_t xargs);
static void	idn_final_fin(int domid);
static void	idn_exit_fin(int domid, uint_t msgtype);

/*
 * We keep a small cache of protojob structures just
 * in case allocation within idn_handler comes back
 * with nothing from the land of kmem.
 */
idn_protojob_t	idn_protojob_cache[IDN_DMV_PENDING_MAX];
idn_protojob_t	*idn_protojob_cache_list;
kmutex_t	idn_protojob_cache_lock;

/*
 *	- receive message.
 *	- call check-function for current state.
 *	- if (check-function == ok) then
 *		call action-function for current state.
 *	  else
 *		call error-function for current state.
 *	- transition state based on check results.
 *	- if (next state == final state) then
 *		call final-function.
 */
static idn_xphase_t xphase_nego = {
	IDNP_NEGO,
	{
		{ IDNDS_NEGO_PEND,
			idn_check_nego,
			idn_action_nego_pend,
			idn_error_nego},
		{ IDNDS_NEGO_SENT,
			idn_check_nego,
			idn_action_nego_sent,
			idn_error_nego},
		{ IDNDS_NEGO_RCVD,
			NULL,
			idn_action_nego_rcvd,
			NULL },
		{ IDNDS_CONFIG, NULL, NULL, NULL },
	},
	idn_final_nego,
	idn_exit_nego
};

static idn_xphase_t xphase_con = {
	IDNP_CON,
	{
		{ IDNDS_CON_PEND,
			idn_check_con,
			idn_action_con_pend,
			idn_error_con},
		{ IDNDS_CON_SENT,
			idn_check_con,
			idn_action_con_sent,
			idn_error_con},
		{ IDNDS_CON_RCVD,
			NULL,
			idn_action_con_rcvd,
			NULL },
		{ IDNDS_CON_READY, NULL, NULL, NULL },
	},
	idn_final_con,
	idn_exit_con
};

static idn_xphase_t xphase_fin = {
	IDNP_FIN,
	{
		{ IDNDS_FIN_PEND,
			idn_check_fin_pend,
			idn_action_fin_pend,
			idn_error_fin_pend },
		{ IDNDS_FIN_SENT,
			idn_check_fin_sent,
			idn_action_fin_sent,
			idn_error_fin_sent },
		{ IDNDS_FIN_RCVD,
			NULL,
			idn_action_fin_rcvd,
			NULL },
		{ IDNDS_DMAP, NULL, NULL, NULL },
	},
	idn_final_fin,
	idn_exit_fin
};

static int idnxs_state_table[4][5][2] = {
	{			/* IDNXS_PEND */
		{ IDNXS_SENT,	IDNXS_PEND },	/* 0 */
		{ IDNXS_RCVD,	IDNXS_PEND },	/* msg */
		{ IDNXS_NIL,	IDNXS_PEND },	/* msg+ack */
		{ IDNXS_PEND,	IDNXS_NIL },	/* ack */
		{ IDNXS_PEND,	IDNXS_NIL },	/* nack */
	},
	{			/* IDNXS_SENT */
		{ IDNXS_NIL,	IDNXS_NIL },	/* 0 */
		{ IDNXS_RCVD,	IDNXS_PEND },	/* msg */
		{ IDNXS_FINAL,	IDNXS_PEND },	/* msg+ack */
		{ IDNXS_NIL,	IDNXS_NIL },	/* ack */
		{ IDNXS_PEND,	IDNXS_NIL },	/* nack */
	},
	{			/* IDNXS_RCVD */
		{ IDNXS_NIL,	IDNXS_NIL },	/* 0 */
		{ IDNXS_NIL,	IDNXS_NIL },	/* msg */
		{ IDNXS_FINAL,	IDNXS_NIL },	/* msg+ack */
		{ IDNXS_FINAL,	IDNXS_NIL },	/* ack */
		{ IDNXS_PEND,	IDNXS_NIL },	/* nack */
	},
	{			/* IDNXS_FINAL */
		{ IDNXS_NIL,	IDNXS_NIL },	/* 0 */
		{ IDNXS_NIL,	IDNXS_NIL },	/* msg */
		{ IDNXS_NIL,	IDNXS_NIL },	/* msg+ack */
		{ IDNXS_NIL,	IDNXS_NIL },	/* ack */
		{ IDNXS_NIL,	IDNXS_NIL },	/* nack */
	}
};

/*
 * NONE		Respective domain does not have a master.
 * OTHER	Respective domain has a master different
 *		than either local or remote.
 * LOCAL	Respective domain has chosen local as master.
 * REMOTE	Respective domain has chosen remote as master.
 *
 * Actions:
 *	VOTE		Compare votes and select one.
 *	VOTE_RCFG	Compare votes and Reconfigure
 *			if necessary, i.e. remote won.
 *	CONNECT		Connect to remote's OTHER if different
 *			than our local master.
 *	LOCAL		Local domain is winner.
 *	REMOTE		Remote domain is winner.
 *	WAIT		Wait for remote to connect to our
 *			master if his is different.
 *	ERROR		An impossible condition.
 *
 * Index:
 *	0 = Local
 *	1 = Remote
 */
static idn_master_select_t master_select_table[4][4] = {
	{				/* local	remote	*/
		MASTER_SELECT_VOTE,	/* NONE		NONE	*/
		MASTER_SELECT_CONNECT,	/* NONE		OTHER	*/
		MASTER_SELECT_LOCAL,	/* NONE		LOCAL	*/
		MASTER_SELECT_REMOTE	/* NONE		REMOTE	*/
	},
	{
		MASTER_SELECT_WAIT,	/* OTHER	NONE	*/
		MASTER_SELECT_CONNECT,	/* OTHER	OTHER	*/
		MASTER_SELECT_WAIT,	/* OTHER	LOCAL	*/
		MASTER_SELECT_WAIT	/* OTHER	REMOTE	*/
	},
	{
		MASTER_SELECT_LOCAL,	/* LOCAL	NONE	*/
		MASTER_SELECT_CONNECT,	/* LOCAL	OTHER	*/
		MASTER_SELECT_LOCAL,	/* LOCAL	LOCAL	*/
		MASTER_SELECT_VOTE_RCFG	/* LOCAL	REMOTE	*/
	},
	{
		MASTER_SELECT_REMOTE,	/* REMOTE	NONE	*/
		MASTER_SELECT_CONNECT,	/* REMOTE	OTHER	*/
		MASTER_SELECT_ERROR,	/* REMOTE	LOCAL	*/
		MASTER_SELECT_REMOTE	/* REMOTE	REMOTE	*/
	}
};

void
idn_assign_cookie(int domid)
{
	static ushort_t	num = 0;
	ushort_t	cookie;
	procname_t	proc = "idn_assign_cookie";

	if ((cookie = idn_domain[domid].dcookie_recv) != 0)
		return;

	cookie = (ushort_t)(((uint64_t)&idn_domain[domid] >> 8) & 0xff);
	while ((cookie ^= num++ & 0xff) == 0)
		;

	PR_PROTO("%s:%d: assigned RECV cookie 0x%x\n", proc, domid, cookie);

	idn_domain[domid].dcookie_recv = cookie;
}

void
idn_update_priority(int domid, int pri)
{
	idn_domain_t	*dp;
	procname_t	proc = "idn_update_priority";

	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	dp = &idn_domain[domid];

	if (pri >= IDNVOTE_MINPRI) {
		dp->dvote.v.priority = pri & IDNVOTE_PRI_MASK;

		PR_PROTO("%s:%d: SETTING PRIORITY to req(%d) "
		    "(localpri = 0x%x)\n",
		    proc, domid, pri, IDNVOTE_PRIVALUE(dp->dvote));
	} else {
		PR_PROTO("%s:%d: PRIORITIES UNCHANGED (pri = 0x%x)\n",
		    proc, domid, IDNVOTE_PRIVALUE(dp->dvote));
	}
}

/*
 * Initiate a link between the local domain and the remote domain
 * containing the given cpuid.
 */
int
idn_link(int domid, int cpuid, int pri, int waittime, idnsb_error_t *sep)
{
	int		rv;
	idn_domain_t	*dp;
	void		*opcookie;
	procname_t	proc = "idn_link";

	if ((cpuid < 0) || (cpuid >= NCPU)) {
		cmn_err(CE_WARN,
		    "IDN: 201: (LINK) invalid CPU ID (%d)", cpuid);
		return (EINVAL);
	}
	if (waittime < 0) {
		cmn_err(CE_WARN,
		    "IDN: 202: (LINK) invalid time-out value (%d)",
		    waittime);
		return (EINVAL);
	}
	if (!VALID_DOMAINID(domid)) {
		cmn_err(CE_WARN,
		    "IDN: 203: (LINK) invalid domain ID (%d)",
		    domid);
		return (EINVAL);
	}
	if (domid == idn.localid)
		return (0);

	IDN_SYNC_LOCK();
	IDN_DLOCK_EXCL(domid);

	dp = &idn_domain[domid];

	switch (dp->dstate) {
	case IDNDS_CLOSED:
		break;

	case IDNDS_CONNECTED:
#ifdef DEBUG
		cmn_err(CE_NOTE,
		    "!IDN: domain %d (CPU ID %d) already connected",
		    domid, cpuid);
#endif /* DEBUG */
		IDN_DUNLOCK(domid);
		IDN_SYNC_UNLOCK();
		return (0);

	default:
		cmn_err(CE_WARN,
		    "IDN: 204: domain %d state (%s) inappropriate",
		    domid, idnds_str[dp->dstate]);
		IDN_DUNLOCK(domid);
		IDN_SYNC_UNLOCK();
		return (EINVAL);
	}

	rv = idn_open_domain(domid, cpuid, 0);
	if (rv != 0) {
		cmn_err(CE_WARN,
		    "IDN: 205: (%s) failed to open-domain(%d,%d)",
		    proc, domid, cpuid);
		IDN_DUNLOCK(domid);
		IDN_SYNC_UNLOCK();
		return (EIO);
	}


	IDN_DLOCK_EXCL(idn.localid);
	idn_update_priority(idn.localid, pri);
	IDN_DUNLOCK(idn.localid);

	if (waittime > 0)
		opcookie = idn_init_op(IDNOP_CONNECTED, DOMAINSET(domid), sep);

	idn_connect(domid);

	IDN_DUNLOCK(domid);
	IDN_SYNC_UNLOCK();

	PR_PROTO("%s:%d: ALLOCATED idn_link(%d)\n", proc, domid, cpuid);

	if (waittime > 0) {
		boardset_t	domset = 0;
		/*
		 * Well we've successfully allocated a domain id,
		 * but the link may not be fully established yet.
		 * Need to wait since it happens asynchronously.
		 */
		PR_PROTO("%s:%d: WAITING for op(%s) for (domset 0%x)...\n",
		    proc, domid, idnop_str[IDNOP_CONNECTED],
		    DOMAINSET(domid));

		rv = idn_wait_op(opcookie, &domset, waittime);
	}

#ifdef DEBUG
	if (rv == 0) {
		if (waittime > 0) {
			PR_PROTO("%s:%d: connect SUCCEEDED (cpu %d)\n",
			    proc, domid, cpuid);
		} else {
			PR_PROTO("%s:%d: connect KICKED OFF (cpu %d)\n",
			    proc, domid, cpuid);
		}
	} else {
		PR_PROTO("%s:%d: connect FAILED (cpu %d)\n",
		    proc, domid, cpuid);
	}
#endif /* DEBUG */

	return (rv);
}

/*
 * Unlink the given domain from any domain cluster of
 * which it might be a member.  Force indicates that domain
 * should not go AWOL and if it's currently AWOL to close
 * and remove it.
 * IMPORTANT: If the (hard) force flag is set, the caller is
 *	      assumed to GUARANTEE that the given domain will
 *	      not attempt to communicate with the local domain
 *	      in any manner.
 */
int
idn_unlink(int domid, boardset_t idnset, idn_fin_t fintype,
		idn_finopt_t finopt, int waittime, idnsb_error_t *sep)
{
	int		rv = 0;
	domainset_t	domset;
	void		*opcookie;
	procname_t	proc = "idn_unlink";


	if (waittime < 0) {
		cmn_err(CE_WARN,
		    "IDN: 202: (UNLINK) invalid time-out value (%d)",
		    waittime);
		SET_IDNKERR_IDNERR(sep, IDNKERR_INVALID_WTIME);
		SET_IDNKERR_PARAM0(sep, waittime);
		return (EINVAL);
	}
	if (!VALID_DOMAINID(domid)) {
		cmn_err(CE_WARN,
		    "IDN: 203: (UNLINK) invalid domain ID (%d)",
		    domid);
		SET_IDNKERR_IDNERR(sep, IDNKERR_INVALID_DOMAIN);
		SET_IDNKERR_PARAM0(sep, domid);
		SET_IDNKERR_PARAM1(sep, -1);
		return (EINVAL);
	}
	if (idn.localid == IDN_NIL_DOMID) {
#ifdef DEBUG
		cmn_err(CE_NOTE,
		    "!IDN: %s: local domain not connected to an IDNnet",
		    proc);
#endif /* DEBUG */
		return (0);
	}

	/*
	 * Lock ordering protocols requires that we grab the
	 * global lock _before_ the local domain's lock.
	 * However, non-local domains must have their lock
	 * grabbed _before_ the global lock.
	 */
	IDN_SYNC_LOCK();
	IDN_GLOCK_EXCL();
	domset = idn.domset.ds_trans_on | idn.domset.ds_trans_off;
	if ((idn.state == IDNGS_OFFLINE) && !domset) {
#ifdef DEBUG
		cmn_err(CE_WARN,
		    "!IDN: %s: local domain not connected to an IDNnet",
		    proc);
#endif /* DEBUG */
		IDN_GUNLOCK();
		IDN_SYNC_UNLOCK();
		return (0);
	}

	if ((domid == IDN_NIL_DOMID) || (domid == idn.localid)) {
		domid = idn.localid;
		IDN_GSTATE_TRANSITION(IDNGS_DISCONNECT);
		IDN_SET_NEW_MASTERID(IDN_NIL_DOMID);
		domset = DOMAINSET_ALL;
		DOMAINSET_DEL(domset, idn.localid);
	} else {
		domset = DOMAINSET(domid);
	}
	IDN_GUNLOCK();

	if (waittime > 0)
		opcookie = idn_init_op(IDNOP_DISCONNECTED, domset, sep);

	idn_unlink_domainset(domset, fintype, IDNFIN_ARG_NONE, finopt, idnset);

	IDN_SYNC_UNLOCK();

	if (waittime > 0) {
		/*
		 * Well the unlink has successfully kicked off.
		 * Since process is asynchronous we need to wait
		 * for it to complete.
		 */
		PR_PROTO("%s:%d: WAITING for op(%s) for (domset 0%x)...\n",
		    proc, domid, idnop_str[IDNOP_DISCONNECTED],
		    domset);

		rv = idn_wait_op(opcookie, &domset, waittime);
	}

	if (rv == 0) {
		if (waittime > 0) {
			PR_PROTO("%s:%d: disconnect SUCCEEDED\n",
			    proc, domid);
		} else {
			PR_PROTO("%s:%d: disconnect KICKED OFF\n",
			    proc, domid);
		}
	} else {
		PR_PROTO("%s:%d: disconnect FAILED\n", proc, domid);
	}

	return (rv);
}

static void
idn_unlink_domainset(domainset_t domset, idn_fin_t fintype,
			idn_finarg_t finarg, idn_finopt_t finopt,
			boardset_t idnset)
{
	int		d;
	domainset_t	offset;
	procname_t	proc = "idn_unlink_domainset";

	ASSERT(IDN_SYNC_IS_LOCKED());

	/*
	 * Determine subset for which we have
	 * no active connections.
	 */
	offset = domset & ~(idn.domset.ds_trans_on |
	    idn.domset.ds_connected |
	    idn.domset.ds_trans_off |
	    idn.domset.ds_relink);
	/*
	 * Determine subset that are really candidates.
	 * Note that we include those already down the path
	 * since it's possible a request came in to upgrade
	 * their fintype (e.g. NORMAL->FORCE_SOFT).
	 */
	domset &= ~offset;

	if (offset)
		idn_update_op(IDNOP_DISCONNECTED, offset, NULL);

	IDN_GLOCK_EXCL();
	if ((finopt == IDNFIN_OPT_RELINK) && (idn.state != IDNGS_DISCONNECT)) {
		/*
		 * Don't add domains already transitioning off.
		 * If they caught on an earlier Reconfig wave then
		 * they'll already be in ds_relink anyway.  Otherwise,
		 * once a domain is transition off we can't upgrade
		 * him to a RELINK.
		 */
#ifdef DEBUG
		if (idn.domset.ds_hitlist & domset) {
			PR_HITLIST("%s: domset=%x, hitlist=%x, trans_off=%x "
			    "-> relink = %x -> %x\n",
			    proc, domset, idn.domset.ds_hitlist,
			    idn.domset.ds_relink, idn.domset.ds_trans_off,
			    idn.domset.ds_relink |
			    (domset & ~idn.domset.ds_trans_off));
		}
#endif /* DEBUG */

		domset &= ~idn.domset.ds_trans_off;
		idn.domset.ds_relink |= domset;
	} else {
		idn.domset.ds_relink &= ~domset;
	}
	/*
	 * Update the ds_trans_on/off so we don't waste
	 * time talking to these folks.
	 */
	idn.domset.ds_trans_on  &= ~domset;
	idn.domset.ds_trans_off |= domset;

	if (domset == 0) {
		if ((idn.domset.ds_trans_on |
		    idn.domset.ds_connected |
		    idn.domset.ds_trans_off |
		    idn.domset.ds_relink) == 0) {
			PR_HITLIST("%s:%x: HITLIST %x -> 0\n",
			    proc, domset, idn.domset.ds_hitlist);
			idn.domset.ds_hitlist = 0;
			IDN_GSTATE_TRANSITION(IDNGS_OFFLINE);
		}
		IDN_GUNLOCK();
		return;
	}
	IDN_GUNLOCK();

	for (d = 0; d < MAX_DOMAINS; d++) {
		idn_domain_t	*dp;
		idn_fin_t	ftype;

		if (!DOMAIN_IN_SET(domset, d))
			continue;

		dp = &idn_domain[d];
		IDN_DLOCK_EXCL(d);
		IDN_HISTORY_LOG(IDNH_RELINK, d, dp->dstate,
		    idn.domset.ds_relink);
		ftype = fintype;
		if ((dp->dcpu != IDN_NIL_DCPU) && dp->dhw.dh_boardset) {
			/*
			 * If domain is not in the IDNSET passed
			 * down then we need to upgrade this to
			 * hard-force in order to prevent possible
			 * system failures (arbstop).  This is simply
			 * extra protection beyond that checked by
			 * the SSP.  IDNSET contains the set of boards
			 * that have a "link" to the local domain,
			 * including the SMD regs.
			 */
			if ((idnset & dp->dhw.dh_boardset) == 0) {
				PR_PROTO("%s:%d: boardset 0x%x "
				    "NOT in IDNSET 0x%x\n",
				    proc, d, dp->dhw.dh_boardset,
				    idnset);
				if (ftype != IDNFIN_FORCE_HARD)
					cmn_err(CE_NOTE,
					    "!IDN: 222: no IDN linkage "
					    "found (b=0x%x, i=0x%x) "
					    "upgrading unlink %s to %s",
					    dp->dhw.dh_boardset,
					    idnset, idnfin_str[ftype],
					    idnfin_str[IDNFIN_FORCE_HARD]);

				ftype = IDNFIN_FORCE_HARD;
			} else {
				PR_PROTO("%s:%d: boardset 0x%x "
				    "FOUND in IDNSET 0x%x\n",
				    proc, d, dp->dhw.dh_boardset,
				    idnset);
			}
		}
		idn_disconnect(d, ftype, finarg, IDNDS_SYNC_TYPE(dp));
		IDN_DUNLOCK(d);
	}
}

/*
 * Return w/locks held.
 */
static int
idn_connect(int domid)
{
	idn_xdcargs_t	xargs;
	idn_domain_t	*dp = &idn_domain[domid];
	procname_t	proc = "idn_connect";

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	ASSERT(dp->dcpu != IDN_NIL_DCPU);

	if (dp->dstate != IDNDS_CLOSED) {
		if (DOMAIN_IN_SET(idn.domset.ds_trans_on |
		    idn.domset.ds_connected, domid)) {
			PR_PROTO("%s:%d: already connected or "
			    "in-progress\n", proc, domid);
		} else {
			PR_PROTO("%s:%d: current state (%s) != "
			    "CLOSED\n", proc, domid,
			    idnds_str[dp->dstate]);
		}
		return (-1);
	}

	ASSERT(!DOMAIN_IN_SET(idn.domset.ds_connected, domid));
	ASSERT(!DOMAIN_IN_SET(idn.domset.ds_trans_off, domid));

	dp->dxp = &xphase_nego;
	IDN_XSTATE_TRANSITION(dp, IDNXS_PEND);

	idn_xphase_transition(domid, NULL, xargs);

	return (0);
}

/*
 * Return w/locks held.
 */
static int
idn_disconnect(int domid, idn_fin_t fintype, idn_finarg_t finarg,
    idn_finsync_t finsync)
{
	int		new_masterid, new_cpuid = IDN_NIL_DCPU;
	uint_t		token;
	uint_t		finmaster;
	idn_xdcargs_t	xargs;
	idn_finopt_t	finopt;
	idn_domain_t	*dp = &idn_domain[domid];
	procname_t	proc = "idn_disconnect";

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	if (dp->dstate == IDNDS_CLOSED) {
		PR_PROTO("%s:%d: already CLOSED\n", proc, domid);
		idn_update_op(IDNOP_DISCONNECTED, DOMAINSET(domid), NULL);
		return (-1);
	}

	/*
	 * Terminate any outstanding commands that were
	 * targeted towards this domain.
	 */
	idn_terminate_cmd(domid, ECANCELED);

	/*
	 * Terminate any and all retries that may have
	 * outstanding for this domain.
	 */
	token = IDN_RETRY_TOKEN(domid, IDN_RETRY_TYPEALL);
	(void) idn_retry_terminate(token);

	/*
	 * Stop all outstanding message timers for
	 * this guy.
	 */
	IDN_MSGTIMER_STOP(domid, 0, 0);

	dp->dxp = &xphase_fin;
	IDN_XSTATE_TRANSITION(dp, IDNXS_PEND);
	if ((int)dp->dfin < (int)fintype) {
		/*
		 * You can only upgrade a fin type.
		 * We don't allow it to be downgraded
		 * as it's too dangerous since some
		 * state may have been blown away while
		 * we were fin'ing at a higher level.
		 */
		IDN_FSTATE_TRANSITION(dp, fintype);
	}

	dp->dfin_sync = finsync;
	PR_PROTO("%s:%d: disconnect synchronously = %s\n",
	    proc, domid, (finsync == IDNFIN_SYNC_OFF) ? "OFF" :
	    (finsync == IDNFIN_SYNC_NO) ? "NO" : "YES");

	IDN_GLOCK_SHARED();
	if (DOMAIN_IN_SET(idn.domset.ds_relink, domid) &&
	    (idn.state != IDNGS_DISCONNECT)) {
		finopt = IDNFIN_OPT_RELINK;
	} else {
		finopt = IDNFIN_OPT_UNLINK;
		PR_HITLIST("%s:%d: HITLIST %x -> %x\n",
		    proc, domid, idn.domset.ds_hitlist,
		    idn.domset.ds_hitlist | DOMAINSET(domid));
		DOMAINSET_ADD(idn.domset.ds_hitlist, domid);
	}

	CLR_XARGS(xargs);
	SET_XARGS_FIN_TYPE(xargs, dp->dfin);
	SET_XARGS_FIN_ARG(xargs, finarg);
	SET_XARGS_FIN_OPT(xargs, finopt);
	SET_XARGS_FIN_DOMSET(xargs, 0);		/* unused when msg = 0 */
	new_masterid = IDN_GET_NEW_MASTERID();
	IDN_GUNLOCK();
	if (new_masterid != IDN_NIL_DOMID)
		new_cpuid = idn_domain[new_masterid].dcpu;
	finmaster = MAKE_FIN_MASTER(new_masterid, new_cpuid);
	SET_XARGS_FIN_MASTER(xargs, finmaster);

	idn_xphase_transition(domid, NULL, xargs);

	return (0);
}

static int
idn_next_xstate(idn_xstate_t o_xstate, int err, uint_t msg)
{
	int		index;
	procname_t	proc = "idn_next_xstate";

	ASSERT(((int)o_xstate >= 0) && ((int)o_xstate <= 4));

	if (!msg)
		index = 0;
	else if ((msg & IDNP_MSGTYPE_MASK) == 0)
		index = (msg & IDNP_ACK) ? 3 : (msg & IDNP_NACK) ? 4 : -1;
	else
		index = (msg & IDNP_ACK) ? 2 :
		    !(msg & IDNP_ACKNACK_MASK) ? 1 : -1;

	if (index == -1) {
		STRING(str);

		INUM2STR(msg, str);
		PR_PROTO("%s: (msg = 0x%x(%s))\n", proc, msg, str);
		return (IDNXS_NIL);
	}

	if (err == -1) {
		int	n_xstate;
		/*
		 * Caller is just interested in querying is this
		 * is a valid message to receive in the current
		 * xstate.  A return value of IDNXS_NIL indicates
		 * that it's not.  A return value of non-IDNXS_NIL
		 * indicates it's cool.  An invalid message is
		 * determined by both err & !err states being IDNXS_NIL.
		 */
		n_xstate = idnxs_state_table[(int)o_xstate][index][0];
		if (n_xstate != IDNXS_NIL)
			return (n_xstate);
		else
			return (idnxs_state_table[(int)o_xstate][index][1]);
	} else {
		return (idnxs_state_table[(int)o_xstate][index][err ? 1 : 0]);
	}
}

static int
idn_select_candidate(domainset_t master_set)
{
	int		d, best_id = IDN_NIL_DOMID;
	uint_t		best_vote = 0;
	idn_domain_t	*dp;
	procname_t	proc = "idn_select_candidate";

	ASSERT(IDN_SYNC_IS_LOCKED());

	if (master_set == 0) {
		PR_PROTO("%s: %x -> %d\n", proc, master_set, IDN_NIL_DOMID);
		return (IDN_NIL_DOMID);
	}

	for (d = 0; d < MAX_DOMAINS; d++) {
		uint_t		vote;
		idn_vote_t	v;

		if (!DOMAIN_IN_SET(master_set, d))
			continue;

		dp = &idn_domain[d];

		if ((dp->domid == IDN_NIL_DOMID) ||
		    (dp->dcpu == IDN_NIL_DCPU) ||
		    ((v.ticket = dp->dvote.ticket) == 0))
			continue;

		vote = IDNVOTE_ELECT(v);

		if (vote > best_vote) {
			best_vote = vote;
			best_id = d;
		}
	}

	PR_PROTO("%s: %x -> %d\n", proc, master_set, best_id);

	return (best_id);
}

/*
 * If a non-zero value is returned then GLOCK will have been dropped.
 * Otherwise, routine returns with all incoming locks still held.
 */
static int
idn_select_master(int domid, int rmasterid, int rcpuid)
{
	char		*sel;
	int		lmasterid, masterid;
	int		do_reconfig = 0;
	int		lindex, rindex;
	idn_domain_t	*ldp, *rdp;
	uint_t		rvote, lvote;
	idn_master_select_t	select;
	procname_t	proc = "idn_select_master";

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_GLOCK_IS_EXCL());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	PR_PROTO("%s:%d: lmasterid = %d, rmasterid = %d, rcpuid = %d\n",
	    proc, domid, IDN_GET_MASTERID(), rmasterid, rcpuid);

	IDN_DLOCK_EXCL(idn.localid);

	ldp = &idn_domain[idn.localid];
	rdp = &idn_domain[domid];

	/*
	 * Clear master bits since mastership is derived from
	 * other information (local/remote idn.masterid/idn.new_masterid)
	 * and we don't want the vote master bit to confuse matters.
	 */
	lvote = IDNVOTE_ELECT(ldp->dvote);
	rvote = IDNVOTE_ELECT(rdp->dvote);

	lmasterid = IDN_GET_MASTERID();

	lindex = (lmasterid == IDN_NIL_DOMID) ? MASTER_IS_NONE :
	    (lmasterid == idn.localid) ? MASTER_IS_LOCAL :
	    (lmasterid == domid) ? MASTER_IS_REMOTE :
	    MASTER_IS_OTHER;

	rindex = (rmasterid == IDN_NIL_DOMID) ? MASTER_IS_NONE :
	    (rmasterid == domid) ? MASTER_IS_REMOTE :
	    (rmasterid == idn.localid) ? MASTER_IS_LOCAL :
	    MASTER_IS_OTHER;

	select = master_select_table[lindex][rindex];

	masterid = IDN_NIL_DOMID;

	/*
	 * Each case is responsible for dropping DLOCK(localid)
	 * and GLOCK if it doesn't select a master, unless a
	 * reconfig is necessary.
	 */
	switch (select) {
	case MASTER_SELECT_VOTE_RCFG:
		sel = "VOTE_RECONFIG";
		if (lvote > rvote) {
			/*
			 * If the local domain is the winner then remote
			 * domain will have to Reconfig.  We'll continue
			 * through the connection process anyway.  The
			 * remote domains will tell us to back-off while
			 * Reconfigs, but that's okay as we'll keep retrying.
			 */
			masterid = idn.localid;
		} else if (lvote < rvote) {
			do_reconfig = 1;
			/*
			 * GLOCK will get dropped once reconfig
			 * is kicked off.
			 */
		} else {
			cmn_err(CE_WARN,
			    "IDN: 206: cannot link domains "
			    "with equal votes (L(%d),R(%d),0x%x)",
			    idn.localid, domid, rvote);
			IDN_GUNLOCK();
		}
		IDN_DUNLOCK(idn.localid);
		break;

	case MASTER_SELECT_VOTE:
		sel = "VOTE";
		if (lvote > rvote) {
			masterid = idn.localid;
			ldp->dvote.v.master = 1;
			rdp->dvote.v.master = 0;
		} else if (lvote < rvote) {
			masterid = domid;
			ldp->dvote.v.master = 0;
			rdp->dvote.v.master = 1;
		} else {
			cmn_err(CE_WARN,
			    "IDN: 206: cannot link domains "
			    "with equal votes (L(%d),R(%d),0x%x)",
			    idn.localid, domid, rvote);
		}
		ASSERT(IDN_GET_MASTERID() == IDN_NIL_DOMID);
		if (masterid != IDN_NIL_DOMID) {
			IDN_SET_MASTERID(masterid);
			IDN_SET_NEW_MASTERID(IDN_NIL_DOMID);
		} else {
			IDN_GUNLOCK();
		}
		IDN_DUNLOCK(idn.localid);
		break;

	case MASTER_SELECT_REMOTE:
		sel = "REMOTE";
		masterid = domid;
		if (IDN_GET_MASTERID() == IDN_NIL_DOMID) {
			IDN_SET_MASTERID(masterid);
			IDN_SET_NEW_MASTERID(IDN_NIL_DOMID);
			ldp->dvote.v.master = 0;
			rdp->dvote.v.master = 1;
		}
		ASSERT(IDN_GET_MASTERID() == domid);
		IDN_DUNLOCK(idn.localid);
		break;

	case MASTER_SELECT_LOCAL:
		sel = "LOCAL";
		masterid = idn.localid;
		if (IDN_GET_MASTERID() == IDN_NIL_DOMID) {
			IDN_SET_MASTERID(masterid);
			IDN_SET_NEW_MASTERID(IDN_NIL_DOMID);
			ldp->dvote.v.master = 1;
			rdp->dvote.v.master = 0;
		}
		ASSERT(IDN_GET_MASTERID() == idn.localid);
		IDN_DUNLOCK(idn.localid);
		break;

	case MASTER_SELECT_CONNECT:
		sel = "CONNECT";
		if (rmasterid == lmasterid) {
			/*
			 * Local and remote have same master,
			 * let him come onboard.
			 */
			masterid = lmasterid;
			IDN_DUNLOCK(idn.localid);

		} else {
			int	rv;

			IDN_DUNLOCK(idn.localid);
			IDN_GUNLOCK();
			IDN_DLOCK_EXCL(rmasterid);
			PR_PROTO("%s:%d: attempting connect w/remote "
			    "master %d\n",
			    proc, domid, rmasterid);
			rv = idn_open_domain(rmasterid, rcpuid, 0);
			if (rv == 0) {
				idn_connect(rmasterid);
			} else if (rv < 0) {
				cmn_err(CE_WARN,
				    "IDN: 205: (%s) failed to "
				    "open-domain(%d,%d)",
				    proc, rmasterid, rcpuid);
			} else {
				/*
				 * Must already have a connection going.
				 */
				PR_PROTO("%s:%d: failed "
				    "idn_open_domain(%d,%d,0) "
				    "(rv = %d)\n",
				    proc, domid, rmasterid,
				    rcpuid, rv);
			}
			IDN_DUNLOCK(rmasterid);
		}
		break;

	case MASTER_SELECT_WAIT:
		sel = "WAIT";
		/*
		 * If the remote domain has the same master as the local
		 * domain then there's no need to wait.
		 */
		if (rmasterid == lmasterid) {
			masterid = lmasterid;
		} else {
			IDN_GUNLOCK();
		}
		IDN_DUNLOCK(idn.localid);
		break;

	case MASTER_SELECT_ERROR:
		sel = "ERROR";
		/*
		 * Hit impossible condition.
		 */
		cmn_err(CE_WARN,
		    "IDN: 207: local/remote master-id conflict "
		    "(%d.lmasterid = %d, %d.rmasterid = %d)",
		    idn.localid, lmasterid, domid, rmasterid);
		IDN_GUNLOCK();
		IDN_DUNLOCK(idn.localid);
		break;

	default:
		cmn_err(CE_WARN,
		    "IDN: 208: %s: unknown case (%d)",
		    proc, (int)select);
		IDN_GUNLOCK();
		IDN_DUNLOCK(idn.localid);
		ASSERT(0);
		break;
	}

	if (masterid == IDN_NIL_DOMID) {
		PR_PROTO("%s:%d: NO MASTER SELECTED (rmstr=%d) sel=%s\n",
		    proc, domid, rmasterid, sel);
	} else {
		PR_PROTO("%s:%d: MASTER SELECTED = %d (%s)\n",
		    proc, domid, masterid,
		    (masterid == idn.localid) ? "LOCAL" :
		    (masterid == domid) ? "REMOTE" : "OTHER");
	}

	if (do_reconfig) {
		domainset_t	dis_set;

		/*
		 * Local domain already has a master.
		 * Need to dismantle all connections
		 * and reestablish one with new master.
		 */
		IDN_GKSTAT_GLOBAL_EVENT(gk_reconfigs, gk_reconfig_last);

		PR_PROTO("%s:%d: RECONFIG new masterid = %d\n",
		    proc, domid, domid);

		IDN_GSTATE_TRANSITION(IDNGS_RECONFIG);
		IDN_SET_NEW_MASTERID(domid);
		IDN_GUNLOCK();

		dis_set = idn.domset.ds_trans_on | idn.domset.ds_connected;
		DOMAINSET_DEL(dis_set, domid);

		idn_unlink_domainset(dis_set, IDNFIN_NORMAL, IDNFIN_ARG_NONE,
		    IDNFIN_OPT_RELINK, BOARDSET_ALL);
	}

	return ((masterid == IDN_NIL_DOMID) ? -1 : 0);
}

/*ARGSUSED1*/
static void
idn_retry_query(uint_t token, void *arg)
{
	idn_retry_t	rtype = IDN_RETRY_TOKEN2TYPE(token);
	int		d, domid = IDN_RETRY_TOKEN2DOMID(token);
	idn_domain_t	*dp = &idn_domain[domid];
	idn_synccmd_t	sync_cmd;
	domainset_t	query_set, my_ready_set;
	procname_t	proc = "idn_retry_query";

	IDN_SYNC_LOCK();
	IDN_DLOCK_EXCL(domid);

	switch (rtype) {
	case IDNRETRY_CONQ:
		sync_cmd = IDNSYNC_CONNECT;
		my_ready_set = idn.domset.ds_ready_on | idn.domset.ds_connected;
		my_ready_set &= ~idn.domset.ds_trans_off;
		DOMAINSET_ADD(my_ready_set, idn.localid);
		break;

	case IDNRETRY_FINQ:
		sync_cmd = IDNSYNC_DISCONNECT;
		my_ready_set = idn.domset.ds_ready_off |
		    ~idn.domset.ds_connected;
		break;

	default:
		IDN_DUNLOCK(domid);
		IDN_SYNC_UNLOCK();
		return;
	}

	if (dp->dsync.s_cmd == sync_cmd)
		my_ready_set |= dp->dsync.s_set_rdy;

	query_set = idn_sync_register(domid, sync_cmd, 0, IDNSYNC_REG_QUERY);

	PR_PROTO("%s:%d: query_set = 0x%x\n", proc, domid, query_set);

	if (query_set == 0) {
		IDN_DUNLOCK(domid);
		IDN_SYNC_UNLOCK();
		return;
	}

	for (d = 0; d < MAX_DOMAINS; d++) {
		if (!DOMAIN_IN_SET(query_set, d))
			continue;

		dp = &idn_domain[d];
		if (d != domid)
			IDN_DLOCK_EXCL(d);

		if ((dp->dsync.s_cmd == sync_cmd) ||
		    (!dp->dcookie_send &&
		    (rtype == IDNRETRY_CONQ))) {
			if (d != domid)
				IDN_DUNLOCK(d);
			continue;
		}

		IDN_SYNC_QUERY_UPDATE(domid, d);

		if (rtype == IDNRETRY_CONQ)
			idn_send_con(d, NULL, IDNCON_QUERY, my_ready_set);
		else
			idn_send_fin(d, NULL, IDNFIN_QUERY, IDNFIN_ARG_NONE,
			    IDNFIN_OPT_NONE, my_ready_set, NIL_FIN_MASTER);
		if (d != domid)
			IDN_DUNLOCK(d);
	}

	IDN_DUNLOCK(domid);
	IDN_SYNC_UNLOCK();
}

static int
idn_send_nego(int domid, idn_msgtype_t *mtp, domainset_t conset)
{
	idn_domain_t	*ldp, *dp;
	int		d, masterid;
	uint_t		dmask;
	uint_t		acknack;
	uint_t		ticket;
	idnneg_dset_t	dset;
	idn_msgtype_t	mt;
	procname_t	proc = "idn_send_nego";

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	if (mtp) {
		acknack = mtp->mt_mtype & IDNP_ACKNACK_MASK;
		mt.mt_mtype = mtp->mt_mtype;
		mt.mt_atype = mtp->mt_atype;
		mt.mt_cookie = mtp->mt_cookie;
	} else {
		acknack = 0;
		mt.mt_mtype = IDNP_NEGO;
		mt.mt_atype = 0;
		mt.mt_cookie = IDN_TIMER_PUBLIC_COOKIE;
	}

	IDN_GLOCK_SHARED();

	dp = &idn_domain[domid];
	ldp = &idn_domain[idn.localid];

	if ((idn.state == IDNGS_RECONFIG) ||
	    ((masterid = IDN_GET_MASTERID()) == IDN_NIL_DOMID)) {
		masterid = IDN_GET_NEW_MASTERID();
		if ((masterid == idn.localid) || (masterid == domid)) {
			/*
			 * We only send the new-master "hint" to
			 * "other" domains.  If the new-master is
			 * ourself or we're talking to the new-master
			 * then we need to be accurate about our
			 * real master so that the correct master
			 * is selected.
			 */
			masterid = IDN_NIL_DOMID;
		}
	}

	DOMAINSET_DEL(conset, idn.localid);
	DOMAINSET_DEL(conset, domid);
	/*
	 * Exclude domains from conset that are on
	 * remote domain's hitlist.  He's not interested
	 * in hearing about them.  SSP is probably requesting
	 * such domains be unlinked - will eventually get to
	 * local domain.
	 */
	conset &= ~idn.domset.ds_hitlist;
	if ((masterid != IDN_NIL_DOMID) &&
	    DOMAIN_IN_SET(idn.domset.ds_hitlist, masterid)) {
		PR_PROTO("%s:%d: masterid(%d) on hitlist(0x%x) -> -1\n",
		    proc, domid, masterid, idn.domset.ds_hitlist);
		/*
		 * Yikes, our chosen master is on the hitlist!
		 */
		masterid = IDN_NIL_DOMID;
	}

	dmask = IDNNEG_DSET_MYMASK();
	IDNNEG_DSET_INIT(dset, dmask);
	for (d = 0; d < MAX_DOMAINS; d++) {
		int	cpuid;

		if (!DOMAIN_IN_SET(conset, d))
			continue;

		if ((cpuid = idn_domain[d].dcpu) == IDN_NIL_DCPU) {
			ASSERT(d != masterid);
			continue;
		}

		IDNNEG_DSET_SET(dset, d, cpuid, dmask);
	}
	IDNNEG_DSET_SET_MASTER(dset, domid, masterid);
	ASSERT((masterid != IDN_NIL_DOMID) ?
	    (idn_domain[masterid].dcpu != IDN_NIL_DCPU) : 1);
	IDN_GUNLOCK();

	IDN_DLOCK_SHARED(idn.localid);
	ticket = IDNVOTE_BASICS(ldp->dvote);
	/*
	 * We just want to send basic vote components without an
	 * indication of mastership (master bit) since that's primarily
	 * for local domain's usage.  There is more correct master
	 * indications in the DSET.  Recall that if we were in a
	 * Reconfig we would have transmitted the "new_masterid"
	 * which might conflict with the local domain's vote.v.master
	 * bit if he was originally the master prior to the Reconfig.
	 */

	PR_PROTO("%s:%d: sending nego%sto (cpu %d) "
	    "[v=0x%x, cs=0x%x, mstr=%d]\n",
	    proc, domid,
	    (acknack & IDNP_ACK) ? "+ack " :
	    (acknack & IDNP_NACK) ? "+nack " : " ",
	    dp->dcpu, ticket, conset, masterid);

	IDN_MSGTIMER_START(domid, IDNP_NEGO, 0,
	    idn_msg_waittime[IDNP_NEGO], &mt.mt_cookie);

	IDNXDC(domid, &mt, ticket, dset[0], dset[1], dset[2]);

	IDN_DUNLOCK(idn.localid);

	return (0);
}

static int
idn_recv_nego(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs,
    ushort_t dcookie)
{
	uint_t		msg = mtp->mt_mtype;
	idn_msgtype_t	mt;
	idn_domain_t	*dp = &idn_domain[domid];
	idn_xdcargs_t	nargs;
	procname_t	proc = "idn_recv_nego";

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	mt.mt_cookie = mtp->mt_cookie;

#ifdef DEBUG
	if (DOMAIN_IN_SET(idn.domset.ds_hitlist, domid)) {
		PR_HITLIST("%s:%d: dcpu=%d, dstate=%s, msg=%x, "
		    "hitlist=%x\n",
		    proc, domid, dp->dcpu, idnds_str[dp->dstate],
		    msg, idn.domset.ds_hitlist);
	}
#endif /* DEBUG */

	if (dp->dcpu == IDN_NIL_DCPU) {
		int		cpuid;
		uint_t		ticket;
		/*
		 * Brandnew link.  Need to open a new domain entry.
		 */
		ticket = GET_XARGS_NEGO_TICKET(xargs);
		cpuid = dp->dcpu_last;
		ASSERT(VALID_CPUID(cpuid));

		if (idn_open_domain(domid, cpuid, ticket) != 0) {
			PR_PROTO("%s:%d: FAILED to open doamin "
			    "(ticket = 0x%x)\n",
			    proc, domid, ticket);
			return (-1);
		}
	}

	if ((msg & IDNP_MSGTYPE_MASK) == IDNP_NEGO) {
		PR_PROTO("%s:%d: assigned SEND cookie 0x%x\n",
		    proc, domid, dcookie);
		dp->dcookie_send = dcookie;
	}

	if ((dp->dxp == NULL) && IDNDS_IS_CLOSED(dp)) {
		dp->dxp = &xphase_nego;
		IDN_XSTATE_TRANSITION(dp, IDNXS_PEND);
	} else if (dp->dxp != &xphase_nego) {
		if (msg & IDNP_MSGTYPE_MASK) {
			/*
			 * If we already have a connection to somebody
			 * trying to initiate a connection to us, then
			 * possibly we've awaken from a coma or he did.
			 * In any case, dismantle current connection
			 * and attempt to establish a new one.
			 */
			if (dp->dstate == IDNDS_CONNECTED) {
				DOMAINSET_ADD(idn.domset.ds_relink, domid);
				IDN_HISTORY_LOG(IDNH_RELINK, domid,
				    dp->dstate, idn.domset.ds_relink);
				idn_disconnect(domid, IDNFIN_NORMAL,
				    IDNFIN_ARG_NONE, IDNFIN_SYNC_YES);
			} else {
				mt.mt_mtype = IDNP_NACK;
				mt.mt_atype = msg;

				CLR_XARGS(nargs);

				if (DOMAIN_IN_SET(idn.domset.ds_hitlist,
				    domid)) {
					SET_XARGS_NACK_TYPE(nargs,
					    IDNNACK_EXIT);
				} else {
					int	new_masterid;
					int	new_cpuid = IDN_NIL_DCPU;

					SET_XARGS_NACK_TYPE(nargs,
					    IDNNACK_RETRY);
					IDN_GLOCK_SHARED();
					new_masterid = IDN_GET_NEW_MASTERID();
					if (new_masterid == IDN_NIL_DOMID)
						new_masterid =
						    IDN_GET_MASTERID();
					if (new_masterid != IDN_NIL_DOMID) {
						idn_domain_t	*mdp;

						mdp = &idn_domain[new_masterid];
						new_cpuid = mdp->dcpu;
					}
					SET_XARGS_NACK_ARG1(nargs,
					    new_masterid);
					SET_XARGS_NACK_ARG2(nargs, new_cpuid);
					IDN_GUNLOCK();
				}
				idn_send_acknack(domid, &mt, nargs);
			}
		}
		return (0);
	}

	idn_xphase_transition(domid, mtp, xargs);

	return (0);
}

/*ARGSUSED1*/
static void
idn_retry_nego(uint_t token, void *arg)
{
	int		domid = IDN_RETRY_TOKEN2DOMID(token);
	int		new_masterid;
	idn_domain_t	*dp = &idn_domain[domid];
	idn_xdcargs_t	xargs;
	procname_t	proc = "idn_retry_nego";

	ASSERT(IDN_RETRY_TOKEN2TYPE(token) == IDNRETRY_NEGO);

	IDN_SYNC_LOCK();
	IDN_DLOCK_EXCL(domid);

	if (dp->dxp != &xphase_nego) {
		STRING(str);

#ifdef DEBUG
		if (dp->dxp) {
			INUM2STR(dp->dxp->xt_msgtype, str);
		}
#endif /* DEBUG */

		PR_PROTO("%s:%d: dxp(%s) != NEGO...bailing...\n",
		    proc, domid, dp->dxp ? str : "NULL");
		IDN_DUNLOCK(domid);
		IDN_SYNC_UNLOCK();
		return;
	}

	if (dp->dxstate != IDNXS_PEND) {
		PR_PROTO("%s:%d: xstate(%s) != %s...bailing\n",
		    proc, domid, idnxs_str[dp->dxstate],
		    idnxs_str[IDNXS_PEND]);
		IDN_DUNLOCK(domid);
		IDN_SYNC_UNLOCK();
		return;
	}

	IDN_GLOCK_SHARED();
	if (idn.state == IDNGS_RECONFIG) {
		/*
		 * Have to try again later after
		 * reconfig has completed.
		 */
		PR_PROTO("%s:%d: reconfig in-progress...try later\n",
		    proc, domid);
		idn_retry_submit(idn_retry_nego, NULL, token,
		    idn_msg_retrytime[IDNP_NEGO]);
		IDN_GUNLOCK();
		IDN_DUNLOCK(domid);
		IDN_SYNC_UNLOCK();
		return;
	}
	new_masterid = IDN_GET_NEW_MASTERID();
	if ((idn.state == IDNGS_CONNECT) &&
	    (new_masterid != IDN_NIL_DOMID) &&
	    (domid != new_masterid) &&
	    (idn.localid != new_masterid)) {
		/*
		 * We have a new master pending and this
		 * guy isn't it.  Wait until the local domain
		 * has a chance to connect with the new
		 * master before going forward with this
		 * guy.
		 */
		PR_PROTO("%s:%d: waiting for connect to new master %d\n",
		    proc, domid, IDN_GET_NEW_MASTERID());
		idn_retry_submit(idn_retry_nego, NULL, token,
		    idn_msg_retrytime[IDNP_NEGO]);
		IDN_GUNLOCK();
		IDN_DUNLOCK(domid);
		IDN_SYNC_UNLOCK();
		return;
	}
	IDN_GUNLOCK();

	idn_xphase_transition(domid, NULL, xargs);

	IDN_DUNLOCK(domid);
	IDN_SYNC_UNLOCK();
}

static int
idn_check_nego(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	int		d, new_masterid, masterid;
	int		cpuid, m_cpuid = -1;
	uint_t		dmask;
	uint_t		msg = mtp ? mtp->mt_mtype : 0;
	idn_domain_t	*dp, *ldp;
	domainset_t	con_set, pending_set;
	idnneg_dset_t	dset;
	procname_t	proc = "idn_check_nego";

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	dp = &idn_domain[domid];
	ldp = &idn_domain[idn.localid];

	if (msg & IDNP_NACK) {
		if (GET_XARGS_NACK_TYPE(xargs) == IDNNACK_EXIT) {
			PR_HITLIST("%s:%d(%s): (msg=%x) EXIT received, "
			    "adding to hitlist %x -> %x\n",
			    proc, domid, idnds_str[dp->dstate], msg,
			    idn.domset.ds_hitlist,
			    idn.domset.ds_hitlist | DOMAINSET(domid));

			DOMAINSET_ADD(idn.domset.ds_hitlist, domid);
			return (-1);
		} else {
			return (0);
		}
	}

	if (DOMAIN_IN_SET(idn.domset.ds_hitlist, domid)) {
		PR_HITLIST("%s:%d(%s): (msg=%x) domain in hitlist (%x) - "
		    "exiting phase\n",
		    proc, domid, idnds_str[dp->dstate], msg,
		    idn.domset.ds_hitlist);
		return (-1);
	}

	if ((dp->dstate == IDNDS_NEGO_PEND) && (msg & IDNP_MSGTYPE_MASK) &&
	    (msg & IDNP_ACK))		/* nego+ack */
		return (1);

	dmask = (uint_t)-1;

	IDN_GLOCK_EXCL();
	if (idn.state == IDNGS_DISCONNECT) {
		PR_PROTO("%s:%d: DISCONNECT in-progress >>> EXIT\n",
		    proc, domid);
		IDN_GUNLOCK();
		return (-1);
	} else if (idn.state == IDNGS_OFFLINE) {
		IDN_GSTATE_TRANSITION(IDNGS_CONNECT);
		IDN_PREP_HWINIT();
		IDN_DLOCK_EXCL(idn.localid);
		ldp->dvote.v.connected = 0;
		IDN_DUNLOCK(idn.localid);
	}

	if (!DOMAIN_IN_SET(idn.domset.ds_trans_on, domid)) {
		DOMAINSET_ADD(idn.domset.ds_trans_on, domid);
		IDN_HISTORY_LOG(IDNH_NEGO, domid,
		    idn.domset.ds_trans_on,
		    idn.domset.ds_connected);
	}

	switch (idn.state) {
	case IDNGS_RECONFIG:
		PR_PROTO("%s:%d: RECONFIG in-progress >>> RETRY\n",
		    proc, domid);
		IDN_GUNLOCK();
		return (1);

	case IDNGS_CONNECT:
		new_masterid = IDN_GET_NEW_MASTERID();
		if ((new_masterid != IDN_NIL_DOMID) &&
		    (domid != new_masterid) &&
		    (idn.localid != new_masterid)) {
			PR_PROTO("%s:%d: waiting for connect to "
			    "new master %d\n",
			    proc, domid, IDN_GET_NEW_MASTERID());
			IDN_GUNLOCK();
			return (1);
		}
		break;

	default:
		break;
	}

	ASSERT((idn.state == IDNGS_CONNECT) || (idn.state == IDNGS_ONLINE));

	con_set = 0;

	if (msg) {
		idn_domain_t	*mdp;
		idn_vote_t	vote;

		vote.ticket = GET_XARGS_NEGO_TICKET(xargs);
		/*
		 * Sender should note have set master bit,
		 * but just in case clear it so local domain
		 * doesn't get confused.
		 */
		vote.v.master = 0;
		dp->dvote.ticket = vote.ticket;
		GET_XARGS_NEGO_DSET(xargs, dset);
		/*LINTED*/
		IDNNEG_DSET_GET_MASK(dset, domid, dmask);
		IDNNEG_DSET_GET_MASTER(dset, new_masterid);
		if (new_masterid == IDNNEG_NO_MASTER) {
			new_masterid = IDN_NIL_DOMID;
		} else {
			/*
			 * Remote domain has a master.  Find
			 * his cpuid in the dset.  We may need
			 * it to initiate a connection.
			 */
			if (new_masterid == domid) {
				m_cpuid = dp->dcpu;
			} else {
				IDNNEG_DSET_GET(dset, new_masterid, m_cpuid,
				    dmask);
				if (m_cpuid == -1) {
					/*
					 * Something is bogus if remote domain
					 * is reporting a valid masterid, but
					 * doesn't have the cpuid for it.
					 */
					cmn_err(CE_WARN,
					    "IDN: 209: remote domain (ID "
					    "%d, CPU %d) reporting master "
					    "(ID %d) without CPU ID",
					    domid, dp->dcpu, new_masterid);
					DOMAINSET_ADD(idn.domset.ds_hitlist,
					    domid);
					IDN_GUNLOCK();
					return (-1);
				}
			}
		}

		for (d = 0; d < MAX_DOMAINS; d++) {
			if ((d == idn.localid) || (d == domid))
				continue;
			IDNNEG_DSET_GET(dset, d, cpuid, dmask);
			if (cpuid != -1) {
				DOMAINSET_ADD(con_set, d);
			}
		}

#ifdef DEBUG
		if (idn.domset.ds_hitlist) {
			PR_HITLIST("%s:%d: con_set %x -> %x (hitlist = %x)\n",
			    proc, domid, con_set,
			    con_set & ~idn.domset.ds_hitlist,
			    idn.domset.ds_hitlist);
		}
#endif /* DEBUG */

		con_set &= ~idn.domset.ds_hitlist;

		ASSERT(!DOMAIN_IN_SET(con_set, idn.localid));
		ASSERT(!DOMAIN_IN_SET(con_set, domid));

		if ((new_masterid != IDN_NIL_DOMID) &&
		    DOMAIN_IN_SET(idn.domset.ds_hitlist, new_masterid)) {
			PR_HITLIST("%s:%d: new_mstr %d -> -1 (hitlist = %x)\n",
			    proc, domid, new_masterid,
			    idn.domset.ds_hitlist);
			IDN_GUNLOCK();
			return (1);
		}

		if (idn_select_master(domid, new_masterid, m_cpuid) < 0) {
			/*
			 * Returns w/GLOCK dropped if error.
			 */
			return (1);
		}

		masterid = IDN_GET_MASTERID();
		ASSERT(masterid != IDN_NIL_DOMID);

		if (idn.state == IDNGS_CONNECT) {
			/*
			 * This is the initial connection for
			 * the local domain.
			 */
			IDN_DLOCK_EXCL(idn.localid);

			if (masterid == idn.localid) {
				if (idn_master_init() < 0) {
					cmn_err(CE_WARN,
					    "IDN: 210: failed to init "
					    "MASTER context");
					ldp->dvote.v.master = 0;
					IDN_DUNLOCK(idn.localid);
					IDN_GSTATE_TRANSITION(IDNGS_DISCONNECT);
					IDN_SET_MASTERID(IDN_NIL_DOMID);
					IDN_GUNLOCK();
					return (-1);
				}
				DSLAB_LOCK_EXCL(idn.localid);
				ldp->dslab_state = DSLAB_STATE_LOCAL;
				DSLAB_UNLOCK(idn.localid);
				ldp->dvote.v.connected = 1;
			} else {
				/*
				 * Either the remote domain is the
				 * master or its a new slave trying
				 * to connect to us.  We can't allow
				 * further progress until we've
				 * sync'd up with the master.
				 */
				if (masterid != domid) {
					IDN_DUNLOCK(idn.localid);
					IDN_GUNLOCK();
					return (1);
				}
				DSLAB_LOCK_EXCL(idn.localid);
				ldp->dslab_state = DSLAB_STATE_REMOTE;
				DSLAB_UNLOCK(idn.localid);
			}
			IDN_DUNLOCK(idn.localid);
			/*
			 * We've sync'd up with the new master.
			 */
			IDN_GSTATE_TRANSITION(IDNGS_ONLINE);
		}

		mdp = &idn_domain[masterid];

		if ((masterid != domid) && !IDNDS_CONFIG_DONE(mdp)) {
			/*
			 * We can't progress any further with
			 * other domains until we've exchanged all
			 * the necessary CFG info with the master,
			 * i.e. until we have a mailbox area from
			 * which we can allocate mailboxes to
			 * other domains.
			 */
			PR_PROTO("%s:%d: still exchanging CFG "
			    "w/master(%d)\n", proc, domid, masterid);
			IDN_GUNLOCK();
			return (1);
		}

		DSLAB_LOCK_EXCL(domid);
		dp->dslab_state = ldp->dslab_state;
		DSLAB_UNLOCK(domid);
		if (idn.state != IDNGS_ONLINE) {
			IDN_GSTATE_TRANSITION(IDNGS_ONLINE);
		}
	}

	IDN_GUNLOCK();

	pending_set = con_set;
	pending_set &= ~(idn.domset.ds_trans_on | idn.domset.ds_connected);
	idn.domset.ds_trans_on |= pending_set;

	con_set |= idn.domset.ds_trans_on | idn.domset.ds_connected;
	con_set &= ~idn.domset.ds_trans_off;
	DOMAINSET_ADD(con_set, idn.localid);

	if (dp->dsync.s_cmd != IDNSYNC_CONNECT) {
		idn_sync_exit(domid, IDNSYNC_DISCONNECT);
		idn_sync_enter(domid, IDNSYNC_CONNECT,
		    con_set, DOMAINSET(idn.localid), idn_xstate_transfunc,
		    (void *)IDNP_CON);
	}

	/*
	 * Get this domain registered as an expected domain on
	 * the remaining domains in the CONNECT synchronization.
	 */
	(void) idn_sync_register(domid, IDNSYNC_CONNECT, 0, IDNSYNC_REG_NEW);

	/*
	 * Note that if (msg == 0), i.e. then there will be
	 * no dset and also pending_set will be 0.
	 * So, the following loop will never attempt to
	 * look at the dset unless (msg != 0), implying
	 * that we've been through the initial code above
	 * and have initialized dmask.
	 */
	ASSERT(pending_set ? (dmask != (uint_t)-1) : 1);

	for (d = 0; d < MAX_DOMAINS; d++) {
		int	rv;

		if (!DOMAIN_IN_SET(pending_set, d))
			continue;

		ASSERT((d != idn.localid) && (d != domid));

		dp = &idn_domain[d];

		IDNNEG_DSET_GET(dset, d, cpuid, dmask);
		if (cpuid == -1) {
			PR_PROTO("%s:%d: failed to get cpuid from dset "
			    "for domain %d (pset = 0x%x)\n",
			    proc, domid, d, pending_set);
			DOMAINSET_DEL(idn.domset.ds_trans_on, d);
			continue;
		}

		IDN_DLOCK_EXCL(d);
		if ((rv = idn_open_domain(d, cpuid, 0)) != 0) {
			PR_PROTO("%s:%d: failed "
			    "idn_open_domain(%d,%d,0) (rv = %d)\n",
			    proc, domid, d, cpuid, rv);
			if (rv < 0) {
				cmn_err(CE_WARN,
				    "IDN: 205: (%s) failed to "
				    "open-domain(%d,%d)",
				    proc, d, cpuid);
				DOMAINSET_DEL(idn.domset.ds_trans_on, d);
			} else if (DOMAIN_IN_SET(idn.domset.ds_trans_off, d)) {
				/*
				 * We've requested to connect to a domain
				 * from which we're disconnecting.  We
				 * better mark this guy for relinking.
				 */
				DOMAINSET_ADD(idn.domset.ds_relink, d);
				IDN_HISTORY_LOG(IDNH_RELINK, d, dp->dstate,
				    idn.domset.ds_relink);
			}
			IDN_DUNLOCK(d);
			continue;
		}

		idn_connect(d);

		IDN_DUNLOCK(d);
	}

	return (0);
}

/*ARGSUSED*/
static void
idn_action_nego_pend(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	uint_t		msg = mtp ? mtp->mt_mtype : 0;
	idn_msgtype_t	mt;
	domainset_t	con_set;

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	con_set = idn.domset.ds_trans_on | idn.domset.ds_connected;
	con_set &= ~idn.domset.ds_trans_off;

	if (!msg) {
		idn_send_nego(domid, NULL, con_set);
	} else {
		mt.mt_mtype = IDNP_NEGO | IDNP_ACK;
		mt.mt_atype = 0;
		mt.mt_cookie = mtp->mt_cookie;
		idn_send_nego(domid, &mt, con_set);
	}
}

/*ARGSUSED*/
static void
idn_error_nego(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	int	new_masterid, new_cpuid;
	int	retry = 1;
	uint_t	msg = mtp ? mtp->mt_mtype : 0;
	uint_t	token;

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	if (msg & IDNP_NACK) {
		idn_nack_t	nack;

		nack = GET_XARGS_NACK_TYPE(xargs);
		switch (nack) {
		case IDNNACK_RETRY:
			new_masterid = (int)GET_XARGS_NACK_ARG1(xargs);
			new_cpuid    = (int)GET_XARGS_NACK_ARG2(xargs);
			break;

		case IDNNACK_EXIT:
			retry = 0;
			/*FALLTHROUGH*/

		default:
			new_masterid = IDN_NIL_DOMID;
			new_cpuid    = IDN_NIL_DCPU;
			break;
		}
		idn_nego_cleanup_check(domid, new_masterid, new_cpuid);
	}

	if (msg & IDNP_MSGTYPE_MASK) {
		idn_msgtype_t	mt;
		idn_xdcargs_t	nargs;

		mt.mt_mtype = IDNP_NACK;
		mt.mt_atype = msg;
		mt.mt_cookie = mtp->mt_cookie;
		CLR_XARGS(nargs);
		SET_XARGS_NACK_TYPE(nargs, IDNNACK_RETRY);
		IDN_GLOCK_SHARED();
		new_masterid = IDN_GET_NEW_MASTERID();
		if (new_masterid == IDN_NIL_DOMID)
			new_masterid = IDN_GET_MASTERID();
		if (new_masterid != IDN_NIL_DOMID)
			new_cpuid = idn_domain[new_masterid].dcpu;
		else
			new_cpuid = IDN_NIL_DCPU;
		SET_XARGS_NACK_ARG1(nargs, new_masterid);
		SET_XARGS_NACK_ARG2(nargs, new_cpuid);
		IDN_GUNLOCK();
		idn_send_acknack(domid, &mt, nargs);
	}

	if (retry) {
		token = IDN_RETRY_TOKEN(domid, IDNRETRY_NEGO);
		idn_retry_submit(idn_retry_nego, NULL, token,
		    idn_msg_retrytime[(int)IDNRETRY_NEGO]);
	} else {
		DOMAINSET_DEL(idn.domset.ds_relink, domid);
		IDN_RESET_COOKIES(domid);
		idn_disconnect(domid, IDNFIN_NORMAL, IDNFIN_ARG_NONE,
		    IDNDS_SYNC_TYPE(&idn_domain[domid]));
	}
}

/*ARGSUSED*/
static void
idn_action_nego_sent(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	uint_t		msg = mtp ? mtp->mt_mtype : 0;
	domainset_t	conset;
	idn_msgtype_t	mt;

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	mt.mt_cookie = mtp ? mtp->mt_cookie : 0;

	conset = idn.domset.ds_trans_on | idn.domset.ds_connected;
	conset &= ~idn.domset.ds_trans_off;

	if ((msg & IDNP_ACKNACK_MASK) == 0) {
		/*
		 * nego
		 */
		mt.mt_mtype = IDNP_NEGO | IDNP_ACK;
		mt.mt_atype = 0;
		idn_send_nego(domid, &mt, conset);
	} else if (msg & IDNP_MSGTYPE_MASK) {
		int		d;
		idn_xdcargs_t	nargs;
		idnneg_dset_t	dset;
		uint_t		dmask;
		idn_vote_t	vote;

		mt.mt_mtype = IDNP_ACK;
		mt.mt_atype = msg;
		DOMAINSET_DEL(conset, idn.localid);
		DOMAINSET_DEL(conset, domid);

		dmask = IDNNEG_DSET_MYMASK();
		IDNNEG_DSET_INIT(dset, dmask);
		for (d = 0; d < MAX_DOMAINS; d++) {
			int	cpuid;

			if (!DOMAIN_IN_SET(conset, d))
				continue;

			if ((cpuid = idn_domain[d].dcpu) == IDN_NIL_DCPU)
				continue;

			IDNNEG_DSET_SET(dset, d, cpuid, dmask);
		}
		IDNNEG_DSET_SET_MASTER(dset, domid, IDN_GET_MASTERID());
		ASSERT((IDN_GET_MASTERID() != IDN_NIL_DOMID) ?
		    (idn_domain[IDN_GET_MASTERID()].dcpu != IDN_NIL_DCPU) : 1);
		vote.ticket = idn_domain[idn.localid].dvote.ticket;
		vote.v.master = 0;
		CLR_XARGS(nargs);
		SET_XARGS_NEGO_TICKET(nargs, vote.ticket);
		SET_XARGS_NEGO_DSET(nargs, dset);
		/*
		 * nego+ack
		 */
		idn_send_acknack(domid, &mt, nargs);
	} else {
		uint_t		token;
		int		new_masterid, new_cpuid;
		int		retry = 1;
		idn_nack_t	nack;
		/*
		 * nack - retry
		 *
		 * It's possible if we've made it this far that
		 * we may have already chosen a master and this
		 * dude might be it!  If it is we need to clean up.
		 */
		nack = GET_XARGS_NACK_TYPE(xargs);
		switch (nack) {
		case IDNNACK_RETRY:
			new_masterid = (int)GET_XARGS_NACK_ARG1(xargs);
			new_cpuid = (int)GET_XARGS_NACK_ARG2(xargs);
			break;

		case IDNNACK_EXIT:
			retry = 0;
			/*FALLTHROUGH*/

		default:
			new_masterid = IDN_NIL_DOMID;
			new_cpuid = IDN_NIL_DCPU;
			break;
		}

		idn_nego_cleanup_check(domid, new_masterid, new_cpuid);

		if (retry) {
			token = IDN_RETRY_TOKEN(domid, IDNRETRY_NEGO);
			idn_retry_submit(idn_retry_nego, NULL, token,
			    idn_msg_retrytime[(int)IDNRETRY_NEGO]);
		} else {
			DOMAINSET_DEL(idn.domset.ds_relink, domid);
			IDN_RESET_COOKIES(domid);
			idn_disconnect(domid, IDNFIN_NORMAL, IDNFIN_ARG_NONE,
			    IDNDS_SYNC_TYPE(&idn_domain[domid]));
		}
	}
}

/*ARGSUSED*/
static void
idn_action_nego_rcvd(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	uint_t	msg = mtp ? mtp->mt_mtype : 0;

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	if (msg & IDNP_NACK) {
		uint_t		token;
		int		new_masterid, new_cpuid;
		int		retry = 1;
		idn_nack_t	nack;
		/*
		 * nack - retry.
		 *
		 * At this stage of receiving a nack we need to
		 * check whether we need to start over again with
		 * selecting a new master.
		 */
		nack = GET_XARGS_NACK_TYPE(xargs);
		switch (nack) {
		case IDNNACK_RETRY:
			new_masterid = (int)GET_XARGS_NACK_ARG1(xargs);
			new_cpuid = (int)GET_XARGS_NACK_ARG2(xargs);
			break;

		case IDNNACK_EXIT:
			retry = 0;
			/*FALLTHROUGH*/

		default:
			new_masterid = IDN_NIL_DOMID;
			new_cpuid = IDN_NIL_DCPU;
			break;
		}

		idn_nego_cleanup_check(domid, new_masterid, new_cpuid);

		if (retry) {
			token = IDN_RETRY_TOKEN(domid, IDNRETRY_NEGO);
			idn_retry_submit(idn_retry_nego, NULL, token,
			    idn_msg_retrytime[(int)IDNRETRY_NEGO]);
		} else {
			DOMAINSET_DEL(idn.domset.ds_relink, domid);
			IDN_RESET_COOKIES(domid);
			idn_disconnect(domid, IDNFIN_NORMAL, IDNFIN_ARG_NONE,
			    IDNDS_SYNC_TYPE(&idn_domain[domid]));
		}
	}
}

static void
idn_final_nego(int domid)
{
	idn_domain_t	*dp = &idn_domain[domid];

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	(void) idn_retry_terminate(IDN_RETRY_TOKEN(domid, IDNRETRY_NEGO));

	ASSERT(dp->dstate == IDNDS_CONFIG);

	dp->dxp = NULL;
	IDN_XSTATE_TRANSITION(dp, IDNXS_NIL);

	idn_send_config(domid, 1);
}

/*
 */
/*ARGSUSED1*/
static void
idn_exit_nego(int domid, uint_t msgtype)
{
	idn_domain_t	*dp;
	idn_fin_t	fintype;

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	dp = &idn_domain[domid];

	fintype = msgtype ? IDNFIN_NORMAL : IDNFIN_FORCE_HARD;

	(void) idn_retry_terminate(IDN_RETRY_TOKEN(domid, IDNRETRY_NEGO));

	ASSERT(!DOMAIN_IN_SET(idn.domset.ds_connected, domid));
	ASSERT(!DOMAIN_IN_SET(idn.domset.ds_ready_on, domid));
	ASSERT(dp->dxp == &xphase_nego);

	idn_nego_cleanup_check(domid, IDN_NIL_DOMID, IDN_NIL_DCPU);

	IDN_GLOCK_SHARED();
	if ((idn.state != IDNGS_DISCONNECT) &&
	    !DOMAIN_IN_SET(idn.domset.ds_hitlist, domid)) {
		DOMAINSET_ADD(idn.domset.ds_relink, domid);
		IDN_HISTORY_LOG(IDNH_RELINK, domid, dp->dstate,
		    idn.domset.ds_relink);
	} else {
		idn_update_op(IDNOP_ERROR, DOMAINSET(domid), NULL);
		DOMAINSET_DEL(idn.domset.ds_relink, domid);
	}
	IDN_GUNLOCK();
	/*
	 * Reset send cookie to 0 so that receiver does not validate
	 * cookie.  This is necessary since at this early stage it's
	 * possible we may not have exchanged appropriate cookies.
	 */
	IDN_RESET_COOKIES(domid);
	idn_disconnect(domid, fintype, IDNFIN_ARG_NONE, IDNDS_SYNC_TYPE(dp));
}

static void
idn_nego_cleanup_check(int domid, int new_masterid, int new_cpuid)
{
	idn_domain_t	*ldp, *dp;
	procname_t	proc = "idn_nego_cleanup_check";

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	dp = &idn_domain[domid];
	ldp = &idn_domain[idn.localid];

	IDN_GLOCK_EXCL();

	if (((idn.state == IDNGS_ONLINE) && !idn.domset.ds_connected) ||
	    (idn.state == IDNGS_CONNECT)) {
		domainset_t	trans_on;
		int		masterid;
		int		retry_domid = IDN_NIL_DOMID;
		int		rv;

		IDN_DLOCK_EXCL(idn.localid);
		masterid = (idn.state == IDNGS_ONLINE) ?
		    IDN_GET_MASTERID() : IDN_GET_NEW_MASTERID();
		trans_on = idn.domset.ds_trans_on;
		DOMAINSET_DEL(trans_on, domid);
		if (trans_on == 0) {
			int		d;
			domainset_t	relink = idn.domset.ds_relink;
			/*
			 * This was the only guy we were trying
			 * to connect with.
			 */
			ASSERT((idn.state == IDNGS_ONLINE) ?
			    ((idn.localid == masterid) ||
			    (domid == masterid)) : 1);
			if (idn.localid == masterid)
				idn_master_deinit();
			ldp->dvote.v.connected = 0;
			ldp->dvote.v.master = 0;
			dp->dvote.v.master = 0;
			IDN_SET_MASTERID(IDN_NIL_DOMID);
			IDN_SET_NEW_MASTERID(new_masterid);
			IDN_GSTATE_TRANSITION(IDNGS_CONNECT);
			IDN_PREP_HWINIT();
			IDN_DUNLOCK(idn.localid);
			IDN_GUNLOCK();
			/*
			 * If there's a new master available then
			 * just try and relink with him unless
			 * it's ourself.
			 */
			if ((new_masterid != IDN_NIL_DOMID) &&
			    (new_masterid != idn.localid) &&
			    (new_masterid != domid)) {
				IDN_DLOCK_EXCL(new_masterid);
				rv = idn_open_domain(new_masterid,
				    new_cpuid, 0);
				if (rv < 0) {
					cmn_err(CE_WARN,
					    "IDN: 205: (%s) failed to "
					    "open-domain(%d,%d)",
					    proc, new_masterid, new_cpuid);
					IDN_GLOCK_EXCL();
					IDN_SET_NEW_MASTERID(IDN_NIL_DOMID);
					IDN_GUNLOCK();
				} else {
					relink = DOMAINSET(new_masterid);
				}
				IDN_DUNLOCK(new_masterid);
			}
			DOMAINSET_DEL(relink, domid);
			if (relink)
				for (d = 0; d < MAX_DOMAINS; d++) {
					if (!DOMAIN_IN_SET(relink, d))
						continue;
					retry_domid = d;
					break;
				}
		} else if (domid == masterid) {
			/*
			 * There are other domains we were trying
			 * to connect to.  As long as the chosen
			 * master was somebody other then this
			 * domain that nack'd us, life is cool, but
			 * if it was this remote domain we'll need
			 * to start over.
			 */
			IDN_DUNLOCK(idn.localid);
			dp->dvote.v.master = 0;
			IDN_SET_MASTERID(IDN_NIL_DOMID);
			IDN_SET_NEW_MASTERID(new_masterid);

			if (idn.state == IDNGS_ONLINE) {
				IDN_GKSTAT_GLOBAL_EVENT(gk_reconfigs,
				    gk_reconfig_last);
				IDN_GSTATE_TRANSITION(IDNGS_RECONFIG);
				IDN_GUNLOCK();
				idn_unlink_domainset(trans_on, IDNFIN_NORMAL,
				    IDNFIN_ARG_NONE,
				    IDNFIN_OPT_RELINK,
				    BOARDSET_ALL);
			} else if ((new_masterid != IDN_NIL_DOMID) &&
			    (new_masterid != idn.localid) &&
			    (new_masterid != domid) &&
			    !DOMAIN_IN_SET(trans_on, new_masterid)) {
				IDN_GUNLOCK();
				IDN_DLOCK_EXCL(new_masterid);
				rv = idn_open_domain(new_masterid,
				    new_cpuid, 0);
				IDN_GLOCK_EXCL();
				IDN_DUNLOCK(new_masterid);
				if (rv < 0) {
					cmn_err(CE_WARN,
					    "IDN: 205: (%s) failed to "
					    "open-domain(%d,%d)",
					    proc, new_masterid,
					    new_cpuid);
					IDN_SET_NEW_MASTERID(IDN_NIL_DOMID);
					new_masterid = IDN_NIL_DOMID;
				} else {
					retry_domid = new_masterid;
				}
				IDN_GUNLOCK();
			} else {
				IDN_GUNLOCK();
			}
		} else {
			IDN_DUNLOCK(idn.localid);
			IDN_GUNLOCK();
		}
		if (retry_domid != IDN_NIL_DOMID) {
			uint_t		token;
			idn_domain_t	*rdp = &idn_domain[retry_domid];

			IDN_DLOCK_EXCL(retry_domid);
			rdp->dxp = &xphase_nego;
			IDN_XSTATE_TRANSITION(rdp, IDNXS_PEND);
			IDN_DUNLOCK(retry_domid);
			token = IDN_RETRY_TOKEN(retry_domid, IDNRETRY_NEGO);
			idn_retry_submit(idn_retry_nego, NULL, token,
			    idn_msg_retrytime[(int)IDNRETRY_NEGO]);
		}
	} else {
		IDN_GUNLOCK();
	}
}

static int
idn_send_con(int domid, idn_msgtype_t *mtp, idn_con_t contype, domainset_t
    conset)
{
	idn_msgtype_t	mt;
	uint_t		acknack;
	procname_t	proc = "idn_send_con";

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	if (mtp) {
		acknack = mtp->mt_mtype & IDNP_ACKNACK_MASK;
		mt.mt_mtype = mtp->mt_mtype;
		mt.mt_atype = mtp->mt_atype;
		mt.mt_cookie = mtp->mt_cookie;
	} else {
		acknack = 0;
		mt.mt_mtype = IDNP_CON;
		mt.mt_atype = 0;
		/*
		 * For simple CON queries we want a unique
		 * timer assigned.  For others, they
		 * effectively share one.
		 */
		if (contype == IDNCON_QUERY)
			mt.mt_cookie = 0;
		else
			mt.mt_cookie = IDN_TIMER_PUBLIC_COOKIE;
	}

	ASSERT((contype == IDNCON_QUERY) ? idn_domain[domid].dcookie_send : 1);

	PR_PROTO("%s:%d: sending con%sto (cpu %d) [ct=%s, cs=0x%x]\n",
	    proc, domid,
	    (acknack & IDNP_ACK) ? "+ack " :
	    (acknack & IDNP_NACK) ? "+nack " : " ",
	    idn_domain[domid].dcpu,
	    idncon_str[contype], conset);

	IDN_MSGTIMER_START(domid, IDNP_CON, (ushort_t)contype,
	    idn_msg_waittime[IDNP_CON], &mt.mt_cookie);

	IDNXDC(domid, &mt, (uint_t)contype, (uint_t)conset, 0, 0);

	return (0);
}

/*
 * Must leave w/DLOCK dropped and SYNC_LOCK held.
 */
static int
idn_recv_con(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	uint_t		msg = mtp ? mtp->mt_mtype : 0;
	uint_t		msgarg = mtp ? mtp->mt_atype : 0;
	idn_con_t	contype;
	domainset_t	my_ready_set, ready_set;
	idn_msgtype_t	mt;
	idn_domain_t	*dp = &idn_domain[domid];
	idn_xdcargs_t	aargs;
	procname_t	proc = "idn_recv_con";

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	mt.mt_cookie = mtp ? mtp->mt_cookie : 0;

	contype   = GET_XARGS_CON_TYPE(xargs);
	ready_set = GET_XARGS_CON_DOMSET(xargs);

	CLR_XARGS(aargs);

	if (!(msg & IDNP_NACK) && (contype == IDNCON_QUERY)) {
		domainset_t	query_set;

		query_set = idn_sync_register(domid, IDNSYNC_CONNECT,
		    ready_set, IDNSYNC_REG_REG);

		my_ready_set = idn.domset.ds_connected | idn.domset.ds_ready_on;
		my_ready_set &= ~idn.domset.ds_trans_off;
		DOMAINSET_ADD(my_ready_set, idn.localid);

		if (msg & IDNP_MSGTYPE_MASK) {
			mt.mt_mtype = IDNP_ACK;
			mt.mt_atype = IDNP_CON;
			SET_XARGS_CON_TYPE(aargs, contype);
			SET_XARGS_CON_DOMSET(aargs, my_ready_set);
			idn_send_acknack(domid, &mt, aargs);
		}

		if (query_set) {
			uint_t	token;

			token = IDN_RETRY_TOKEN(domid, IDNRETRY_CONQ);
			idn_retry_submit(idn_retry_query, NULL, token,
			    idn_msg_retrytime[(int)IDNRETRY_CONQ]);
		}

		return (0);
	}

	if (dp->dxp == NULL) {
		STRING(mstr);
		STRING(lstr);
		/*
		 * Must have received an inappropriate error
		 * message as we should already be registered
		 * by the time we reach here.
		 */
		INUM2STR(msg, mstr);
		INUM2STR(msgarg, lstr);

		PR_PROTO("%s:%d: ERROR: NOT YET REGISTERED (%s/%s)\n",
		    proc, domid, mstr, lstr);

		if (msg & IDNP_MSGTYPE_MASK) {
			mt.mt_mtype = IDNP_NACK;
			mt.mt_atype = msg;
			SET_XARGS_NACK_TYPE(aargs, IDNNACK_RETRY);
			idn_send_acknack(domid, &mt, aargs);
		}

		return (-1);
	}

	idn_xphase_transition(domid, mtp, xargs);

	return (0);
}

/*ARGSUSED1*/
static void
idn_retry_con(uint_t token, void *arg)
{
	int		domid = IDN_RETRY_TOKEN2DOMID(token);
	idn_domain_t	*dp = &idn_domain[domid];
	idn_xdcargs_t	xargs;
	procname_t	proc = "idn_retry_con";

	ASSERT(IDN_RETRY_TOKEN2TYPE(token) == IDNRETRY_CON);

	IDN_SYNC_LOCK();
	IDN_DLOCK_EXCL(domid);

	if (dp->dxp != &xphase_con) {
		STRING(str);

#ifdef DEBUG
		if (dp->dxp) {
			INUM2STR(dp->dxp->xt_msgtype, str);
		}
#endif /* DEBUG */

		PR_PROTO("%s:%d: dxp(%s) != CON...bailing...\n",
		    proc, domid, dp->dxp ? str : "NULL");
		IDN_DUNLOCK(domid);
		IDN_SYNC_UNLOCK();
		return;
	}

	if ((dp->dsync.s_cmd != IDNSYNC_CONNECT) ||
	    (dp->dxstate != IDNXS_PEND)) {
		PR_PROTO("%s:%d: cmd (%s) and/or xstate (%s) not "
		    "expected (%s/%s)\n",
		    proc, domid, idnsync_str[dp->dsync.s_cmd],
		    idnxs_str[dp->dxstate], idnsync_str[IDNSYNC_CONNECT],
		    idnxs_str[IDNXS_PEND]);
		IDN_DUNLOCK(domid);
		IDN_SYNC_UNLOCK();
		return;
	}

	idn_xphase_transition(domid, NULL, xargs);

	IDN_DUNLOCK(domid);
	IDN_SYNC_UNLOCK();
}

static int
idn_check_con(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	int		ready;
	uint_t		msg = mtp ? mtp->mt_mtype : 0;
	idn_domain_t	*dp = &idn_domain[domid];
	domainset_t	ready_set, my_ready_set, query_set;

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	if (msg & IDNP_NACK)
		return (0);

	if ((dp->dstate == IDNDS_CON_PEND) &&
	    (msg & IDNP_MSGTYPE_MASK) && (msg & IDNP_ACK))	/* con+ack */
		return (1);

	if (msg == 0) {
		ready_set = idn.domset.ds_connected &
		    ~idn.domset.ds_trans_off;
	} else {
		ready_set = GET_XARGS_CON_DOMSET(xargs);
		DOMAINSET_ADD(idn.domset.ds_ready_on, domid);
	}

	DOMAINSET_ADD(ready_set, idn.localid);

	query_set = idn_sync_register(domid, IDNSYNC_CONNECT,
	    ready_set, IDNSYNC_REG_REG);
	/*
	 * No need to query this domain as he's already
	 * in the CON sequence.
	 */
	DOMAINSET_DEL(query_set, domid);

	ready = (dp->dsync.s_set_exp == dp->dsync.s_set_rdy) ? 1 : 0;
	if (ready) {
		DOMAINSET_DEL(idn.domset.ds_ready_on, domid);
		DOMAINSET_ADD(idn.domset.ds_connected, domid);
	}

	if (query_set) {
		int	d;

		my_ready_set = idn.domset.ds_ready_on |
		    idn.domset.ds_connected;
		my_ready_set &= ~idn.domset.ds_trans_off;
		DOMAINSET_ADD(my_ready_set, idn.localid);

		for (d = 0; d < MAX_DOMAINS; d++) {
			if (!DOMAIN_IN_SET(query_set, d))
				continue;

			dp = &idn_domain[d];

			IDN_DLOCK_EXCL(d);
			if ((dp->dsync.s_cmd == IDNSYNC_CONNECT) ||
			    !dp->dcookie_send) {
				IDN_DUNLOCK(d);
				continue;
			}

			IDN_SYNC_QUERY_UPDATE(domid, d);

			idn_send_con(d, NULL, IDNCON_QUERY, my_ready_set);
			IDN_DUNLOCK(d);
		}
	}

	return (!msg ? 0 : (ready ? 0 : 1));
}

/*ARGSUSED2*/
static void
idn_error_con(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	uint_t	token;
	uint_t	msg = mtp ? mtp->mt_mtype : 0;

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	if (msg & IDNP_MSGTYPE_MASK) {
		idn_msgtype_t	mt;
		idn_xdcargs_t	nargs;

		mt.mt_mtype = IDNP_NACK;
		mt.mt_atype = msg;
		mt.mt_cookie = mtp->mt_cookie;
		CLR_XARGS(nargs);
		SET_XARGS_NACK_TYPE(nargs, IDNNACK_RETRY);
		idn_send_acknack(domid, &mt, nargs);
	}

	token = IDN_RETRY_TOKEN(domid, IDNRETRY_CON);
	idn_retry_submit(idn_retry_con, NULL, token,
	    idn_msg_retrytime[(int)IDNRETRY_CON]);
}

/*ARGSUSED*/
static void
idn_action_con_pend(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	uint_t		msg = mtp ? mtp->mt_mtype : 0;
	idn_domain_t	*dp = &idn_domain[domid];
	idn_msgtype_t	mt;
	domainset_t	my_ready_set;

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	my_ready_set = dp->dsync.s_set_rdy | idn.domset.ds_ready_on |
	    idn.domset.ds_connected;
	my_ready_set &= ~idn.domset.ds_trans_off;
	DOMAINSET_ADD(my_ready_set, idn.localid);

	if (!msg) {
		idn_send_con(domid, NULL, IDNCON_NORMAL, my_ready_set);
	} else {
		mt.mt_mtype = IDNP_CON | IDNP_ACK;
		mt.mt_atype = 0;
		mt.mt_cookie = mtp->mt_cookie;
		idn_send_con(domid, &mt, IDNCON_NORMAL, my_ready_set);
	}
}

static void
idn_action_con_sent(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	uint_t		msg = mtp ? mtp->mt_mtype : 0;
	idn_domain_t	*dp = &idn_domain[domid];
	idn_con_t	contype;
	domainset_t	my_ready_set;
	idn_msgtype_t	mt;

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	mt.mt_cookie = mtp ? mtp->mt_cookie : 0;

	my_ready_set = dp->dsync.s_set_rdy | idn.domset.ds_ready_on |
	    idn.domset.ds_connected;
	my_ready_set &= ~idn.domset.ds_trans_off;
	DOMAINSET_ADD(my_ready_set, idn.localid);

	contype = GET_XARGS_CON_TYPE(xargs);

	if ((msg & IDNP_ACKNACK_MASK) == 0) {
		/*
		 * con
		 */
		mt.mt_mtype = IDNP_CON | IDNP_ACK;
		mt.mt_atype = 0;
		idn_send_con(domid, &mt, contype, my_ready_set);
	} else if (msg & IDNP_MSGTYPE_MASK) {
		idn_xdcargs_t	cargs;

		mt.mt_mtype = IDNP_ACK;
		mt.mt_atype = msg;
		CLR_XARGS(cargs);
		SET_XARGS_CON_TYPE(cargs, contype);
		SET_XARGS_CON_DOMSET(cargs, my_ready_set);
		/*
		 * con+ack
		 */
		idn_send_acknack(domid, &mt, cargs);
	} else {
		uint_t	token;
		/*
		 * nack - retry
		 */
		token = IDN_RETRY_TOKEN(domid, IDNRETRY_CON);
		idn_retry_submit(idn_retry_con, NULL, token,
		    idn_msg_retrytime[(int)IDNRETRY_CON]);
	}
}

/*ARGSUSED*/
static void
idn_action_con_rcvd(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	uint_t	msg = mtp ? mtp->mt_mtype : 0;

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	if (msg & IDNP_NACK) {
		uint_t	token;
		/*
		 * nack - retry
		 */
		token = IDN_RETRY_TOKEN(domid, IDNRETRY_CON);
		idn_retry_submit(idn_retry_con, NULL, token,
		    idn_msg_retrytime[(int)IDNRETRY_CON]);
	}
}

static void
idn_final_con(int domid)
{
	uint_t		targ;
	uint_t		token = IDN_RETRY_TOKEN(domid, IDNRETRY_CON);
	idn_domain_t	*dp = &idn_domain[domid];
	procname_t	proc = "idn_final_con";

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	(void) idn_retry_terminate(token);

	dp->dxp = NULL;
	IDN_XSTATE_TRANSITION(dp, IDNXS_NIL);

	idn_sync_exit(domid, IDNSYNC_CONNECT);

	CHECKPOINT_OPENED(IDNSB_CHKPT_LINK, dp->dhw.dh_boardset, 1);

	DOMAINSET_DEL(idn.domset.ds_trans_on, domid);
	DOMAINSET_DEL(idn.domset.ds_relink, domid);
	IDN_FSTATE_TRANSITION(dp, IDNFIN_OFF);

	PR_PROTO("%s:%d: CONNECTED\n", proc, domid);

	if (idn.domset.ds_trans_on == 0) {
		if ((idn.domset.ds_trans_off | idn.domset.ds_relink) == 0) {
			PR_HITLIST("%s:%d: HITLIST %x -> 0\n",
			    proc, domid, idn.domset.ds_hitlist);
			idn.domset.ds_hitlist = 0;
		}
		PR_PROTO("%s:%d: ALL CONNECTED ************ "
		    "(0x%x + 0x%x) = 0x%x\n", proc, domid,
		    DOMAINSET(idn.localid), idn.domset.ds_connected,
		    DOMAINSET(idn.localid) | idn.domset.ds_connected);
	} else {
		PR_PROTO("%s:%d: >>> ds_trans_on = 0x%x, ds_ready_on = 0x%x\n",
		    proc, domid,
		    idn.domset.ds_trans_on, idn.domset.ds_ready_on);
	}

	if (idn_verify_config_mbox(domid)) {
		idnsb_error_t	idnerr;
		/*
		 * Mailbox is not cool. Need to disconnect.
		 */
		INIT_IDNKERR(&idnerr);
		SET_IDNKERR_ERRNO(&idnerr, EPROTO);
		SET_IDNKERR_IDNERR(&idnerr, IDNKERR_SMR_CORRUPTED);
		SET_IDNKERR_PARAM0(&idnerr, domid);
		idn_update_op(IDNOP_ERROR, DOMAINSET(domid), &idnerr);
		/*
		 * We cannot disconnect from an individual domain
		 * unless all domains are attempting to disconnect
		 * from him also, especially now since we touched
		 * the SMR and now we have a potential cache conflicts
		 * with the other domains with respect to this
		 * domain.  Disconnect attempt will effectively
		 * shutdown connection with respective domain
		 * which is the effect we really want anyway.
		 */
		idn_disconnect(domid, IDNFIN_NORMAL, IDNFIN_ARG_SMRBAD,
		    IDNFIN_SYNC_YES);

		return;
	}

	if (lock_try(&idn.first_swlink)) {
		/*
		 * This is our first connection.  Need to
		 * kick some stuff into gear.
		 */
		idndl_dlpi_init();
		(void) idn_activate_channel(CHANSET_ALL, IDNCHAN_ONLINE);

		targ = 0xf0;
	} else {
		targ = 0;
	}

	idn_mainmbox_activate(domid);

	idn_update_op(IDNOP_CONNECTED, DOMAINSET(domid), NULL);

	IDN_GKSTAT_GLOBAL_EVENT(gk_links, gk_link_last);

	membar_stst_ldst();

	IDN_DSTATE_TRANSITION(dp, IDNDS_CONNECTED);
	/*
	 * Need to kick off initial commands in background.
	 * We do not want to do them within the context of
	 * a protocol server because they may sleep and thus
	 * cause the protocol server to incur a soft-deadlock,
	 * i.e. he's sleeping waiting in the slab-waiting area
	 * for a response that will arrive on his protojob
	 * queue, but which he obviously can't process since
	 * he's not waiting on his protojob queue.
	 */
	targ |= domid & 0x0f;
	(void) timeout(idn_link_established, (void *)(uintptr_t)targ, 50);

	cmn_err(CE_NOTE,
	    "!IDN: 200: link (domain %d, CPU %d) connected",
	    dp->domid, dp->dcpu);
}

static void
idn_exit_con(int domid, uint_t msgtype)
{
	idn_domain_t	*dp = &idn_domain[domid];
	idn_fin_t	fintype;
	procname_t	proc = "idn_exit_con";
	STRING(str);

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	INUM2STR(msgtype, str);
	PR_PROTO("%s:%d: msgtype = 0x%x(%s)\n", proc, domid, msgtype, str);

	fintype = msgtype ? IDNFIN_NORMAL : IDNFIN_FORCE_HARD;

	IDN_GLOCK_SHARED();
	if (idn.state != IDNGS_DISCONNECT) {
		DOMAINSET_ADD(idn.domset.ds_relink, domid);
		IDN_HISTORY_LOG(IDNH_RELINK, domid, dp->dstate,
		    idn.domset.ds_relink);
	} else {
		DOMAINSET_DEL(idn.domset.ds_relink, domid);
	}
	IDN_GUNLOCK();

	idn_disconnect(domid, fintype, IDNFIN_ARG_NONE, IDNDS_SYNC_TYPE(dp));
}

static int
idn_send_fin(int domid, idn_msgtype_t *mtp, idn_fin_t fintype, idn_finarg_t
    finarg, idn_finopt_t finopt, domainset_t finset, uint_t finmaster)
{
	int		need_timer = 1;
	uint_t		acknack;
	uint_t		fintypearg = 0;
	idn_msgtype_t	mt;
	idn_domain_t	*dp = &idn_domain[domid];
	procname_t	proc = "idn_send_fin";

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_HELD(domid));

	ASSERT((fintype != IDNFIN_QUERY) ? (finopt != IDNFIN_OPT_NONE) : 1);

	if (mtp) {
		acknack = mtp->mt_mtype & IDNP_ACKNACK_MASK;
		mt.mt_mtype = mtp->mt_mtype;
		mt.mt_atype = mtp->mt_atype;
		mt.mt_cookie = mtp->mt_cookie;
	} else {
		acknack = 0;
		mt.mt_mtype = IDNP_FIN;
		mt.mt_atype = 0;
		/*
		 * For simple FIN queries we want a unique
		 * timer assigned.  For others, they
		 * effectively share one.
		 */
		if (fintype == IDNFIN_QUERY)
			mt.mt_cookie = 0;
		else
			mt.mt_cookie = IDN_TIMER_PUBLIC_COOKIE;
	}

	PR_PROTO("%s:%d: sending fin%sto (cpu %d) "
	    "[ft=%s, fa=%s, fs=0x%x, fo=%s, fm=(%d,%d)]\n",
	    proc, domid,
	    (acknack & IDNP_ACK) ? "+ack " :
	    (acknack & IDNP_NACK) ? "+nack " : " ",
	    dp->dcpu, idnfin_str[fintype], idnfinarg_str[finarg],
	    (int)finset, idnfinopt_str[finopt],
	    FIN_MASTER_DOMID(finmaster), FIN_MASTER_CPUID(finmaster));

	if (need_timer) {
		IDN_MSGTIMER_START(domid, IDNP_FIN, (ushort_t)fintype,
		    idn_msg_waittime[IDNP_FIN], &mt.mt_cookie);
	}

	SET_FIN_TYPE(fintypearg, fintype);
	SET_FIN_ARG(fintypearg, finarg);

	IDNXDC(domid, &mt, fintypearg, (uint_t)finset, (uint_t)finopt,
	    finmaster);

	return (0);
}

/*
 * Must leave w/DLOCK dropped and SYNC_LOCK held.
 */
static int
idn_recv_fin(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	uint_t		msg = mtp ? mtp->mt_mtype : 0;
	idn_fin_t	fintype;
	idn_finarg_t	finarg;
	idn_finopt_t	finopt;
	domainset_t	my_ready_set, ready_set;
	idn_msgtype_t	mt;
	idn_domain_t	*dp = &idn_domain[domid];
	idn_xdcargs_t	aargs;
	procname_t	proc = "idn_recv_fin";

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	mt.mt_cookie = mtp ? mtp->mt_cookie : 0;

	fintype   = GET_XARGS_FIN_TYPE(xargs);
	finarg    = GET_XARGS_FIN_ARG(xargs);
	ready_set = GET_XARGS_FIN_DOMSET(xargs);
	finopt    = GET_XARGS_FIN_OPT(xargs);

	CLR_XARGS(aargs);

	if (msg & IDNP_NACK) {
		PR_PROTO("%s:%d: received NACK (type = %s)\n",
		    proc, domid, idnnack_str[xargs[0]]);
	} else {
		PR_PROTO("%s:%d: fintype = %s, finopt = %s, "
		    "finarg = %s, ready_set = 0x%x\n",
		    proc, domid, idnfin_str[fintype],
		    idnfinopt_str[finopt],
		    idnfinarg_str[finarg], ready_set);
	}

	if (!(msg & IDNP_NACK) && (fintype == IDNFIN_QUERY)) {
		domainset_t	query_set;

		query_set = idn_sync_register(domid, IDNSYNC_DISCONNECT,
		    ready_set, IDNSYNC_REG_REG);

		my_ready_set = ~idn.domset.ds_connected |
		    idn.domset.ds_ready_off;

		if (msg & IDNP_MSGTYPE_MASK) {
			mt.mt_mtype = IDNP_ACK;
			mt.mt_atype = IDNP_FIN;
			SET_XARGS_FIN_TYPE(aargs, fintype);
			SET_XARGS_FIN_ARG(aargs, finarg);
			SET_XARGS_FIN_DOMSET(aargs, my_ready_set);
			SET_XARGS_FIN_OPT(aargs, IDNFIN_OPT_NONE);
			SET_XARGS_FIN_MASTER(aargs, NIL_FIN_MASTER);
			idn_send_acknack(domid, &mt, aargs);
		}

		if (query_set) {
			uint_t	token;

			token = IDN_RETRY_TOKEN(domid, IDNRETRY_FINQ);
			idn_retry_submit(idn_retry_query, NULL, token,
			    idn_msg_retrytime[(int)IDNRETRY_FINQ]);
		}

		return (0);
	}

	if (dp->dxp != &xphase_fin) {
		uint_t	token;

		if (IDNDS_IS_CLOSED(dp)) {
			PR_PROTO("%s:%d: domain already closed (%s)\n",
			    proc, domid, idnds_str[dp->dstate]);
			if (msg & IDNP_MSGTYPE_MASK) {
				/*
				 * fin or fin+ack.
				 */
				mt.mt_mtype = IDNP_NACK;
				mt.mt_atype = msg;
				SET_XARGS_NACK_TYPE(aargs, IDNNACK_NOCONN);
				idn_send_acknack(domid, &mt, aargs);
			}
			return (0);
		}
		dp->dfin_sync = IDNDS_SYNC_TYPE(dp);

		/*
		 * Need to do some clean-up ala idn_disconnect().
		 *
		 * Terminate any outstanding commands that were
		 * targeted towards this domain.
		 */
		idn_terminate_cmd(domid, ECANCELED);

		/*
		 * Terminate any and all retries that may have
		 * outstanding for this domain.
		 */
		token = IDN_RETRY_TOKEN(domid, IDN_RETRY_TYPEALL);
		(void) idn_retry_terminate(token);

		/*
		 * Stop all outstanding message timers for
		 * this guy.
		 */
		IDN_MSGTIMER_STOP(domid, 0, 0);

		dp->dxp = &xphase_fin;
		IDN_XSTATE_TRANSITION(dp, IDNXS_PEND);
	}

	if (msg & IDNP_NACK) {
		idn_nack_t	nack;

		nack = GET_XARGS_NACK_TYPE(xargs);
		if (nack == IDNNACK_NOCONN) {
			/*
			 * We're trying to FIN with somebody we're
			 * already disconnected from.  Need to
			 * speed this guy through.
			 */
			DOMAINSET_ADD(idn.domset.ds_ready_off, domid);
			(void) idn_sync_register(domid, IDNSYNC_DISCONNECT,
			    DOMAINSET_ALL, IDNSYNC_REG_REG);
			ready_set = (uint_t)DOMAINSET_ALL;
			/*
			 * Need to transform message to allow us to
			 * pass this guy right through and not waste time
			 * talking to him.
			 */
			IDN_FSTATE_TRANSITION(dp, IDNFIN_FORCE_HARD);

			switch (dp->dstate) {
			case IDNDS_FIN_PEND:
				mtp->mt_mtype = 0;
				mtp->mt_atype = 0;
				break;

			case IDNDS_FIN_SENT:
				mtp->mt_mtype = IDNP_FIN | IDNP_ACK;
				mtp->mt_atype = 0;
				break;

			case IDNDS_FIN_RCVD:
				mtp->mt_mtype = IDNP_ACK;
				mtp->mt_atype = IDNP_FIN | IDNP_ACK;
				break;

			default:
#ifdef DEBUG
				cmn_err(CE_PANIC,
				    "%s:%d: UNEXPECTED state = %s",
				    proc, domid,
				    idnds_str[dp->dstate]);
#endif /* DEBUG */
				break;
			}
		}
		fintype = (uint_t)dp->dfin;
		finopt = DOMAIN_IN_SET(idn.domset.ds_relink, domid) ?
		    IDNFIN_OPT_RELINK : IDNFIN_OPT_UNLINK;

		CLR_XARGS(xargs);
		SET_XARGS_FIN_TYPE(xargs, fintype);
		SET_XARGS_FIN_ARG(xargs, finarg);
		SET_XARGS_FIN_DOMSET(xargs, ready_set);
		SET_XARGS_FIN_OPT(xargs, finopt);
		SET_XARGS_FIN_MASTER(xargs, NIL_FIN_MASTER);
	}

	idn_xphase_transition(domid, mtp, xargs);

	return (0);
}

/*ARGSUSED1*/
static void
idn_retry_fin(uint_t token, void *arg)
{
	int		domid = IDN_RETRY_TOKEN2DOMID(token);
	int		new_masterid, new_cpuid = IDN_NIL_DCPU;
	uint_t		finmaster;
	idn_domain_t	*dp = &idn_domain[domid];
	idn_xdcargs_t	xargs;
	idn_finopt_t	finopt;
	procname_t	proc = "idn_retry_fin";

	ASSERT(IDN_RETRY_TOKEN2TYPE(token) == IDNRETRY_FIN);

	IDN_SYNC_LOCK();
	IDN_DLOCK_EXCL(domid);

	if (dp->dxp != &xphase_fin) {
		PR_PROTO("%s:%d: dxp(0x%p) != xstate_fin(0x%p)...bailing\n",
		    proc, domid, dp->dxp, &xphase_fin);
		IDN_DUNLOCK(domid);
		IDN_SYNC_UNLOCK();
		return;
	}

	if (dp->dxstate != IDNXS_PEND) {
		PR_PROTO("%s:%d: xstate(%s) != %s...bailing\n",
		    proc, domid, idnxs_str[dp->dxstate],
		    idnxs_str[IDNXS_PEND]);
		IDN_DUNLOCK(domid);
		IDN_SYNC_UNLOCK();
		return;
	}

	finopt = DOMAIN_IN_SET(idn.domset.ds_relink, domid) ?
	    IDNFIN_OPT_RELINK : IDNFIN_OPT_UNLINK;

	CLR_XARGS(xargs);
	SET_XARGS_FIN_TYPE(xargs, dp->dfin);
	/*LINTED*/
	SET_XARGS_FIN_ARG(xargs, IDNFIN_ARG_NONE);
	SET_XARGS_FIN_OPT(xargs, finopt);
	SET_XARGS_FIN_DOMSET(xargs, 0);		/* unused when msg == 0 */
	IDN_GLOCK_SHARED();
	new_masterid = IDN_GET_NEW_MASTERID();
	IDN_GUNLOCK();
	if (new_masterid != IDN_NIL_DOMID)
		new_cpuid = idn_domain[new_masterid].dcpu;
	finmaster = MAKE_FIN_MASTER(new_masterid, new_cpuid);
	SET_XARGS_FIN_MASTER(xargs, finmaster);

	idn_xphase_transition(domid, NULL, xargs);

	IDN_DUNLOCK(domid);
	IDN_SYNC_UNLOCK();
}

static int
idn_check_fin_pend(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	idn_domain_t	*dp = &idn_domain[domid];
	idn_fin_t	fintype;
	idn_finopt_t	finopt;
	idn_finarg_t	finarg;
	int		ready;
	int		finmasterid;
	int		fincpuid;
	uint_t		finmaster;
	uint_t		msg = mtp ? mtp->mt_mtype : 0;
	domainset_t	query_set, ready_set, conn_set;
	domainset_t	my_ready_set, shutdown_set;
	procname_t	proc = "idn_check_fin_pend";

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	if (msg & IDNP_NACK)
		return (0);

	if ((dp->dstate == IDNDS_FIN_PEND) && (msg & IDNP_MSGTYPE_MASK) &&
	    (msg & IDNP_ACK))		/* fin+ack */
		return (1);

	query_set = 0;

	if (!DOMAIN_IN_SET(idn.domset.ds_trans_off, domid)) {
		/*
		 * Can't remove domain from ds_connected yet,
		 * since he's still officially connected until
		 * we get an ACK from him.
		 */
		DOMAINSET_DEL(idn.domset.ds_trans_on, domid);
		DOMAINSET_ADD(idn.domset.ds_trans_off, domid);
	}

	IDN_GLOCK_SHARED();
	conn_set = (idn.domset.ds_connected | idn.domset.ds_trans_on) &
	    ~idn.domset.ds_trans_off;
	if ((idn.state == IDNGS_DISCONNECT) ||
	    (idn.state == IDNGS_RECONFIG) ||
	    (domid == IDN_GET_MASTERID()) || !conn_set) {
		/*
		 * If we're disconnecting, reconfiguring,
		 * unlinking from the master, or unlinking
		 * the last of our connections, then we need
		 * to shutdown all the channels.
		 */
		shutdown_set = DOMAINSET_ALL;
	} else {
		shutdown_set = DOMAINSET(domid);
	}
	IDN_GUNLOCK();

	idn_shutdown_datapath(shutdown_set, (dp->dfin == IDNFIN_FORCE_HARD));

	IDN_GLOCK_EXCL();
	/*
	 * Remap the SMR back to our local space if the remote
	 * domain going down is the master.  We do this now before
	 * flushing caches.  This will help guarantee that any
	 * accidental accesses to the SMR after the cache flush
	 * will only go to local memory.
	 */
	if ((domid == IDN_GET_MASTERID()) && (idn.smr.rempfn != PFN_INVALID)) {
		PR_PROTO("%s:%d: deconfiging CURRENT MASTER - SMR remap\n",
		    proc, domid);
		IDN_DLOCK_EXCL(idn.localid);
		/*
		 * We're going to remap the SMR,
		 * so gotta blow away our local
		 * pointer to the mbox table.
		 */
		idn_domain[idn.localid].dmbox.m_tbl = NULL;
		IDN_DUNLOCK(idn.localid);

		idn.smr.rempfn = PFN_INVALID;
		idn.smr.rempfnlim = PFN_INVALID;

		smr_remap(&kas, idn.smr.vaddr, idn.smr.locpfn, IDN_SMR_SIZE);
	}
	IDN_GUNLOCK();

	if (DOMAIN_IN_SET(idn.domset.ds_flush, domid)) {
		idnxf_flushall_ecache();
		CHECKPOINT_CLOSED(IDNSB_CHKPT_CACHE, dp->dhw.dh_boardset, 2);
		DOMAINSET_DEL(idn.domset.ds_flush, domid);
	}

	fintype   = GET_XARGS_FIN_TYPE(xargs);
	finarg    = GET_XARGS_FIN_ARG(xargs);
	ready_set = GET_XARGS_FIN_DOMSET(xargs);
	finopt    = GET_XARGS_FIN_OPT(xargs);

	ASSERT(fintype != IDNFIN_QUERY);
	if (!VALID_FIN(fintype)) {
		/*
		 * If for some reason remote domain
		 * sent us an invalid FIN type,
		 * override it to a  NORMAL fin.
		 */
		PR_PROTO("%s:%d: WARNING invalid fintype (%d) -> %s(%d)\n",
		    proc, domid, (int)fintype,
		    idnfin_str[IDNFIN_NORMAL], (int)IDNFIN_NORMAL);
		fintype = IDNFIN_NORMAL;
	}

	if (!VALID_FINOPT(finopt)) {
		PR_PROTO("%s:%d: WARNING invalid finopt (%d) -> %s(%d)\n",
		    proc, domid, (int)finopt,
		    idnfinopt_str[IDNFIN_OPT_UNLINK],
		    (int)IDNFIN_OPT_UNLINK);
		finopt = IDNFIN_OPT_UNLINK;
	}

	finmaster = GET_XARGS_FIN_MASTER(xargs);
	finmasterid = FIN_MASTER_DOMID(finmaster);
	fincpuid = FIN_MASTER_CPUID(finmaster);

	if ((finarg != IDNFIN_ARG_NONE) &&
	    !DOMAIN_IN_SET(idn.domset.ds_hitlist, domid)) {
		idnsb_error_t	idnerr;

		INIT_IDNKERR(&idnerr);
		SET_IDNKERR_ERRNO(&idnerr, EPROTO);
		SET_IDNKERR_IDNERR(&idnerr, FINARG2IDNKERR(finarg));
		SET_IDNKERR_PARAM0(&idnerr, domid);

		if (IDNFIN_ARG_IS_FATAL(finarg)) {
			finopt = IDNFIN_OPT_UNLINK;
			DOMAINSET_DEL(idn.domset.ds_relink, domid);
			DOMAINSET_ADD(idn.domset.ds_hitlist, domid);

			if (idn.domset.ds_connected == 0) {
				domainset_t	domset;

				IDN_GLOCK_EXCL();
				domset = ~idn.domset.ds_relink;
				if (idn.domset.ds_relink == 0) {
					IDN_GSTATE_TRANSITION(IDNGS_DISCONNECT);
				}
				domset &= ~idn.domset.ds_hitlist;
				/*
				 * The primary domain we were trying to
				 * connect to fin'd us with a fatal argument.
				 * Something isn't cool in our IDN environment,
				 * e.g. corrupted SMR or non-compatible CONFIG
				 * parameters.  In any case we need to dismantle
				 * ourselves completely.
				 */
				IDN_SET_NEW_MASTERID(IDN_NIL_DOMID);
				IDN_GUNLOCK();
				IDN_DUNLOCK(domid);

				DOMAINSET_DEL(domset, idn.localid);
				DOMAINSET_DEL(domset, domid);

				idn_update_op(IDNOP_ERROR, DOMAINSET_ALL,
				    &idnerr);

				PR_HITLIST("%s:%d: unlink_domainset(%x) "
				    "due to CFG error (relink=%x, "
				    "hitlist=%x)\n", proc, domid, domset,
				    idn.domset.ds_relink,
				    idn.domset.ds_hitlist);

				idn_unlink_domainset(domset, IDNFIN_NORMAL,
				    finarg, IDNFIN_OPT_UNLINK, BOARDSET_ALL);
				IDN_DLOCK_EXCL(domid);
			}
			PR_HITLIST("%s:%d: CFG error, (conn=%x, relink=%x, "
			    "hitlist=%x)\n",
			    proc, domid, idn.domset.ds_connected,
			    idn.domset.ds_relink, idn.domset.ds_hitlist);
		}
		idn_update_op(IDNOP_ERROR, DOMAINSET(domid), &idnerr);
	}

	if ((finmasterid != IDN_NIL_DOMID) && (!VALID_DOMAINID(finmasterid) ||
	    DOMAIN_IN_SET(idn.domset.ds_hitlist, domid))) {
		PR_HITLIST("%s:%d: finmasterid = %d -> -1, relink=%x, "
		    "hitlist=%x\n",
		    proc, domid, finmasterid, idn.domset.ds_relink,
		    idn.domset.ds_hitlist);
		PR_PROTO("%s:%d: WARNING invalid finmasterid (%d) -> -1\n",
		    proc, domid, finmasterid);
		finmasterid = IDN_NIL_DOMID;
	}

	IDN_GLOCK_EXCL();

	if ((finopt == IDNFIN_OPT_RELINK) && (idn.state != IDNGS_DISCONNECT)) {
		DOMAINSET_ADD(idn.domset.ds_relink, domid);
		IDN_HISTORY_LOG(IDNH_RELINK, domid, dp->dstate,
		    idn.domset.ds_relink);
	} else {
		DOMAINSET_DEL(idn.domset.ds_relink, domid);
		DOMAINSET_ADD(idn.domset.ds_hitlist, domid);
	}

	if ((domid == IDN_GET_NEW_MASTERID()) &&
	    !DOMAIN_IN_SET(idn.domset.ds_relink, domid)) {
		IDN_SET_NEW_MASTERID(IDN_NIL_DOMID);
	}

	if ((idn.state != IDNGS_DISCONNECT) && (idn.state != IDNGS_RECONFIG) &&
	    (domid == IDN_GET_MASTERID())) {
		domainset_t	dis_set, master_candidates;

		IDN_GKSTAT_GLOBAL_EVENT(gk_reconfigs, gk_reconfig_last);

		IDN_GSTATE_TRANSITION(IDNGS_RECONFIG);
		IDN_GUNLOCK();

		if ((finmasterid != IDN_NIL_DOMID) &&
		    (finmasterid != idn.localid)) {
			if (finmasterid != domid)
				IDN_DLOCK_EXCL(finmasterid);
			if (idn_open_domain(finmasterid, fincpuid, 0) < 0) {
				cmn_err(CE_WARN,
				    "IDN: 205: (%s) failed to "
				    "open-domain(%d,%d)",
				    proc, finmasterid, fincpuid);
				if (finmasterid != domid)
					IDN_DUNLOCK(finmasterid);
				finmasterid = IDN_NIL_DOMID;
			}
			if (finmasterid != domid)
				IDN_DUNLOCK(finmasterid);
		}

		IDN_GLOCK_EXCL();
		if (finmasterid == IDN_NIL_DOMID) {
			int	m;

			master_candidates = idn.domset.ds_trans_on |
			    idn.domset.ds_connected |
			    idn.domset.ds_relink;
			master_candidates &= ~(idn.domset.ds_trans_off &
			    ~idn.domset.ds_relink);
			DOMAINSET_DEL(master_candidates, domid);
			/*
			 * Local domain gets to participate also.
			 */
			DOMAINSET_ADD(master_candidates, idn.localid);

			m = idn_select_candidate(master_candidates);
			IDN_SET_NEW_MASTERID(m);
		} else {
			IDN_SET_NEW_MASTERID(finmasterid);
		}
		IDN_GUNLOCK();

		dis_set = idn.domset.ds_trans_on | idn.domset.ds_connected;
		DOMAINSET_DEL(dis_set, domid);

		idn_unlink_domainset(dis_set, IDNFIN_NORMAL, IDNFIN_ARG_NONE,
		    IDNFIN_OPT_RELINK, BOARDSET_ALL);
	} else {
		IDN_GUNLOCK();
	}

	/*
	 * My local ready-set are those domains from which I
	 * have confirmed no datapaths exist.
	 */
	my_ready_set = ~idn.domset.ds_connected;

	switch (dp->dfin) {
	case IDNFIN_NORMAL:
	case IDNFIN_FORCE_SOFT:
	case IDNFIN_FORCE_HARD:
		if (fintype < dp->dfin) {
			/*
			 * Remote domain has requested a
			 * FIN of lower priority than what
			 * we're currently running.  Just
			 * leave the priority where it is.
			 */
			break;
		}
		/*FALLTHROUGH*/

	default:
		IDN_FSTATE_TRANSITION(dp, fintype);
		break;
	}

	ASSERT(dp->dfin_sync != IDNFIN_SYNC_OFF);

	if (msg == 0) {
		/*
		 * Local domain is initiating a FIN sequence
		 * to remote domid.  Note that remote domain
		 * remains in ds_connected even though he's
		 * in thet ready-set from the local domain's
		 * perspective.  We can't remove him from
		 * ds_connected until we get a confirmed message
		 * from him indicating he has ceased communication.
		 */
		ready_set = my_ready_set;
	} else {
		/*
		 * Remote domain initiated a FIN sequence
		 * to local domain.  This implies that he
		 * has shutdown his datapath to us.  Since
		 * we shutdown our datapath to him, we're
		 * effectively now in his ready-set.
		 */
		DOMAINSET_ADD(ready_set, idn.localid);
		/*
		 * Since we know both sides of the connection
		 * have ceased, this remote domain is effectively
		 * considered disconnected.
		 */
		DOMAINSET_ADD(idn.domset.ds_ready_off, domid);
	}

	if (dp->dfin == IDNFIN_FORCE_HARD) {
		/*
		 * If we're doing a hard disconnect
		 * of this domain then we want to
		 * blow straight through and not
		 * waste time trying to talk to the
		 * remote domain nor to domains we
		 * believe are AWOL.  Although we will
		 * try and do it cleanly with
		 * everybody else.
		 */
		DOMAINSET_ADD(my_ready_set, domid);
		my_ready_set |= idn.domset.ds_awol;
		ready_set = DOMAINSET_ALL;

	} else if (dp->dfin_sync == IDNFIN_SYNC_NO) {
		/*
		 * If we're not fin'ing this domain
		 * synchronously then the only
		 * expected domain set is himself.
		 */
		ready_set |= ~DOMAINSET(domid);
		my_ready_set |= ~DOMAINSET(domid);
	}

	if (dp->dsync.s_cmd != IDNSYNC_DISCONNECT) {
		idn_sync_exit(domid, IDNSYNC_CONNECT);
		idn_sync_enter(domid, IDNSYNC_DISCONNECT, DOMAINSET_ALL,
		    my_ready_set, idn_xstate_transfunc,	(void *)IDNP_FIN);
	}

	query_set = idn_sync_register(domid, IDNSYNC_DISCONNECT, ready_set,
	    IDNSYNC_REG_REG);

	/*
	 * No need to query this domain as he's already
	 * in the FIN sequence.
	 */
	DOMAINSET_DEL(query_set, domid);

	ready = (dp->dsync.s_set_exp == dp->dsync.s_set_rdy) ? 1 : 0;
	if (ready) {
		DOMAINSET_DEL(idn.domset.ds_ready_off, domid);
		DOMAINSET_DEL(idn.domset.ds_connected, domid);
	}

	if (query_set) {
		int	d;

		my_ready_set = idn.domset.ds_ready_off |
		    ~idn.domset.ds_connected;

		for (d = 0; d < MAX_DOMAINS; d++) {
			if (!DOMAIN_IN_SET(query_set, d))
				continue;

			dp = &idn_domain[d];

			IDN_DLOCK_EXCL(d);

			if (dp->dsync.s_cmd == IDNSYNC_DISCONNECT) {
				IDN_DUNLOCK(d);
				continue;
			}

			IDN_SYNC_QUERY_UPDATE(domid, d);

			idn_send_fin(d, NULL, IDNFIN_QUERY, IDNFIN_ARG_NONE,
			    IDNFIN_OPT_NONE, my_ready_set, NIL_FIN_MASTER);
			IDN_DUNLOCK(d);
		}
	}

	return (!msg ? 0 : (ready ? 0 : 1));
}

/*ARGSUSED*/
static void
idn_error_fin_pend(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	uint_t	msg = mtp ? mtp->mt_mtype : 0;
	uint_t	token;

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_HELD(domid));

	/*
	 * Don't communicate with domains that
	 * we're forcing a hard disconnect.
	 */
	if ((idn_domain[domid].dfin != IDNFIN_FORCE_HARD) &&
	    (msg & IDNP_MSGTYPE_MASK)) {
		idn_msgtype_t	mt;
		idn_xdcargs_t	nargs;

		mt.mt_mtype = IDNP_NACK;
		mt.mt_atype = msg;
		mt.mt_cookie = mtp->mt_cookie;
		CLR_XARGS(nargs);
		SET_XARGS_NACK_TYPE(nargs, IDNNACK_RETRY);
		idn_send_acknack(domid, &mt, nargs);
	}

	token = IDN_RETRY_TOKEN(domid, IDNRETRY_FIN);
	idn_retry_submit(idn_retry_fin, NULL, token,
	    idn_msg_retrytime[(int)IDNRETRY_FIN]);
}

static void
idn_action_fin_pend(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	idn_domain_t	*dp = &idn_domain[domid];
	domainset_t	my_ready_set;
	idn_finopt_t	finopt;
	idn_finarg_t	finarg;
	uint_t		finmaster;
	int		new_masterid, new_cpuid = IDN_NIL_DCPU;
	uint_t		msg = mtp ? mtp->mt_mtype : 0;
	idn_msgtype_t	mt;

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_HELD(domid));

	my_ready_set = dp->dsync.s_set_rdy | idn.domset.ds_ready_off |
	    ~idn.domset.ds_connected;

	ASSERT(xargs[0] != (uint_t)IDNFIN_QUERY);

	finarg = GET_XARGS_FIN_ARG(xargs);
	finopt = DOMAIN_IN_SET(idn.domset.ds_relink, domid) ?
	    IDNFIN_OPT_RELINK : IDNFIN_OPT_UNLINK;

	mt.mt_cookie = mtp ? mtp->mt_cookie : 0;

	IDN_GLOCK_SHARED();
	new_masterid = IDN_GET_NEW_MASTERID();
	IDN_GUNLOCK();
	if (new_masterid != IDN_NIL_DOMID)
		new_cpuid = idn_domain[new_masterid].dcpu;
	finmaster = MAKE_FIN_MASTER(new_masterid, new_cpuid);

	if (dp->dfin == IDNFIN_FORCE_HARD) {
		ASSERT(IDN_DLOCK_IS_EXCL(domid));

		if (!msg) {
			mt.mt_mtype = IDNP_FIN | IDNP_ACK;
			mt.mt_atype = 0;
		} else {
			mt.mt_mtype = IDNP_ACK;
			mt.mt_atype = IDNP_FIN | IDNP_ACK;
		}
		idn_xphase_transition(domid, &mt, xargs);
	} else if (!msg) {
		idn_send_fin(domid, NULL, dp->dfin, finarg, finopt,
		    my_ready_set, finmaster);
	} else if ((msg & IDNP_ACKNACK_MASK) == 0) {
		/*
		 * fin
		 */
		mt.mt_mtype = IDNP_FIN | IDNP_ACK;
		mt.mt_atype = 0;
		idn_send_fin(domid, &mt, dp->dfin, finarg, finopt,
		    my_ready_set, finmaster);
	} else {
		uint_t	token;
		/*
		 * nack - retry
		 */
		token = IDN_RETRY_TOKEN(domid, IDNRETRY_FIN);
		idn_retry_submit(idn_retry_fin, NULL, token,
		    idn_msg_retrytime[(int)IDNRETRY_FIN]);
	}
}

static int
idn_check_fin_sent(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	int		ready;
	uint_t		msg = mtp ? mtp->mt_mtype : 0;
	idn_fin_t	fintype;
	idn_finopt_t	finopt;
	idn_domain_t	*dp = &idn_domain[domid];
	domainset_t	query_set, ready_set;

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	if (msg & IDNP_NACK)
		return (0);

	fintype   = GET_XARGS_FIN_TYPE(xargs);
	ready_set = GET_XARGS_FIN_DOMSET(xargs);
	finopt    = GET_XARGS_FIN_OPT(xargs);

	ASSERT(fintype != IDNFIN_QUERY);
	if (!VALID_FIN(fintype)) {
		/*
		 * If for some reason remote domain
		 * sent us an invalid FIN type,
		 * override it to a  NORMAL fin.
		 */
		fintype = IDNFIN_NORMAL;
	}

	if (!VALID_FINOPT(finopt)) {
		finopt = IDNFIN_OPT_UNLINK;
	}
	IDN_GLOCK_SHARED();
	if ((finopt == IDNFIN_OPT_RELINK) && (idn.state != IDNGS_DISCONNECT)) {
		DOMAINSET_ADD(idn.domset.ds_relink, domid);
		IDN_HISTORY_LOG(IDNH_RELINK, domid, dp->dstate,
		    idn.domset.ds_relink);
	} else {
		DOMAINSET_DEL(idn.domset.ds_relink, domid);
	}
	IDN_GUNLOCK();

	switch (dp->dfin) {
	case IDNFIN_NORMAL:
	case IDNFIN_FORCE_SOFT:
	case IDNFIN_FORCE_HARD:
		if (fintype < dp->dfin) {
			/*
			 * Remote domain has requested a
			 * FIN of lower priority than what
			 * we're current running.  Just
			 * leave the priority where it is.
			 */
			break;
		}
		/*FALLTHROUGH*/

	default:
		IDN_FSTATE_TRANSITION(dp, fintype);
		break;
	}

	if (dp->dfin == IDNFIN_FORCE_HARD) {
		/*
		 * If we're doing a hard disconnect
		 * of this domain then we want to
		 * blow straight through and not
		 * waste time trying to talk to the
		 * remote domain.  By registering him
		 * as ready with respect to all
		 * possible domains he'll transition
		 * immediately.  Note that we'll still
		 * try and do it coherently with
		 * other domains to which we're connected.
		 */
		ready_set = DOMAINSET_ALL;
	} else {
		DOMAINSET_ADD(ready_set, idn.localid);
	}

	DOMAINSET_ADD(idn.domset.ds_ready_off, domid);

	query_set = idn_sync_register(domid, IDNSYNC_DISCONNECT,
	    ready_set, IDNSYNC_REG_REG);
	/*
	 * No need to query this domain as he's already
	 * in the FIN sequence.
	 */
	DOMAINSET_DEL(query_set, domid);

	ready = (dp->dsync.s_set_exp == dp->dsync.s_set_rdy) ? 1 : 0;
	if (ready) {
		DOMAINSET_DEL(idn.domset.ds_ready_off, domid);
		DOMAINSET_DEL(idn.domset.ds_connected, domid);
	}

	if (query_set) {
		int		d;
		domainset_t	my_ready_set;

		my_ready_set = idn.domset.ds_ready_off |
		    ~idn.domset.ds_connected;

		for (d = 0; d < MAX_DOMAINS; d++) {
			if (!DOMAIN_IN_SET(query_set, d))
				continue;

			dp = &idn_domain[d];

			IDN_DLOCK_EXCL(d);

			if (dp->dsync.s_cmd == IDNSYNC_DISCONNECT) {
				IDN_DUNLOCK(d);
				continue;
			}

			IDN_SYNC_QUERY_UPDATE(domid, d);

			idn_send_fin(d, NULL, IDNFIN_QUERY, IDNFIN_ARG_NONE,
			    IDNFIN_OPT_NONE, my_ready_set, NIL_FIN_MASTER);
			IDN_DUNLOCK(d);
		}
	}

	return ((ready > 0) ? 0 : 1);
}

/*ARGSUSED*/
static void
idn_error_fin_sent(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	uint_t	msg = mtp ? mtp->mt_mtype : 0;
	uint_t	token;

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	/*
	 * Don't communicate with domains that
	 * we're forcing a hard disconnect.
	 */
	if ((idn_domain[domid].dfin != IDNFIN_FORCE_HARD) &&
	    (msg & IDNP_MSGTYPE_MASK)) {
		idn_msgtype_t	mt;
		idn_xdcargs_t	nargs;

		mt.mt_mtype = IDNP_NACK;
		mt.mt_atype = msg;
		mt.mt_cookie = mtp->mt_cookie;
		CLR_XARGS(nargs);
		SET_XARGS_NACK_TYPE(nargs, IDNNACK_RETRY);
		idn_send_acknack(domid, &mt, nargs);
	}

	token = IDN_RETRY_TOKEN(domid, IDNRETRY_FIN);
	idn_retry_submit(idn_retry_fin, NULL, token,
	    idn_msg_retrytime[(int)IDNRETRY_FIN]);
}

static void
idn_action_fin_sent(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	uint_t		msg = mtp ? mtp->mt_mtype : 0;
	int		new_masterid, new_cpuid = IDN_NIL_DCPU;
	uint_t		finmaster;
	idn_msgtype_t	mt;
	idn_finopt_t	finopt;
	idn_finarg_t	finarg;
	domainset_t	my_ready_set;
	idn_domain_t	*dp = &idn_domain[domid];

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	mt.mt_cookie = mtp ? mtp->mt_cookie : 0;

	finopt = DOMAIN_IN_SET(idn.domset.ds_relink, domid) ?
	    IDNFIN_OPT_RELINK : IDNFIN_OPT_UNLINK;

	finarg = GET_XARGS_FIN_ARG(xargs);

	my_ready_set = dp->dsync.s_set_rdy | idn.domset.ds_ready_off |
	    ~idn.domset.ds_connected;

	IDN_GLOCK_SHARED();
	new_masterid = IDN_GET_NEW_MASTERID();
	IDN_GUNLOCK();
	if (new_masterid != IDN_NIL_DOMID)
		new_cpuid = idn_domain[new_masterid].dcpu;
	finmaster = MAKE_FIN_MASTER(new_masterid, new_cpuid);

	if ((msg & IDNP_ACKNACK_MASK) == 0) {
		/*
		 * fin
		 */
		if (dp->dfin == IDNFIN_FORCE_HARD) {
			mt.mt_mtype = IDNP_ACK;
			mt.mt_atype = IDNP_FIN | IDNP_ACK;
			idn_xphase_transition(domid, &mt, xargs);
		} else {
			mt.mt_mtype = IDNP_FIN | IDNP_ACK;
			mt.mt_atype = 0;
			idn_send_fin(domid, &mt, dp->dfin, finarg, finopt,
			    my_ready_set, finmaster);
		}
	} else if (msg & IDNP_MSGTYPE_MASK) {
		/*
		 * fin+ack
		 */
		if (dp->dfin != IDNFIN_FORCE_HARD) {
			idn_xdcargs_t	fargs;

			mt.mt_mtype = IDNP_ACK;
			mt.mt_atype = msg;
			CLR_XARGS(fargs);
			SET_XARGS_FIN_TYPE(fargs, dp->dfin);
			SET_XARGS_FIN_ARG(fargs, finarg);
			SET_XARGS_FIN_DOMSET(fargs, my_ready_set);
			SET_XARGS_FIN_OPT(fargs, finopt);
			SET_XARGS_FIN_MASTER(fargs, finmaster);
			idn_send_acknack(domid, &mt, fargs);
		}
	} else {
		uint_t	token;
		/*
		 * nack - retry
		 */
		token = IDN_RETRY_TOKEN(domid, IDNRETRY_FIN);
		idn_retry_submit(idn_retry_fin, NULL, token,
		    idn_msg_retrytime[(int)IDNRETRY_FIN]);
	}
}

/*ARGSUSED*/
static void
idn_action_fin_rcvd(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	uint_t	msg = mtp ? mtp->mt_mtype : 0;

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	if (msg & IDNP_NACK) {
		uint_t	token;
		/*
		 * nack - retry.
		 */
		token = IDN_RETRY_TOKEN(domid, IDNRETRY_FIN);
		idn_retry_submit(idn_retry_fin, NULL, token,
		    idn_msg_retrytime[(int)IDNRETRY_FIN]);
	}
}

static void
idn_final_fin(int domid)
{
	int		do_relink;
	int		rv, d, new_masterid = IDN_NIL_DOMID;
	idn_gstate_t	next_gstate;
	domainset_t	relinkset;
	uint_t		token = IDN_RETRY_TOKEN(domid, IDNRETRY_FIN);
	idn_domain_t	*ldp, *dp = &idn_domain[domid];
	procname_t	proc = "idn_final_fin";

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));
	ASSERT(dp->dstate == IDNDS_DMAP);

	(void) idn_retry_terminate(token);

	dp->dxp = NULL;
	IDN_XSTATE_TRANSITION(dp, IDNXS_NIL);

	idn_sync_exit(domid, IDNSYNC_DISCONNECT);

	DOMAINSET_DEL(idn.domset.ds_trans_off, domid);

	do_relink = DOMAIN_IN_SET(idn.domset.ds_relink, domid) ? 1 : 0;

	/*
	 * idn_deconfig will idn_close_domain.
	 */
	idn_deconfig(domid);

	PR_PROTO("%s:%d: DISCONNECTED\n", proc, domid);

	IDN_GLOCK_EXCL();
	/*
	 * It's important that this update-op occur within
	 * the context of holding the glock(EXCL).  There is
	 * still some additional state stuff to cleanup which
	 * will be completed once the glock is dropped in
	 * this flow.  Which means anybody that's doing a
	 * SSI_INFO and waiting on glock will not actually
	 * run until the clean-up is completed, which is what
	 * we want.  Recall that a separate thread processes
	 * the SSI_LINK/UNLINK calls and when they complete
	 * (i.e. are awakened) they will immediately SSI_INFO
	 * and we don't want them to prematurely pick up stale
	 * information.
	 */
	idn_update_op(IDNOP_DISCONNECTED, DOMAINSET(domid), NULL);

	ASSERT(idn.state != IDNGS_OFFLINE);
	ASSERT(!DOMAIN_IN_SET(idn.domset.ds_trans_on, domid));

	if (domid == IDN_GET_MASTERID()) {
		IDN_SET_MASTERID(IDN_NIL_DOMID);
		dp->dvote.v.master = 0;
	}

	if ((domid == IDN_GET_NEW_MASTERID()) && !do_relink) {
		IDN_SET_NEW_MASTERID(IDN_NIL_DOMID);
	}

	if (idn.state == IDNGS_RECONFIG)
		new_masterid = IDN_GET_NEW_MASTERID();

	if ((idn.domset.ds_trans_on | idn.domset.ds_trans_off |
	    idn.domset.ds_relink) == 0) {
		PR_HITLIST("%s:%d: HITLIST %x -> 0\n",
		    proc, domid, idn.domset.ds_hitlist);
		idn.domset.ds_hitlist = 0;
	}

	if (idn.domset.ds_connected || idn.domset.ds_trans_off) {
		PR_PROTO("%s:%d: ds_connected = 0x%x, ds_trans_off = 0x%x\n",
		    proc, domid, idn.domset.ds_connected,
		    idn.domset.ds_trans_off);
		IDN_GUNLOCK();
		goto fin_done;
	}

	IDN_DLOCK_EXCL(idn.localid);
	ldp = &idn_domain[idn.localid];

	if (idn.domset.ds_trans_on != 0) {
		ASSERT((idn.state != IDNGS_DISCONNECT) &&
		    (idn.state != IDNGS_OFFLINE));

		switch (idn.state) {
		case IDNGS_CONNECT:
			if (idn.localid == IDN_GET_MASTERID()) {
				idn_master_deinit();
				IDN_SET_MASTERID(IDN_NIL_DOMID);
				ldp->dvote.v.master = 0;
			}
			/*FALLTHROUGH*/
		case IDNGS_ONLINE:
			next_gstate = idn.state;
			break;

		case IDNGS_RECONFIG:
			if (idn.localid == IDN_GET_MASTERID()) {
				idn_master_deinit();
				IDN_SET_MASTERID(IDN_NIL_DOMID);
				ldp->dvote.v.master = 0;
			}
			ASSERT(IDN_GET_MASTERID() == IDN_NIL_DOMID);
			next_gstate = IDNGS_CONNECT;
			ldp->dvote.v.connected = 0;
			/*
			 * Need to do HWINIT since we won't
			 * be transitioning through OFFLINE
			 * which would normally be caught in
			 * idn_check_nego() when we
			 * initially go to CONNECT.
			 */
			IDN_PREP_HWINIT();
			break;

		case IDNGS_DISCONNECT:
		case IDNGS_OFFLINE:
			cmn_err(CE_WARN,
			    "IDN: 211: disconnect domain %d, "
			    "unexpected Gstate (%s)",
			    domid, idngs_str[idn.state]);
			IDN_DUNLOCK(idn.localid);
			IDN_GUNLOCK();
			goto fin_done;

		default:
			/*
			 * XXX
			 * Go into FATAL state?
			 */
			cmn_err(CE_PANIC,
			    "IDN: 212: disconnect domain %d, "
			    "bad Gstate (%d)",
			    domid, idn.state);
			/* not reached */
			break;
		}
	} else {
		if (idn.localid == IDN_GET_MASTERID()) {
			idn_master_deinit();
			IDN_SET_MASTERID(IDN_NIL_DOMID);
			ldp->dvote.v.master = 0;
		}
		next_gstate = IDNGS_OFFLINE;
		if (idn.domset.ds_relink == 0) {
			IDN_SET_NEW_MASTERID(IDN_NIL_DOMID);
		}
	}
	IDN_DUNLOCK(idn.localid);

	/*
	 * If we reach here we've effectively disconnected all
	 * existing links, however new ones may be pending.
	 */
	PR_PROTO("%s:%d: ALL DISCONNECTED *****************\n", proc, domid);

	IDN_GSTATE_TRANSITION(next_gstate);

	ASSERT((idn.state == IDNGS_OFFLINE) ?
	    (IDN_GET_MASTERID() == IDN_NIL_DOMID) : 1);

	IDN_GUNLOCK();

	/*
	 * If we have no new masterid and yet there are relinkers
	 * out there, then force us to attempt to link with one
	 * of them.
	 */
	if ((new_masterid == IDN_NIL_DOMID) && idn.domset.ds_relink)
		new_masterid = idn.localid;

	if (new_masterid != IDN_NIL_DOMID) {
		/*
		 * If the local domain is the selected
		 * master then we'll want to initiate
		 * a link with one of the other candidates.
		 * If not, then we want to initiate a link
		 * with the master only.
		 */
		relinkset = (new_masterid == idn.localid) ?
		    idn.domset.ds_relink : DOMAINSET(new_masterid);

		DOMAINSET_DEL(relinkset, idn.localid);

		for (d = 0; d < MAX_DOMAINS; d++) {
			int	lock_held;

			if (!DOMAIN_IN_SET(relinkset, d))
				continue;

			if (d == domid) {
				do_relink = 0;
				lock_held = 0;
			} else {
				IDN_DLOCK_EXCL(d);
				lock_held = 1;
			}

			rv = idn_open_domain(d, -1, 0);
			if (rv == 0) {
				rv = idn_connect(d);
				if (lock_held)
					IDN_DUNLOCK(d);
				/*
				 * If we're able to kick off at
				 * least one connect then that's
				 * good enough for now.  The others
				 * will fall into place normally.
				 */
				if (rv == 0)
					break;
			} else if (rv < 0) {
				if (lock_held)
					IDN_DUNLOCK(d);
				cmn_err(CE_WARN,
				    "IDN: 205: (%s.1) failed to "
				    "open-domain(%d,%d)",
				    proc, domid, -1);
				DOMAINSET_DEL(idn.domset.ds_relink, d);
			} else {
				if (lock_held)
					IDN_DUNLOCK(d);
				PR_PROTO("%s:%d: failed to "
				    "re-open domain %d "
				    "(cpu %d) [rv = %d]\n",
				    proc, domid, d, idn_domain[d].dcpu,
				    rv);
			}
		}
	}

fin_done:
	if (do_relink) {
		ASSERT(IDN_DLOCK_IS_EXCL(domid));

		rv = idn_open_domain(domid, -1, 0);
		if (rv == 0) {
			(void) idn_connect(domid);
		} else if (rv < 0) {
			cmn_err(CE_WARN,
			    "IDN: 205: (%s.2) failed to "
			    "open-domain(%d,%d)",
			    proc, domid, -1);
			DOMAINSET_DEL(idn.domset.ds_relink, domid);
		}
	}
}

static void
idn_exit_fin(int domid, uint_t msgtype)
{
	idn_domain_t	*dp = &idn_domain[domid];
	uint_t		token;
	procname_t	proc = "idn_exit_fin";
	STRING(str);

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	INUM2STR(msgtype, str);
	PR_PROTO("%s:%d: msgtype = 0x%x(%s)\n", proc, domid, msgtype, str);

	token = IDN_RETRY_TOKEN(domid, IDNRETRY_FIN);
	(void) idn_retry_terminate(token);

	DOMAINSET_DEL(idn.domset.ds_ready_off, domid);

	dp->dxp = &xphase_fin;
	IDN_XSTATE_TRANSITION(dp, IDNXS_PEND);

	idn_retry_submit(idn_retry_fin, NULL, token,
	    idn_msg_retrytime[(int)IDNRETRY_FIN]);
}

/*
 * Must return w/locks held.
 */
static int
idn_xphase_transition(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	uint_t		msg = mtp ? mtp->mt_mtype : 0;
	uint_t		msgarg = mtp ? mtp->mt_atype : 0;
	idn_xphase_t	*xp;
	idn_domain_t	*dp;
	int		(*cfunc)(int, idn_msgtype_t *, idn_xdcargs_t);
	void		(*ffunc)(int);
	void		(*afunc)(int, idn_msgtype_t *, idn_xdcargs_t);
	void		(*efunc)(int, idn_msgtype_t *, idn_xdcargs_t);
	void		(*xfunc)(int, uint_t);
	int		err = 0;
	uint_t		msgtype;
	idn_xstate_t	o_xstate, n_xstate;
	procname_t	proc = "idn_xphase_transition";
	STRING(mstr);
	STRING(astr);

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	INUM2STR(msg, mstr);
	INUM2STR(msgarg, astr);

	dp = &idn_domain[domid];
	if ((xp = dp->dxp) == NULL) {
		PR_PROTO("%s:%d: WARNING: domain xsp is NULL (msg = %s, "
		    "msgarg = %s) <<<<<<<<<<<<\n",
		    proc, domid, mstr, astr);
		return (-1);
	}
	o_xstate = dp->dxstate;

	xfunc = xp->xt_exit;

	if ((msgtype = (msg & IDNP_MSGTYPE_MASK)) == 0)
		msgtype = msgarg & IDNP_MSGTYPE_MASK;

	if ((o_xstate == IDNXS_PEND) && msg &&
	    ((msg & IDNP_ACKNACK_MASK) == msg)) {
		PR_PROTO("%s:%d: unwanted acknack received (o_xstate = %s, "
		    "msg = %s/%s - dropping message\n",
		    proc, domid, idnxs_str[(int)o_xstate], mstr, astr);
		return (0);
	}

	/*
	 * Validate that message received is following
	 * the expected protocol for the current state.
	 */
	if (idn_next_xstate(o_xstate, -1, msg) == IDNXS_NIL) {
		PR_PROTO("%s:%d: WARNING: o_xstate = %s, msg = %s -> NIL "
		    "<<<<<<<<<\n",
		    proc, domid, idnxs_str[(int)o_xstate], mstr);
		if (xfunc)
			(*xfunc)(domid, msgtype);
		return (-1);
	}

	if (msg || msgarg) {
		/*
		 * Verify that message type is correct for
		 * the given xstate.
		 */
		if (msgtype != xp->xt_msgtype) {
			STRING(xstr);
			STRING(tstr);

			INUM2STR(xp->xt_msgtype, xstr);
			INUM2STR(msgtype, tstr);
			PR_PROTO("%s:%d: WARNING: msg expected %s(0x%x), "
			    "actual %s(0x%x) [msg=%s(0x%x), "
			    "msgarg=%s(0x%x)]\n",
			    proc, domid, xstr, xp->xt_msgtype,
			    tstr, msgtype, mstr, msg, astr, msgarg);
			if (xfunc)
				(*xfunc)(domid, msgtype);
			return (-1);
		}
	}

	cfunc = xp->xt_trans[(int)o_xstate].t_check;

	if (cfunc && ((err = (*cfunc)(domid, mtp, xargs)) < 0)) {
		if (o_xstate != IDNXS_PEND) {
			IDN_XSTATE_TRANSITION(dp, IDNXS_PEND);
		}
		if (xfunc)
			(*xfunc)(domid, msgtype);
		return (-1);
	}

	n_xstate = idn_next_xstate(o_xstate, err, msg);

	if (n_xstate == IDNXS_NIL) {
		PR_PROTO("%s:%d: WARNING: n_xstate = %s, msg = %s -> NIL "
		    "<<<<<<<<<\n",
		    proc, domid, idnxs_str[(int)n_xstate], mstr);
		if (xfunc)
			(*xfunc)(domid, msgtype);
		return (-1);
	}

	if (n_xstate != o_xstate) {
		IDN_XSTATE_TRANSITION(dp, n_xstate);
	}

	if (err) {
		if ((efunc = xp->xt_trans[(int)o_xstate].t_error) != NULL)
			(*efunc)(domid, mtp, xargs);
	} else if ((afunc = xp->xt_trans[(int)o_xstate].t_action) != NULL) {
		(*afunc)(domid, mtp, xargs);
	}

	if ((n_xstate == IDNXS_FINAL) && ((ffunc = xp->xt_final) != NULL))
		(*ffunc)(domid);

	return (0);
}

/*
 * Entered and returns w/DLOCK & SYNC_LOCK held.
 */
static int
idn_xstate_transfunc(int domid, void *transarg)
{
	uint_t		msg = (uint_t)(uintptr_t)transarg;
	uint_t		token;
	procname_t	proc = "idn_xstate_transfunc";

	ASSERT(IDN_SYNC_IS_LOCKED());

	switch (msg) {
	case IDNP_CON:
		DOMAINSET_ADD(idn.domset.ds_connected, domid);
		break;

	case IDNP_FIN:
		DOMAINSET_DEL(idn.domset.ds_connected, domid);
		break;

	default:
		PR_PROTO("%s:%d: ERROR: unknown msg (0x%x) <<<<<<<<\n",
		    proc, domid, msg);
		return (0);
	}

	token = IDN_RETRY_TOKEN(domid, (msg == IDNP_CON) ?
	    IDNRETRY_CON : IDNRETRY_FIN);
	if (msg == IDNP_CON)
		idn_retry_submit(idn_retry_con, NULL, token,
		    idn_msg_retrytime[(int)IDNRETRY_CON]);
	else
		idn_retry_submit(idn_retry_fin, NULL, token,
		    idn_msg_retrytime[(int)IDNRETRY_FIN]);

	return (1);
}

/*
 * Entered and returns w/DLOCK & SYNC_LOCK held.
 */
static void
idn_sync_enter(int domid, idn_synccmd_t cmd, domainset_t xset,
    domainset_t rset, int (*transfunc)(), void *transarg)
{
	int		z;
	idn_syncop_t	*sp;
	idn_synczone_t	*zp;
	procname_t	proc = "idn_sync_enter";

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	z = IDN_SYNC_GETZONE(cmd);
	ASSERT(z >= 0);
	zp = &idn.sync.sz_zone[z];

	PR_SYNC("%s:%d: cmd=%s(%d), z=%d, xs=0x%x, rx=0x%x, cnt=%d\n",
	    proc, domid, idnsync_str[cmd], cmd, z, xset, rset, zp->sc_cnt);

	sp = &idn_domain[domid].dsync;

	sp->s_domid = domid;
	sp->s_cmd = cmd;
	sp->s_msg = 0;
	sp->s_set_exp = xset;
	sp->s_set_rdy = rset;
	sp->s_transfunc = transfunc;
	sp->s_transarg = transarg;
	IDN_SYNC_QUERY_INIT(domid);

	sp->s_next = zp->sc_op;
	zp->sc_op = sp;
	zp->sc_cnt++;
}

/*
 * Entered and returns w/DLOCK & SYNC_LOCK held.
 */
void
idn_sync_exit(int domid, idn_synccmd_t cmd)
{
	int		d, z, zone, tot_queries, tot_domains;
	idn_syncop_t	*sp;
	idn_synczone_t	*zp = NULL;
	procname_t	proc = "idn_sync_exit";

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	sp = &idn_domain[domid].dsync;

	z = IDN_SYNC_GETZONE(sp->s_cmd);

	zone = IDN_SYNC_GETZONE(cmd);

	PR_SYNC("%s:%d: cmd=%s(%d) (z=%d, zone=%d)\n",
	    proc, domid, idnsync_str[cmd], cmd, z, zone);

#ifdef DEBUG
	if (z != -1) {
		tot_queries = tot_domains = 0;

		for (d = 0; d < MAX_DOMAINS; d++) {
			int	qv;

			if ((qv = sp->s_query[d]) > 0) {
				tot_queries += qv;
				tot_domains++;
				PR_SYNC("%s:%d: query_count = %d\n",
				    proc, domid, qv);
			}
		}
		PR_SYNC("%s:%d: tot_queries = %d, tot_domaines = %d\n",
		    proc, domid, tot_queries, tot_domains);
	}
#endif /* DEBUG */

	zp = (z != -1) ? &idn.sync.sz_zone[z] : NULL;

	if (zp) {
		idn_syncop_t	**spp;

		for (spp = &zp->sc_op; *spp; spp = &((*spp)->s_next)) {
			if (*spp == sp) {
				*spp = sp->s_next;
				sp->s_next = NULL;
				zp->sc_cnt--;
				break;
			}
		}
	}

	sp->s_cmd = IDNSYNC_NIL;

	for (z = 0; z < IDN_SYNC_NUMZONE; z++) {
		idn_syncop_t	**spp, **nspp;

		if ((zone != -1) && (z != zone))
			continue;

		zp = &idn.sync.sz_zone[z];

		for (spp = &zp->sc_op; *spp; spp = nspp) {
			sp = *spp;
			nspp = &sp->s_next;

			if (!DOMAIN_IN_SET(sp->s_set_exp, domid))
				continue;

			DOMAINSET_DEL(sp->s_set_exp, domid);
			DOMAINSET_DEL(sp->s_set_rdy, domid);

			if ((sp->s_set_exp == sp->s_set_rdy) &&
			    sp->s_transfunc) {
				int	delok;

				ASSERT(sp->s_domid != domid);

				PR_SYNC("%s:%d invoking transfunc "
				    "for domain %d\n",
				    proc, domid, sp->s_domid);
				delok = (*sp->s_transfunc)(sp->s_domid,
				    sp->s_transarg);
				if (delok) {
					*spp = sp->s_next;
					sp->s_next = NULL;
					zp->sc_cnt--;
					nspp = spp;
				}
			}
		}
	}
}

/*
 * Entered and returns w/DLOCK & SYNC_LOCK held.
 */
static domainset_t
idn_sync_register(int domid, idn_synccmd_t cmd, domainset_t ready_set,
    idn_syncreg_t regtype)
{
	int		z;
	idn_synczone_t	*zp;
	idn_syncop_t	*sp, **spp, **nspp;
	domainset_t	query_set = 0, trans_set;
	procname_t	proc = "idn_sync_register";

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	if ((z = IDN_SYNC_GETZONE(cmd)) == -1) {
		PR_SYNC("%s:%d: ERROR: unexpected sync cmd(%d)\n",
		    proc, domid, cmd);
		return (0);
	}

	/*
	 * Find out what domains are in transition with respect
	 * to given command.  There will be no need to query
	 * these folks.
	 */
	trans_set = IDN_SYNC_GETTRANS(cmd);

	zp = &idn.sync.sz_zone[z];

	PR_SYNC("%s:%d: cmd=%s(%d), z=%d, rset=0x%x, "
	    "regtype=%s(%d), sc_op=%s\n",
	    proc, domid, idnsync_str[cmd], cmd, z, ready_set,
	    idnreg_str[regtype], regtype,
	    zp->sc_op ? idnsync_str[zp->sc_op->s_cmd] : "NULL");

	for (spp = &zp->sc_op; *spp; spp = nspp) {
		sp = *spp;
		nspp = &sp->s_next;

		if (regtype == IDNSYNC_REG_NEW) {
			DOMAINSET_ADD(sp->s_set_exp, domid);
			PR_SYNC("%s:%d: adding new to %d (exp=0x%x)\n",
			    proc, domid, sp->s_domid, sp->s_set_exp);
		} else if (regtype == IDNSYNC_REG_QUERY) {
			query_set |= ~sp->s_set_rdy & sp->s_set_exp;
			continue;
		}

		if (!DOMAIN_IN_SET(sp->s_set_exp, domid))
			continue;

		if (!DOMAIN_IN_SET(ready_set, sp->s_domid)) {
			/*
			 * Given domid doesn't have a desired
			 * domain in his ready-set.  We'll need
			 * to query him again.
			 */
			DOMAINSET_ADD(query_set, domid);
			continue;
		}

		/*
		 * If we reach here, then an expected domain
		 * has marked its respective datapath to
		 * sp->s_domid as down (i.e. in his ready_set).
		 */
		DOMAINSET_ADD(sp->s_set_rdy, domid);

		PR_SYNC("%s:%d: mark READY for domain %d "
		    "(r=0x%x, x=0x%x)\n",
		    proc, domid, sp->s_domid,
		    sp->s_set_rdy, sp->s_set_exp);

		query_set |= ~sp->s_set_rdy & sp->s_set_exp;

		if (sp->s_set_exp == sp->s_set_rdy) {
#ifdef DEBUG
			if (sp->s_msg == 0) {
				sp->s_msg = 1;
				PR_SYNC("%s:%d: >>>>>>>>>>> DOMAIN %d "
				    "ALL CHECKED IN (0x%x)\n",
				    proc, domid, sp->s_domid,
				    sp->s_set_exp);
			}
#endif /* DEBUG */

			if ((sp->s_domid != domid) && sp->s_transfunc) {
				int	delok;

				PR_SYNC("%s:%d invoking transfunc "
				    "for domain %d\n",
				    proc, domid, sp->s_domid);
				delok = (*sp->s_transfunc)(sp->s_domid,
				    sp->s_transarg);
				if (delok) {
					*spp = sp->s_next;
					sp->s_next = NULL;
					zp->sc_cnt--;
					nspp = spp;
				}
			}
		}
	}

	PR_SYNC("%s:%d: trans_set = 0x%x, query_set = 0x%x -> 0x%x\n",
	    proc, domid, trans_set, query_set, query_set & ~trans_set);

	query_set &= ~trans_set;

	return (query_set);
}

static void
idn_sync_register_awol(int domid)
{
	int		z;
	idn_synccmd_t	cmd = IDNSYNC_DISCONNECT;
	idn_synczone_t	*zp;
	idn_syncop_t	*sp;
	procname_t	proc = "idn_sync_register_awol";

	ASSERT(IDN_SYNC_IS_LOCKED());

	if ((z = IDN_SYNC_GETZONE(cmd)) == -1) {
		PR_SYNC("%s:%d: ERROR: unexpected sync cmd(%d)\n",
		    proc, domid, cmd);
		return;
	}

	zp = &idn.sync.sz_zone[z];

	PR_SYNC("%s:%d: cmd=%s(%d), z=%d (domain %d = AWOL)\n",
	    proc, domid, idnsync_str[cmd], cmd, z, domid);

	for (sp = zp->sc_op; sp; sp = sp->s_next) {
		idn_domain_t	*dp;

		dp = &idn_domain[sp->s_domid];
		if (dp->dfin == IDNFIN_FORCE_HARD) {
			DOMAINSET_ADD(sp->s_set_rdy, domid);
			PR_SYNC("%s:%d: adding new to %d (rdy=0x%x)\n",
			    proc, domid, sp->s_domid, sp->s_set_rdy);
		}
	}
}

static void
idn_link_established(void *arg)
{
	int	first_link;
	int	domid, masterid;
	uint_t	info = (uint_t)(uintptr_t)arg;

	first_link = (int)(info & 0xf0);
	domid = (int)(info & 0x0f);

	IDN_GLOCK_SHARED();
	masterid = IDN_GET_MASTERID();
	if ((masterid == IDN_NIL_DOMID) ||
	    (idn_domain[masterid].dstate != IDNDS_CONNECTED)) {
		/*
		 * No point in doing this unless we're connected
		 * to the master.
		 */
		if ((masterid != IDN_NIL_DOMID) &&
		    (idn.state == IDNGS_ONLINE)) {
			/*
			 * As long as we're still online keep
			 * trying.
			 */
			(void) timeout(idn_link_established, arg, 50);
		}
		IDN_GUNLOCK();
		return;
	}
	IDN_GUNLOCK();

	if (first_link && IDN_SLAB_PREALLOC)
		idn_prealloc_slab(IDN_SLAB_PREALLOC);

	/*
	 * No guarantee, but it might save a little
	 * time.
	 */
	if (idn_domain[domid].dstate == IDNDS_CONNECTED) {
		/*
		 * Get the remote domain's dname.
		 */
		idn_send_nodename_req(domid);
	}

	/*
	 * May have had some streams backed up waiting for
	 * this connection.  Prod them.
	 */
	rw_enter(&idn.struprwlock, RW_READER);
	mutex_enter(&idn.sipwenlock);
	idndl_wenable(NULL);
	mutex_exit(&idn.sipwenlock);
	rw_exit(&idn.struprwlock);
}

/*
 * Send the following chunk of data received from above onto
 * the IDN wire.  This is raw data as far as the IDN driver
 * is concerned.
 * Returns:
 *	IDNXMIT_LOOP	- Msg handled in loopback and thus
 *			  still active (i.e. don't free).
 *	IDNXMIT_OKAY	- Data handled (freemsg).
 *	IDNXMIT_DROP	- Packet should be dropped.
 *	IDNXMIT_RETRY	- Packet should be requeued and retried.
 *	IDNXMIT_REQUEUE	- Packet should be requeued, but not
 *			  immediatetly retried.
 */
int
idn_send_data(int dst_domid, idn_netaddr_t dst_netaddr, queue_t *wq, mblk_t *mp)
{
	int		pktcnt = 0;
	int		msglen;
	int		rv = IDNXMIT_OKAY;
	int		xfersize = 0;
	caddr_t		iobufp, iodatap;
	uchar_t		*data_rptr;
	int		cpuindex;
	int		serrno;
	int		channel;
	int		retry_reclaim;
	idn_chansvr_t	*csp = NULL;
	uint_t		netports = 0;
	struct idnstr	*stp;
	struct idn	*sip;
	idn_domain_t	*dp;
	struct ether_header	*ehp;
	smr_pkthdr_t	*hdrp;
	idn_msgtype_t	mt;
	procname_t	proc = "idn_send_data";
#ifdef DEBUG
	size_t		orig_msglen = msgsize(mp);
#endif /* DEBUG */

	ASSERT(DB_TYPE(mp) == M_DATA);

	mt.mt_mtype = IDNP_DATA;
	mt.mt_atype = 0;
	mt.mt_cookie = 0;

	channel = (int)dst_netaddr.net.chan;

	msglen = msgdsize(mp);
	PR_DATA("%s:%d: (netaddr 0x%x) msgsize=%ld, msgdsize=%d\n",
	    proc, dst_domid, dst_netaddr.netaddr, msgsize(mp), msglen);

	ASSERT(wq->q_ptr);

	stp = (struct idnstr *)wq->q_ptr;
	sip = stp->ss_sip;
	ASSERT(sip);

	if (msglen < 0) {
		/*
		 * No data to send.  That was easy!
		 */
		PR_DATA("%s:%d: BAD msg length (%d) (netaddr 0x%x)\n",
		    proc, dst_domid, msglen, dst_netaddr.netaddr);
		return (IDNXMIT_DROP);
	}

	ASSERT(RW_READ_HELD(&stp->ss_rwlock));

	if (dst_domid == IDN_NIL_DOMID) {
		cmn_err(CE_WARN,
		    "IDN: 213: no destination specified "
		    "(d=%d, c=%d, n=0x%x)",
		    dst_domid, dst_netaddr.net.chan,
		    dst_netaddr.net.netid);
		IDN_KSTAT_INC(sip, si_nolink);
		IDN_KSTAT_INC(sip, si_macxmt_errors);
		rv = IDNXMIT_DROP;
		goto nocando;
	}

	ehp = (struct ether_header *)mp->b_rptr;
	PR_DATA("%s:%d: destination channel = %d\n", proc, dst_domid, channel);

#ifdef DEBUG
	{
		uchar_t	echn;

		echn = (uchar_t)
		    ehp->ether_shost.ether_addr_octet[IDNETHER_CHANNEL];
		ASSERT((uchar_t)channel == echn);
	}
#endif /* DEBUG */
	ASSERT(msglen <= IDN_DATA_SIZE);

	dp = &idn_domain[dst_domid];
	/*
	 * Get reader lock.  We hold for the duration
	 * of the transfer so that our state doesn't
	 * change during this activity.  Note that since
	 * we grab the reader lock, we can still permit
	 * simultaneous tranfers from different threads
	 * to the same domain.
	 * Before we waste a bunch of time gathering locks, etc.
	 * do a an unprotected check to make sure things are
	 * semi-copesetic.  If these values are in flux,
	 * that's okay.
	 */
	if ((dp->dstate != IDNDS_CONNECTED) || (idn.state != IDNGS_ONLINE)) {
		IDN_KSTAT_INC(sip, si_linkdown);
		if (idn.state != IDNGS_ONLINE) {
			rv = IDNXMIT_REQUEUE;
		} else {
			IDN_KSTAT_INC(sip, si_macxmt_errors);
			rv = IDNXMIT_DROP;
		}
		goto nocando;
	}

	if (idn.chan_servers[channel].ch_send.c_checkin) {
		/*
		 * Gotta bail, somethin' s'up.
		 */
		rv = IDNXMIT_REQUEUE;
		goto nocando;
	}

	csp = &idn.chan_servers[channel];
	IDN_CHAN_LOCK_SEND(csp);

	if (dst_netaddr.net.netid == IDN_BROADCAST_ALLNETID) {
		/*
		 * We're doing a broadcast.  Need to set
		 * up IDN netaddr's one at a time.
		 * We set the ethernet destination to the same
		 * instance as the sending address.  The instance
		 * numbers effectively represent subnets.
		 */
		dst_netaddr.net.netid = dp->dnetid;

		(void) idndl_domain_etheraddr(dst_domid, channel,
		    &ehp->ether_dhost);

		if (dst_domid == idn.localid) {
			mblk_t	*nmp;
			/*
			 * If this is a broadcast and going to
			 * the local domain, then we need to make
			 * a private copy of the message since
			 * the current one will be reused when
			 * transmitting to other domains.
			 */
			PR_DATA("%s:%d: dup broadcast msg for local domain\n",
			    proc, dst_domid);
			if ((nmp = copymsg(mp)) == NULL) {
				/*
				 * Couldn't get a duplicate copy.
				 */
				IDN_CHAN_UNLOCK_SEND(csp);
				csp = NULL;
				IDN_KSTAT_INC(sip, si_allocbfail);
				IDN_KSTAT_INC(sip, si_noxmtbuf);
				rv = IDNXMIT_DROP;
				goto nocando;
			}
			mp = nmp;
		}
	}

	if (dp->dnetid != dst_netaddr.net.netid) {
		PR_DATA("%s:%d: dest netid (0x%x) != expected (0x%x)\n",
		    proc, dst_domid, (uint_t)dst_netaddr.net.netid,
		    (uint_t)dp->dnetid);
		IDN_CHAN_UNLOCK_SEND(csp);
		csp = NULL;
		IDN_KSTAT_INC(sip, si_nolink);
		IDN_KSTAT_INC(sip, si_macxmt_errors);
		rv = IDNXMIT_DROP;
		goto nocando;
	}

	if (dst_domid == idn.localid) {
		int	lbrv;
		/*
		 * Sending to our local domain! Loopback.
		 * Note that idn_send_data_loop returning 0
		 * does not mean the message can now be freed.
		 * We need to return (-1) so that caller doesn't
		 * try to free mblk.
		 */
		IDN_CHAN_UNLOCK_SEND(csp);
		rw_exit(&stp->ss_rwlock);
		lbrv = idn_send_data_loopback(dst_netaddr, wq, mp);
		rw_enter(&stp->ss_rwlock, RW_READER);
		if (lbrv == 0) {
			return (IDNXMIT_LOOP);
		} else {
			IDN_KSTAT_INC(sip, si_macxmt_errors);
			return (IDNXMIT_DROP);
		}
	}

	if (dp->dstate != IDNDS_CONNECTED) {
		/*
		 * Can't send data unless a link has already been
		 * established with the target domain.  Normally,
		 * a user cannot set the remote netaddr unless a
		 * link has already been established, however it
		 * is possible the connection may have become
		 * disconnected since that time.
		 */
		IDN_CHAN_UNLOCK_SEND(csp);
		csp = NULL;
		IDN_KSTAT_INC(sip, si_linkdown);
		IDN_KSTAT_INC(sip, si_macxmt_errors);
		rv = IDNXMIT_DROP;
		goto nocando;
	}

	/*
	 * Need to make sure the channel is active and that the
	 * domain to which we're sending is allowed to receive stuff.
	 */
	if (!IDN_CHANNEL_IS_SEND_ACTIVE(csp)) {
		int	not_active;
		/*
		 * See if we can activate channel.
		 */
		IDN_CHAN_UNLOCK_SEND(csp);
		not_active = idn_activate_channel(CHANSET(channel),
		    IDNCHAN_OPEN);
		if (!not_active) {
			/*
			 * Only grab the lock for a recheck if we were
			 * able to activate the channel.
			 */
			IDN_CHAN_LOCK_SEND(csp);
		}
		/*
		 * Verify channel still active now that we have the lock.
		 */
		if (not_active || !IDN_CHANNEL_IS_SEND_ACTIVE(csp)) {
			if (!not_active) {
				/*
				 * Only need to drop the lock if it was
				 * acquired while we thought we had
				 * activated the channel.
				 */
				IDN_CHAN_UNLOCK_SEND(csp);
			}
			ASSERT(!IDN_CHAN_SEND_IS_LOCKED(csp));
			/*
			 * Damn!   Must have went inactive during the window
			 * before we regrabbed the send lock.  Oh well, can't
			 * spend all day doing this, bail out.  Set csp to
			 * NULL to prevent inprogress update at bottom.
			 */
			csp = NULL;
			/*
			 * Channel is not active, should not be used.
			 */
			PR_DATA("%s:%d: dest channel %d NOT ACTIVE\n",
			    proc, dst_domid, channel);
			IDN_KSTAT_INC(sip, si_linkdown);
			rv = IDNXMIT_REQUEUE;
			goto nocando;
		}
		ASSERT(IDN_CHAN_SEND_IS_LOCKED(csp));
	}
	/*
	 * If we made it here then the channel is active
	 * Make sure the target domain is registered to receive stuff,
	 * i.e. we're still linked.
	 */
	if (!IDN_CHAN_DOMAIN_IS_REGISTERED(csp, dst_domid)) {
		/*
		 * If domain is not even registered with this channel
		 * then we have no business being here.  Doesn't matter
		 * whether it's active or not.
		 */
		PR_DATA("%s:%d: domain not registered with channel %d\n",
		    proc, dst_domid, channel);
		/*
		 * Set csp to NULL to prevent in-progress update below.
		 */
		IDN_CHAN_UNLOCK_SEND(csp);
		csp = NULL;
		IDN_KSTAT_INC(sip, si_linkdown);
		IDN_KSTAT_INC(sip, si_macxmt_errors);
		rv = IDNXMIT_DROP;
		goto nocando;
	}

	IDN_CHAN_SEND_INPROGRESS(csp);
	IDN_CHAN_UNLOCK_SEND(csp);

	/*
	 * Find a target cpu to send interrupt to if
	 * it becomes necessary (i.e. remote channel
	 * server is idle).
	 */
	cpuindex = dp->dcpuindex;

	/*
	 * dcpuindex is atomically incremented, but other than
	 * that is not well protected and that's okay.  The
	 * intention is to simply spread around the interrupts
	 * at the destination domain, however we don't have to
	 * anal about it.  If we hit the same cpu multiple times
	 * in a row that's okay, it will only be for a very short
	 * period anyway before the cpuindex is incremented
	 * to the next cpu.
	 */
	if (cpuindex < NCPU) {
		ATOMIC_INC(dp->dcpuindex);
	}
	if (dp->dcpuindex >= NCPU)
		dp->dcpuindex = 0;

	IDN_ASSIGN_DCPU(dp, cpuindex);

#ifdef XXX_DLPI_UNFRIENDLY
	{
		ushort_t	dstport = (ushort_t)dp->dcpu;

		/*
		 * XXX
		 * This is not DLPI friendly, but we need some way
		 * of distributing our XDC interrupts to the cpus
		 * on the remote domain in a relatively random fashion
		 * while trying to remain constant for an individual
		 * network connection.  Don't want the target network
		 * appl pinging around cpus thrashing the caches.
		 * So, we'll pick target cpus based on the destination
		 * TCP/IP port (socket).  The (simple) alternative to
		 * this is to simply send all messages destined for
		 * particular domain to the same cpu (dcpu), but
		 * will lower our bandwidth and introduce a lot of
		 * contention on that target cpu.
		 */
		if (ehp->ether_type == ETHERTYPE_IP) {
			ipha_t	*ipha;
			uchar_t	*dstporta;
			int	hdr_length;
			mblk_t	*nmp = mp;
			uchar_t	*rptr = mp->b_rptr +
			    sizeof (struct ether_header);
			if (nmp->b_wptr <= rptr) {
				/*
				 * Only the ethernet header was contained
				 * in the first block.  Check for the
				 * next packet.
				 */
				if ((nmp = mp->b_cont) != NULL)
					rptr = nmp->b_rptr;
			}
			/*
			 * If we still haven't found the IP header packet
			 * then don't bother.  Can't search forever.
			 */
			if (nmp &&
			    ((nmp->b_wptr - rptr) >= IP_SIMPLE_HDR_LENGTH)) {
				ipha = (ipha_t *)ALIGN32(rptr);

				ASSERT(DB_TYPE(mp) == M_DATA);
				hdr_length = IPH_HDR_LENGTH(ipha);

				switch (ipha->ipha_protocol) {
				case IPPROTO_UDP:
				case IPPROTO_TCP:
					/*
					 * TCP/UDP Protocol Header (1st word)
					 * 0	    15,16	31
					 * -----------------------
					 * | src port | dst port |
					 * -----------------------
					 */
					dstporta = (uchar_t *)ipha + hdr_length;
					netports = *(uint_t *)dstporta;
					dstporta += 2;
					dstport  = *(ushort_t *)dstporta;
					break;
				default:
					break;
				}
			}

		}
		IDN_ASSIGN_DCPU(dp, dstport);

		PR_DATA("%s:%d: (dstport %d) assigned %d\n",
		    proc, dst_domid, (int)dstport, dp->dcpu);
	}
#endif /* XXX_DLPI_UNFRIENDLY */

	data_rptr = mp->b_rptr;

	ASSERT(dp->dcpu != IDN_NIL_DCPU);

	ASSERT(idn_domain[dst_domid].dmbox.m_send);

	retry_reclaim = 1;
retry:
	if ((dp->dio >= IDN_RECLAIM_MIN) || dp->diowanted) {
		int	reclaim_req;
		/*
		 * Reclaim however many outstanding buffers
		 * there are up to IDN_RECLAIM_MAX if it's set.
		 */
		reclaim_req = dp->diowanted ? -1 : IDN_RECLAIM_MAX ?
		    MIN(dp->dio, IDN_RECLAIM_MAX) : dp->dio;
		(void) idn_reclaim_mboxdata(dst_domid, channel,
		    reclaim_req);
	}

	if (dp->dio >= IDN_WINDOW_EMAX) {

		if (lock_try(&dp->diocheck)) {
			IDN_MSGTIMER_START(dst_domid, IDNP_DATA, 0,
			    idn_msg_waittime[IDNP_DATA],
			    &mt.mt_cookie);
			/*
			 * We have exceeded the minimum window for
			 * outstanding I/O buffers to this domain.
			 * Need to start the MSG timer to check for
			 * possible response from remote domain.
			 * The remote domain may be hung.  Send a
			 * wakeup!  Specify all channels for given
			 * domain since we don't know precisely which
			 * is backed up (dio is global).
			 */
			IDNXDC(dst_domid, &mt,
			    (uint_t)dst_netaddr.net.chan, 0, 0, 0);
		}

		/*
		 * Yikes!  We have exceeded the maximum window
		 * which means no more packets going to remote
		 * domain until he frees some up.
		 */
		IDN_KSTAT_INC(sip, si_txmax);
		IDN_KSTAT_INC(sip, si_macxmt_errors);
		rv = IDNXMIT_DROP;
		goto nocando;
	}

	/*
	 * Allocate a SMR I/O buffer and send it.
	 */
	if (msglen == 0) {
		/*
		 * A zero length messages is effectively a signal
		 * to just send an interrupt to the remote domain.
		 */
		IDN_MSGTIMER_START(dst_domid, IDNP_DATA, 0,
		    idn_msg_waittime[IDNP_DATA],
		    &mt.mt_cookie);
		IDNXDC(dst_domid, &mt,
		    (uint_t)dst_netaddr.net.chan, 0, 0, 0);
	}
	for (; (msglen > 0) && mp; msglen -= xfersize) {
		int		xrv;
		smr_offset_t	bufoffset;
#ifdef DEBUG
		int		n_xfersize;
#endif /* DEBUG */

		ASSERT(msglen <= IDN_DATA_SIZE);
		xfersize = msglen;

		serrno = smr_buf_alloc(dst_domid, xfersize, &iobufp);
		if (serrno) {
			PR_DATA("%s:%d: failed to alloc SMR I/O buffer "
			    "(serrno = %d)\n",
			    proc, dst_domid, serrno);
			/*
			 * Failure is either due to a timeout waiting
			 * for the master to give us a slab, OR the
			 * local domain exhausted its slab quota!
			 * In either case we'll have to bail from
			 * here and let higher layers decide what
			 * to do.
			 * We also could have had locking problems.
			 * A negative serrno indicates we lost the lock
			 * on dst_domid, so no need in dropping lock.
			 */

			if (lock_try(&dp->diowanted) && retry_reclaim) {
				/*
				 * We were the first to acquire the
				 * lock indicating that it wasn't
				 * set on entry to idn_send_data.
				 * So, let's go back and see if we
				 * can't reclaim some buffers and
				 * try again.
				 * It's very likely diowanted will be
				 * enough to prevent us from looping
				 * on retrying here, however to protect
				 * against the small window where a
				 * race condition might exist, we use
				 * the retry_reclaim flag so that we
				 * don't retry more than once.
				 */
				retry_reclaim = 0;
				goto retry;
			}

			rv = (serrno > 0) ? serrno : -serrno;
			IDN_KSTAT_INC(sip, si_notbufs);
			IDN_KSTAT_INC(sip, si_noxmtbuf);	/* MIB II */
			switch (rv) {
			case ENOMEM:
			case EBUSY:
			case ENOLCK:
			case ETIMEDOUT:
			case EDQUOT:
				/*
				 * These are all transient conditions
				 * which should be recoverable over
				 * time.
				 */
				rv = IDNXMIT_REQUEUE;
				break;

			default:
				rv = IDNXMIT_DROP;
				break;
			}
			goto nocando;
		}

		lock_clear(&dp->diowanted);

		hdrp = IDN_BUF2HDR(iobufp);
		bufoffset = (smr_offset_t)IDN_ALIGNPTR(sizeof (smr_pkthdr_t),
		    data_rptr);
		/*
		 * If the alignment of bufoffset took us pass the
		 * length of a smr_pkthdr_t then we need to possibly
		 * lower xfersize since it was calulated based on
		 * a perfect alignment.  However, if we're in DLPI
		 * mode then shouldn't be necessary since the length
		 * of the incoming packet (mblk) should have already
		 * taken into consideration this possible adjustment.
		 */
#ifdef DEBUG
		if (bufoffset != sizeof (smr_pkthdr_t))
			PR_DATA("%s:%d: offset ALIGNMENT (%lu -> %u) "
			    "(data_rptr = %p)\n",
			    proc, dst_domid, sizeof (smr_pkthdr_t),
			    bufoffset, data_rptr);

		n_xfersize = MIN(xfersize, (IDN_SMR_BUFSIZE - bufoffset));
		if (xfersize != n_xfersize) {
			PR_DATA("%s:%d: xfersize ADJUST (%d -> %d)\n",
			    proc, dst_domid, xfersize, n_xfersize);
			cmn_err(CE_WARN, "%s: ERROR (xfersize = %d, > "
			    "bufsize(%d)-bufoffset(%d) = %d)",
			    proc, xfersize, IDN_SMR_BUFSIZE,
			    bufoffset,
			    IDN_SMR_BUFSIZE - bufoffset);
		}
#endif /* DEBUG */
		xfersize = MIN(xfersize, (int)(IDN_SMR_BUFSIZE - bufoffset));

		iodatap = IDN_BUF2DATA(iobufp, bufoffset);
		mp = idn_fill_buffer(iodatap, xfersize, mp, &data_rptr);

		hdrp->b_netaddr  = dst_netaddr.netaddr;
		hdrp->b_netports = netports;
		hdrp->b_offset   = bufoffset;
		hdrp->b_length   = xfersize;
		hdrp->b_next	 = IDN_NIL_SMROFFSET;
		hdrp->b_rawio	 = 0;
		hdrp->b_cksum    = IDN_CKSUM_PKT(hdrp);

		xrv = idn_send_mboxdata(dst_domid, sip, channel, iobufp);
		if (xrv) {
			/*
			 * Reclaim packet.
			 * Return error on this packet so it can be retried
			 * (putbq).  Note that it should be safe to assume
			 * that this for-loop is only executed once when in
			 * DLPI mode and so no need to worry about fractured
			 * mblk packet.
			 */
			PR_DATA("%s:%d: DATA XFER to chan %d FAILED "
			    "(ret=%d)\n",
			    proc, dst_domid, channel, xrv);
			smr_buf_free(dst_domid, iobufp, xfersize);

			PR_DATA("%s:%d: (line %d) dec(dio) -> %d\n",
			    proc, dst_domid, __LINE__, dp->dio);

			rv = IDNXMIT_DROP;
			IDN_KSTAT_INC(sip, si_macxmt_errors);
			goto nocando;
		} else {
			pktcnt++;
			/*
			 * Packet will get freed on a subsequent send
			 * when we reclaim buffers that the receivers
			 * has finished consuming.
			 */
		}
	}

#ifdef DEBUG
	if (pktcnt > 1)
		cmn_err(CE_WARN,
		    "%s: ERROR: sent multi-pkts (%d), len = %ld",
		    proc, pktcnt, orig_msglen);
#endif /* DEBUG */

	PR_DATA("%s:%d: SENT %d packets (%d @ 0x%x)\n",
	    proc, dst_domid, pktcnt, dst_netaddr.net.chan,
	    dst_netaddr.net.netid);

	IDN_CHAN_LOCK_SEND(csp);
	IDN_CHAN_SEND_DONE(csp);
	IDN_CHAN_UNLOCK_SEND(csp);

	return (IDNXMIT_OKAY);

nocando:

	if (csp) {
		IDN_CHAN_LOCK_SEND(csp);
		IDN_CHAN_SEND_DONE(csp);
		IDN_CHAN_UNLOCK_SEND(csp);
	}

	if (rv == IDNXMIT_REQUEUE) {
		/*
		 * Better kick off monitor to check when
		 * it's ready to reenable the queues for
		 * this channel.
		 */
		idn_xmit_monitor_kickoff(channel);
	}

	return (rv);
}

/*
 * Function to support local loopback testing of IDN driver.
 * Primarily geared towards measuring stream-head and IDN driver
 * overhead with respect to data messages.  Setting idn_strhead_only
 * allows routine to focus on stream-head overhead by simply putting
 * the message straight to the 'next' queue of the destination
 * read-queue.  Current implementation puts the message directly to
 * the read-queue thus sending the message right back to the IDN driver
 * as though the data came in off the wire.  No need to worry about
 * any IDN layers attempting to ack data as that's normally handled
 * by idnh_recv_data.
 *
 * dst_netaddr = destination port-n-addr on local domain.
 * wq          = write queue from whence message came.
 * mp          = the (data-only) message.
 *
 * Returns 0		Indicates data handled.
 *	   errno	EAGAIN indicates data can be retried.
 *			Other errno's indicate failure to handle.
 */
static int
idn_send_data_loopback(idn_netaddr_t dst_netaddr, queue_t *wq, mblk_t *mp)
{
	register struct idnstr	*stp;
	struct idn	*sip;
	int		rv = 0;
	procname_t	proc = "idn_send_data_loopback";

	if (dst_netaddr.net.netid != idn_domain[idn.localid].dnetid) {
		PR_DATA("%s: dst_netaddr.net.netid 0x%x != local 0x%x\n",
		    proc, dst_netaddr.net.netid,
		    idn_domain[idn.localid].dnetid);
		rv = EADDRNOTAVAIL;
		goto done;
	}
	stp = (struct idnstr *)wq->q_ptr;
	if (!stp || !stp->ss_rq) {
		rv = EDESTADDRREQ;
		goto done;
	}
	sip = stp->ss_sip;

	idndl_read(sip, mp);
	rv = 0;

done:
	return (rv);
}

/*
 * Fill bufp with as much data as possible from the message pointed
 * to by mp up to size bytes.
 * Save our current read pointer in the variable parameter (data_rptrp)
 * so we know where to start on the next go around.  Don't want to
 * bump the actual b_rptr in the mblk because the mblk may need to
 * be reused, e.g. broadcast.
 * Return the mblk pointer to the position we had to stop.
 */
static mblk_t *
idn_fill_buffer(caddr_t bufp, int size, mblk_t *mp, uchar_t **data_rptrp)
{
	int	copysize;

	ASSERT(bufp && size);

	if (mp == NULL)
		return (NULL);

	while ((size > 0) && mp) {

		copysize = MIN(mp->b_wptr - (*data_rptrp), size);

		if (copysize > 0) {
			/*
			 * If there's data to copy, do it.
			 */
			bcopy((*data_rptrp), bufp, copysize);
			(*data_rptrp) += copysize;
			bufp += copysize;
			size -= copysize;
		}
		if (mp->b_wptr <= (*data_rptrp)) {
			/*
			 * If we emptied the mblk, then
			 * move on to the next one.
			 */
			for (mp = mp->b_cont;
			    mp && (mp->b_datap->db_type != M_DATA);
			    mp = mp->b_cont)
				;
			if (mp)
				*data_rptrp = mp->b_rptr;
		}
	}
	return (mp);
}

/*
 * Messages received here do NOT arrive on a stream, but are
 * instead handled via the idn_protocol_servers.  This routine
 * is effectively the job processor for the protocol servers.
 */
static void
idn_recv_proto(idn_protomsg_t *hp)
{
	int		domid, cpuid;
	int		sync_lock = 0;
	idn_domain_t	*dp;
	register uint_t	mtype;
	register uint_t	msgtype, acktype;
	idn_msgtype_t	mt;
	ushort_t	dcookie, tcookie;
	procname_t	proc = "idn_recv_proto";


	if (idn.state == IDNGS_IGNORE) {
		/*
		 * Fault injection to simulate non-responsive domain.
		 */
		return;
	}

	domid   = hp->m_domid;
	cpuid   = hp->m_cpuid;
	msgtype = hp->m_msgtype;
	acktype = hp->m_acktype;
	dcookie = IDN_DCOOKIE(hp->m_cookie);
	tcookie = IDN_TCOOKIE(hp->m_cookie);
	/*
	 * msgtype =	Is the type of message we received,
	 *		e.g. nego, ack, nego+ack, etc.
	 *
	 * acktype =	If we received a pure ack or nack
	 *		then this variable is set to the
	 *		type of message that was ack/nack'd.
	 */
	if ((mtype = msgtype & IDNP_MSGTYPE_MASK) == 0) {
		/*
		 * Received a pure ack/nack.
		 */
		mtype = acktype & IDNP_MSGTYPE_MASK;
	}

	if (!VALID_MSGTYPE(mtype)) {
		PR_PROTO("%s:%d: ERROR: invalid message type (0x%x)\n",
		    proc, domid, mtype);
		return;
	}
	if (!VALID_CPUID(cpuid)) {
		PR_PROTO("%s:%d: ERROR: invalid cpuid (%d)\n",
		    proc, domid, cpuid);
		return;
	}

	/*
	 * No pure data packets should reach this level.
	 * Data+ack messages will reach here, but only
	 * for the purpose of stopping the timer which
	 * happens by default when this routine is called.
	 */
	ASSERT(msgtype != IDNP_DATA);

	/*
	 * We should never receive a request from ourself,
	 * except for commands in the case of broadcasts!
	 */
	if ((domid == idn.localid) && (mtype != IDNP_CMD)) {
		char	str[15];

		inum2str(hp->m_msgtype, str);

		cmn_err(CE_WARN,
		    "IDN: 214: received message (%s[0x%x]) from self "
		    "(domid %d)",
		    str, hp->m_msgtype, domid);
		return;
	}

	IDN_SYNC_LOCK();
	/*
	 * Set a flag indicating whether we really need
	 * SYNC-LOCK.  We'll drop it in a little bit if
	 * we really don't need it.
	 */
	switch (mtype) {
	case IDNP_CON:
	case IDNP_FIN:
	case IDNP_NEGO:
		sync_lock = 1;
		break;

	default:
		break;
	}

	dp = &idn_domain[domid];
	IDN_DLOCK_EXCL(domid);

	/*
	 * The only messages we do _not_ check the cookie are:
	 *	nego
	 *	nego+ack
	 *	fin	 - if received cookie is 0.
	 *	fin+ack	 - if received cookie is 0.
	 *	ack/fin	 - if received cookie is 0.
	 *	nack/fin - if received cookie is 0.
	 */
	if (((msgtype & IDNP_MSGTYPE_MASK) != IDNP_NEGO) &&
	    ((mtype != IDNP_FIN) || (dcookie && dp->dcookie_recv))) {
		if (dp->dcookie_recv != dcookie) {
			dp->dcookie_errcnt++;
			if (dp->dcookie_err == 0) {
				/*
				 * Set cookie error to prevent a
				 * possible flood of bogus cookies
				 * and thus error messages.
				 */
				dp->dcookie_err = 1;
				cmn_err(CE_WARN,
				    "IDN: 215: invalid cookie (0x%x) "
				    "for message (0x%x) from domain %d",
				    dcookie, hp->m_msgtype, domid);

				PR_PROTO("%s:%d: received cookie (0x%x), "
				    "expected (0x%x) [errcnt = %d]\n",
				    proc, domid, dcookie,
				    dp->dcookie_recv, dp->dcookie_errcnt);
			}
			IDN_DUNLOCK(domid);
			IDN_SYNC_UNLOCK();
			return;
		}
	}
	dp->dcookie_err = 0;
	IDN_GLOCK_EXCL();

	idn_clear_awol(domid);

	IDN_GUNLOCK();
	if (!sync_lock)		/* really don't need SYNC-LOCK past here */
		IDN_SYNC_UNLOCK();

	/*
	 * Stop any timers that may have been outstanding for
	 * this domain, for this particular message type.
	 * Note that CFG timers are directly managed by
	 * config recv/send code.
	 */
	if ((mtype != IDNP_CFG) && (msgtype & IDNP_ACKNACK_MASK) && tcookie) {
		IDN_MSGTIMER_STOP(domid, mtype, tcookie);
	}

	/*
	 * Keep track of the last cpu to send us a message.
	 * If the domain has not yet been assigned, we'll need
	 * this cpuid in order to send back a respond.
	 */
	dp->dcpu_last = cpuid;

	mt.mt_mtype = (ushort_t)msgtype;
	mt.mt_atype = (ushort_t)acktype;
	mt.mt_cookie = tcookie;

	switch (mtype) {
	case IDNP_NEGO:
		idn_recv_nego(domid, &mt, hp->m_xargs, dcookie);
		break;

	case IDNP_CFG:
		idn_recv_config(domid, &mt, hp->m_xargs);
		break;

	case IDNP_CON:
		idn_recv_con(domid, &mt, hp->m_xargs);
		break;

	case IDNP_FIN:
		idn_recv_fin(domid, &mt, hp->m_xargs);
		break;

	case IDNP_CMD:
		idn_recv_cmd(domid, &mt, hp->m_xargs);
		break;

	case IDNP_DATA:
		ASSERT(msgtype & IDNP_ACKNACK_MASK);
		/*
		 * When doing the fast track we simply process
		 * possible nack error conditions.  The actual
		 * processing of the SMR data buffer is taken
		 * care of in idnh_recv_dataack.  When NOT doing
		 * the fast track, we do all the processing here
		 * in the protocol server.
		 */
		idn_recv_data(domid, &mt, hp->m_xargs);
		break;

	default:
		/*
		 * Should be receiving 0 inum and 0 acknack.
		 */
#ifdef DEBUG
		cmn_err(CE_PANIC,
#else /* DEBUG */
		    cmn_err(CE_WARN,
#endif /* DEBUG */
			/* CSTYLED */
			"IDN: 216: (0x%x)msgtype/(0x%x)acktype rcvd from "
			/* CSTYLED */
			"domain %d", msgtype, acktype, domid);
		break;
	}

	IDN_DUNLOCK(domid);
	/*
	 * All receiving routines are responsible for dropping drwlock.
	 */

	if (sync_lock)
		IDN_SYNC_UNLOCK();
}

/*
 * Once the CONFIG state is hit we immediately blast out all
 * of our config info.  This guarantees that the CONFIG state
 * effectively signifies that the sender has sent _all_ of
 * their config info.
 */
static void
idn_send_config(int domid, int phase)
{
	idn_domain_t	*dp;
	int		rv;
	clock_t		cfg_waittime = idn_msg_waittime[IDNP_CFG];
	procname_t	proc = "idn_send_config";

	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	dp = &idn_domain[domid];

	ASSERT(dp->dstate == IDNDS_CONFIG);

	if (phase == 1) {
		/*
		 * Reset stuff in dtmp to 0:
		 *	dcfgphase
		 *	dcksum
		 *	dncfgitems
		 *	dmaxnets
		 *	dmboxpernet
		 */
		dp->dtmp = 0;
	}

	if (dp->dcfgsnddone) {
		if (!dp->dcfgrcvdone) {
			IDN_MSGTIMER_START(domid, IDNP_CFG, 0,
			    cfg_waittime, NULL);
		}
		return;
	}

	IDN_DLOCK_SHARED(idn.localid);

	PR_PROTO("%s:%d: sending %s config (phase %d)\n",
	    proc, domid,
	    idn_domain[idn.localid].dvote.v.master ? "MASTER" : "SLAVE",
	    phase);

	if (idn_domain[idn.localid].dvote.v.master)
		rv = idn_send_master_config(domid, phase);
	else
		rv = idn_send_slave_config(domid, phase);

	IDN_DUNLOCK(idn.localid);

	if (rv >= 0) {

		if (rv == 1) {
			dp->dcfgsnddone = 1;
			PR_PROTO("%s:%d: SEND config DONE\n", proc, domid);
			if (!dp->dcfgrcvdone) {
				IDN_MSGTIMER_START(domid, IDNP_CFG, 0,
				    cfg_waittime, NULL);
			}
		} else {
			IDN_MSGTIMER_START(domid, IDNP_CFG, 0,
			    cfg_waittime, NULL);
		}
	}
}

/*
 * Clear out the mailbox table.
 * NOTE: This routine touches the SMR.
 */
static void
idn_reset_mboxtbl(idn_mboxtbl_t *mtp)
{
	int		qi;
	idn_mboxmsg_t	*mp = &mtp->mt_queue[0];

	qi = 0;
	do {
		mp[qi].ms_bframe = 0;
		mp[qi].ms_owner = 0;
		mp[qi].ms_flag = 0;
		IDN_MMBOXINDEX_INC(qi);
	} while (qi);
}

static int
idn_get_mbox_config(int domid, int *mindex, smr_offset_t *mtable,
    smr_offset_t *mdomain)
{
	idn_domain_t	*dp, *ldp;

	dp = &idn_domain[domid];
	ldp = &idn_domain[idn.localid];

	ASSERT(IDN_DLOCK_IS_EXCL(domid));
	ASSERT(IDN_DLOCK_IS_SHARED(idn.localid));
	ASSERT(IDN_GET_MASTERID() != IDN_NIL_DOMID);

	/*
	 * Get SMR offset of receive mailbox assigned
	 * to respective domain.  If I'm a slave then
	 * my dmbox.m_tbl will not have been assigned yet.
	 * Instead of sending the actual offset I send
	 * the master his assigned index.  Since the
	 * master knows what offset it will assign to
	 * me he can determine his assigned (recv) mailbox
	 * based on the offset and given index.  The local
	 * domain can also use this information once the
	 * dmbox.m_tbl is received to properly assign the
	 * correct mbox offset to the master.
	 */
	if (ldp->dmbox.m_tbl == NULL) {
		/*
		 * Local domain has not yet been assigned a
		 * (recv) mailbox table.  This must be the
		 * initial connection of this domain.
		 */
		ASSERT(dp->dvote.v.master && !ldp->dvote.v.master);
		ASSERT(mindex);
		*mindex = domid;
	} else {
		idn_mboxtbl_t	*mtp;

		mtp = IDN_MBOXTBL_PTR(ldp->dmbox.m_tbl, domid);

		ASSERT(mdomain);
		*mdomain = IDN_ADDR2OFFSET(mtp);

		if (ldp->dvote.v.master) {
			/*
			 * Need to calculate mailbox table to
			 * assign to the given domain.  Since
			 * I'm the master his mailbox is in
			 * the (all-domains) mailbox table.
			 */
			mtp = IDN_MBOXAREA_BASE(idn.mboxarea, domid);
			ASSERT(mtable);
			*mtable = IDN_ADDR2OFFSET(mtp);

			dp->dmbox.m_tbl = mtp;
		}
	}

	return (0);
}

/*
 * RETURNS:
 *	1	Unexpected/unnecessary phase.
 *	0	Successfully handled, timer needed.
 */
static int
idn_send_master_config(int domid, int phase)
{
	idn_cfgsubtype_t	cfg_subtype;
	int		rv = 0;
	idn_domain_t	*dp, *ldp;
	idn_msgtype_t	mt;
	int		nmcadr;
	uint_t		barpfn, larpfn;
	uint_t		cpus_u32, cpus_l32;
	uint_t		mcadr[3];
	smr_offset_t	mbox_table, mbox_domain;
	register int	b, p, m;
	procname_t	proc = "idn_send_master_config";

	ASSERT(IDN_DLOCK_IS_EXCL(domid));
	ASSERT(IDN_DLOCK_IS_SHARED(idn.localid));

	dp = &idn_domain[domid];
	ldp = &idn_domain[idn.localid];

	ASSERT(dp->dstate == IDNDS_CONFIG);
	ASSERT(dp->dvote.v.master == 0);
	ASSERT(ldp->dvote.v.master == 1);

	mt.mt_mtype = IDNP_CFG;
	mt.mt_atype = 0;
	mt.mt_cookie = 0;
	m = 0;
	mcadr[0] = mcadr[1] = mcadr[2] = 0;
	cfg_subtype.val = 0;

	switch (phase) {

	case 1:
		mbox_table = mbox_domain = IDN_NIL_SMROFFSET;
		idn_get_mbox_config(domid, NULL, &mbox_table, &mbox_domain);
		/*
		 * ----------------------------------------------------
		 * Send: SLABSIZE, DATAMBOX.DOMAIN, DATAMBOX.TABLE
		 * ----------------------------------------------------
		 */
		cfg_subtype.param.p[0] = IDN_CFGPARAM(IDNCFG_SIZE,
		    IDNCFGARG_SIZE_SLAB);
		cfg_subtype.param.p[1] = IDN_CFGPARAM(IDNCFG_DATAMBOX,
		    IDNCFGARG_DATAMBOX_DOMAIN);
		cfg_subtype.param.p[2] = IDN_CFGPARAM(IDNCFG_DATAMBOX,
		    IDNCFGARG_DATAMBOX_TABLE);
		cfg_subtype.info.num = 3;
		cfg_subtype.info.phase = phase;
		dp->dcfgphase = phase;

		ASSERT(mbox_domain != IDN_NIL_SMROFFSET);
		ASSERT(mbox_table != IDN_NIL_SMROFFSET);

		PR_PROTO("%s:%d:%d: sending SLABSIZE (%d), "
		    "DATAMBOX.DOMAIN (0x%x), DATAMBOX.TABLE (0x%x)\n",
		    proc, domid, phase, IDN_SLAB_BUFCOUNT, mbox_domain,
		    mbox_table);

		IDNXDC(domid, &mt, cfg_subtype.val, IDN_SLAB_BUFCOUNT,
		    mbox_domain, mbox_table);
		break;

	case 2:
		barpfn = idn.smr.locpfn;
		larpfn = barpfn + (uint_t)btop(MB2B(IDN_SMR_SIZE));
		/*
		 * ----------------------------------------------------
		 * Send: NETID, BARLAR
		 * ----------------------------------------------------
		 */
		cfg_subtype.param.p[0] = IDN_CFGPARAM(IDNCFG_NETID, 0);
		cfg_subtype.param.p[1] = IDN_CFGPARAM(IDNCFG_BARLAR,
		    IDNCFGARG_BARLAR_BAR);
		cfg_subtype.param.p[2] = IDN_CFGPARAM(IDNCFG_BARLAR,
		    IDNCFGARG_BARLAR_LAR);
		cfg_subtype.info.num = 3;
		cfg_subtype.info.phase = phase;
		dp->dcfgphase = phase;

		PR_PROTO("%s:%d:%d: sending NETID (%d), "
		    "BARPFN/LARPFN (0x%x/0x%x)\n",
		    proc, domid, phase, ldp->dnetid, barpfn, larpfn);

		IDNXDC(domid, &mt, cfg_subtype.val,
		    (uint_t)ldp->dnetid, barpfn, larpfn);
		break;

	case 3:
		nmcadr = ldp->dhw.dh_nmcadr;
		cpus_u32 = UPPER32_CPUMASK(ldp->dcpuset);
		cpus_l32 = LOWER32_CPUMASK(ldp->dcpuset);
		/*
		 * ----------------------------------------------------
		 * Send: CPUSET, NMCADR
		 * ----------------------------------------------------
		 */
		cfg_subtype.param.p[0] = IDN_CFGPARAM(IDNCFG_CPUSET,
		    IDNCFGARG_CPUSET_UPPER);
		cfg_subtype.param.p[1] = IDN_CFGPARAM(IDNCFG_CPUSET,
		    IDNCFGARG_CPUSET_LOWER);
		cfg_subtype.param.p[2] = IDN_CFGPARAM(IDNCFG_NMCADR, 0);
		cfg_subtype.info.num = 3;
		cfg_subtype.info.phase = phase;
		dp->dcfgphase = phase;

		PR_PROTO("%s:%d:%d: sending CPUSET (0x%x.%x), NMCADR (%d)\n",
		    proc, domid, phase, cpus_u32, cpus_l32, nmcadr);

		IDNXDC(domid, &mt, cfg_subtype.val,
		    cpus_u32, cpus_l32, nmcadr);
		break;

	case 4:
		/*
		 * ----------------------------------------------------
		 * Send: BOARDSET, MTU, BUFSIZE
		 * ----------------------------------------------------
		 */
		cfg_subtype.param.p[0] = IDN_CFGPARAM(IDNCFG_BOARDSET, 0);
		cfg_subtype.param.p[1] = IDN_CFGPARAM(IDNCFG_SIZE,
		    IDNCFGARG_SIZE_MTU);
		cfg_subtype.param.p[2] = IDN_CFGPARAM(IDNCFG_SIZE,
		    IDNCFGARG_SIZE_BUF);
		cfg_subtype.info.num = 3;
		cfg_subtype.info.phase = phase;
		dp->dcfgphase = phase;

		PR_PROTO("%s:%d:%d: sending BOARDSET (0x%x), MTU (0x%lx), "
		    "BUFSIZE (0x%x)\n", proc, domid, phase,
		    ldp->dhw.dh_boardset, IDN_MTU, IDN_SMR_BUFSIZE);

		IDNXDC(domid, &mt, cfg_subtype.val,
		    ldp->dhw.dh_boardset, IDN_MTU, IDN_SMR_BUFSIZE);
		break;

	case 5:
		/*
		 * ----------------------------------------------------
		 * Send: MAXNETS, MBOXPERNET, CKSUM
		 * ----------------------------------------------------
		 */
		cfg_subtype.param.p[0] = IDN_CFGPARAM(IDNCFG_DATASVR,
		    IDNCFGARG_DATASVR_MAXNETS);
		cfg_subtype.param.p[1] = IDN_CFGPARAM(IDNCFG_DATASVR,
		    IDNCFGARG_DATASVR_MBXPERNET);
		cfg_subtype.param.p[2] = IDN_CFGPARAM(IDNCFG_OPTIONS,
		    IDNCFGARG_CHECKSUM);
		cfg_subtype.info.num = 3;
		cfg_subtype.info.phase = phase;
		dp->dcfgphase = phase;

		PR_PROTO("%s:%d:%d: sending MAXNETS (%d), "
		    "MBOXPERNET (%d), CKSUM (%d)\n",
		    proc, domid, phase,
		    IDN_MAX_NETS, IDN_MBOX_PER_NET,
		    IDN_CHECKSUM);

		IDNXDC(domid, &mt, cfg_subtype.val,
		    IDN_MAX_NETS, IDN_MBOX_PER_NET, IDN_CHECKSUM);
		break;

	case 6:
		/*
		 * ----------------------------------------------------
		 * Send: NWRSIZE (piggyback on MCADRs)
		 * ----------------------------------------------------
		 */
		cfg_subtype.param.p[0] = IDN_CFGPARAM(IDNCFG_SIZE,
		    IDNCFGARG_SIZE_NWR);
		mcadr[0] = IDN_NWR_SIZE;
		m = 1;

		/*FALLTHROUGH*/

	default:	/* case 7 and above */
		/*
		 * ----------------------------------------------------
		 * Send: MCADR's
		 * ----------------------------------------------------
		 * First need to figure how many we've already sent
		 * based on what phase of CONFIG we're in.
		 * ----------------------------------------------------
		 */
		if (phase > 6) {
			p = ((phase - 7) * 3) + 2;
			for (b = 0; (b < MAX_BOARDS) && (p > 0); b++)
				if (ldp->dhw.dh_mcadr[b])
					p--;
		} else {
			b = 0;
		}

		for (; (b < MAX_BOARDS) && (m < 3); b++) {
			if (ldp->dhw.dh_mcadr[b] == 0)
				continue;
			mcadr[m] = ldp->dhw.dh_mcadr[b];
			cfg_subtype.param.p[m] = IDN_CFGPARAM(IDNCFG_MCADR, b);
			m++;
		}
		if (m > 0) {
			if (phase == 6) {
				PR_PROTO("%s:%d:%d: sending NWRSIZE (%d), "
				    "MCADRs (0x%x, 0x%x)\n",
				    proc, domid, phase,
				    mcadr[0], mcadr[1], mcadr[2]);
			} else {
				PR_PROTO("%s:%d:%d: sending MCADRs "
				    "(0x%x, 0x%x, 0x%x)\n",
				    proc, domid, phase,
				    mcadr[0], mcadr[1], mcadr[2]);
			}
			cfg_subtype.info.num = m;
			cfg_subtype.info.phase = phase;
			dp->dcfgphase = phase;

			IDNXDC(domid, &mt, cfg_subtype.val,
			    mcadr[0], mcadr[1], mcadr[2]);
		} else {
			rv = 1;
		}
		break;
	}

	return (rv);
}

/*
 * RETURNS:
 *	1	Unexpected/unnecessary phase.
 *	0	Successfully handled.
 */
static int
idn_send_slave_config(int domid, int phase)
{
	idn_cfgsubtype_t	cfg_subtype;
	int		rv = 0;
	idn_domain_t	*dp, *ldp;
	smr_offset_t	mbox_domain;
	idn_msgtype_t	mt;
	int		mbox_index;
	uint_t		cpus_u32, cpus_l32;
	procname_t	proc = "idn_send_slave_config";

	ASSERT(IDN_DLOCK_IS_EXCL(domid));
	ASSERT(IDN_DLOCK_IS_SHARED(idn.localid));

	mt.mt_mtype = IDNP_CFG;
	mt.mt_atype = 0;
	dp = &idn_domain[domid];
	ldp = &idn_domain[idn.localid];

	ASSERT(dp->dstate == IDNDS_CONFIG);
	ASSERT(ldp->dvote.v.master == 0);

	switch (phase) {

	case 1:
		mbox_index = IDN_NIL_DOMID;
		mbox_domain = IDN_NIL_SMROFFSET;
		idn_get_mbox_config(domid, &mbox_index, NULL, &mbox_domain);
		/*
		 * ----------------------------------------------------
		 * Send: DATAMBOX.DOMAIN or DATAMBOX.INDEX,
		 *	 DATASVR.MAXNETS, DATASVR.MBXPERNET
		 * ----------------------------------------------------
		 */
		cfg_subtype.val = 0;
		if (mbox_index == IDN_NIL_DOMID) {
			ASSERT(mbox_domain != IDN_NIL_SMROFFSET);
			cfg_subtype.param.p[0] = IDN_CFGPARAM(IDNCFG_DATAMBOX,
			    IDNCFGARG_DATAMBOX_DOMAIN);
		} else {
			/*
			 * Should only be sending Index to
			 * the master and not another slave.
			 */
			ASSERT(dp->dvote.v.master);
			ASSERT(mbox_domain == IDN_NIL_SMROFFSET);
			cfg_subtype.param.p[0] = IDN_CFGPARAM(IDNCFG_DATAMBOX,
			    IDNCFGARG_DATAMBOX_INDEX);
		}
		cfg_subtype.param.p[1] = IDN_CFGPARAM(IDNCFG_DATASVR,
		    IDNCFGARG_DATASVR_MAXNETS);
		cfg_subtype.param.p[2] = IDN_CFGPARAM(IDNCFG_DATASVR,
		    IDNCFGARG_DATASVR_MBXPERNET);
		cfg_subtype.info.num = 3;
		cfg_subtype.info.phase = phase;
		dp->dcfgphase = phase;

		PR_PROTO("%s:%d:%d: sending DATAMBOX.%s (0x%x), "
		    "MAXNETS (%d), MBXPERNET (%d)\n",
		    proc, domid, phase,
		    (IDN_CFGPARAM_ARG(cfg_subtype.param.p[0])
		    == IDNCFGARG_DATAMBOX_INDEX) ? "INDEX" : "DOMAIN",
		    (mbox_index == IDN_NIL_DOMID) ? mbox_domain : mbox_index,
		    IDN_MAX_NETS, IDN_MBOX_PER_NET);

		IDNXDC(domid, &mt, cfg_subtype.val,
		    ((mbox_index == IDN_NIL_DOMID) ? mbox_domain : mbox_index),
		    IDN_MAX_NETS, IDN_MBOX_PER_NET);
		break;

	case 2:
		cpus_u32 = UPPER32_CPUMASK(ldp->dcpuset);
		cpus_l32 = LOWER32_CPUMASK(ldp->dcpuset);
		/*
		 * ----------------------------------------------------
		 * Send: NETID, CPUSET
		 * ----------------------------------------------------
		 */
		cfg_subtype.val = 0;
		cfg_subtype.param.p[0] = IDN_CFGPARAM(IDNCFG_NETID, 0);
		cfg_subtype.param.p[1] = IDN_CFGPARAM(IDNCFG_CPUSET,
		    IDNCFGARG_CPUSET_UPPER);
		cfg_subtype.param.p[2] = IDN_CFGPARAM(IDNCFG_CPUSET,
		    IDNCFGARG_CPUSET_LOWER);
		cfg_subtype.info.num = 3;
		cfg_subtype.info.phase = phase;
		dp->dcfgphase = phase;

		PR_PROTO("%s:%d:%d: sending NETID (%d), "
		    "CPUSET (0x%x.%x)\n", proc, domid, phase,
		    ldp->dnetid, cpus_u32, cpus_l32);

		IDNXDC(domid, &mt, cfg_subtype.val,
		    (uint_t)ldp->dnetid, cpus_u32, cpus_l32);
		break;

	case 3:
		/*
		 * ----------------------------------------------------
		 * Send: BOARDSET, MTU, BUFSIZE
		 * ----------------------------------------------------
		 */
		cfg_subtype.val = 0;
		cfg_subtype.param.p[0] = IDN_CFGPARAM(IDNCFG_BOARDSET, 0);
		cfg_subtype.param.p[1] = IDN_CFGPARAM(IDNCFG_SIZE,
		    IDNCFGARG_SIZE_MTU);
		cfg_subtype.param.p[2] = IDN_CFGPARAM(IDNCFG_SIZE,
		    IDNCFGARG_SIZE_BUF);
		cfg_subtype.info.num = 3;
		cfg_subtype.info.phase = phase;
		dp->dcfgphase = phase;

		PR_PROTO("%s:%d:%d: sending BOARDSET (0x%x), MTU (0x%lx), "
		    "BUFSIZE (0x%x)\n",
		    proc, domid, phase, ldp->dhw.dh_boardset, IDN_MTU,
		    IDN_SMR_BUFSIZE);

		IDNXDC(domid, &mt, cfg_subtype.val,
		    ldp->dhw.dh_boardset, IDN_MTU, IDN_SMR_BUFSIZE);
		break;

	case 4:
		/*
		 * ----------------------------------------------------
		 * Send: SLABSIZE, OPTIONS.CHECKSUM, NWR_SIZE
		 * ----------------------------------------------------
		 */
		cfg_subtype.val = 0;
		cfg_subtype.param.p[0] = IDN_CFGPARAM(IDNCFG_SIZE,
		    IDNCFGARG_SIZE_SLAB);
		cfg_subtype.param.p[1] = IDN_CFGPARAM(IDNCFG_OPTIONS,
		    IDNCFGARG_CHECKSUM);
		cfg_subtype.param.p[2] = IDN_CFGPARAM(IDNCFG_SIZE,
		    IDNCFGARG_SIZE_NWR);
		cfg_subtype.info.num = 3;
		cfg_subtype.info.phase = phase;
		dp->dcfgphase = phase;

		PR_PROTO("%s:%d:%d: sending SLABSIZE (%d), CKSUM (%d), "
		    "NWRSIZE (%d)\n",
		    proc, domid, phase, IDN_SLAB_BUFCOUNT,
		    IDN_CHECKSUM, IDN_NWR_SIZE);

		IDNXDC(domid, &mt, cfg_subtype.val,
		    IDN_SLAB_BUFCOUNT, IDN_CHECKSUM, IDN_NWR_SIZE);
		break;

	default:
		rv = 1;
		break;
	}

	return (rv);
}

#define	CFG_FATAL	((uint_t)-1)	/* reset link */
#define	CFG_CONTINUE	0x0000		/* looking for more */
#define	CFG_DONE	0x0001		/* got everything expected */
#define	CFG_ERR_MTU	0x0002
#define	CFG_ERR_BUF	0x0004
#define	CFG_ERR_SLAB	0x0008
#define	CFG_ERR_NWR	0x0010
#define	CFG_ERR_NETS	0x0020
#define	CFG_ERR_MBOX	0x0040
#define	CFG_ERR_NMCADR	0x0080
#define	CFG_ERR_MCADR	0x0100
#define	CFG_ERR_CKSUM	0x0200
#define	CFG_ERR_SMR	0x0400
#define	CFG_MAX_ERRORS	16

#define	CFGERR2IDNKERR(ce) \
	(((ce) & CFG_ERR_MTU)	? IDNKERR_CONFIG_MTU 	: \
	((ce) & CFG_ERR_BUF)	? IDNKERR_CONFIG_BUF 	: \
	((ce) & CFG_ERR_SLAB)	? IDNKERR_CONFIG_SLAB 	: \
	((ce) & CFG_ERR_NWR)	? IDNKERR_CONFIG_NWR 	: \
	((ce) & CFG_ERR_NETS)	? IDNKERR_CONFIG_NETS 	: \
	((ce) & CFG_ERR_MBOX)	? IDNKERR_CONFIG_MBOX 	: \
	((ce) & CFG_ERR_NMCADR)	? IDNKERR_CONFIG_NMCADR	: \
	((ce) & CFG_ERR_MCADR)	? IDNKERR_CONFIG_MCADR	: \
	((ce) & CFG_ERR_CKSUM)	? IDNKERR_CONFIG_CKSUM	: \
	((ce) & CFG_ERR_SMR)	? IDNKERR_CONFIG_SMR	: 0)

#define	CFGERR2FINARG(ce) \
	(((ce) & CFG_ERR_MTU)	? IDNFIN_ARG_CFGERR_MTU    : \
	((ce) & CFG_ERR_BUF)	? IDNFIN_ARG_CFGERR_BUF    : \
	((ce) & CFG_ERR_SLAB)	? IDNFIN_ARG_CFGERR_SLAB   : \
	((ce) & CFG_ERR_NWR)	? IDNFIN_ARG_CFGERR_NWR    : \
	((ce) & CFG_ERR_NETS)	? IDNFIN_ARG_CFGERR_NETS   : \
	((ce) & CFG_ERR_MBOX)	? IDNFIN_ARG_CFGERR_MBOX   : \
	((ce) & CFG_ERR_NMCADR)	? IDNFIN_ARG_CFGERR_NMCADR : \
	((ce) & CFG_ERR_MCADR)	? IDNFIN_ARG_CFGERR_MCADR  : \
	((ce) & CFG_ERR_CKSUM)	? IDNFIN_ARG_CFGERR_CKSUM  : \
	((ce) & CFG_ERR_SMR)	? IDNFIN_ARG_CFGERR_SMR	   : IDNFIN_ARG_NONE)

/*
 * Called when some CFG messages arrive.  We use dncfgitems to count the
 * total number of items received so far since we'll receive multiple CFG
 * messages during the CONFIG phase.  Note that dncfgitems is initialized
 * in idn_send_config.
 */
static void
idn_recv_config(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	uint_t		msg = mtp->mt_mtype;
	uint_t		rv, rv_expected, rv_actual;
	int		pnum;
	int		phase;
	register int	p;
	register int	c;
	idn_mainmbox_t	*mmp;
	register uint_t	subtype, subtype_arg;
	idn_domain_t	*dp;
	int		index;
	idn_domain_t	*ldp = &idn_domain[idn.localid];
	idn_mboxtbl_t	*mbtp;
	idn_cfgsubtype_t	cfg_subtype;
	idn_xdcargs_t	cfg_arg;
	idn_msgtype_t	mt;
	idnsb_error_t	idnerr;
	procname_t	proc = "idn_recv_config";

	ASSERT(domid != idn.localid);

	GET_XARGS(xargs, &cfg_subtype.val, &cfg_arg[0], &cfg_arg[1],
	    &cfg_arg[2]);
	cfg_arg[3] = 0;

	dp = &idn_domain[domid];

	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	if (dp->dstate != IDNDS_CONFIG) {
		/*
		 * Not ready to receive config info.
		 * Drop whatever he sent us.  Let the
		 * timer continue and timeout if needed.
		 */
		PR_PROTO("%s:%d: WARNING state(%s) != CONFIG\n",
		    proc, domid, idnds_str[dp->dstate]);
		return;
	}

	if ((msg & IDNP_ACKNACK_MASK) || dp->dcfgsnddone) {
		IDN_MSGTIMER_STOP(domid, IDNP_CFG, 0);
	}

	if (msg & IDNP_ACKNACK_MASK) {
		/*
		 * ack/cfg
		 */
		phase = GET_XARGS_CFG_PHASE(xargs);

		PR_PROTO("%s:%d: received ACK for CFG phase %d\n",
		    proc, domid, phase);
		if (phase != (int)dp->dcfgphase) {
			/*
			 * Phase is not what we were
			 * expecting.  Something got lost
			 * in the shuffle.  Restart the
			 * timer and let it timeout if necessary
			 * and reestablish the connection.
			 */
			IDN_MSGTIMER_START(domid, IDNP_CFG, dp->dcfgphase,
			    idn_msg_waittime[IDNP_CFG], NULL);
		} else {
			idn_send_config(domid, phase + 1);

			if (dp->dcfgsnddone && dp->dcfgrcvdone) {
				IDN_DUNLOCK(domid);
				IDN_SYNC_LOCK();
				IDN_DLOCK_EXCL(domid);
				if (dp->dstate == IDNDS_CONFIG) {
					dp->dxp = &xphase_con;
					IDN_XSTATE_TRANSITION(dp, IDNXS_PEND);
					bzero(xargs, sizeof (xargs));

					idn_xphase_transition(domid, NULL,
					    xargs);
				}
				IDN_SYNC_UNLOCK();
			}
		}
		return;
	}

	pnum = (int)cfg_subtype.info.num;
	phase = (int)cfg_subtype.info.phase;

	for (p = 0; p < pnum; p++) {
		int	board;
#ifdef DEBUG
		uint_t	val;
		char	*str;

		val = 0;
		str = NULL;
#define	RCVCFG(s, v)	{ str = (s); val = (v); }
#else
#define	RCVCFG(s, v)	{}
#endif /* DEBUG */

		subtype	    = IDN_CFGPARAM_TYPE(cfg_subtype.param.p[p]);
		subtype_arg = IDN_CFGPARAM_ARG(cfg_subtype.param.p[p]);

		switch (subtype) {

		case IDNCFG_BARLAR:
			IDN_GLOCK_EXCL();
			switch (subtype_arg) {

			case IDNCFGARG_BARLAR_BAR:
				if (idn.smr.rempfn == PFN_INVALID) {
					idn.smr.rempfn = (pfn_t)cfg_arg[p];
					dp->dncfgitems++;
					RCVCFG("BARLAR_BAR", cfg_arg[p]);
				}
				break;

			case IDNCFGARG_BARLAR_LAR:
				if (idn.smr.rempfnlim == PFN_INVALID) {
					idn.smr.rempfnlim = (pfn_t)cfg_arg[p];
					dp->dncfgitems++;
					RCVCFG("BARLAR_LAR", cfg_arg[p]);
				}
				break;

			default:
				cmn_err(CE_WARN,
				    "IDN 217: unknown CFGARG type (%d) "
				    "from domain %d",
				    subtype_arg, domid);
				break;
			}
			IDN_GUNLOCK();
			break;

		case IDNCFG_MCADR:
			board = subtype_arg;
			if ((board >= 0) && (board < MAX_BOARDS) &&
			    (dp->dhw.dh_mcadr[board] == 0)) {
				dp->dhw.dh_mcadr[board] = cfg_arg[p];
				dp->dncfgitems++;
				RCVCFG("MCADR", cfg_arg[p]);
			}
			break;

		case IDNCFG_NMCADR:
			if (dp->dhw.dh_nmcadr == 0) {
				dp->dhw.dh_nmcadr = cfg_arg[p];
				dp->dncfgitems++;
				RCVCFG("NMCADR", cfg_arg[p]);
			}
			break;

		case IDNCFG_CPUSET:
			switch (subtype_arg) {

			case IDNCFGARG_CPUSET_UPPER:
			{
				cpuset_t	tmpset;

				MAKE64_CPUMASK(tmpset, cfg_arg[p], 0);
				CPUSET_OR(dp->dcpuset, tmpset);
				dp->dncfgitems++;
				RCVCFG("CPUSET_UPPER", cfg_arg[p]);
				break;
			}
			case IDNCFGARG_CPUSET_LOWER:
			{
				cpuset_t	tmpset;

				MAKE64_CPUMASK(tmpset, 0, cfg_arg[p]);
				CPUSET_OR(dp->dcpuset, tmpset);
				dp->dncfgitems++;
				RCVCFG("CPUSET_LOWER", cfg_arg[p]);
				break;
			}
			default:
				ASSERT(0);
				break;
			}
			break;

		case IDNCFG_NETID:
			if (dp->dnetid == (ushort_t)-1) {
				dp->dnetid = (ushort_t)cfg_arg[p];
				dp->dncfgitems++;
				RCVCFG("NETID", cfg_arg[p]);
			}
			break;

		case IDNCFG_BOARDSET:
			if ((dp->dhw.dh_boardset & cfg_arg[p])
			    == dp->dhw.dh_boardset) {
				/*
				 * Boardset better include what we
				 * already know about.
				 */
				dp->dhw.dh_boardset = cfg_arg[p];
				dp->dncfgitems++;
				RCVCFG("BOARDSET", cfg_arg[p]);
			}
			break;

		case IDNCFG_SIZE:
			switch (subtype_arg) {

			case IDNCFGARG_SIZE_MTU:
				if (dp->dmtu == 0) {
					dp->dmtu = cfg_arg[p];
					dp->dncfgitems++;
					RCVCFG("MTU", cfg_arg[p]);
				}
				break;

			case IDNCFGARG_SIZE_BUF:
				if (dp->dbufsize == 0) {
					dp->dbufsize = cfg_arg[p];
					dp->dncfgitems++;
					RCVCFG("BUFSIZE", cfg_arg[p]);
				}
				break;

			case IDNCFGARG_SIZE_SLAB:
				if (dp->dslabsize == 0) {
					dp->dslabsize = (short)cfg_arg[p];
					dp->dncfgitems++;
					RCVCFG("SLABSIZE", cfg_arg[p]);
				}
				break;

			case IDNCFGARG_SIZE_NWR:
				if (dp->dnwrsize == 0) {
					dp->dnwrsize = (short)cfg_arg[p];
					dp->dncfgitems++;
					RCVCFG("NWRSIZE", cfg_arg[p]);
				}
				break;

			default:
				ASSERT(0);
				break;
			}
			break;

		case IDNCFG_DATAMBOX:
			switch (subtype_arg) {

			case IDNCFGARG_DATAMBOX_TABLE:
				if (ldp->dmbox.m_tbl ||
				    !dp->dvote.v.master ||
				    !VALID_NWROFFSET(cfg_arg[p], 4)) {
					/*
					 * Only a master should be
					 * sending us a datambox table.
					 */
					break;
				}
				IDN_DLOCK_EXCL(idn.localid);
				ldp->dmbox.m_tbl = (idn_mboxtbl_t *)
				    IDN_OFFSET2ADDR(cfg_arg[p]);
				IDN_DUNLOCK(idn.localid);
				dp->dncfgitems++;
				RCVCFG("DATAMBOX.TABLE", cfg_arg[p]);
				break;

			case IDNCFGARG_DATAMBOX_DOMAIN:
				if (dp->dmbox.m_send->mm_smr_mboxp ||
				    !VALID_NWROFFSET(cfg_arg[p], 4))
					break;
				mbtp = (idn_mboxtbl_t *)
				    IDN_OFFSET2ADDR(cfg_arg[p]);
				mmp = dp->dmbox.m_send;
				for (c = 0; c < IDN_MAX_NETS; c++) {

					mutex_enter(&mmp[c].mm_mutex);
					mmp[c].mm_smr_mboxp = mbtp;
					mutex_exit(&mmp[c].mm_mutex);

					IDN_MBOXTBL_PTR_INC(mbtp);
				}
				if (c <= 0)
					break;
				dp->dncfgitems++;
				RCVCFG("DATAMBOX.DOMAIN", cfg_arg[p]);
				break;

			case IDNCFGARG_DATAMBOX_INDEX:
				if (!ldp->dvote.v.master ||
				    dp->dmbox.m_send->mm_smr_mboxp) {
					/*
					 * If I'm not the master then
					 * I can't handle processing a
					 * mailbox index.
					 * OR, if I already have the send
					 * mailbox, I'm done with this
					 * config item.
					 */
					break;
				}
				ASSERT(dp->dmbox.m_tbl);
				index = (int)cfg_arg[p];
				/*
				 * The given index is the local domain's
				 * index into the remote domain's mailbox
				 * table that contains the mailbox that
				 * remote domain wants the local domain to
				 * use as the send mailbox for messages
				 * destined for the remote domain.
				 * I.e. from the remote domain's
				 *	perspective, this is his receive
				 *	mailbox.
				 */
				mbtp = IDN_MBOXTBL_PTR(dp->dmbox.m_tbl, index);
				mmp = dp->dmbox.m_send;
				for (c = 0; c < IDN_MAX_NETS; c++) {

					mutex_enter(&mmp[c].mm_mutex);
					mmp[c].mm_smr_mboxp = mbtp;
					mutex_exit(&mmp[c].mm_mutex);

					IDN_MBOXTBL_PTR_INC(mbtp);
				}
				if (c <= 0)
					break;
				dp->dncfgitems++;
				RCVCFG("DATAMBOX.INDEX", cfg_arg[p]);
				break;

			default:
				ASSERT(0);
				break;
			}
			break;

		case IDNCFG_DATASVR:
			switch (subtype_arg) {

			case IDNCFGARG_DATASVR_MAXNETS:
				if (dp->dmaxnets)
					break;
				dp->dmaxnets = (uint_t)(cfg_arg[p] & 0x3f);
				dp->dncfgitems++;
				RCVCFG("DATASVR.MAXNETS", cfg_arg[p]);
				break;

			case IDNCFGARG_DATASVR_MBXPERNET:
				if (dp->dmboxpernet)
					break;
				dp->dmboxpernet = (uint_t)(cfg_arg[p] & 0x1ff);
				dp->dncfgitems++;
				RCVCFG("DATASVR.MBXPERNET", cfg_arg[p]);
				break;

			default:
				ASSERT(0);
				break;
			}
			break;

		case IDNCFG_OPTIONS:
			switch (subtype_arg) {

			case IDNCFGARG_CHECKSUM:
				if (dp->dcksum)
					break;
				if ((cfg_arg[p] & 0xff) == 0)
					dp->dcksum = 1;		/* off */
				else
					dp->dcksum = 2;		/* on */
				dp->dncfgitems++;
				RCVCFG("OPTIONS.CHECKSUM", cfg_arg[p]);
				break;

			default:
				ASSERT(0);
				break;
			}

		default:
			break;
		}
#ifdef DEBUG
		PR_PROTO("%s:%d: received %s (0x%x)\n",
		    proc, domid, str ? str : "<empty>", val);
#endif /* DEBUG */
	}

	mt.mt_mtype = IDNP_ACK;
	mt.mt_atype = IDNP_CFG;
	mt.mt_cookie = mtp->mt_cookie;
	CLR_XARGS(cfg_arg);
	SET_XARGS_CFG_PHASE(cfg_arg, phase);
	idn_send_acknack(domid, &mt, cfg_arg);

	rv_expected = rv_actual = 0;

	if (dp->dvote.v.master == 0) {
		/*
		 * Remote domain is a slave, check if we've received
		 * all that we were expecting, and if so transition to
		 * the next state.
		 */
		rv = idn_check_slave_config(domid, &rv_expected, &rv_actual);
	} else {
		/*
		 * Remote domain is a master, check if this slave has
		 * received all that it was expecting, and if so
		 * transition to the next state.
		 */
		rv = idn_check_master_config(domid, &rv_expected, &rv_actual);
	}

	switch (rv) {
	case CFG_DONE:
		/*
		 * All config info received that was expected, wrap up.
		 */
		if (!idn_recv_config_done(domid) && dp->dvote.v.master) {
			IDN_DLOCK_EXCL(idn.localid);
			ldp->dvote.v.connected = 1;
			IDN_DUNLOCK(idn.localid);
		}
		break;

	case CFG_CONTINUE:
		/*
		 * If we're not done sending our own config, then
		 * there's no need to set a timer since one will
		 * automatically be set when we send a config
		 * message waiting for an acknowledgement.
		 */
		if (dp->dcfgsnddone) {
			/*
			 * We haven't yet received all the config
			 * information we were expecting.  Need to
			 * restart CFG timer if we've sent everything..
			 */
			IDN_MSGTIMER_START(domid, IDNP_CFG, 0,
			    idn_msg_waittime[IDNP_CFG], NULL);
		}
		break;

	case CFG_FATAL:
		/*
		 * Fatal error occurred during config exchange.
		 * We need to shutdown connection in this
		 * case, so initiate a (non-relink) FIN.
		 * so let's get the show on the road.
		 */
		IDN_DUNLOCK(domid);
		IDN_SYNC_LOCK();
		IDN_DLOCK_EXCL(domid);
		/*
		 * If the state has changed from CONFIG
		 * then somebody else has taken over
		 * control of this domain so we can just
		 * bail out.
		 */
		if (dp->dstate == IDNDS_CONFIG) {
			INIT_IDNKERR(&idnerr);
			SET_IDNKERR_ERRNO(&idnerr, EPROTO);
			SET_IDNKERR_IDNERR(&idnerr, IDNKERR_CONFIG_FATAL);
			SET_IDNKERR_PARAM0(&idnerr, domid);
			idn_update_op(IDNOP_ERROR, DOMAINSET(domid), &idnerr);
			/*
			 * Keep this guy around so we can try again.
			 */
			DOMAINSET_ADD(idn.domset.ds_relink, domid);
			IDN_HISTORY_LOG(IDNH_RELINK, domid, dp->dstate,
			    idn.domset.ds_relink);
			idn_disconnect(domid, IDNFIN_NORMAL,
			    IDNFIN_ARG_CFGERR_FATAL,
			    IDNFIN_SYNC_NO);
		}
		IDN_SYNC_UNLOCK();
		break;

	default:	/* parameter conflict */
		IDN_DUNLOCK(domid);
		IDN_SYNC_LOCK();
		IDN_DLOCK_EXCL(domid);
		if (dp->dstate != IDNDS_CONFIG) {
			/*
			 * Hmmm...changed in the short period
			 * we had dropped the lock, oh well.
			 */
			IDN_SYNC_UNLOCK();
			break;
		}
		c = 0;
		for (p = 0; p < CFG_MAX_ERRORS; p++)
			if (rv & (1 << p))
				c++;
		INIT_IDNKERR(&idnerr);
		SET_IDNKERR_ERRNO(&idnerr, EINVAL);
		SET_IDNKERR_PARAM0(&idnerr, domid);
		if (c > 1) {
			SET_IDNKERR_IDNERR(&idnerr, IDNKERR_CONFIG_MULTIPLE);
			SET_IDNKERR_PARAM1(&idnerr, c);
		} else {
			SET_IDNKERR_IDNERR(&idnerr, CFGERR2IDNKERR(rv));
			SET_IDNKERR_PARAM1(&idnerr, rv_expected);
			SET_IDNKERR_PARAM2(&idnerr, rv_actual);
		}
		/*
		 * Any parameter conflicts are grounds for dismissal.
		 */
		if (idn.domset.ds_connected == 0) {
			domainset_t	domset;
			/*
			 * We have no other connections yet.
			 * We must blow out of here completely
			 * unless we have relinkers left from
			 * a RECONFIG.
			 */
			IDN_GLOCK_EXCL();
			domset = ~idn.domset.ds_relink;
			if (idn.domset.ds_relink == 0) {
				IDN_GSTATE_TRANSITION(IDNGS_DISCONNECT);
			}
			domset &= ~idn.domset.ds_hitlist;
			IDN_SET_NEW_MASTERID(IDN_NIL_DOMID);
			IDN_GUNLOCK();
			IDN_DUNLOCK(domid);

			DOMAINSET_DEL(domset, idn.localid);

			idn_update_op(IDNOP_ERROR, DOMAINSET_ALL, &idnerr);

			PR_HITLIST("%s:%d: unlink_domainset(%x) due to "
			    "CFG error (relink=%x, hitlist=%x)\n",
			    proc, domid, domset, idn.domset.ds_relink,
			    idn.domset.ds_hitlist);

			idn_unlink_domainset(domset, IDNFIN_NORMAL,
			    CFGERR2FINARG(rv),
			    IDNFIN_OPT_UNLINK,
			    BOARDSET_ALL);
			IDN_SYNC_UNLOCK();
			IDN_DLOCK_EXCL(domid);
		} else {
			PR_HITLIST("%s:%d: idn_disconnect(%d) due to CFG "
			    "error (conn=%x, relink=%x, hitlist=%x)\n",
			    proc, domid, domid, idn.domset.ds_connected,
			    idn.domset.ds_relink, idn.domset.ds_hitlist);
			/*
			 * If we have other connections then
			 * we're only going to blow away this
			 * single connection.
			 */
			idn_update_op(IDNOP_ERROR, DOMAINSET(domid), &idnerr);

			DOMAINSET_DEL(idn.domset.ds_relink, domid);
			idn_disconnect(domid, IDNFIN_NORMAL,
			    CFGERR2FINARG(rv), IDNFIN_SYNC_NO);
			IDN_SYNC_UNLOCK();
		}
		break;
	}
}

/*
 * Called by master or slave which expects exactly the following
 * with respect to config info received from a SLAVE:
 * 	IDNCFG_CPUSET
 *	IDNCFG_NETID
 *	IDNCFG_BOARDSET
 *	IDNCFG_SIZE (MTU, BUF, SLAB, NWR)
 *	IDNCFG_DATAMBOX (DOMAIN or INDEX if caller is master)
 *	IDNCFG_DATASVR (MAXNETS, MBXPERNET)
 *	IDNCFG_OPTIONS (CHECKSUM)
 */
static uint_t
idn_check_slave_config(int domid, uint_t *exp, uint_t *act)
{
	uint_t		rv = 0;
	idn_domain_t	*ldp, *dp;
	procname_t	proc = "idn_check_slave_config";

	dp = &idn_domain[domid];
	ldp = &idn_domain[idn.localid];

	ASSERT(domid != idn.localid);
	ASSERT(IDN_DLOCK_IS_EXCL(domid));
	ASSERT(dp->dstate == IDNDS_CONFIG);

	PR_PROTO("%s:%d: number received %d, number expected %d\n",
	    proc, domid, (int)dp->dncfgitems, IDN_SLAVE_NCFGITEMS);

	if ((int)dp->dncfgitems < IDN_SLAVE_NCFGITEMS)
		return (CFG_CONTINUE);

	if ((dp->dnetid == (ushort_t)-1) ||
	    CPUSET_ISNULL(dp->dcpuset) ||
	    (dp->dhw.dh_boardset == 0) ||
	    (dp->dmbox.m_send->mm_smr_mboxp == NULL) ||
	    (dp->dmaxnets == 0) ||
	    (dp->dmboxpernet == 0) ||
	    (dp->dcksum == 0) ||
	    (dp->dmtu == 0) ||
	    (dp->dbufsize == 0) ||
	    (dp->dslabsize == 0) ||
	    (dp->dnwrsize == 0)) {
		/*
		 * We received our IDN_SLAVE_NCFGITEMS config items,
		 * but not all what we were expecting!  Gotta nack and
		 * close connection.
		 */
		cmn_err(CE_WARN,
		    "IDN: 218: missing some required config items from "
		    "domain %d", domid);

		rv = CFG_FATAL;
		goto done;
	}

	if (!valid_mtu(dp->dmtu)) {
		cmn_err(CE_WARN,
		    "IDN: 219: remote domain %d MTU (%d) invalid "
		    "(local.mtu = %d)", dp->domid, dp->dmtu, ldp->dmtu);

		*exp = (uint_t)ldp->dmtu;
		*act = (uint_t)dp->dmtu;
		rv |= CFG_ERR_MTU;
	}
	if (!valid_bufsize(dp->dbufsize)) {
		cmn_err(CE_WARN,
		    "IDN: 220: remote domain %d BUFSIZE (%d) invalid "
		    "(local.bufsize = %d)", dp->domid, dp->dbufsize,
		    ldp->dbufsize);

		*exp = (uint_t)ldp->dbufsize;
		*act = (uint_t)dp->dbufsize;
		rv |= CFG_ERR_BUF;
	}
	if (!valid_slabsize((int)dp->dslabsize)) {
		cmn_err(CE_WARN,
		    "IDN: 221: remote domain %d SLABSIZE (%d) invalid "
		    "(local.slabsize = %d)",
		    dp->domid, dp->dslabsize, ldp->dslabsize);

		*exp = (uint_t)ldp->dslabsize;
		*act = (uint_t)dp->dslabsize;
		rv |= CFG_ERR_SLAB;
	}
	if (!valid_nwrsize((int)dp->dnwrsize)) {
		cmn_err(CE_WARN,
		    "IDN: 223: remote domain %d NWRSIZE (%d) invalid "
		    "(local.nwrsize = %d)",
		    dp->domid, dp->dnwrsize, ldp->dnwrsize);

		*exp = (uint_t)ldp->dnwrsize;
		*act = (uint_t)dp->dnwrsize;
		rv |= CFG_ERR_NWR;
	}
	if ((int)dp->dmaxnets != IDN_MAX_NETS) {
		cmn_err(CE_WARN,
		    "IDN: 224: remote domain %d MAX_NETS (%d) invalid "
		    "(local.maxnets = %d)",
		    dp->domid, (int)dp->dmaxnets, IDN_MAX_NETS);

		*exp = (uint_t)IDN_MAX_NETS;
		*act = (uint_t)dp->dmaxnets;
		rv |= CFG_ERR_NETS;
	}
	if ((int)dp->dmboxpernet != IDN_MBOX_PER_NET) {
		cmn_err(CE_WARN,
		    "IDN: 225: remote domain %d MBOX_PER_NET (%d) "
		    "invalid (local.mboxpernet = %d)",
		    dp->domid, (int)dp->dmboxpernet, IDN_MBOX_PER_NET);

		*exp = (uint_t)IDN_MBOX_PER_NET;
		*act = (uint_t)dp->dmboxpernet;
		rv |= CFG_ERR_MBOX;
	}
	if ((dp->dcksum - 1) != (uchar_t)IDN_CHECKSUM) {
		cmn_err(CE_WARN,
		    "IDN: 226: remote domain %d CHECKSUM flag (%d) "
		    "mismatches local domain's (%d)",
		    dp->domid, (int)dp->dcksum - 1, IDN_CHECKSUM);

		*exp = (uint_t)IDN_CHECKSUM;
		*act = (uint_t)(dp->dcksum - 1);
		rv |= CFG_ERR_CKSUM;
	}

done:

	return (rv ? rv : CFG_DONE);
}

/*
 * Called by slave ONLY which expects exactly the following
 * config info from the MASTER:
 *	IDNCFG_BARLAR
 *	IDNCFG_MCADR
 *	IDNCFG_NMCADR
 * 	IDNCFG_CPUSET
 *	IDNCFG_NETID
 *	IDNCFG_BOARDSET
 *	IDNCFG_SIZE (MTU, BUF, SLAB, NWR)
 *	IDNCFG_DATAMBOX (TABLE, DOMAIN)
 *	IDNCFG_DATASVR (MAXNETS, MBXPERNET)
 *	IDNCFG_OPTIONS (CHECKSUM)
 */
static uint_t
idn_check_master_config(int domid, uint_t *exp, uint_t *act)
{
	uint_t		rv = 0;
	int		nmcadr;
	int		total_expitems;
	int		p, m, err;
	idn_domain_t	*dp;
	idn_domain_t	*ldp = &idn_domain[idn.localid];
	procname_t	proc = "idn_check_master_config";

	dp = &idn_domain[domid];

	ASSERT(IDN_GET_MASTERID() != idn.localid);
	ASSERT(domid != idn.localid);
	ASSERT(IDN_DLOCK_IS_EXCL(domid));
	ASSERT(dp->dstate == IDNDS_CONFIG);

	PR_PROTO("%s:%d: number received %d, minimum number expected %d\n",
	    proc, domid, (int)dp->dncfgitems, IDN_MASTER_NCFGITEMS);

	if ((int)dp->dncfgitems < IDN_MASTER_NCFGITEMS)
		return (CFG_CONTINUE);

	/*
	 * We have at least IDN_MASTER_NCFGITEMS items which
	 * means we have at least one MCADR.  Need to make sure
	 * we have all that we're expecting, NMCADR.
	 */
	total_expitems = IDN_MASTER_NCFGITEMS + dp->dhw.dh_nmcadr - 1;
	if ((dp->dhw.dh_nmcadr == 0) ||
	    ((int)dp->dncfgitems < total_expitems)) {
		/*
		 * We have not yet received all the MCADRs
		 * we're expecting.
		 */
		PR_PROTO("%s:%d: haven't received all MCADRs yet.\n",
		    proc, domid);
		return (CFG_CONTINUE);
	}

	nmcadr = 0;
	for (p = 0; p < MAX_BOARDS; p++)
		if (dp->dhw.dh_mcadr[p] != 0)
			nmcadr++;

	IDN_GLOCK_SHARED();
	if ((idn.smr.rempfn == PFN_INVALID) ||
	    (idn.smr.rempfnlim == PFN_INVALID) ||
	    (dp->dnetid == (ushort_t)-1) ||
	    CPUSET_ISNULL(dp->dcpuset) ||
	    (dp->dhw.dh_boardset == 0) ||
	    (nmcadr != dp->dhw.dh_nmcadr) ||
	    (dp->dmbox.m_send->mm_smr_mboxp == NULL) ||
	    (ldp->dmbox.m_tbl == NULL) ||
	    (dp->dmaxnets == 0) ||
	    (dp->dmboxpernet == 0) ||
	    (dp->dcksum == 0) ||
	    (dp->dmtu == 0) ||
	    (dp->dbufsize == 0) ||
	    (dp->dnwrsize == 0)) {

		IDN_GUNLOCK();
		/*
		 * We received all of our config items, but not
		 * all what we were expecting!  Gotta reset and
		 * close connection.
		 */
		cmn_err(CE_WARN,
		    "IDN: 227: missing some required config items from "
		    "domain %d", domid);

		rv = CFG_FATAL;
		goto done;
	}
	if ((idn.smr.rempfnlim - idn.smr.rempfn) > btop(MB2B(IDN_SMR_SIZE))) {
		/*
		 * The master's SMR region is larger than
		 * mine!  This means that this domain may
		 * receive I/O buffers which are out of the
		 * range of this local domain's SMR virtual
		 * address space.  The master SMR has to be
		 * no larger than the local SMR in order to
		 * guarantee enough local virtual addresses
		 * to see all of the SMR space.
		 * XXX - Possibly add negotiating SMR size.
		 *	 Try to create a new virtual mapping.
		 *	 Could let domains negotiate SMR size.
		 *	 Winning size would have to be smallest
		 *	 in DC.  If so, how to handle incoming
		 *	 domains with even smaller SMRs?
		 *	 - Could either disallow connection
		 *	 - Could reconfigure to use smaller SMR.
		 */
		cmn_err(CE_WARN,
		    "IDN: 228: master's SMR (%ld) larger than "
		    "local's SMR (%ld)",
		    idn.smr.rempfnlim - idn.smr.rempfn,
		    btop(MB2B(IDN_SMR_SIZE)));

		*exp = (uint_t)IDN_SMR_SIZE;
		*act = (uint_t)B2MB(ptob(idn.smr.rempfnlim - idn.smr.rempfn));
		rv |= CFG_ERR_SMR;
	}
	IDN_GUNLOCK();

	if (!valid_mtu(dp->dmtu)) {
		cmn_err(CE_WARN,
		    "IDN: 219: remote domain %d MTU (%d) invalid "
		    "(local.mtu = %d)", dp->domid, dp->dmtu, ldp->dmtu);

		*exp = (uint_t)ldp->dmtu;
		*act = (uint_t)dp->dmtu;
		rv |= CFG_ERR_MTU;
	}
	if (!valid_bufsize(dp->dbufsize)) {
		cmn_err(CE_WARN,
		    "IDN: 220: remote domain %d BUFSIZE (%d) invalid "
		    "(local.bufsize = %d)", dp->domid, dp->dbufsize,
		    ldp->dbufsize);

		*exp = (uint_t)ldp->dbufsize;
		*act = (uint_t)dp->dbufsize;
		rv |= CFG_ERR_BUF;
	}
	if (!valid_nwrsize((int)dp->dnwrsize)) {
		cmn_err(CE_WARN,
		    "IDN: 223: remote domain %d NWRSIZE (%d) invalid "
		    "(local.nwrsize = %d)",
		    dp->domid, dp->dnwrsize, ldp->dnwrsize);

		*exp = (uint_t)ldp->dnwrsize;
		*act = (uint_t)dp->dnwrsize;
		rv |= CFG_ERR_NWR;
	}
	if ((int)dp->dmaxnets != IDN_MAX_NETS) {
		cmn_err(CE_WARN,
		    "IDN: 224: remote domain %d MAX_NETS (%d) invalid "
		    "(local.maxnets = %d)",
		    dp->domid, (int)dp->dmaxnets, IDN_MAX_NETS);

		*exp = (uint_t)IDN_MAX_NETS;
		*act = (uint_t)dp->dmaxnets;
		rv |= CFG_ERR_NETS;
	}
	if ((int)dp->dmboxpernet != IDN_MBOX_PER_NET) {
		cmn_err(CE_WARN,
		    "IDN: 225: remote domain %d MBOX_PER_NET (%d) "
		    "invalid (local.mboxpernet = %d)",
		    dp->domid, (int)dp->dmboxpernet, IDN_MBOX_PER_NET);

		*exp = (uint_t)IDN_MBOX_PER_NET;
		*act = (uint_t)dp->dmboxpernet;
		rv |= CFG_ERR_MBOX;
	}
	if ((dp->dcksum - 1) != (uchar_t)IDN_CHECKSUM) {
		cmn_err(CE_WARN,
		    "IDN: 226: remote domain %d CHECKSUM flag (%d) "
		    "mismatches local domain's (%d)",
		    dp->domid, (int)dp->dcksum - 1, IDN_CHECKSUM);

		*exp = (uint_t)IDN_CHECKSUM;
		*act = (uint_t)(dp->dcksum - 1);
		rv |= CFG_ERR_CKSUM;
	}
	nmcadr = 0;
	err = 0;
	for (m = 0; m < MAX_BOARDS; m++) {
		if (!BOARD_IN_SET(dp->dhw.dh_boardset, m) &&
		    dp->dhw.dh_mcadr[m]) {
			cmn_err(CE_WARN,
			    "IDN: 229: remote domain %d boardset (0x%x) "
			    "conflicts with MCADR(board %d) [0x%x]",
			    dp->domid, (uint_t)dp->dhw.dh_boardset, m,
			    dp->dhw.dh_mcadr[m]);
			err++;
		}
		if (dp->dhw.dh_mcadr[m])
			nmcadr++;
	}
	if (err) {
		*exp = 0;
		*act = err;
		rv |= CFG_ERR_MCADR;
	} else if (nmcadr != dp->dhw.dh_nmcadr) {
		cmn_err(CE_WARN,
		    "IDN: 230: remote domain %d reported number of "
		    "MCADRs (%d) mismatches received (%d)",
		    dp->domid, dp->dhw.dh_nmcadr, nmcadr);
		*exp = (uint_t)dp->dhw.dh_nmcadr;
		*act = (uint_t)nmcadr;
		rv |= CFG_ERR_NMCADR;
	}

done:

	return (rv ? rv : CFG_DONE);
}

static int
idn_recv_config_done(int domid)
{
	boardset_t		b_conflicts;
	cpuset_t		p_conflicts;
	register int		p, i;
	register idn_domain_t	*dp;
	idnsb_error_t		idnerr;
	procname_t		proc = "idn_recv_config_done";

	ASSERT(domid != IDN_NIL_DOMID);
	dp = &idn_domain[domid];
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	/*
	 * Well, we received all that we were expecting
	 * so stop any CFG timers we had going.
	 */
	IDN_MSGTIMER_STOP(domid, IDNP_CFG, 0);

	dp->dncpus = 0;
	for (p = 0; p < NCPU; p++)
		if (CPU_IN_SET(dp->dcpuset, p))
			dp->dncpus++;
	dp->dhw.dh_nboards = 0;
	for (p = 0; p < MAX_BOARDS; p++)
		if (BOARD_IN_SET(dp->dhw.dh_boardset, p))
			dp->dhw.dh_nboards++;

	IDN_GLOCK_EXCL();
	/*
	 * Verify dcpuset and dhw.dh_boardset don't
	 * conflict with any existing DC member.
	 */
	b_conflicts = idn.dc_boardset & dp->dhw.dh_boardset;
	CPUSET_ZERO(p_conflicts);
	CPUSET_OR(p_conflicts, idn.dc_cpuset);
	CPUSET_AND(p_conflicts, dp->dcpuset);

	if (b_conflicts || !CPUSET_ISNULL(p_conflicts)) {
		if (b_conflicts) {
			cmn_err(CE_WARN,
			    "IDN: 231: domain %d boardset "
			    "(0x%x) conflicts with existing "
			    "IDN boardset (0x%x)",
			    domid, dp->dhw.dh_boardset,
			    b_conflicts);
		}
		if (!CPUSET_ISNULL(p_conflicts)) {
			cmn_err(CE_WARN,
			    "IDN: 232: domain %d cpuset "
			    "(0x%x.%0x) conflicts with existing "
			    "IDN cpuset (0x%x.%0x)", domid,
			    UPPER32_CPUMASK(dp->dcpuset),
			    LOWER32_CPUMASK(dp->dcpuset),
			    UPPER32_CPUMASK(p_conflicts),
			    LOWER32_CPUMASK(p_conflicts));
		}
		IDN_GUNLOCK();
		/*
		 * Need to disconnect and not retry with this guy.
		 */
		IDN_DUNLOCK(domid);
		IDN_SYNC_LOCK();
		DOMAINSET_DEL(idn.domset.ds_relink, domid);
		IDN_DLOCK_EXCL(domid);

		INIT_IDNKERR(&idnerr);
		SET_IDNKERR_ERRNO(&idnerr, EPROTO);
		SET_IDNKERR_IDNERR(&idnerr, IDNKERR_CONFIG_FATAL);
		SET_IDNKERR_PARAM0(&idnerr, domid);
		idn_update_op(IDNOP_ERROR, DOMAINSET(domid), &idnerr);

		idn_disconnect(domid, IDNFIN_FORCE_HARD,
		    IDNFIN_ARG_CFGERR_FATAL, IDNFIN_SYNC_NO);
		IDN_SYNC_UNLOCK();

		return (-1);
	}

	idn_mainmbox_reset(domid, dp->dmbox.m_send);
	idn_mainmbox_reset(domid, dp->dmbox.m_recv);

#ifdef IDNBUG_CPUPERBOARD
	/*
	 * We only allow connections to domains whose (mem) boards
	 * all have at least one cpu.  This is necessary so that
	 * we can program the CICs of that respective board.  This
	 * is primarily only a requirement if the remote domain
	 * is the master _and_ has the SMR in that particular board.
	 * To simplify the checking we simply restrict connections to
	 * domains that have at least one cpu on all boards that
	 * contain memory.
	 */
	if (!idn_cpu_per_board((void *)NULL, dp->dcpuset, &dp->dhw)) {
		cmn_err(CE_WARN,
		    "IDN: 233: domain %d missing CPU per "
		    "memory boardset (0x%x), CPU boardset (0x%x)",
		    domid, dp->dhw.dh_boardset,
		    cpuset2boardset(dp->dcpuset));

		IDN_GUNLOCK();
		/*
		 * Need to disconnect and not retry with this guy.
		 */
		IDN_DUNLOCK(domid);
		IDN_SYNC_LOCK();
		DOMAINSET_DEL(idn.domset.ds_relink, domid);
		IDN_DLOCK_EXCL(domid);

		INIT_IDNKERR(&idnerr);
		SET_IDNKERR_ERRNO(&idnerr, EINVAL);
		SET_IDNKERR_IDNERR(&idnerr, IDNKERR_CPU_CONFIG);
		SET_IDNKERR_PARAM0(&idnerr, domid);
		idn_update_op(IDNOP_ERROR, DOMAINSET(domid), &idnerr);

		idn_disconnect(domid, IDNFIN_FORCE_HARD,
		    IDNFIN_ARG_CPUCFG, IDNFIN_SYNC_NO);
		IDN_SYNC_UNLOCK();

		return (-1);
	}
#endif /* IDNBUG_CPUPERBOARD */

	CPUSET_OR(idn.dc_cpuset, dp->dcpuset);
	idn.dc_boardset |= dp->dhw.dh_boardset;

	IDN_GUNLOCK();

	/*
	 * Set up the portmap for this domain.
	 */
	i = -1;
	for (p = 0; p < NCPU; p++) {
		BUMP_INDEX(dp->dcpuset, i);
		dp->dcpumap[p] = (uchar_t)i;
	}

	/*
	 * Got everything we need from the remote
	 * domain, now we can program hardware as needed.
	 */
	if (idn_program_hardware(domid) != 0) {
		domainset_t	domset;
		/*
		 * Yikes!  Failed to program hardware.
		 * Gotta bail.
		 */
		cmn_err(CE_WARN,
		    "IDN: 234: failed to program hardware for domain %d "
		    "(boardset = 0x%x)",
		    domid, dp->dhw.dh_boardset);

		IDN_DUNLOCK(domid);
		/*
		 * If we're having problems programming our
		 * hardware we better unlink completely from
		 * the IDN before things get really bad.
		 */
		IDN_SYNC_LOCK();
		IDN_GLOCK_EXCL();
		IDN_GSTATE_TRANSITION(IDNGS_DISCONNECT);
		domset = DOMAINSET_ALL;
		DOMAINSET_DEL(domset, idn.localid);
		IDN_SET_NEW_MASTERID(IDN_NIL_DOMID);
		IDN_GUNLOCK();

		INIT_IDNKERR(&idnerr);
		SET_IDNKERR_ERRNO(&idnerr, EINVAL);
		SET_IDNKERR_IDNERR(&idnerr, IDNKERR_HW_ERROR);
		SET_IDNKERR_PARAM0(&idnerr, domid);
		idn_update_op(IDNOP_ERROR, DOMAINSET_ALL, &idnerr);

		idn_unlink_domainset(domset, IDNFIN_NORMAL, IDNFIN_ARG_HWERR,
		    IDNFIN_OPT_UNLINK, BOARDSET_ALL);

		IDN_SYNC_UNLOCK();
		IDN_DLOCK_EXCL(domid);

		return (-1);
	}

	/*
	 * Now that hardware has been programmed we can
	 * remap the SMR into our local space, if necessary.
	 */
	IDN_GLOCK_EXCL();
	if (domid == IDN_GET_MASTERID()) {
		/*
		 * No need to worry about disabling the data
		 * server since at this stage there is only
		 * one and he doesn't go active until his
		 * mailbox (dmbox.m_recv->mm_smr_mboxp) is set up.
		 */
		smr_remap(&kas, idn.smr.vaddr, idn.smr.rempfn, IDN_SMR_SIZE);
	}
	IDN_GUNLOCK();

	/*
	 * There is no need to ACK the CFG messages since remote
	 * domain would not progress to the next state (CON_SENT)
	 * unless he has received everything.
	 */

	dp->dcfgrcvdone = 1;
	PR_PROTO("%s:%d: RECV config DONE\n", proc, domid);

	if (dp->dcfgsnddone) {
		idn_xdcargs_t	xargs;
		/*
		 * Well, we've received all that we were expecting,
		 * but we don't know if the remote domain has
		 * received all that it was expecting from us,
		 * although we know we transferred everything
		 * so let's get the show on the road.
		 */
		IDN_DUNLOCK(domid);
		IDN_SYNC_LOCK();
		IDN_DLOCK_EXCL(domid);
		/*
		 * If the state has changed from CONFIG
		 * then somebody else has taken over
		 * control of this domain so we can just
		 * bail out.
		 */
		if (dp->dstate == IDNDS_CONFIG) {
			dp->dxp = &xphase_con;
			IDN_XSTATE_TRANSITION(dp, IDNXS_PEND);
			bzero(xargs, sizeof (xargs));

			idn_xphase_transition(domid, NULL, xargs);
		}
		IDN_SYNC_UNLOCK();
	}

	return (0);
}

static int
idn_verify_config_mbox(int domid)
{
	idn_domain_t	*ldp, *dp;
	idn_mainmbox_t	*mmp;
	idn_mboxtbl_t	*mtp;
	int		c, rv = 0;
	uint_t		activeptr, readyptr;
	ushort_t	mbox_csum;

	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	dp = &idn_domain[domid];
	ldp = &idn_domain[idn.localid];

	/*
	 * The master will have assigned us the dmbox.m_tbl
	 * from which we assign our receive mailboxes.
	 * The first (0) entry contains the cookie used
	 * for verification.
	 */
	IDN_DLOCK_SHARED(idn.localid);
	/*
	 * Now that we have an assigned mboxtbl from the
	 * master, we can determine which receive mailbox
	 * we indirectly assigned to him at the time we
	 * sent him his MBOX_INDEX.  Prep it, however note
	 * that the master will have not been able to
	 * validate it because of the chicken 'n egg
	 * problem between a master and slave.  Thus we
	 * need to reset the cookie after the prep.
	 */
	mmp = dp->dmbox.m_recv;
	mtp = IDN_MBOXTBL_PTR(ldp->dmbox.m_tbl, domid);
	for (c = 0; c < IDN_MAX_NETS; c++) {
		mutex_enter(&mmp[c].mm_mutex);
		ASSERT(!mmp[c].mm_smr_mboxp);

		mmp[c].mm_smr_mboxp = mtp;
		mbox_csum = IDN_CKSUM_MBOX(&mtp->mt_header);
		if (!VALID_MBOXHDR(&mtp->mt_header, c, mbox_csum)) {
			cmn_err(CE_WARN,
			    "IDN: 235: [recv] mailbox (domain %d, "
			    "channel %d) SMR CORRUPTED - RELINK",
			    domid, c);
			cmn_err(CE_CONT,
			    "IDN: 235: [recv] expected (cookie 0x%x, "
			    "cksum 0x%x) actual (cookie 0x%x, "
			    "cksum 0x%x)\n",
			    IDN_GET_MBOXHDR_COOKIE(&mtp->mt_header),
			    (int)mtp->mt_header.mh_cksum,
			    IDN_MAKE_MBOXHDR_COOKIE(0, 0, c),
			    (int)mbox_csum);
			mutex_exit(&mmp[c].mm_mutex);
			rv = -1;
			break;
		}
		activeptr = mtp->mt_header.mh_svr_active_ptr;
		readyptr = mtp->mt_header.mh_svr_ready_ptr;
		/*
		 * Verify pointers are valid.
		 */
		if (!activeptr || !VALID_NWROFFSET(activeptr, 2) ||
		    !readyptr || !VALID_NWROFFSET(readyptr, 2)) {
			cmn_err(CE_WARN,
			    "IDN: 235: [recv] mailbox (domain %d, "
			    "channel %d) SMR CORRUPTED - RELINK",
			    domid, c);
			cmn_err(CE_CONT,
			    "IDN: 235: [recv] activeptr (0x%x), "
			    "readyptr (0x%x)\n",
			    activeptr, readyptr);
			mutex_exit(&mmp[c].mm_mutex);
			rv = -1;
			break;
		}
		mmp[c].mm_smr_activep =	(ushort_t *)IDN_OFFSET2ADDR(activeptr);
		mmp[c].mm_smr_readyp =	(ushort_t *)IDN_OFFSET2ADDR(readyptr);
		mutex_exit(&mmp[c].mm_mutex);
		IDN_MBOXTBL_PTR_INC(mtp);
	}

	IDN_DUNLOCK(idn.localid);

	if (rv)
		return (rv);

	/*
	 * Now we need to translate SMR offsets for send mailboxes
	 * to actual virtual addresses.
	 */
	mmp = dp->dmbox.m_send;
	for (c = 0; c < IDN_MAX_NETS; mmp++, c++) {
		mutex_enter(&mmp->mm_mutex);
		if ((mtp = mmp->mm_smr_mboxp) == NULL) {
			mutex_exit(&mmp->mm_mutex);
			rv = -1;
			break;
		}

		mbox_csum = IDN_CKSUM_MBOX(&mtp->mt_header);

		if (!VALID_MBOXHDR(&mtp->mt_header, c, mbox_csum)) {
			cmn_err(CE_WARN,
			    "IDN: 235: [send] mailbox (domain %d, "
			    "channel %d) SMR CORRUPTED - RELINK",
			    domid, c);
			cmn_err(CE_CONT,
			    "IDN: 235: [send] expected (cookie 0x%x, "
			    "cksum 0x%x) actual (cookie 0x%x, "
			    "cksum 0x%x)\n",
			    IDN_GET_MBOXHDR_COOKIE(&mtp->mt_header),
			    (int)mtp->mt_header.mh_cksum,
			    IDN_MAKE_MBOXHDR_COOKIE(0, 0, c),
			    (int)mbox_csum);
			mutex_exit(&mmp->mm_mutex);
			rv = -1;
			break;
		}
		activeptr = mtp->mt_header.mh_svr_active_ptr;
		readyptr = mtp->mt_header.mh_svr_ready_ptr;
		/*
		 * Paranoid check.
		 */
		if (!activeptr || !VALID_NWROFFSET(activeptr, 2) ||
		    !readyptr || !VALID_NWROFFSET(readyptr, 2)) {
			cmn_err(CE_WARN,
			    "IDN: 235: [send] mailbox (domain %d, "
			    "channel %d) SMR CORRUPTED - RELINK",
			    domid, c);
			cmn_err(CE_CONT,
			    "IDN: 235: [send] activeptr (0x%x), "
			    "readyptr (0x%x)\n",
			    activeptr, readyptr);
			mutex_exit(&mmp->mm_mutex);
			rv = -1;
			break;
		}
		mmp->mm_smr_activep = (ushort_t *)IDN_OFFSET2ADDR(activeptr);
		mmp->mm_smr_readyp = (ushort_t *)IDN_OFFSET2ADDR(readyptr);
		idn_reset_mboxtbl(mtp);
		mutex_exit(&mmp->mm_mutex);
		IDN_MBOXTBL_PTR_INC(mtp);
	}

	return (rv);
}

/*
 * The BUFSIZEs between domains have to be equal so that slave buffers
 * and the master's slabpool are consistent.
 * The MTUs between domains have to be equal so they can transfer
 * packets consistently without possible data truncation.
 *
 * ZZZ - Perhaps these could be negotiated?
 */
static int
valid_mtu(uint_t mtu)
{
	return ((mtu == idn_domain[idn.localid].dmtu) && mtu);
}

static int
valid_bufsize(uint_t bufsize)
{
	return ((bufsize == idn_domain[idn.localid].dbufsize) && bufsize);
}

static int
valid_slabsize(int slabsize)
{
	return ((slabsize == idn_domain[idn.localid].dslabsize) && slabsize);
}

static int
valid_nwrsize(int nwrsize)
{
	return ((nwrsize == idn_domain[idn.localid].dnwrsize) && nwrsize);
}

static int
idn_program_hardware(int domid)
{
	int		rv, is_master;
	idn_domain_t	*dp;
	uint_t		*mcadrp;
	pfn_t		rem_pfn, rem_pfnlimit;
	procname_t	proc = "idn_program_hardware";

	PR_PROTO("%s:%d: program hw in domain %d w.r.t remote domain %d\n",
	    proc, domid, idn.localid, domid);

	dp = &idn_domain[domid];

	ASSERT(domid != idn.localid);
	ASSERT(IDN_DLOCK_IS_EXCL(domid));
	ASSERT(dp->dstate == IDNDS_CONFIG);

	IDN_GLOCK_EXCL();

	if (DOMAIN_IN_SET(idn.domset.ds_hwlinked, domid)) {
		IDN_GUNLOCK();
		return (0);
	}

	DOMAINSET_ADD(idn.domset.ds_flush, domid);
	CHECKPOINT_OPENED(IDNSB_CHKPT_CACHE, dp->dhw.dh_boardset, 1);

	if (domid != IDN_GET_MASTERID()) {
		/*
		 * If the remote domain is a slave, then
		 * all we have to program is the CIC sm_mask.
		 */
		is_master = 0;
		if ((idn.localid == IDN_GET_MASTERID()) &&
		    lock_try(&idn.first_hwlink)) {
			/*
			 * This is our first HW link and I'm the
			 * master, which means we need to program
			 * our local bar/lar.
			 */
			ASSERT(idn.first_hwmasterid == (short)IDN_NIL_DOMID);
			idn.first_hwmasterid = (short)idn.localid;
			rem_pfn = idn.smr.locpfn;
			rem_pfnlimit = idn.smr.locpfn +
			    btop(MB2B(IDN_SMR_SIZE));
		} else {
			/*
			 * Otherwise, just a slave linking to
			 * another slave.  No bar/lar updating
			 * necessary.
			 */
			rem_pfn = rem_pfnlimit = PFN_INVALID;
		}
		mcadrp = NULL;
	} else {
		/*
		 * If the remote domain is a master, then
		 * we need to program the CIC sm_mask/sm_bar/sm_lar,
		 * and PC's.
		 */
		is_master = 1;
		rem_pfn = idn.smr.rempfn;
		rem_pfnlimit = idn.smr.rempfnlim;
		mcadrp = dp->dhw.dh_mcadr;
		ASSERT(idn.first_hwmasterid == (short)IDN_NIL_DOMID);
		idn.first_hwmasterid = (short)domid;
	}

	PR_PROTO("%s:%d: ADD bset (0x%x)\n", proc, domid, dp->dhw.dh_boardset);

	rv = idnxf_shmem_add(is_master, dp->dhw.dh_boardset,
	    rem_pfn, rem_pfnlimit, mcadrp);

	if (rv == 0) {
		DOMAINSET_ADD(idn.domset.ds_hwlinked, domid);
	} else {
		if (rem_pfn == idn.smr.locpfn)
			lock_clear(&idn.first_hwlink);

		if (idn.first_hwmasterid == (short)domid)
			idn.first_hwmasterid = (short)IDN_NIL_DOMID;

		(void) idnxf_shmem_sub(is_master, dp->dhw.dh_boardset);
	}

	IDN_GUNLOCK();

	return (rv);
}

static int
idn_deprogram_hardware(int domid)
{
	int		rv, is_master;
	idn_domain_t	*dp;
	procname_t	proc = "idn_deprogram_hardware";


	dp = &idn_domain[domid];

	ASSERT(domid != idn.localid);
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	/*
	 * Need to take into consideration what boards remote
	 * domain was connected to.  If we don't have a connection to
	 * them ourself, then we better remove them now , otherwise
	 * they'll never be removed (unless we link to them at some point).
	 */
#if 0
	DEBUG_USECDELAY(500000);
#endif /* 0 */

	IDN_GLOCK_EXCL();

	if (!DOMAIN_IN_SET(idn.domset.ds_hwlinked, domid)) {
		IDN_GUNLOCK();
		return (0);
	}

	PR_PROTO("%s:%d: DEprogram hw in domain %d w.r.t remote domain %d\n",
	    proc, domid, idn.localid, domid);

	/*
	 * It's possible to come through this flow for domains that
	 * have not been programmed, i.e. not in idn.hwlinked_domset,
	 * so don't bother asserting that they might be in there.
	 * This can occur if we lose a domain during the config/syn
	 * sequence.  If this occurs we won't know whether the remote
	 * domain has programmed its hardware or not.  If it has then
	 * it will have to go through the DMAP sequence and thus we
	 * have to go through it also.  So, if we reach at least the
	 * CONFIG state, we need to go through the DMAP handshake.
	 */

	PR_PROTO("%s:%d: SUB bset (0x%x)\n", proc, domid, dp->dhw.dh_boardset);

	if (idn.first_hwmasterid == (short)domid) {
		is_master = 1;
		idn.first_hwmasterid = (short)IDN_NIL_DOMID;
	} else {
		is_master = 0;
	}
	rv = idnxf_shmem_sub(is_master, dp->dhw.dh_boardset);

	if (rv == 0)
		DOMAINSET_DEL(idn.domset.ds_hwlinked, domid);

	IDN_GUNLOCK();

	return (rv);
}

/*
 * Remember can't send slabs back to master at this point.
 * Entered with write-drwlock held.
 * Returns with drwlock dropped.
 */
static void
idn_deconfig(int domid)
{
	idn_domain_t	*dp, *ldp;
	smr_slab_t	*sp;
	int		c, masterid;
	procname_t	proc = "idn_deconfig";

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_DLOCK_IS_EXCL(domid));
	ASSERT(domid != idn.localid);

	ldp = &idn_domain[idn.localid];
	dp = &idn_domain[domid];

	ASSERT(dp->dstate == IDNDS_DMAP);

	PR_PROTO("%s:%d: (dio=%d, dioerr=%d, dnslabs=%d)\n",
	    proc, domid, dp->dio, dp->dioerr, dp->dnslabs);

	IDN_GLOCK_EXCL();
	masterid = IDN_GET_MASTERID();

	idn.dc_boardset &= ~dp->dhw.dh_boardset;
	for (c = 0; c < NCPU; c++) {
		if (CPU_IN_SET(dp->dcpuset, c)) {
			CPUSET_DEL(idn.dc_cpuset, c);
		}
	}

	IDN_GUNLOCK();

	(void) smr_buf_free_all(domid);

	if (idn.localid == masterid) {
		/*
		 * Since I'm the master there may
		 * have been slabs in this domain's
		 * idn_domain[] entry.
		 */
		DSLAB_LOCK_EXCL(domid);
		if ((sp = dp->dslab) != NULL) {
			PR_PROTO("%s:%d: freeing up %d dead slabs\n",
			    proc, domid, dp->dnslabs);
			smr_slab_free(domid, sp);
			dp->dslab = NULL;
			dp->dnslabs = 0;
			dp->dslab_state = DSLAB_STATE_UNKNOWN;
		}
		DSLAB_UNLOCK(domid);
	} else if (domid == masterid) {
		/*
		 * We're shutting down the master!
		 * We need to blow away our local slab
		 * data structures.
		 * Since I'm not the master, there should
		 * be no slab structures in the given
		 * domain's idn_domain[] entry.  They should
		 * only exist in the local domain's entry.
		 */
		DSLAB_LOCK_EXCL(idn.localid);
		ASSERT(dp->dslab == NULL);
#ifdef DEBUG
		{
			int	nbusy = 0;
			uint_t	dommask = 0;
			for (sp = ldp->dslab; sp; sp = sp->sl_next) {
				smr_slabbuf_t *bp;

				if (!smr_slab_busy(sp))
					continue;
				nbusy++;
				for (bp = sp->sl_inuse; bp; bp = bp->sb_next)
					if (bp->sb_domid != IDN_NIL_DOMID)
						DOMAINSET_ADD(dommask,
						    bp->sb_domid);
			}
			if (nbusy)
				PR_PROTO("%s:%d: found %d busy slabs "
				    "(dommask = 0x%x)\n",
				    proc, domid, nbusy, dommask);
		}
#endif /* DEBUG */
		if ((sp = ldp->dslab) != NULL) {
			PR_PROTO("%s:%d: freeing up %d local slab "
			    "structs\n", proc, domid, ldp->dnslabs);
			smr_slab_garbage_collection(sp);
			ldp->dslab = NULL;
			ldp->dnslabs = 0;
			ldp->dslab_state = DSLAB_STATE_UNKNOWN;
		}
		DSLAB_UNLOCK(idn.localid);
	}
	if (dp->dio) {
		PR_PROTO("%s:%d: reset dio (%d) to 0\n", proc, domid, dp->dio);
		dp->dio = 0;
	}
	dp->dioerr = 0;

	PR_PROTO("%s:%d: reset diocheck (%x) to 0\n",
	    proc, domid, dp->diocheck);
	lock_clear(&dp->diocheck);

	CHECKPOINT_CLOSED(IDNSB_CHKPT_LINK, dp->dhw.dh_boardset, 2);

	/*
	 * Should have already flush our memory before
	 * reaching this stage.  The issue is that by the
	 * time we reach here the remote domains may have
	 * already reprogrammed their hardware and so flushing
	 * out caches now could result in a arbstop/hang
	 * if we have data that needs to go back to one
	 * of the remote domains that has already reprogrammed
	 * its hardware.
	 */
	ASSERT(!DOMAIN_IN_SET(idn.domset.ds_flush, domid));

	(void) idn_deprogram_hardware(domid);
	/*
	 * XXX - what to do if we
	 *	 fail to program hardware
	 *	 probably should panic since
	 *	 demise of system may be near?
	 *	 Sufficient to just shutdown network?
	 */

	IDN_DSTATE_TRANSITION(dp, IDNDS_CLOSED);

	idn_close_domain(domid);
}

/*
 * If we're sending a Reset we better make sure we don't have any
 * references or traffic headed in the direction of this guy, since
 * when he receives the reset, he'll start shutting down which means
 * we effectively have to shutdown _before_ sending the reset.
 * DO NOT HOLD ANY DOMAIN RWLOCKS ON ENTRY.  Could result in deadlock
 * due to channel server looping back through STREAMs and attempting
 * to acquire domain lock, i.e. channel server will never "stop".
 */
static void
idn_shutdown_datapath(domainset_t domset, int force)
{
	int		do_allchan;
	idn_domain_t	*dp;
	register int	d;
	procname_t	proc = "idn_shutdown_datapath";


	PR_CHAN("%s: domset = 0x%x\n", proc, (uint_t)domset);

	do_allchan = (domset == DOMAINSET_ALL) ? 1 : 0;

	DOMAINSET_DEL(domset, idn.localid);

	if (do_allchan) {
		/*
		 * Need to stop all outgoing and
		 * incoming SMR references.
		 */
		idn_deactivate_channel(CHANSET_ALL, IDNCHAN_OFFLINE);
	}

	/*
	 * If force is set then we don't want to reference
	 * the SMR at all, so deactivate the domains from
	 * channels first.  This will result in the mainmbox-flush
	 * routines to just clean up without referencing the
	 * SMR space.
	 */
	if (force)
		idn_mainmbox_deactivate(domset);

	/*
	 * Flush out mailboxes (clear smr reference).
	 */
	for (d = 0; d < MAX_DOMAINS; d++) {
		if (!DOMAIN_IN_SET(domset, d))
			continue;

		dp = &idn_domain[d];
		if ((dp->dmbox.m_send == NULL) && (dp->dmbox.m_recv == NULL))
			continue;

		IDN_MBOX_LOCK(d);
		if (dp->dmbox.m_send)
			(void) idn_mainmbox_flush(d, dp->dmbox.m_send);
		if (dp->dmbox.m_recv)
			(void) idn_mainmbox_flush(d, dp->dmbox.m_recv);
		IDN_MBOX_UNLOCK(d);
	}
	/*
	 * Deactivate all domain references also.
	 * Only necessary if it wasn't already done above.
	 */
	if (!force)
		idn_mainmbox_deactivate(domset);
}

void
idn_send_cmd(int domid, idn_cmd_t cmdtype, uint_t arg1, uint_t arg2, uint_t
    arg3)
{
	idn_msgtype_t	mt;
	procname_t	proc = "idn_send_cmd";

	mt.mt_mtype = IDNP_CMD;
	mt.mt_atype = 0;
	mt.mt_cookie = 0;

	ASSERT(IDN_DLOCK_IS_HELD(domid));

	PR_PROTO("%s:%d: sending command %s\n", proc, domid,
	    VALID_IDNCMD(cmdtype) ? idncmd_str[cmdtype] : "unknown");

	IDN_MSGTIMER_START(domid, IDNP_CMD, (ushort_t)cmdtype,
	    idn_msg_waittime[IDNP_CMD], &mt.mt_cookie);

	IDNXDC(domid, &mt, (uint_t)cmdtype, arg1, arg2, arg3);
}

void
idn_send_cmdresp(int domid, idn_msgtype_t *mtp, idn_cmd_t cmdtype, uint_t arg1,
    uint_t arg2, uint_t cerrno)
{
	idn_msgtype_t	mt;

	ASSERT(IDN_DLOCK_IS_HELD(domid));

	if (domid == idn.localid) {
		/*
		 * It's possible local domain received a command
		 * from itself.  However, we cannot send a normal
		 * "ack" response (XDC) to ourself.
		 */
		return;
	}

	mt.mt_mtype = IDNP_CMD | IDNP_ACK;
	mt.mt_atype = 0;
	mt.mt_cookie = mtp->mt_cookie;

	IDNXDC(domid, &mt, (uint_t)cmdtype, arg1, arg2, cerrno);
}

static void
idn_send_cmd_nackresp(int domid, idn_msgtype_t *mtp, idn_cmd_t cmdtype,
    idn_nack_t nacktype)
{
	idn_msgtype_t	mt;

	if (domid == idn.localid)
		return;

	mt.mt_mtype = IDNP_CMD | IDNP_NACK;
	mt.mt_atype = 0;
	mt.mt_cookie = mtp->mt_cookie;

	(void) IDNXDC(domid, &mt, (uint_t)cmdtype, (uint_t)nacktype, 0, 0);
}

void
idn_broadcast_cmd(idn_cmd_t cmdtype, uint_t arg1, uint_t arg2, uint_t arg3)
{
	idn_msgtype_t	mt;
	domainset_t	domset;
	procname_t	proc = "idn_broadcast_cmd";

	IDN_GLOCK_SHARED();

	domset = idn.domset.ds_connected;
	DOMAINSET_DEL(domset, idn.localid);

	PR_PROTO("%s: broadcasting command (%s) to domainset 0x%x\n",
	    proc, VALID_IDNCMD(cmdtype) ? idncmd_str[cmdtype] : "unknown",
	    domset);

	mt.mt_mtype = IDNP_CMD;
	mt.mt_atype = 0;
	mt.mt_cookie = 0;

	IDNXDC_BROADCAST(domset, &mt, (uint_t)cmdtype, arg1, arg2, arg3);

	IDN_GUNLOCK();
	/*
	 * This is a broadcast which means local domain needs
	 * to process it also.  Since we can't XDC to ourselves
	 * we simply call a local function.
	 */
	idn_local_cmd(cmdtype, arg1, arg2, arg3);
}

/*
 * Since xargs[0] contains the cmdtype, only xargs[1], xargs[2], xargs[3]
 * are valid possible response arguments.
 */
static void
idn_recv_cmd(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	uint_t			msg = mtp->mt_mtype;
	register idn_domain_t	*dp;
	idn_cmd_t		cmdtype;
	uint_t			acknack;
	uint_t			cmdarg1, cmdarg2, cmdarg3;
	int			islocal;
	int			unsup_cmd_sent, unsup_cmd_recvd;
	procname_t		proc = "idn_recv_cmd";

	acknack = msg & IDNP_ACKNACK_MASK;
	GET_XARGS(xargs, &cmdtype, &cmdarg1, &cmdarg2, &cmdarg3);

	dp = &idn_domain[domid];
	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	IDN_GLOCK_SHARED();

	islocal = (domid == idn.localid);

	ASSERT(!acknack || (acknack & IDNP_ACKNACK_MASK));

	PR_PROTO("%s:%d: (local=%d) acknack=0x%x, cmdtype=%s(%d), "
	    "a1=0x%x, a2=0x%x, a3=0x%x\n",
	    proc, domid, islocal, acknack,
	    VALID_IDNCMD(cmdtype) ? idncmd_str[cmdtype] : "unknown",
	    cmdtype, cmdarg1, cmdarg2, cmdarg3);

	unsup_cmd_sent = unsup_cmd_recvd = 0;

	if ((IDN_GET_MASTERID() == IDN_NIL_DOMID) ||
	    (dp->dstate != IDNDS_CONNECTED)) {
		/*
		 * Commands cannot be handled without a valid
		 * master.  If this is a request then nack him.
		 */
		PR_PROTO("%s:%d: cannot process CMD w/o master (%d, %s)\n",
		    proc, domid, IDN_GET_MASTERID(),
		    idnds_str[dp->dstate]);

		if (!islocal && !(acknack & IDNP_ACKNACK_MASK))
			idn_send_cmd_nackresp(domid, mtp, cmdtype,
			    IDNNACK_NOCONN);
		IDN_GUNLOCK();
		return;
	}
	IDN_GUNLOCK();

	if (acknack & IDNP_ACKNACK_MASK) {
		idn_nack_t	nack;
		/*
		 * Receiving a cmd+ack or cmd+nack in response to some
		 * earlier command we must have issued.
		 * If the response is a nack, there are two possibilites:
		 *
		 *	1. Remote domain failed to allocate due
		 *	   to limited resources.
		 *
		 *	2. Remote domain does not support this
		 *	   particular command.
		 *
		 * In the case of #2, the argument immediately after
		 * the cmdtype (xargs[1]) will be (-1).
		 */
		nack = (idn_nack_t)cmdarg1;
		if ((acknack & IDNP_NACK) && (nack == IDNNACK_BADCMD))
			unsup_cmd_sent++;

		if (islocal) {
			/*
			 * Shouldn't be receiving local commands w/acks.
			 */
			cmdtype = (idn_cmd_t)0;
		}

		switch (cmdtype) {
		case IDNCMD_SLABALLOC:
			idn_recv_slaballoc_resp(domid, cmdarg1, cmdarg2,
			    cmdarg3);
			break;

		case IDNCMD_SLABFREE:
			idn_recv_slabfree_resp(domid, cmdarg1, cmdarg2,
			    cmdarg3);
			break;

		case IDNCMD_SLABREAP:
			/*
			 * We only care if successful.
			 */
			if (acknack & IDNP_ACK)
				idn_recv_slabreap_resp(domid, cmdarg1, cmdarg3);
			break;

		case IDNCMD_NODENAME:
			if ((acknack & IDNP_NACK) == 0) {
				idn_recv_nodename_resp(domid, cmdarg1, cmdarg3);
				break;
			}
			switch (nack) {
			case IDNNACK_NOCONN:
			case IDNNACK_RETRY:
				/*
				 * Remote domain was not quite
				 * ready, try again.
				 */
				PR_PROTO("%s:%d: remote not ready "
				    "for %s - retrying "
				    "[dstate=%s]\n",
				    proc, domid,
				    idncmd_str[IDNCMD_NODENAME],
				    idnds_str[dp->dstate]);

				if (dp->dstate == IDNDS_CONNECTED)
					(void) timeout(idn_retry_nodename_req,
					    (void *)(uintptr_t)domid, hz);
			default:
				break;
			}
			break;

		default:
			/*
			 * Unsupported command.
			 */
			unsup_cmd_recvd++;
			break;
		}
		if (unsup_cmd_sent) {
			PR_PROTO("%s:%d: unsupported command "
			    "requested (0x%x)\n",
			    proc, domid, cmdtype);
		}
		if (unsup_cmd_recvd) {
			PR_PROTO("%s:%d: unsupported command "
			    "response (0x%x)\n",
			    proc, domid, cmdtype);
		}
	} else {
		/*
		 * Receiving a regular cmd from a remote domain.
		 */
		switch (cmdtype) {
		case IDNCMD_SLABALLOC:
			idn_recv_slaballoc_req(domid, mtp, cmdarg1);
			break;

		case IDNCMD_SLABFREE:
			idn_recv_slabfree_req(domid, mtp, cmdarg1, cmdarg2);
			break;

		case IDNCMD_SLABREAP:
			idn_recv_slabreap_req(domid, mtp, cmdarg1);
			break;

		case IDNCMD_NODENAME:
			idn_recv_nodename_req(domid, mtp, cmdarg1);
			break;

		default:
			/*
			 * Unsupported command.
			 */
			unsup_cmd_recvd++;
			break;
		}
		if (!islocal && unsup_cmd_recvd) {
			/*
			 * Received an unsupported IDN command.
			 */
			idn_send_cmd_nackresp(domid, mtp, cmdtype,
			    IDNNACK_BADCMD);
		}
	}
}

/*
 * This is a supporting routine for idn_broadcast_cmd() to
 * handle processing of the requested command for the local
 * domain.  Currently the only support broadcast command
 * supported is reaping.
 */
/*ARGSUSED2*/
static void
idn_local_cmd(idn_cmd_t cmdtype, uint_t arg1, uint_t arg2, uint_t arg3)
{
	idn_protojob_t	*jp;
	idn_domain_t	*ldp = &idn_domain[idn.localid];
	procname_t	proc = "idn_local_cmd";

	PR_PROTO("%s: submitting local command %s on domain %d\n",
	    proc, VALID_IDNCMD(cmdtype) ? idncmd_str[cmdtype] : "unknown",
	    idn.localid);


	jp = idn_protojob_alloc(KM_SLEEP);

	jp->j_msg.m_domid    = ldp->domid;
	jp->j_msg.m_msgtype  = IDNP_CMD;
	jp->j_msg.m_cookie   = ldp->dcookie_recv;
	SET_XARGS(jp->j_msg.m_xargs, cmdtype, arg1, arg2, arg3);

	idn_protojob_submit(ldp->domid, jp);
}

/*
 * Terminate any outstanding commands that may have
 * been targeted for the given domain.  A command is
 * designated as outstanding if it has an active timer.
 *
 * serrno = ECANCELED.
 */
static void
idn_terminate_cmd(int domid, int serrno)
{
	idn_domain_t	*dp;
	idn_timer_t	*tplist = NULL, *tp;
	procname_t	proc = "idn_terminate_cmd";

	dp = &idn_domain[domid];

	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	IDN_MSGTIMER_GET(dp, IDNP_CMD, tplist, 0);
	/*
	 * At this point the timers are effectively terminated
	 * since when they're t_onq indication is set false.
	 */
	if (tplist == NULL) {
		PR_PROTO("%s:%d: no outstanding cmds found\n",
		    proc, domid);
		/*
		 * There is a window where we may have caught a
		 * request just prior to issuing the actual
		 * command (SLABALLOC).  We're guaranteed if there
		 * was, then he will have at least registered.
		 * So, if we abort the command now, he'll catch
		 * it before going to sleep.
		 * Drop through.
		 */
	}
	ASSERT(tplist ? (tplist->t_back->t_forw == NULL) : 1);

	for (tp = tplist; tp; tp = tp->t_forw) {
		ASSERT(tp->t_type == IDNP_CMD);

		PR_PROTO("%s:%d: found outstanding cmd: %s\n",
		    proc, domid, idncmd_str[tp->t_subtype]);

		switch (tp->t_subtype) {
		case IDNCMD_SLABALLOC:
			/*
			 * Outstanding slaballoc request may have
			 * slab waiters hanging around.  Need to
			 * tell them to bail out.  The given domain
			 * must be the master if we have an outstanding
			 * command to him.  This also presumes that
			 * if there are any waiters they're only in
			 * the local domain's waiting area (i.e. we're
			 * a slave).
			 */
#ifdef DEBUG
			IDN_GLOCK_SHARED();
			ASSERT(domid == IDN_GET_MASTERID());
			ASSERT(idn.localid != IDN_GET_MASTERID());
			IDN_GUNLOCK();
#endif /* DEBUG */
			(void) smr_slabwaiter_abort(idn.localid, serrno);
			break;

		case IDNCMD_SLABFREE:
		case IDNCMD_SLABREAP:
		case IDNCMD_NODENAME:
			/*
			 * Nothing really waiting for these operations
			 * so no biggy if we just drop.
			 * Note that NODENAME may have an outstanding
			 * buffer, however that will be reclaimed
			 * when we actually unlink from domain.
			 */
			break;

		default:
			ASSERT(0);
			break;
		}
	}
	/*
	 * As mentioned before the timers are effectively no-op'd
	 * once they're dequeued, however let's cleanup house and
	 * get rid of the useless entries in the timeout queue.
	 */
	if (tplist) {
		IDN_TIMER_STOPALL(tplist);
	}

	if (idn_domain[idn.localid].dvote.v.master) {
		/*
		 * I'm the master so it's possible I had
		 * outstanding commands (SLABALLOC) waiting
		 * to be satisfied for the given domain.
		 * Since we're forcing an error it's okay
		 * to continue holding onto the drwlock.
		 */
		PR_PROTO("%s:%d: abort slaballoc waiters\n", proc, domid);
		(void) smr_slabwaiter_abort(domid, serrno);

	} else if (dp->dvote.v.master) {
		PR_PROTO("%s:%d: abort (local domain) slaballoc waiters\n",
		    proc, domid);
		(void) smr_slabwaiter_abort(idn.localid, serrno);
	}
}

static void
idn_send_acknack(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
	idn_domain_t	*dp = &idn_domain[domid];
	procname_t	proc = "idn_send_acknack";

	ASSERT(mtp ? (mtp->mt_mtype & IDNP_ACKNACK_MASK) : 1);
	ASSERT(domid != IDN_NIL_DOMID);

#ifdef DEBUG
	{
		STRING(mstr);
		STRING(astr);

		INUM2STR(mtp->mt_mtype, mstr);
		INUM2STR(mtp->mt_atype, astr);

		if (mtp->mt_mtype & IDNP_ACK) {
			PR_PROTO("%s:%d: dstate=%s, msg=(%s/%s), "
			    "a1=0x%x, a2=0x%x, a3=0x%x, a4 = 0x%x\n",
			    proc, domid, idnds_str[dp->dstate],
			    astr, mstr, xargs[0], xargs[1],
			    xargs[2], xargs[3]);
		} else {
			idn_nack_t	nack;

			nack = GET_XARGS_NACK_TYPE(xargs);
			PR_PROTO("%s:%d: dstate=%s, msg=(%s/%s), "
			    "nack=%s(0x%x)\n",
			    proc, domid, idnds_str[dp->dstate],
			    astr, mstr, idnnack_str[nack],
			    (uint_t)nack);
		}
	}
#endif /* DEBUG */

	(void) IDNXDC(domid, mtp, xargs[0], xargs[1], xargs[2], xargs[3]);
}

/*ARGSUSED0*/
static void
idn_prealloc_slab(int nslabs)
{
	register int	s, serrno;
	smr_slab_t	*sp;
	idn_domain_t	*ldp = &idn_domain[idn.localid];
	procname_t	proc = "idn_prealloc_slab";

	IDN_GLOCK_SHARED();
	DSLAB_LOCK_SHARED(idn.localid);
	if ((idn.state != IDNGS_ONLINE) || (ldp->dnslabs > 0)) {
		/*
		 * Not in the proper state or slab already allocated.
		 */
		DSLAB_UNLOCK(idn.localid);
		IDN_GUNLOCK();
		return;
	}
	IDN_GUNLOCK();
	ASSERT(!ldp->dslab);

	serrno = 0;
	for (s = 0; (s < nslabs) && ((int)ldp->dnslabs < nslabs); s++) {
		/*
		 * Returns with ldp->drwlock dropped.
		 */
		serrno = smr_slab_alloc(idn.localid, &sp);
		if (serrno != 0) {
			PR_PROTO("%s: FAILED to pre-alloc'd "
			    "slab (serrno = %d)\n", proc, serrno);
			break;
		}
		/*
		 * State may have changed since smr_slab_alloc
		 * temporarily drops drwlock.  Make sure we're
		 * still connected.
		 */
		PR_PROTO("%s: SUCCESSFULLY pre-alloc'd slab\n", proc);

		if (idn.state != IDNGS_ONLINE) {
			PR_PROTO("%s: Lost connection..leaving\n", proc);
			break;
		}
	}

	DSLAB_UNLOCK(idn.localid);
}

/*
 * Received a request from a remote domain to
 * allocate a slab from the master SMR for him.
 * Allocate slab and return the response.
 */
static void
idn_recv_slaballoc_req(int domid, idn_msgtype_t *mtp, uint_t slab_size)
{
	register idn_domain_t	*dp;
	procname_t		proc = "idn_recv_slaballoc_req";

	PR_PROTO("%s: slaballoc req from domain %d (size=0x%x)\n",
	    proc, domid, slab_size);

	dp = &idn_domain[domid];

	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	IDN_GLOCK_SHARED();

	if (idn.localid != IDN_GET_MASTERID()) {
		IDN_GUNLOCK();
		/*
		 * It's a fatal error if the remote domain thinks
		 * we're the master.
		 */
		idn_send_slaballoc_resp(domid, mtp, 0, 0, EACCES);

	} else if (dp->dstate != IDNDS_CONNECTED) {

		IDN_GUNLOCK();
		/*
		 * It's a fatal error if we don't yet have a
		 * connection established with the requestor.
		 */
		idn_send_slaballoc_resp(domid, mtp, 0, 0, ENOLINK);
	} else {
		int		serrno;
		smr_slab_t	*sp;
		smr_offset_t	slab_offset;

		IDN_GUNLOCK();
		DSLAB_LOCK_SHARED(domid);
		IDN_DUNLOCK(domid);
		/*
		 * We're connected and we're the master.
		 * smr_slab_alloc() returns with dp->drwlock dropped.
		 */
		if ((serrno = smr_slab_alloc(domid, &sp)) == 0) {
			/*
			 * Successfully allocated slab for remote slave.
			 */
			slab_offset = IDN_ADDR2OFFSET(sp->sl_start);
			slab_size   = sp->sl_end - sp->sl_start;
			ASSERT((slab_offset != 0) && (slab_size != 0));
		} else {
			slab_offset = slab_size = 0;
		}
		DSLAB_UNLOCK(domid);
		/*
		 * The drwlock is dropped during smr_slab_alloc.
		 * During that time our connection with the given
		 * domain may have changed.  Better check again.
		 */
		IDN_DLOCK_SHARED(domid);
		if ((dp->dstate != IDNDS_CONNECTED) && !serrno) {
			/*
			 * Connection broke.  Keep the slab here.
			 */
			DSLAB_LOCK_EXCL(domid);
			IDN_DUNLOCK(domid);
			smr_slab_free(domid, sp);
			DSLAB_UNLOCK(domid);
			slab_offset = slab_size = 0;
			serrno = ECANCELED;
			IDN_DLOCK_SHARED(domid);
		}
		/*
		 * Send response.
		 * Note that smr_slab_alloc automatically installs
		 * slab into domains respective idn_domain entry
		 * to be associated with that domain.
		 */
		idn_send_slaballoc_resp(domid, mtp, slab_offset, slab_size,
		    serrno);
	}
}

static void
idn_send_slaballoc_resp(int domid, idn_msgtype_t *mtp, smr_offset_t slab_offset,
    uint_t slab_size, int serrno)
{
	procname_t	proc = "idn_send_slaballoc_resp";

	PR_PROTO("%s: slaballoc resp to domain %d (off=0x%x, size=0x%x) "
	    "[serrno = %d]\n",
	    proc, domid, slab_offset, slab_size, serrno);

	idn_send_cmdresp(domid, mtp, IDNCMD_SLABALLOC, slab_offset, slab_size,
	    serrno);
}

/*
 * Received the ack or nack to a previous allocation request
 * made by the local domain to the master for a slab.  Need
 * to "put" the response into the waiting area for any
 * waiters.
 */
static void
idn_recv_slaballoc_resp(int domid, smr_offset_t slab_offset, uint_t slab_size,
    int serrno)
{
	smr_slab_t		*sp = NULL;
	int			rv;
	procname_t		proc = "idn_recv_slaballoc_resp";


	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	PR_PROTO("%s: slaballoc resp from domain %d (off=0x%x, size=0x%x) "
	    "[serrno = %d]\n",
	    proc, domid, slab_offset, slab_size, serrno);

	if (!serrno) {
		IDN_GLOCK_SHARED();
		if (domid != IDN_GET_MASTERID()) {
			/*
			 * We should only be receiving responses from
			 * our master.  This is either a bogus message
			 * or an old response.  In either case dump it.
			 */
			PR_PROTO("%s: BOGUS slaballoc resp from domid %d "
			    "(master = %d)\n",
			    proc, domid, IDN_GET_MASTERID());
			serrno = EPROTO;
		}
		IDN_GUNLOCK();

		if (!serrno &&
		    !VALID_NWROFFSET(slab_offset, IDN_SMR_BUFSIZE)) {
			PR_PROTO("%s: slab offset (0x%x) out of range "
			    "(0-0x%lx)\n",
			    proc, slab_offset, MB2B(IDN_NWR_SIZE));
			serrno = EPROTO;
		} else if (!serrno) {
			sp = GETSTRUCT(smr_slab_t, 1);
			sp->sl_start = IDN_OFFSET2ADDR(slab_offset);
			sp->sl_end   = sp->sl_start + slab_size;
			smr_alloc_buflist(sp);
		}
	}

	/*
	 * Always "put" slabs back to yourself since you're a slave.
	 * Note that we set the forceflag so that even if there are
	 * no waiters we still install the slab for the domain.
	 */
	if (!serrno) {
		DSLAB_LOCK_EXCL(idn.localid);
	}
	rv = smr_slaballoc_put(idn.localid, sp, 1, serrno);
	if (!serrno) {
		DSLAB_UNLOCK(idn.localid);
	}

	if (rv < 0) {
		/*
		 * Some kind of error trying to install response.
		 * If there was a valid slab sent to us, we'll
		 * just have to send it back.
		 */
		PR_PROTO("%s: failed to install response in waiting area\n",
		    proc);
		if (slab_size != 0) {
			PR_PROTO("%s: sending slab back to domain %d "
			    "(master = %d)\n",
			    proc, domid, IDN_GET_MASTERID());
			idn_send_cmd(domid, IDNCMD_SLABFREE, slab_offset,
			    slab_size, 0);
		}
		if (sp) {
			smr_free_buflist(sp);
			FREESTRUCT(sp, smr_slab_t, 1);
		}
	}
}

/*
 * Note that slab reaping is effectively performed asynchronously
 * since the request will be received a protocol server.
 */
static void
idn_recv_slabreap_req(int domid, idn_msgtype_t *mtp, int nslabs)
{
	procname_t	proc = "idn_recv_slabreap_req";

	PR_PROTO("%s: slab reap request (nslabs = %d)\n", proc, nslabs);

	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	IDN_GLOCK_SHARED();
	if (domid != IDN_GET_MASTERID()) {
		/*
		 * Only the master can request that slabs be reaped.
		 */
		IDN_GUNLOCK();
		PR_PROTO("%s: only master can request slab reaping\n", proc);

		idn_send_cmdresp(domid, mtp, IDNCMD_SLABREAP, 0, 0, EACCES);

		return;
	}
	IDN_GUNLOCK();

	if (nslabs != 0) {
		IDN_DUNLOCK(domid);
		smr_slab_reap(idn.localid, &nslabs);
		IDN_DLOCK_SHARED(domid);
	}

	PR_PROTO("%s: slab reap result (nslabs = %d)\n", proc, nslabs);

	/*
	 * Go ahead and send the reap response back before we start
	 * free'ing off the individual slabs.
	 */
	idn_send_slabreap_resp(domid, mtp, nslabs, 0);
}

static void
idn_recv_slabreap_resp(int domid, int nslabs, int serrno)
{
	procname_t	proc = "idn_recv_slabreap_resp";

	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	if ((idn.localid != IDN_GET_MASTERID()) || (idn.localid == domid)) {
		PR_PROTO("%s: unexpected slabreap resp received "
		    "(domid = %d)\n", proc, domid);
		ASSERT(0);
		return;
	}
	PR_PROTO("%s: recvd reap response from domain %d for %d slabs "
	    "[serrno = %d]\n", proc, domid, nslabs, serrno);
}

/*
 * Not really necessary to send slabreap response.
 * XXX - perhaps useful to master for accounting or
 *	 throttling of further reaping?
 */
static void
idn_send_slabreap_resp(int domid, idn_msgtype_t *mtp, int nslabs, int serrno)
{
	idn_send_cmdresp(domid, mtp, IDNCMD_SLABREAP, nslabs, 0, serrno);
}

/*
 * Slave -> Master ONLY
 * Master never sends slabfree request to itself.
 */
static void
idn_recv_slabfree_req(int domid, idn_msgtype_t *mtp, smr_offset_t slab_offset,
    uint_t slab_size)
{
	smr_slab_t	*sp;
	int		serrno;
	caddr_t		s_start, s_end;
	procname_t	proc = "idn_recv_slabfree_req";

	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	if (domid == IDN_GET_MASTERID()) {
		PR_PROTO("%s: unexpected slabfree req received (domid = %d)\n",
		    proc, domid);
		idn_send_slabfree_resp(domid, mtp, slab_offset, slab_size,
		    EACCES);
		return;
	}
	if (slab_size > IDN_SLAB_SIZE) {
		PR_PROTO("%s: unexpected slab size. exp %d, recvd %d\n",
		    proc, IDN_SLAB_SIZE, slab_size);
		idn_send_slabfree_resp(domid, mtp, slab_offset, slab_size,
		    EINVAL);
		return;
	}
	s_start = IDN_OFFSET2ADDR(slab_offset);
	s_end   = s_start + slab_size;
	/*
	 * Master has received a SLABFREE request (effectively a response
	 * to some earlier SLABREAP request.
	 * Find the slab associated with this slab and free it up.
	 */
	DSLAB_LOCK_EXCL(domid);
	if ((sp = smr_slaballoc_get(domid, s_start, s_end)) != NULL) {
		smr_slab_free(domid, sp);
		serrno = 0;
	} else {
		serrno = EINVAL;
	}
	DSLAB_UNLOCK(domid);

	idn_send_slabfree_resp(domid, mtp, slab_offset, slab_size, serrno);
}

/*
 * Master -> Slave ONLY
 */
static void
idn_recv_slabfree_resp(int domid, uint_t slab_offset, uint_t slab_size, int
    serrno)
{
	procname_t	proc = "idn_recv_slabfree_resp";

	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	if (domid != IDN_GET_MASTERID()) {
		PR_PROTO("%s: unexpected slabfree resp received (domid = %d)\n",
		    proc, domid);
		ASSERT(0);
		return;
	}
	if (slab_size > IDN_SLAB_SIZE) {
		PR_PROTO("%s: unexpected slab size. exp %d, recvd %d\n",
		    proc, IDN_SLAB_SIZE, slab_size);
		ASSERT(0);
		return;
	}
	PR_PROTO("%s: recvd free resp from dom %d "
	    "- slab (off/size) 0x%x/0x%x [serrno = %d]\n",
	    proc, domid, slab_offset, slab_size, serrno);
}

static void
idn_send_slabfree_resp(int domid, idn_msgtype_t *mtp, uint_t slab_offset,
    uint_t slab_size, int serrno)
{
	idn_send_cmdresp(domid, mtp, IDNCMD_SLABFREE, slab_offset, slab_size,
	    serrno);
}

static void
idn_retry_nodename_req(void *arg)
{
	int	domid = (int)(uintptr_t)arg;

	idn_send_nodename_req(domid);
}

static void
idn_send_nodename_req(int domid)
{
	caddr_t		b_bufp;
	smr_offset_t	bufoffset;
	int		serrno;
	idn_domain_t	*dp = &idn_domain[domid];
	procname_t	proc = "idn_send_nodename_req";

	/*
	 * Need to drop domain lock across
	 * SMR allocation.
	 */
	serrno = smr_buf_alloc(domid, MAXDNAME+1, &b_bufp);

	IDN_DLOCK_SHARED(domid);
	if (dp->dstate != IDNDS_CONNECTED) {
		/*
		 * Lost connection.
		 */
		PR_PROTO("%s:%d: connection lost [dstate = %s]\n",
		    proc, domid, idnds_str[dp->dstate]);
		IDN_DUNLOCK(domid);
		if (!serrno)
			(void) smr_buf_free(domid, b_bufp, MAXDNAME+1);
		return;
	}
	if (serrno) {
		/*
		 * Failed to allocate buffer, but still have
		 * connection so keep trying.  We may have queried
		 * the master a little too earlier.
		 */
		PR_PROTO("%s:%d: buffer alloc failed [dstate = %s]\n",
		    proc, domid, idnds_str[dp->dstate]);
		(void) timeout(idn_retry_nodename_req, (void *)(uintptr_t)domid,
		    hz);
		IDN_DUNLOCK(domid);
		return;
	}

	*b_bufp = (char)MAXDNAME;
	bufoffset = IDN_ADDR2OFFSET(b_bufp);

	idn_send_cmd(domid, IDNCMD_NODENAME, bufoffset, 0, 0);
	IDN_DUNLOCK(domid);
}

static void
idn_send_nodename_resp(int domid, idn_msgtype_t *mtp, smr_offset_t bufoffset,
    int serrno)
{
	idn_send_cmdresp(domid, mtp, IDNCMD_NODENAME, (uint_t)bufoffset, 0,
	    serrno);
}

static void
idn_recv_nodename_req(int domid, idn_msgtype_t *mtp, smr_offset_t bufoffset)
{
	caddr_t		b_bufp;
	int		length;
	idn_domain_t	*ldp = &idn_domain[idn.localid];
	procname_t	proc = "idn_recv_nodename_req";

	IDN_DLOCK_EXCL(idn.localid);
	if (!strlen(ldp->dname)) {
		if (!strlen(utsname.nodename)) {
			/*
			 * Local domain's nodename hasn't been
			 * set yet.
			 */
			IDN_DUNLOCK(idn.localid);
			idn_send_cmd_nackresp(domid, mtp, IDNCMD_NODENAME,
			    IDNNACK_RETRY);
			return;
		}
		strncpy(ldp->dname, utsname.nodename, MAXDNAME - 1);
	}
	IDN_DLOCK_DOWNGRADE(idn.localid);

	if (!VALID_NWROFFSET(bufoffset, IDN_SMR_BUFSIZE)) {
		PR_PROTO("%s:%d: invalid SMR offset received (0x%x)\n",
		    proc, domid, bufoffset);
		IDN_DUNLOCK(idn.localid);
		idn_send_nodename_resp(domid, mtp, bufoffset, EINVAL);
		return;
	}

	b_bufp = IDN_OFFSET2ADDR(bufoffset);
	length = (int)(*b_bufp++ & 0xff);

	if (length < strlen(ldp->dname)) {
		PR_PROTO("%s:%d: buffer not big enough (req %lu, got %d)\n",
		    proc, domid, strlen(ldp->dname), length);
		IDN_DUNLOCK(idn.localid);
		idn_send_nodename_resp(domid, mtp, bufoffset, EINVAL);
		return;
	}

	strncpy(b_bufp, ldp->dname, MAXDNAME);
	b_bufp[MAXDNAME-1] = 0;
	IDN_DUNLOCK(idn.localid);

	idn_send_nodename_resp(domid, mtp, bufoffset, 0);
}

static void
idn_recv_nodename_resp(int domid, smr_offset_t bufoffset, int serrno)
{
	caddr_t		b_bufp;
	idn_domain_t	*dp = &idn_domain[domid];
	procname_t	proc = "idn_recv_nodename_resp";

	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	if (!VALID_NWROFFSET(bufoffset, IDN_SMR_BUFSIZE)) {
		PR_PROTO("%s:%d: invalid SMR offset received (0x%x)\n",
		    proc, domid, bufoffset);
		return;
	}

	if (serrno == 0) {
		b_bufp = IDN_OFFSET2ADDR(bufoffset) + 1;
		b_bufp[MAXDNAME-1] = 0;

		if (strlen(b_bufp) > 0) {
			strncpy(dp->dname, b_bufp, MAXDNAME);
			PR_PROTO("%s:%d: received nodename(%s)\n",
			    proc, domid, dp->dname);
		}
	}

	(void) smr_buf_free(domid, b_bufp - 1, MAXDNAME + 1);
}

/*
 * The master allocations the SMR management structures.
 */
static int
idn_master_init()
{
	idn_domain_t	*ldp = &idn_domain[idn.localid];
	size_t		reserved_size = 0;
	caddr_t		reserved_area = NULL;
	procname_t	proc = "idn_master_init";

	ASSERT(IDN_GLOCK_IS_EXCL());
	ASSERT(IDN_DLOCK_IS_EXCL(idn.localid));

	if (idn.mboxarea != NULL) {
		PR_PROTO("%s: master data already initialized\n", proc);
		return (0);
	}

	PR_PROTO("%s: initializing master data (domid = %d)\n",
	    proc, idn.localid);

	/*
	 * Reserve an area of the SMR for mailbox usage.
	 * This area is allocated to other domains via
	 * the master.  Round it up to IDN_SMR_BUFSIZE multiple.
	 */
	reserved_size = IDNROUNDUP(IDN_MBOXAREA_SIZE, IDN_SMR_BUFSIZE);

	PR_PROTO("%s: reserving %lu bytes for mailbox area\n",
	    proc, reserved_size);

#ifdef DEBUG
	if (reserved_size > (size_t)IDN_SLAB_SIZE) {
		PR_PROTO("%s: WARNING mbox area (%ld) > slab size (%d)\n",
		    proc, reserved_size, IDN_SLAB_SIZE);
	}
#endif /* DEBUG */
	/*
	 * Initialize the pool of slabs and SMR I/O buffers.
	 */
	if (smr_slabpool_init(reserved_size, &reserved_area) != 0) {
		idn_master_deinit();
		return (-1);
	}

	ASSERT(idn.mboxarea == NULL);
	ASSERT(reserved_area);

	bzero(reserved_area, reserved_size);

	idn.mboxarea = (idn_mboxtbl_t *)reserved_area;
	ldp->dmbox.m_tbl = IDN_MBOXAREA_BASE(idn.mboxarea, idn.localid);
	/*
	 * Initialize the SMR pointers in the entire
	 * mailbox table.
	 */
	idn_mboxarea_init(idn.mboxarea, IDN_MBOXAREA_SIZE / IDN_MBOXTBL_SIZE);

	return (0);
}

static void
idn_master_deinit()
{
	idn_domain_t	*ldp;
	smr_slab_t	*sp;
	procname_t	proc = "idn_master_deinit";

	ASSERT(IDN_GLOCK_IS_EXCL());
	ASSERT(IDN_DLOCK_IS_EXCL(idn.localid));

	if (idn.mboxarea == NULL) {
		PR_PROTO("%s: master data already deinitialized\n", proc);
		return;
	}

	ldp = &idn_domain[idn.localid];

	PR_PROTO("%s: deinitializing master data (domid = %d)\n",
	    proc, idn.localid);

	ldp->dmbox.m_tbl = NULL;
	idn.mboxarea = NULL;
	/*
	 * Master may still be holding onto slabs of his own.
	 */
	DSLAB_LOCK_EXCL(idn.localid);
	sp = ldp->dslab;
	ldp->dslab = NULL;
	ldp->dnslabs = 0;
	if (sp)
		smr_slab_free(idn.localid, sp);
	ldp->dslab_state = DSLAB_STATE_UNKNOWN;
	DSLAB_UNLOCK(idn.localid);

	smr_slabpool_deinit();
}

static int
idn_mark_awol(int domid, clock_t *atime)
{
	clock_t		awol;
	idn_domain_t	*dp = &idn_domain[domid];

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_GLOCK_IS_EXCL());

	if (!DOMAIN_IN_SET(idn.domset.ds_awol, domid)) {
		DOMAINSET_ADD(idn.domset.ds_awol, domid);
		idn.nawols++;
	}
	awol = ddi_get_lbolt();
	if (dp->dawol.a_count++ == 0)
		dp->dawol.a_time = awol;
	dp->dawol.a_last = awol;
	if ((awol - dp->dawol.a_msg) >= (clock_t)(idn_awolmsg_interval * hz))
		dp->dawol.a_msg = awol;
	else
		awol = 0;

	*atime = awol;

	idn_awol_event_set(dp->dhw.dh_boardset);

	return (dp->dawol.a_count);
}

void
idn_clear_awol(int domid)
{
	idn_domain_t	*dp = &idn_domain[domid];

	ASSERT(IDN_SYNC_IS_LOCKED());
	ASSERT(IDN_GLOCK_IS_EXCL());
	if (DOMAIN_IN_SET(idn.domset.ds_awol, domid)) {
		DOMAINSET_DEL(idn.domset.ds_awol, domid);
		idn.nawols--;
	}
	if (dp->dawol.a_count > 0) {
		dp->dawol.a_count = 0;
		dp->dawol.a_last = dp->dawol.a_time;
		dp->dawol.a_time = 0;
		dp->dawol.a_msg = 0;

		idn_awol_event_clear(dp->dhw.dh_boardset);
	}
}

/*
 * A timer expired.
 */
void
idn_timer_expired(void *arg)
{
	idn_domain_t	*dp;
	char		*op = "UNKNOWN";
	clock_t		awol = 0;
	int		awolcount, dcpu, domid;
	idn_timer_t	*tp = (idn_timer_t *)arg;
	idn_timerq_t	*tq = NULL;
	uint_t		token;
	char		dname[MAXDNAME];
	procname_t	proc = "idn_timer_expired";
	STRING(str);

	tq = tp->t_q;

	ASSERT(tp->t_domid != IDN_NIL_DOMID);

	IDN_TIMERQ_LOCK(tq);

	INUM2STR(tp->t_type, str);

	if (tp->t_onq == 0) {
		PR_TIMER("%s: timer CAUGHT TERMINATION (type = %s)\n",
		    proc, str);
		/*
		 * Timer was dequeued.  Somebody is trying
		 * to shut it down.
		 */
		IDN_TIMERQ_UNLOCK(tq);
		return;
	}

	IDN_TIMER_DEQUEUE(tq, tp);

	IDN_TIMERQ_UNLOCK(tq);

	IDN_SYNC_LOCK();
	IDN_DLOCK_EXCL(tp->t_domid);

	domid = tp->t_domid;

	dp = &idn_domain[domid];
	strcpy(dname, dp->dname);
	dcpu = dp->dcpu;

	IDN_TIMER_EXEC(tp);

#ifdef DEBUG
	PR_TIMER("%s:%d: [%s] timer EXPIRED (C=0x%x, P=0x%llx, X=0x%llx)\n",
	    proc, tp->t_domid, str, tp->t_cookie,
	    tp->t_posttime, tp->t_exectime);
#endif /* DEBUG */

	/*
	 * IMPORTANT:
	 * Each case is responsible for dropping SYNC_LOCK & DLOCK.
	 */
	switch (tp->t_type) {
	case IDNP_DATA:
		IDN_SYNC_UNLOCK();
		/*
		 * Timed out waiting for a data packet response.
		 * We can't close domain since he may just be
		 * temporarily AWOL.
		 * Note that dio and diocheck do not get cleared.
		 * This is taken care of when the domain restarts
		 * or is fatally closed.
		 * We only need a reader lock for this.
		 */
		IDN_DLOCK_DOWNGRADE(domid);
		if (dp->diocheck && dp->dmbox.m_send) {
			(void) idn_reclaim_mboxdata(domid, 0, -1);
			if (dp->dio >= IDN_WINDOW_EMAX) {
				idn_msgtype_t	mt;
				/*
				 * Restart timer for another
				 * go around.
				 */
				IDN_MSGTIMER_START(domid, IDNP_DATA, 0,
				    idn_msg_waittime[IDNP_DATA],
				    &mt.mt_cookie);
			} else {
				lock_clear(&dp->diocheck);
			}
		}
		IDN_DUNLOCK(domid);
		break;

	case IDNP_NEGO:
		/*
		 * If we're not in a NEGO transition, then
		 * just ignore this timeout.
		 */
		if (dp->dxp == &xphase_nego) {
			uint_t		token;

			IDN_GLOCK_EXCL();
			op = "CONNECT";
			awolcount = idn_mark_awol(domid, &awol);
			IDN_GUNLOCK();

			idn_nego_cleanup_check(domid, IDN_NIL_DOMID,
			    IDN_NIL_DCPU);

			IDN_XSTATE_TRANSITION(dp, IDNXS_PEND);
			token = IDN_RETRY_TOKEN(domid, IDNRETRY_NEGO);
			idn_retry_submit(idn_retry_nego, NULL, token,
			    idn_msg_retrytime[(int)IDNRETRY_NEGO]);
		}
		IDN_DUNLOCK(domid);
		IDN_SYNC_UNLOCK();
		break;

	case IDNP_CMD:
		/*
		 * Timeouts on commands typically mean that the
		 * the master is not responding.  Furthermore, we
		 * can't FORCE a FIN disconnect since at this stage
		 * we are CONNECTED and thus other domains may
		 * have cache entries that we're sharing with them.
		 * Only choice is to completely disconnect from
		 * IDN and try to reestablish connection.
		 *
		 * However, timeouts attempting to get nodename
		 * are not fatal.  Although we don't want to retry
		 * either since each timeout is a lost buffer to
		 * the remote domain.
		 */
		if (tp->t_subtype == (ushort_t)IDNCMD_NODENAME) {
			PR_PROTO("%s:%d: timedout waiting for nodename\n",
			    proc, domid);
			IDN_DUNLOCK(domid);
			IDN_SYNC_UNLOCK();
			break;
		}

		IDN_GLOCK_EXCL();
		if (idn.state == IDNGS_ONLINE) {
			domainset_t	domset;
			int		masterid = IDN_GET_MASTERID();

			IDN_GKSTAT_GLOBAL_EVENT(gk_reconfigs,
			    gk_reconfig_last);

			PR_PROTO("%s:%d: RECONFIG trying old masterid = %d\n",
			    proc, domid, masterid);

			IDN_GSTATE_TRANSITION(IDNGS_RECONFIG);
			IDN_SET_NEW_MASTERID(masterid);
			IDN_GUNLOCK();
			IDN_DUNLOCK(domid);

			domset = idn.domset.ds_trans_on |
			    idn.domset.ds_connected;

			idn_unlink_domainset(domset, IDNFIN_NORMAL,
			    IDNFIN_ARG_NONE, IDNFIN_OPT_RELINK,	BOARDSET_ALL);
		} else {
			IDN_GUNLOCK();
			IDN_DUNLOCK(domid);
		}
		IDN_SYNC_UNLOCK();
		break;

	case IDNP_CON:
		if (tp->t_subtype == (ushort_t)IDNCON_QUERY) {
			/*
			 * Timed out sending a CON-query.  This is
			 * non-fatal.  We simply need to retry.
			 */
			IDN_GLOCK_EXCL();
			op = "CONNECT";
			awolcount = idn_mark_awol(domid, &awol);
			IDN_GUNLOCK();
			token = IDN_RETRY_TOKEN(domid, IDNRETRY_CONQ);
			idn_retry_submit(idn_retry_query, NULL, token,
			    idn_msg_retrytime[(int)IDNRETRY_CONQ]);
			IDN_DUNLOCK(domid);
			IDN_SYNC_UNLOCK();
			break;
		}
		/*FALLTHROUGH*/
	case IDNP_CFG:
		/*
		 * Any timeouts here we simply try to disconnect
		 * and reestablish the link.  Since we haven't
		 * reached the connected state w.r.t. this domain
		 * we put his fin state to FORCE-HARD in order
		 * to shoot right through without involving other
		 * domains.  Recall that other domains may have
		 * established connections with the given domain
		 * which means any FIN queries to them will always
		 * return connected to the given domain.  Since
		 * neither the given domain nor the local domain
		 * plan on disconnecting from the IDN the connection
		 * to the other domains will remain thereby preventing
		 * the local FIN from ever completing.  Recall that
		 * a FIN depends on all member domains FIN'ing also.
		 */
		IDN_GLOCK_EXCL();
		op = "CONNECT";
		awolcount = idn_mark_awol(domid, &awol);
		IDN_GUNLOCK();
		DOMAINSET_ADD(idn.domset.ds_relink, domid);
		IDN_HISTORY_LOG(IDNH_RELINK, domid, dp->dstate,
		    idn.domset.ds_relink);
		idn_disconnect(domid, IDNFIN_FORCE_SOFT,
		    IDNFIN_ARG_NONE, IDNFIN_SYNC_NO);
		IDN_DUNLOCK(domid);
		IDN_SYNC_UNLOCK();
		break;

	case IDNP_FIN:
		/*
		 * Timeouts here simply try to retry.
		 */
		IDN_GLOCK_EXCL();
		op = "DISCONNECT";
		awolcount = idn_mark_awol(domid, &awol);
		IDN_GUNLOCK();
		if (tp->t_subtype == (ushort_t)IDNFIN_QUERY) {
			int		d;
			domainset_t	rdyset;
			/*
			 * Timed out sending a FIN-query.  This is
			 * non-fatal.  We simply need to retry.
			 * If we were doing a forced unlink of any
			 * domains, we don't want this awol guy
			 * to hold us up.  Looks for any forced
			 * unlinks and make them "ready" with
			 * respect to this awol domain.
			 */
			rdyset = 0;
			for (d = 0; d < MAX_DOMAINS; d++) {
				if (FIN_IS_FORCE(idn_domain[d].dfin)) {
					DOMAINSET_ADD(rdyset, d);
				}
			}
			if (rdyset)
				(void) idn_sync_register(domid,
				    IDNSYNC_DISCONNECT,
				    rdyset, IDNSYNC_REG_REG);

			token = IDN_RETRY_TOKEN(domid, IDNRETRY_FINQ);
			idn_retry_submit(idn_retry_query, NULL, token,
			    idn_msg_retrytime[(int)IDNRETRY_FINQ]);
			IDN_DUNLOCK(domid);
			IDN_SYNC_UNLOCK();
			break;
		}

		if (dp->dfin == IDNFIN_FORCE_SOFT) {
			IDN_FSTATE_TRANSITION(dp, IDNFIN_FORCE_HARD);
		}
		/*
		 * Anybody that was waiting on this domain and
		 * had a hard-force in action gets this guy for
		 * free in their base ready-set.
		 */
		idn_sync_register_awol(domid);

		dp->dxp = &xphase_fin;
		IDN_XSTATE_TRANSITION(dp, IDNXS_PEND);
		token = IDN_RETRY_TOKEN(domid, IDNRETRY_FIN);
		idn_retry_submit(idn_retry_fin, NULL, token,
		    idn_msg_retrytime[(int)IDNRETRY_FIN]);
		IDN_DUNLOCK(domid);
		IDN_SYNC_UNLOCK();
		break;

	default:

		ASSERT(0);
		IDN_DUNLOCK(domid);
		IDN_SYNC_UNLOCK();
		break;
	}

	IDN_TIMER_FREE(tp);

	if (awol) {
		if (strlen(dname) > 0) {
			cmn_err(CE_WARN,
			    "IDN: 236: domain (%s) [ID %d] not "
			    "responding to %s [#%d]",
			    dname, domid, op, awolcount);
		} else {
			cmn_err(CE_WARN,
			    "IDN: 236: domain [ID %d, CPU %d] not "
			    "responding to %s [#%d]",
			    domid, dcpu, op, awolcount);
		}
	}
}

#if 0
static int
idn_retry_check(uint_t token)
{
	int			i, count = 0;
	int			domid = IDN_RETRY_TOKEN2DOMID(token);
	int			key = IDN_RETRY_TOKEN2TYPE(token);
	idn_retry_job_t		*rp;
	idn_retry_queue_t	*qp;

	qp = &idn.retryqueue;

	mutex_enter(&qp->rq_mutex);

	for (i = 0, rp = qp->rq_jobs; i < qp->rq_count; i++, rp = rp->rj_next)
		if ((domid == IDN_RETRY_TOKEN2DOMID(rp->rj_token)) &&
		    ((key == IDN_RETRY_TYPEALL) || (rp->rj_token == token)))
			count++;

	mutex_exit(&qp->rq_mutex);

	return (count);
}
#endif /* 0 */

static void
idn_retry_execute(void *arg)
{
	idn_retry_job_t		*rp = (idn_retry_job_t *)arg;
	idn_retry_queue_t	*qp;

	qp = &idn.retryqueue;

	mutex_enter(&qp->rq_mutex);
	if (rp->rj_onq == 0) {
		/*
		 * Job has already been claimed by
		 * retry termination routine.
		 * Bail out.
		 */
		mutex_exit(&qp->rq_mutex);
		return;
	}
	rp->rj_next->rj_prev = rp->rj_prev;
	rp->rj_prev->rj_next = rp->rj_next;
	if (--(qp->rq_count) == 0)
		qp->rq_jobs = NULL;
	else if (qp->rq_jobs == rp)
		qp->rq_jobs = rp->rj_next;
	mutex_exit(&qp->rq_mutex);

	(*rp->rj_func)(rp->rj_token, rp->rj_arg);

	IDNRETRY_FREEJOB(rp);
}

/*
 *
 */
static void
idn_retry_submit(void (*func)(uint_t token, void *arg), void *arg, uint_t token,
    clock_t ticks)
{
	idn_retry_job_t		*rp, *cp;
	idn_retry_queue_t	*qp;
	int			c;
	procname_t		proc = "idn_retry_submit";

	if (ticks < 0) {
		PR_PROTO("%s: (token = 0x%x) WARNING ticks = %ld\n",
		    proc, token, ticks);
		return;
	}
	if (ticks == 0)		/* At least one tick to get into background */
		ticks++;

	PR_PROTO("%s: token = 0x%x\n", proc, token);

	qp = &idn.retryqueue;

	mutex_enter(&qp->rq_mutex);
	for (c = 0, cp = qp->rq_jobs; c < qp->rq_count; cp = cp->rj_next, c++) {
		if (cp->rj_token == token) {
			PR_PROTO("%s: token = (%d,0x%x) already present\n",
			    proc, IDN_RETRY_TOKEN2DOMID(token),
			    IDN_RETRY_TOKEN2TYPE(token));
			break;
		}
	}

	if (c < qp->rq_count) {
		mutex_exit(&qp->rq_mutex);
		return;
	}

	rp = IDNRETRY_ALLOCJOB();
	rp->rj_func = func;
	rp->rj_arg = arg;
	rp->rj_token = token;
	rp->rj_prev = rp->rj_next = rp;

	if (qp->rq_jobs == NULL) {
		qp->rq_jobs = rp;
	} else {
		rp->rj_next = qp->rq_jobs;
		rp->rj_prev = qp->rq_jobs->rj_prev;
		rp->rj_next->rj_prev = rp;
		rp->rj_prev->rj_next = rp;
	}
	rp->rj_onq = 1;
	qp->rq_count++;
	rp->rj_id = timeout(idn_retry_execute, (caddr_t)rp, ticks);
	mutex_exit(&qp->rq_mutex);
}

int
idn_retry_terminate(uint_t token)
{
	int			i, domid;
	uint_t			key, count;
	idn_retry_job_t		*rp, *nrp, *fp;
	idn_retry_queue_t	*qp;
	procname_t		proc = "idn_retry_terminate";

	key = IDN_RETRY_TOKEN2TYPE(token);
	domid = IDN_RETRY_TOKEN2DOMID(token);
	fp = NULL;
	qp = &idn.retryqueue;

	mutex_enter(&qp->rq_mutex);
	for (i = count = 0, rp = qp->rq_jobs; i < qp->rq_count; i++) {
		nrp = rp->rj_next;
		if ((domid == IDN_RETRY_TOKEN2DOMID(rp->rj_token)) &&
		    ((key == IDN_RETRY_TYPEALL) ||
		    (rp->rj_token == token))) {
			/*
			 * Turn off onq field as a signal to
			 * the execution routine that this
			 * retry has been terminated.  This
			 * is necessary since we can't untimeout
			 * while holding the rq_mutex otherwise
			 * we'll deadlock with the execution
			 * routine.  We'll untimeout these guys
			 * _after_ we drop rq_mutex.
			 */
			rp->rj_onq = 0;
			rp->rj_next->rj_prev = rp->rj_prev;
			rp->rj_prev->rj_next = rp->rj_next;
			if (qp->rq_jobs == rp)
				qp->rq_jobs = rp->rj_next;
			rp->rj_next = fp;
			fp = rp;
			count++;
		}
		rp = nrp;
	}

	if ((qp->rq_count -= count) == 0)
		qp->rq_jobs = NULL;

	mutex_exit(&qp->rq_mutex);

	PR_PROTO("%s: token = (%d,0x%x), dequeued = %d\n",
	    proc, domid, key, count);

	for (; fp; fp = nrp) {
		(void) untimeout(fp->rj_id);

		nrp = fp->rj_next;
		IDNRETRY_FREEJOB(fp);
	}

	return (count);
}

/*
 * -----------------------------------------------------------------------
 * The sole purpose of the idn_protocol_server is to manage the IDN
 * protocols between the various domains.  These messages do _not_ go
 * through the regular streams queues since they are not dependent on
 * any user process or module necessarily having the IDN driver open.
 * There may be multiple instances of these servers to enhance performance
 * of domain management.  Each server is assigned a idn_protoqueue_t
 * from which to obtain the work they need to do.
 * -----------------------------------------------------------------------
 */
int
idn_protocol_init(int nservers)
{
	int		i;
	idn_protojob_t	*jp;
	register idn_protoqueue_t	*protoq;

	if (nservers <= 0) {
		cmn_err(CE_WARN,
		    "IDN: 237: invalid number (%d) of protocol servers",
		    nservers);
		return (-1);
	}

	idn.protocol.p_jobpool = kmem_cache_create("idn_protocol_jobcache",
	    sizeof (idn_protojob_t), 0, NULL, NULL, NULL, NULL, NULL, 0);
	if (idn.protocol.p_jobpool == NULL) {
		cmn_err(CE_WARN,
		    "IDN: 238: kmem_cache_create(jobcache) failed");
		return (-1);
	}

	/*
	 * Initialize static cache for protojob.
	 */
	mutex_init(&idn_protojob_cache_lock, NULL, MUTEX_DRIVER, NULL);
	jp = &idn_protojob_cache[0];
	for (i = 1; i < IDN_DMV_PENDING_MAX; jp = jp->j_next, i++) {
		jp->j_cache = 1;
		jp->j_next = &idn_protojob_cache[i];
	}
	jp->j_cache = 1;
	jp->j_next = NULL;
	idn_protojob_cache_list = &idn_protojob_cache[0];

	/*
	 * Init morgue semaphore.
	 */
	sema_init(&idn.protocol.p_morgue, 0, NULL, SEMA_DEFAULT, NULL);
	/*
	 * Alloc server queues.
	 */
	idn.protocol.p_serverq = GETSTRUCT(idn_protoqueue_t, nservers);

	/*
	 * Init server queues.
	 */
	protoq = idn.protocol.p_serverq;
	for (i = 0; i < nservers; protoq++, i++) {
		mutex_init(&protoq->q_mutex, NULL, MUTEX_DRIVER, NULL);
		cv_init(&protoq->q_cv, NULL, CV_DEFAULT, NULL);
		protoq->q_id	  = i;
		protoq->q_joblist = NULL;
		protoq->q_joblist_tail = NULL;
		protoq->q_die	  = 0;
		protoq->q_morgue  = &idn.protocol.p_morgue;
		/*
		 * Create protocol server thread.
		 */
		protoq->q_threadp = thread_create(NULL, 0,
		    idn_protocol_server, (caddr_t)&i, sizeof (i), &p0,
		    TS_RUN, maxclsyspri);
	}
	/*
	 * The servers are kept in the p_server[] array, however
	 * we'll build a linked list of them to facilitate debugging.
	 */
	protoq = idn.protocol.p_serverq;
	for (i = 0; i < (nservers - 1); protoq++, i++)
		protoq->q_next = (protoq + 1);
	protoq->q_next = NULL;

	idn.nservers = nservers;

	return (idn.nservers);
}

void
idn_protocol_deinit()
{
	register int	i;
	int		nservers;
	register idn_protoqueue_t	*protoq;

	nservers = idn.nservers;

	if (nservers <= 0)
		return;

	/*
	 * Make sure the servers are dead.
	 */
	idn_protocol_server_killall();
	ASSERT(idn.nservers == 0);
	/*
	 * Destroy the mutexes.
	 */
	protoq = idn.protocol.p_serverq;
	for (i = 0; i < nservers; protoq++, i++) {
		mutex_destroy(&protoq->q_mutex);
		cv_destroy(&protoq->q_cv);
	}
	/*
	 * Free up the protoqueue memory.
	 */
	FREESTRUCT(idn.protocol.p_serverq, idn_protoqueue_t, nservers);
	idn.protocol.p_serverq = NULL;
	/*
	 * Destroy the morgue semaphore.
	 */
	sema_destroy(&idn.protocol.p_morgue);

	if (idn.protocol.p_jobpool) {
		kmem_cache_destroy(idn.protocol.p_jobpool);
		idn.protocol.p_jobpool = NULL;
	}
}

static void
idn_protocol_server(int *id)
{
	idn_protoqueue_t	*pq;
	idn_protojob_t		*jl;
	register idn_protojob_t	*jp;
	procname_t		proc = "idn_protocol_server";

	if (id == NULL) {
		PR_PROTO("%s: id == NULL, thread exiting\n", proc);
		return;
	}
	ASSERT((*id >= 0) && (*id < idn_protocol_nservers));

	pq = &idn.protocol.p_serverq[*id];

	ASSERT(pq->q_id == *id);

	PR_PROTO("%s: id %d starting up (pq = 0x%p)\n", proc, pq->q_id, pq);

	/*CONSTCOND*/
	while (1) {
		mutex_enter(&pq->q_mutex);

		while (((jl = pq->q_joblist) == NULL) && !pq->q_die)
			cv_wait(&pq->q_cv, &pq->q_mutex);

		pq->q_joblist = pq->q_joblist_tail = NULL;

		if (pq->q_die) {
			/*
			 * We've been killed.  Need to check-in
			 * at the morgue.
			 */
			pq->q_threadp = NULL;
			mutex_exit(&pq->q_mutex);
			PR_PROTO("%s: thread (%d) killed...bye bye\n",
			    proc, pq->q_id);
			for (jp = jl; jp; jp = jl) {
				jl = jp->j_next;
				idn_protojob_free(jp);
			}
			sema_v(pq->q_morgue);
			thread_exit();
			/*NOTREACHED*/
		}
		mutex_exit(&pq->q_mutex);

		/*
		 * We can process the jobs asynchronously while more are
		 * put on.
		 */
		for (jp = jl; jp; jp = jl) {
			jl = jp->j_next;
			idn_recv_proto(&(jp->j_msg));
			idn_protojob_free(jp);
		}
	}
}

/*
 * Kill off all the protocol servers.
 */
static void
idn_protocol_server_killall()
{
	register idn_protoqueue_t	*pq;
	int		i;
	procname_t	proc = "idn_protocol_server_killall";

	PR_PROTO("%s: killing off %d protocol servers\n",
	    proc, idn.nservers);

	pq = idn.protocol.p_serverq;
	for (i = 0; i < idn.nservers; pq++, i++) {
		mutex_enter(&pq->q_mutex);
		pq->q_die = 1;
		cv_signal(&pq->q_cv);
		mutex_exit(&pq->q_mutex);
	}

	while (idn.nservers > 0) {
		sema_p(&idn.protocol.p_morgue);
		idn.nservers--;
	}
}

idn_protojob_t *
idn_protojob_alloc(int kmflag)
{
	idn_protojob_t	*jp;

	jp = kmem_cache_alloc(idn.protocol.p_jobpool, kmflag);
	if (jp == NULL) {
		mutex_enter(&idn_protojob_cache_lock);
		if ((jp = idn_protojob_cache_list) != NULL)
			idn_protojob_cache_list = jp->j_next;
		mutex_exit(&idn_protojob_cache_lock);
	} else {
		jp->j_cache = 0;
	}

	return (jp);
}

static void
idn_protojob_free(idn_protojob_t *jp)
{
	ASSERT(jp);

	if (jp->j_cache) {
		mutex_enter(&idn_protojob_cache_lock);
		jp->j_next = idn_protojob_cache_list;
		idn_protojob_cache_list = jp;
		mutex_exit(&idn_protojob_cache_lock);
	} else {
		kmem_cache_free(idn.protocol.p_jobpool, (void *)jp);
	}
}

void
idn_protojob_submit(int cookie, idn_protojob_t *jp)
{
	idn_protoqueue_t	*pq;
	int			serverid;
	procname_t		proc = "idn_protojob_submit";
	STRING(str);

	if (jp == NULL)
		return;

	serverid = IDN_PROTOCOL_SERVER_HASH(cookie);

	pq = &idn.protocol.p_serverq[serverid];

	INUM2STR(jp->j_msg.m_msgtype, str);
	PR_PROTO("%s: job (d=%d, m=0x%x, %s) submitted to "
	    "protocol server %d\n", proc, jp->j_msg.m_domid,
	    jp->j_msg.m_msgtype, str, serverid);

	mutex_enter(&pq->q_mutex);
	/*
	 * Can't submit jobs to dying servers.
	 */
	if (!pq->q_die) {
		if (pq->q_joblist_tail) {
			pq->q_joblist_tail->j_next = jp;
			pq->q_joblist_tail = jp;
		} else {
			pq->q_joblist = pq->q_joblist_tail = jp;
		}
		jp->j_next = NULL;
		cv_signal(&pq->q_cv);
	} else {
		PR_PROTO("%s: protocol server dead.  freeing protojob\n",
		    proc);
		idn_protojob_free(jp);
	}
	mutex_exit(&pq->q_mutex);
}

static void
idn_mboxarea_init(idn_mboxtbl_t *mtp, register int ntbls)
{
	register int	d;
	caddr_t		state_ptr = NULL, mtbasep = (caddr_t)mtp;
	idn_mboxtbl_t	*amtp;
	procname_t	proc = "idn_mboxarea_init";

	ASSERT(mtp && (ntbls > 0));

	PR_PROTO("%s: init mboxtbl (0x%p) ntbls = %d\n", proc, mtp, ntbls);

	for (d = 0; d < ntbls; d++) {
		register int	pd, sd;
		register int	ch;

		mtp->mt_header.mh_svr_active = 0;
		mtp->mt_header.mh_svr_ready = 0;
		/*
		 * Initialize the header of each mbox table
		 * with a cookie for identity.
		 */
		/*
		 * Format: 0xc0c0DSCC
		 *	 D = primary domain
		 *	 S = sub-domain of primary
		 *	CC = channel of sub-domain.
		 */
		pd = (d / MAX_DOMAINS) / IDN_MAX_NETS;
		sd = (d / IDN_MAX_NETS) % MAX_DOMAINS;
		ch = d % IDN_MAX_NETS;

		/*
		 * We point all sub-domains in the same channel
		 * to the same active sync flag since a single server
		 * services all domains in the same channel.
		 */
		amtp = IDN_MBOXTBL_ABS_PTR(mtbasep, pd, 0, ch);

		state_ptr = (caddr_t)&amtp->mt_header.mh_svr_active;
		mtp->mt_header.mh_svr_active_ptr = IDN_ADDR2OFFSET(state_ptr);

		state_ptr = (caddr_t)&amtp->mt_header.mh_svr_ready;
		mtp->mt_header.mh_svr_ready_ptr = IDN_ADDR2OFFSET(state_ptr);

		mtp->mt_header.mh_cookie = IDN_MAKE_MBOXHDR_COOKIE(pd, sd, ch);

		mtp->mt_header.mh_cksum = IDN_CKSUM_MBOX(&mtp->mt_header);

		IDN_MBOXTBL_PTR_INC(mtp);
	}
	/*
	 * Now that the master has initialized the entire mailbox
	 * region the referenced memory may not necessarily be up-to-date
	 * with respect to the actual SMR memory due to caching.
	 * In order to make sure future connecting domains get a
	 * consistent picture of the mailbox region, it's necessary
	 * for the master to flush its caches.
	 */
	PR_PROTO("%s: flushing ecache's of local (master) domain\n", proc);

	idnxf_flushall_ecache();
}

idn_mainmbox_t *
idn_mainmbox_init(int domid, int mbx)
{
	idn_mainmbox_t	*mmp;
	int		c;
	idn_mainmbox_t	*cmp;
	procname_t	proc = "idn_mainmbox_init";

	ASSERT(idn_domain[domid].dcpu != IDN_NIL_DCPU);
	ASSERT(IDN_DLOCK_IS_HELD(domid));

	PR_PROTO("%s: initializing main %s mailbox for domain %d\n",
	    proc, IDNMBOX_IS_RECV(mbx) ? "RECV" : "SEND", domid);

	cmp = GETSTRUCT(idn_mainmbox_t, IDN_MAX_NETS);
	for (c = 0; c < IDN_MAX_NETS; c++) {
		mmp = &cmp[c];
		mmp->mm_channel = (short)c;
		mutex_init(&mmp->mm_mutex, NULL, MUTEX_DRIVER, NULL);
		mmp->mm_domid = (short)domid;
		mmp->mm_type = mbx;
	}
	mmp = cmp;
	/*
	 * The actual SMR mailbox (mmp->mm_smr_mboxp) gets setup
	 * when the SMR is setup.
	 */

	return (mmp);
}

static void
idn_mainmbox_reset(int domid, idn_mainmbox_t *cmp)
{
	idn_mainmbox_t	*mmp;
	int		c;
	procname_t	proc = "idn_mainmbox_reset";

	ASSERT(IDN_DLOCK_IS_EXCL(domid));

	PR_PROTO("%s: reseting main %s mailbox for domain %d\n",
	    proc, IDNMBOX_IS_RECV(cmp->mm_type) ? "RECV" : "SEND", domid);

	for (c = 0; c < IDN_MAX_NETS; c++) {
		mmp = &cmp[c];

		mmp->mm_channel = (short)c;
		mmp->mm_domid = (short)domid;
		mmp->mm_count = 0;
		mmp->mm_flags = 0;
		mmp->mm_qiget = mmp->mm_qiput = 0;
		mmp->mm_csp = NULL;
		ASSERT(mmp->mm_type == cmp->mm_type);
	}
}

void
idn_mainmbox_deinit(int domid, idn_mainmbox_t *mmp)
{
	procname_t	proc = "idn_mainmbox_deinit";

	ASSERT(IDN_DLOCK_IS_HELD(domid));

	PR_PROTO("%s: deinitializing main %s mailbox for domain %d\n",
	    proc, IDNMBOX_IS_RECV(mmp->mm_type) ? "RECV" : "SEND", domid);

	ASSERT(idn_domain_is_registered(domid, -1, NULL) == 0);

	FREESTRUCT(mmp, idn_mainmbox_t, IDN_MAX_NETS);
}

static void
idn_mainmbox_activate(int domid)
{
	register int	c;
	idn_domain_t	*dp = &idn_domain[domid];
	procname_t	proc = "idn_mainmbox_activate";

	ASSERT(IDN_DLOCK_IS_HELD(domid));

	PR_PROTO("%s:%d: activating main mailbox\n", proc, domid);

	for (c = 0; c < IDN_MAX_NETS; c++)
		idn_mainmbox_chan_register(domid, &dp->dmbox.m_send[c],
		    &dp->dmbox.m_recv[c], c);
}

/*
 * Called upon disabling the SMR to deactivate all the mailboxes
 * so that they no longer reference the SMR that's going away.
 *
 * stopall - Indicates to stop all channel services, across the board.
 */
static void
idn_mainmbox_deactivate(ushort_t domset)
{
	int		svr_count;
	procname_t	proc = "idn_mainmbox_deactivate";


	if (domset == 0)
		return;

	PR_PROTO("%s: %s deactivating main mailboxes for domset 0x%x\n",
	    proc, (domset == (ushort_t)-1) ? "STOP-ALL" : "NORMAL", domset);

	svr_count = idn_mainmbox_chan_unregister(domset, -1);

	PR_PROTO("%s: deactivated %d chansvrs (domset 0x%x)\n",
	    proc, svr_count, domset);
}

static void
idn_mainmbox_chan_register(int domid, idn_mainmbox_t *send_mmp,
    idn_mainmbox_t *recv_mmp, int channel)
{
	ASSERT(IDN_DLOCK_IS_HELD(domid));

	/*
	 * Obtain receive mailbox lock first.
	 */
	mutex_enter(&recv_mmp->mm_mutex);
	mutex_enter(&send_mmp->mm_mutex);

	ASSERT(recv_mmp->mm_channel == (short)channel);
	ASSERT(send_mmp->mm_channel == (short)channel);

	recv_mmp->mm_csp = &idn.chan_servers[channel];
	recv_mmp->mm_count = 0;
	recv_mmp->mm_dropped = 0;
	recv_mmp->mm_flags = 0;

	send_mmp->mm_csp = &idn.chan_servers[channel];
	send_mmp->mm_count = 0;
	send_mmp->mm_dropped = 0;
	send_mmp->mm_flags = 0;

	mutex_exit(&send_mmp->mm_mutex);
	mutex_exit(&recv_mmp->mm_mutex);

	/*
	 * We have to add ourselves to the respective
	 * channel server's service table.
	 * Note that the channel may not necessarily be
	 * active at this time.
	 */
	ASSERT(idn.chan_servers);
	/*
	 * Have to get the channel server under
	 * control so we can add ourselves.
	 * Returns w/c_mutex.
	 */
	IDN_CHAN_LOCK_GLOBAL(&idn.chan_servers[channel]);
	/*
	 * Add the following domain (mailbox) for monitoring
	 * by the respective channel server.
	 */
	idn_chan_addmbox(channel, DOMAINSET(domid));

	IDN_CHAN_UNLOCK_GLOBAL(&idn.chan_servers[channel]);
}

/*
 * Unregister the given domain from the specified channel(s) for monitoring.
 */
static int
idn_mainmbox_chan_unregister(ushort_t domset, int channel)
{
	int		c, dd_count;
	int		min_chan, max_chan;
	procname_t	proc = "idn_mainmbox_chan_unregister";

	PR_CHAN("%s: deactivating main mailboxes (channel %d) "
	    "for domset 0x%x\n", proc, channel, domset);

	if (channel == -1) {
		min_chan = 0;
		max_chan = IDN_MAX_NETS - 1;
	} else {
		min_chan = max_chan = channel;
	}
	/*
	 * Point all the data dispatchers to the same morgue
	 * so we can kill them all at once.
	 */
	dd_count = 0;
	for (c = min_chan; c <= max_chan; c++) {

		/*
		 * Have to get the channel server under
		 * control so we can remove ourselves.
		 * Returns w/c_mutex held.
		 */
		IDN_CHAN_LOCK_GLOBAL(&idn.chan_servers[c]);
		/*
		 * Delete the following domain (mailbox) from
		 * monitoring by the respective channel server.
		 */
		idn_chan_delmbox(c, (ushort_t)domset);

		IDN_CHAN_UNLOCK_GLOBAL(&idn.chan_servers[c]);
		dd_count++;
	}
	PR_CHAN("%s: deactivated %d channel mboxes for domset 0x%x, chan %d\n",
	    proc, dd_count, domset, channel);
	return (dd_count);
}

/*
 * Check if the given domain is registered with the given channel(s).
 */
int
idn_domain_is_registered(int domid, int channel, idn_chanset_t *chansetp)
{
	int		regcount;
	int		c, min_chan, max_chan;
	idn_chanset_t	chanset;
	procname_t	proc = "idn_domain_is_registered";

	CHANSET_ZERO(chanset);

	if (idn.chan_servers == NULL) {
		PR_CHAN("%s: idn.chan_servers == NULL!!\n", proc);
		return (0);
	}

	if (channel == -1) {
		min_chan = 0;
		max_chan = IDN_MAX_NETS - 1;
	} else {
		min_chan = max_chan = channel;
	}

	regcount = 0;

	for (c = min_chan; c <= max_chan; c++) {
		idn_chansvr_t	*csp;

		csp = &idn.chan_servers[c];
		IDN_CHAN_LOCK_SEND(csp);
		/*
		 * Don't really need recv side lock since registeration
		 * can't change while we're holding send side.
		 * No need to wait for send side to actually suspend
		 * since all we want to do is prevent the registered
		 * information from changing.
		 */
		if (IDN_CHAN_DOMAIN_IS_REGISTERED(csp, domid)) {
			regcount++;
			CHANSET_ADD(chanset, c);
		}

		IDN_CHAN_UNLOCK_SEND(csp);
	}

	PR_CHAN("%s: domid %d mbox reg'd with %d channels [0x%x] (req=%d)\n",
	    proc, domid, regcount, chanset, channel);

	if (chansetp)
		*chansetp = chanset;

	return (regcount);
}

static int
idn_mainmbox_flush(int domid, idn_mainmbox_t *mmp)
{
	register int		qi;
	register idn_mboxmsg_t	*mqp;
	int		total_count = 0;
	int		c, count;
	int		mbox_type;
	char		*mbox_str;
	int		lost_io, total_lost_io = 0;
	idn_chanset_t	chanset;
	procname_t	proc = "idn_mainmbox_flush";


	if (mmp == NULL)
		return (0);

	CHANSET_ZERO(chanset);

	mbox_type = mmp->mm_type;
	ASSERT((mbox_type == IDNMMBOX_TYPE_SEND) ||
	    (mbox_type == IDNMMBOX_TYPE_RECV));

	mbox_str = (mbox_type == IDNMMBOX_TYPE_SEND) ? "SEND" : "RECV";

	/*
	 * Determine which channels this domain is registered
	 * with.  If he's not registered with any, then we
	 * can't touch the SMR.
	 */
	(void) idn_domain_is_registered(domid, -1, &chanset);

	for (c = 0; c < IDN_MAX_NETS; c++) {
		ushort_t	mbox_csum;

		if (mmp[c].mm_smr_mboxp == NULL)
			continue;
		mutex_enter(&mmp[c].mm_mutex);
		ASSERT(mmp[c].mm_type == mbox_type);
		if (CHAN_IN_SET(chanset, c) == 0) {
			/*
			 * Domain is no longer registered.
			 * DON'T TOUCH THE SMR - IT'S POISON!
			 */
			if (mmp[c].mm_smr_mboxp) {
				PR_CHAN("%s:%d:%s: domain unregistered "
				    "w/chan %d - DUMPING SMR reference\n",
				    proc, domid, mbox_str, c);
				lost_io = IDN_MMBOXINDEX_DIFF(mmp[c].mm_qiput,
				    mmp[c].mm_qiget);
#ifdef DEBUG
				if (mbox_type == IDNMMBOX_TYPE_RECV) {
					PR_CHAN("%s:%d:%s: blowing away %d "
					    "incoming pkts\n",
					    proc, domid, mbox_str, lost_io);
				} else {
					PR_CHAN("%s:%d:%s: blowing away %d/%d "
					    "outstanding pkts\n",
					    proc, domid, mbox_str, lost_io,
					    idn_domain[domid].dio);
				}
#endif /* DEBUG */
			}
			mmp[c].mm_qiput = mmp[c].mm_qiget = 0;
			mmp[c].mm_smr_mboxp = NULL;
			total_lost_io += lost_io;
		}
		if (mmp[c].mm_smr_mboxp) {
			mbox_csum =
			    IDN_CKSUM_MBOX(&mmp[c].mm_smr_mboxp->mt_header);
			if (!VALID_NWRADDR(mmp[c].mm_smr_mboxp, 4) ||
			    !VALID_MBOXHDR(&mmp[c].mm_smr_mboxp->mt_header,
			    c, mbox_csum)) {
				lost_io = IDN_MMBOXINDEX_DIFF(mmp[c].mm_qiput,
				    mmp[c].mm_qiget);
#ifdef DEBUG
				if (mbox_type == IDNMMBOX_TYPE_RECV) {
					PR_CHAN("%s:%d:%s: bad mbox.  blowing "
					    "away %d incoming pkts\n",
					    proc, domid, mbox_str, lost_io);
				} else {
					PR_CHAN("%s:%d:%s: bad mbox.  blowing "
					    "away %d/%d outstanding pkts\n",
					    proc, domid, mbox_str, lost_io,
					    idn_domain[domid].dio);
				}
#endif /* DEBUG */
				mmp[c].mm_smr_mboxp = NULL;
				mmp[c].mm_qiput = mmp[c].mm_qiget = 0;
				total_lost_io += lost_io;
			}
		}
		if (mmp[c].mm_smr_mboxp == NULL) {
			mutex_exit(&mmp[c].mm_mutex);
			continue;
		}
		mqp = &mmp[c].mm_smr_mboxp->mt_queue[0];
		qi = 0;
		count = 0;
		/*
		 * It's quite possible the remote domain may be accessing
		 * these mailbox entries at the exact same time we're
		 * clearing the owner bit.  That's okay.  All we're trying
		 * to do at this point is to minimize the number of packets
		 * the remote domain might try to process unnecessarily.
		 */
		do {
			if (mqp[qi].ms_owner)
				count++;
			mqp[qi].ms_owner = 0;
			IDN_MMBOXINDEX_INC(qi);
		} while (qi);

		lost_io = IDN_MMBOXINDEX_DIFF(mmp[c].mm_qiput, mmp[c].mm_qiget);
		total_lost_io += lost_io;

		mmp[c].mm_qiput = mmp[c].mm_qiget = 0;
		mmp[c].mm_smr_mboxp = NULL;
		mutex_exit(&mmp[c].mm_mutex);

		total_count += count;

		PR_CHAN("%s:%d:%s: flushed out %d mbox entries for chan %d\n",
		    proc, domid, mbox_str, count, c);
	}

	if (total_lost_io && (mbox_type == IDNMMBOX_TYPE_SEND)) {
		int	lost_bufs;
		/*
		 * If we lost all our outstanding I/O.  We could
		 * possible could have slabs now with mistakenly
		 * outstanding I/O buffers.  Need to clean them up.
		 * Clean up of leftovers our self.
		 */
		lost_bufs = smr_buf_free_all(domid);

		PR_CHAN("%s:%d:%s: flushed %d/%d buffers from slabs\n",
		    proc, domid, mbox_str, lost_bufs, total_lost_io);
	}

	PR_CHAN("%s:%d:%s: flushed total of %d mailbox entries (lost %d)\n",
	    proc, domid, mbox_str, total_count, total_lost_io);

	return (total_count);
}

void
idn_chanserver_bind(int net, int cpuid)
{
	int		ocpuid;
	cpu_t		*cp;
	idn_chansvr_t	*csp;
	kthread_id_t	tp;
	procname_t	proc = "idn_chanserver_bind";

	csp = &idn.chan_servers[net];
	IDN_CHAN_LOCK_GLOBAL(csp);

	mutex_enter(&cpu_lock);		/* protect checking cpu_ready_set */
	ocpuid = csp->ch_bound_cpuid;
	cp = cpu_get(cpuid);
	if ((cpuid != -1) && ((cp == NULL) || !cpu_is_online(cp))) {
		mutex_exit(&cpu_lock);
		cmn_err(CE_WARN,
		    "IDN: 239: invalid CPU ID (%d) specified for "
		    "IDN net %d",
		    cpuid, net);
		IDN_CHAN_UNLOCK_GLOBAL(csp);
		return;
	}
	if ((tp = csp->ch_recv_threadp) == NULL) {
		/*
		 * Thread is not yet active.  Set ch_bound_cpuid
		 * so when thread activates it will automatically
		 * bind itself.
		 */
		csp->ch_bound_cpuid = -1;
		csp->ch_bound_cpuid_pending = cpuid;
	} else {
		if (ocpuid != -1) {
			thread_affinity_clear(tp);
			csp->ch_bound_cpuid = -1;
		}
		if (cpuid >= 0) {
			thread_affinity_set(tp, cpuid);
			csp->ch_bound_cpuid = cpuid;
		}
		csp->ch_bound_cpuid_pending = -1;
	}
	mutex_exit(&cpu_lock);

	PR_CHAN("%s: bound net/channel (%d) from cpuid %d to%scpuid %d\n",
	    proc, net, ocpuid, tp ? " " : " (pending) ", cpuid);

	IDN_CHAN_UNLOCK_GLOBAL(csp);
}

#ifdef DEBUG
static idn_mboxhdr_t	*prev_mhp[IDN_MAXMAX_NETS];
#endif /* DEBUG */
/*
 * Get access to the respective channel server's synchronization
 * header which resides in SMR space.
 */
static idn_mboxhdr_t *
idn_chan_server_syncheader(int channel)
{
	idn_domain_t	*ldp = &idn_domain[idn.localid];
	idn_mboxtbl_t	*mtp;
	idn_mboxhdr_t	*mhp;
	ushort_t	mbox_csum;
	procname_t	proc = "idn_chan_server_syncheader";

	ASSERT(IDN_CHAN_RECV_IS_LOCKED(&idn.chan_servers[channel]));

	IDN_DLOCK_SHARED(idn.localid);

	if (ldp->dmbox.m_tbl == NULL) {
		PR_CHAN("%s: local dmbox.m_tbl == NULL\n", proc);
		IDN_DUNLOCK(idn.localid);
		return (NULL);
	}

	mtp = IDN_MBOXTBL_PTR_CHAN(ldp->dmbox.m_tbl, channel);
	mhp = &mtp->mt_header;
	mbox_csum = IDN_CKSUM_MBOX(&mtp->mt_header);

#ifdef DEBUG
	if (mhp != prev_mhp[channel]) {
		prev_mhp[channel] = mhp;
		PR_CHAN("%s: chan_server (%d) cookie = 0x%x (exp 0x%x)\n",
		    proc, channel, IDN_GET_MBOXHDR_COOKIE(mhp),
		    IDN_MAKE_MBOXHDR_COOKIE(0, 0, channel));
		PR_CHAN("%s: chan_server (%d) actv_ptr = 0x%x (exp 0x%x)\n",
		    proc, channel, mhp->mh_svr_active_ptr,
		    IDN_ADDR2OFFSET(&mhp->mh_svr_active));
		PR_CHAN("%s: chan_server (%d) ready_ptr = 0x%x (exp 0x%x)\n",
		    proc, channel, mhp->mh_svr_ready_ptr,
		    IDN_ADDR2OFFSET(&mhp->mh_svr_ready));
		PR_CHAN("%s: chan_server (%d) mbox_cksum = 0x%x (exp 0x%x)\n",
		    proc, channel, (int)mhp->mh_cksum, (int)mbox_csum);
	}
#endif /* DEBUG */

	if ((IDN_ADDR2OFFSET(&mhp->mh_svr_active) !=
	    mhp->mh_svr_active_ptr) ||
	    (IDN_ADDR2OFFSET(&mhp->mh_svr_ready) != mhp->mh_svr_ready_ptr) ||
	    !VALID_MBOXHDR(mhp, channel, mbox_csum)) {
		idn_chansvr_t	*csp;

		csp = &idn.chan_servers[channel];
		if (IDN_CHANNEL_IS_RECV_CORRUPTED(csp) == 0) {
			IDN_CHANSVC_MARK_RECV_CORRUPTED(csp);

			cmn_err(CE_WARN,
			    "IDN: 240: (channel %d) SMR CORRUPTED "
			    "- RELINK", channel);
			cmn_err(CE_CONT,
			    "IDN: 240: (channel %d) cookie "
			    "(expected 0x%x, actual 0x%x)\n",
			    channel,
			    IDN_MAKE_MBOXHDR_COOKIE(0, 0, channel),
			    mhp->mh_cookie);
			cmn_err(CE_CONT,
			    "IDN: 240: (channel %d) actv_flg "
			    "(expected 0x%x, actual 0x%x)\n",
			    channel, mhp->mh_svr_active_ptr,
			    IDN_ADDR2OFFSET(&mhp->mh_svr_active));
			cmn_err(CE_CONT,
			    "IDN: 240: (channel %d) ready_flg "
			    "(expected 0x%x, actual 0x%x)\n",
			    channel, mhp->mh_svr_ready_ptr,
			    IDN_ADDR2OFFSET(&mhp->mh_svr_ready));
		}

		mhp = NULL;
	}
	IDN_DUNLOCK(idn.localid);

	PR_CHAN("%s: channel(%d) mainhp = 0x%p\n", proc, channel, mhp);

	return (mhp);
}

#define	CHANSVR_SYNC_CACHE(csp, mmp, chan) \
{ \
	ASSERT(IDN_CHAN_RECV_IS_LOCKED(csp)); \
	if ((csp)->ch_recv_changed) { \
		register int _d; \
		(csp)->ch_recv_scanset = (csp)->ch_recv_scanset_pending; \
		(csp)->ch_recv_domset = (csp)->ch_recv_domset_pending; \
		for (_d = 0; _d < MAX_DOMAINS; _d++) { \
			if (DOMAIN_IN_SET((csp)->ch_recv_domset, _d)) { \
				(mmp)[_d] = \
				    &idn_domain[_d].dmbox.m_recv[chan]; \
			} else { \
				(mmp)[_d] = NULL; \
			} \
		} \
		(csp)->ch_recv_changed = 0; \
	} \
}
#define	CHANSVR_NEXT_DOMID(csp, i, d) \
{ \
	(i) = ((i) + 1) & (MAX_DOMAINS - 1); \
	(d) = (int)(((csp)->ch_recv_scanset >> ((i) << 2)) & 0xf); \
}
#define	CHANSVR_RESET_INDEX(i)	((i) = -1)

#ifdef DEBUG
static idn_mainmbox_t	*Mmp[IDN_MAXMAX_NETS][MAX_DOMAINS];
#endif /* DEBUG */

static void
idn_chan_server(idn_chansvr_t **cspp)
{
	idn_mboxhdr_t	*mainhp;
	register idn_chansvr_t		*csp;
	register idn_mboxmsg_t		*mqp;
#ifdef DEBUG
	idn_mainmbox_t			**mmp;
#else
	idn_mainmbox_t			*mmp[MAX_DOMAINS];
#endif /* DEBUG */
	register int	qi;
	struct idn	*sip;
	int		channel;
	int		cpuid;
	int		empty;
	int		tot_pktcount, tot_dropcount;
	register int	index;
	register int	domid;
	register int	idleloops;
	procname_t	proc = "idn_chan_server";


#ifdef DEBUG
	mmp = &Mmp[(*cspp)->ch_id][0];
	bzero(mmp, MAX_DOMAINS * sizeof (idn_mainmbox_t *));
#else /* DEBUG */
	bzero(mmp, sizeof (mmp));
#endif /* DEBUG */

	tot_pktcount = tot_dropcount = 0;

	ASSERT(cspp && *cspp);

	csp = *cspp;
	channel = csp->ch_id;
	sip = IDN_INST2SIP(channel);
	ASSERT(sip);

	PR_CHAN("%s: CHANNEL SERVER (channel %d) GOING ACTIVE...\n",
	    proc, channel);

	IDN_CHAN_LOCK_RECV(csp);
	IDN_CHAN_RECV_INPROGRESS(csp);
	ASSERT(csp->ch_recv_threadp == curthread);
	mutex_enter(&cpu_lock);
	if ((cpuid = csp->ch_bound_cpuid_pending) != -1) {
		cpu_t	*cp = cpu_get(cpuid);
		/*
		 * We've been requested to bind to
		 * a particular cpu.
		 */
		if ((cp == NULL) || !cpu_is_online(cp)) {
			/*
			 * Cpu seems to have gone away or gone offline
			 * since originally requested.
			 */
			mutex_exit(&cpu_lock);
			cmn_err(CE_WARN,
			    "IDN: 239: invalid CPU ID (%d) specified for "
			    "IDN net %d",
			    cpuid, channel);
		} else {
			csp->ch_bound_cpuid = cpuid;
			affinity_set(csp->ch_bound_cpuid);
			mutex_exit(&cpu_lock);
		}
		csp->ch_bound_cpuid_pending = -1;
	} else {
		mutex_exit(&cpu_lock);
	}
	if (csp->ch_bound_cpuid != -1) {
		PR_CHAN("%s: thread bound to cpuid %d\n",
		    proc, csp->ch_bound_cpuid);
	}
	/*
	 * Only the first (main) mbox header is used for
	 * synchronization with data delivery since there is
	 * only data server for all mailboxes for this
	 * given channel.
	 */
	CHANSVR_SYNC_CACHE(csp, mmp, channel);

	mainhp = ((csp->ch_recv_domcount > 0) &&
	    IDN_CHANNEL_IS_RECV_ACTIVE(csp))
	    ? idn_chan_server_syncheader(channel) : NULL;

	if (mainhp && IDN_CHANNEL_IS_RECV_ACTIVE(csp))
		mainhp->mh_svr_active = 1;

	ASSERT(csp->ch_recv_domcount ?
	    (csp->ch_recv_scanset && csp->ch_recv_domset) : 1);

	IDN_CHAN_UNLOCK_RECV(csp);

	empty = 0;
	idleloops = 0;
	CHANSVR_RESET_INDEX(index);

	/*
	 * ---------------------------------------------
	 */
	/*CONSTCOND*/
	while (1) {
		register int	pktcount;
		register int	dropcount;
		ushort_t		mbox_csum;
		idn_mboxtbl_t	*smr_mboxp;	/* points to SMR space */
		register smr_offset_t	bufoffset;
#ifdef DEBUG
		register smr_pkthdr_t	*hdrp;
		idn_netaddr_t		netaddr;
#endif /* DEBUG */

		/*
		 * Speed through and find the next available domid.
		 */
		CHANSVR_NEXT_DOMID(csp, index, domid);

		if (!index) {
			/*
			 * We only check state changes when
			 * we wrap around.  Done for performance.
			 */
			if (!IDN_CHANNEL_IS_RECV_ACTIVE(csp) ||
			    csp->ch_recv.c_checkin ||
			    (idn.state != IDNGS_ONLINE)) {

				PR_DATA("%s: (channel %d) %s\n",
				    proc, channel,
				    IDN_CHANNEL_IS_DETACHED(csp)
				    ? "DEAD" :
				    IDN_CHANNEL_IS_PENDING(csp)
				    ? "IDLED" :
				    IDN_CHANNEL_IS_ACTIVE(csp)
				    ? "ACTIVE" : "DISABLED");
				goto cc_sleep;
			}
		}
		if (csp->ch_recv.c_checkin)
			goto cc_sleep;

		if (empty == csp->ch_recv_domcount) {
			empty = 0;
			goto cc_slowdown;
		}

		ASSERT(mmp[domid] != NULL);

		mutex_enter(&mmp[domid]->mm_mutex);
		if ((smr_mboxp = mmp[domid]->mm_smr_mboxp) == NULL) {
			/*
			 * Somebody is trying to shut things down.
			 */
			empty++;
			mutex_exit(&mmp[domid]->mm_mutex);
			continue;
		}
		ASSERT(mmp[domid]->mm_channel == (short)channel);
		/*
		 * We don't care if the mm_smr_mboxp is nullified
		 * after this point.  The thread attempting to shut
		 * us down has to formally pause this channel before
		 * anything is official anyway.  So, we can continue
		 * with our local SMR reference until the thread
		 * shutting us down really stops us.
		 *
		 * Need to get the qiget index _before_ we drop the
		 * lock since it might get flushed (idn_mainmbox_flush)
		 * once we drop the mm_mutex.
		 *
		 * We prefer not to hold the mm_mutex across the
		 * idn_recv_mboxdata() call since that may be time-
		 * consuming.
		 */
		qi  = mmp[domid]->mm_qiget;

		/*
		 * Check the mailbox header if checksum is turned on.
		 */
		mbox_csum = IDN_CKSUM_MBOX(&smr_mboxp->mt_header);
		if (!VALID_MBOXHDR(&smr_mboxp->mt_header, channel, mbox_csum)) {
			IDN_KSTAT_INC(sip, si_mboxcrc);
			IDN_KSTAT_INC(sip, si_ierrors);
			if (!(mmp[domid]->mm_flags & IDNMMBOX_FLAG_CORRUPTED)) {
				cmn_err(CE_WARN,
				    "IDN: 241: [recv] (domain %d, "
				    "channel %d) SMR CORRUPTED - RELINK",
				    domid, channel);
				mmp[domid]->mm_flags |= IDNMMBOX_FLAG_CORRUPTED;
			}
			empty = 0;
			mutex_exit(&mmp[domid]->mm_mutex);
			goto cc_sleep;
		}
		mutex_exit(&mmp[domid]->mm_mutex);
		mqp = &smr_mboxp->mt_queue[0];

		pktcount = dropcount = 0;

		if (mqp[qi].ms_owner == 0)
			goto cc_next;

		bufoffset = IDN_BFRAME2OFFSET(mqp[qi].ms_bframe);

		if (!VALID_NWROFFSET(bufoffset, IDN_SMR_BUFSIZE)) {
			/* ASSERT(0); */
			mqp[qi].ms_flag |= IDN_MBOXMSG_FLAG_ERR_BADOFFSET;
			mqp[qi].ms_owner = 0;
			IDN_MMBOXINDEX_INC(qi);
			dropcount++;

			IDN_KSTAT_INC(sip, si_smraddr);
			IDN_KSTAT_INC(sip, si_ierrors);

		} else {
			PR_DATA("%s: (channel %d) pkt (off 0x%x, "
			    "qiget %d) from domain %d\n",
			    proc, channel, bufoffset, qi, domid);
#ifdef DEBUG

			hdrp = IDN_BUF2HDR(IDN_OFFSET2ADDR(bufoffset));
			netaddr.netaddr = hdrp->b_netaddr;
			ASSERT(netaddr.net.chan == (ushort_t)channel);
#endif /* DEBUG */

			if (idn_recv_mboxdata(channel,
			    IDN_OFFSET2ADDR(bufoffset)) < 0) {
				mutex_enter(&mmp[domid]->mm_mutex);
				if (!(mmp[domid]->mm_flags &
				    IDNMMBOX_FLAG_CORRUPTED)) {
					cmn_err(CE_WARN,
					    "IDN: 241: [recv] (domain "
					    "%d, channel %d) SMR "
					    "CORRUPTED - RELINK",
					    domid, channel);
					mmp[domid]->mm_flags |=
					    IDNMMBOX_FLAG_CORRUPTED;
				}
				mutex_exit(&mmp[domid]->mm_mutex);
			}

			mqp[qi].ms_owner = 0;
			IDN_MMBOXINDEX_INC(qi);
			pktcount++;
		}

cc_next:

		mutex_enter(&mmp[domid]->mm_mutex);
		if (mmp[domid]->mm_smr_mboxp) {
			if (dropcount)
				mmp[domid]->mm_dropped += dropcount;
			mmp[domid]->mm_qiget = qi;
			mmp[domid]->mm_count += pktcount;
		}
		mutex_exit(&mmp[domid]->mm_mutex);

		if (pktcount == 0) {
			empty++;
		} else {
			csp->ch_recv_waittime = IDN_NETSVR_WAIT_MIN;
			empty = 0;
			idleloops = 0;

			PR_DATA("%s: (channel %d) dom=%d, pktcnt=%d\n",
			    proc, channel, domid, pktcount);
		}

		continue;

cc_slowdown:

#ifdef DEBUG
		if (idleloops == 0) {
			PR_DATA("%s: (channel %d) going SOFT IDLE...\n",
			    proc, channel);
		}
#endif /* DEBUG */
		if (idleloops++ < IDN_NETSVR_SPIN_COUNT) {
			/*
			 * At this level we only busy-wait.
			 * Get back into action.
			 */
			continue;
		}
		idleloops = 0;

cc_sleep:

		if (mainhp)
			mainhp->mh_svr_active = 0;

		IDN_CHAN_LOCK_RECV(csp);

cc_die:

		ASSERT(IDN_CHAN_RECV_IS_LOCKED(csp));

		if (!IDN_CHANNEL_IS_RECV_ACTIVE(csp) &&
		    IDN_CHANNEL_IS_DETACHED(csp)) {
			/*
			 * Time to die...
			 */
			PR_CHAN("%s: (channel %d) serviced %d "
			    "packets, drop = %d\n", proc, channel,
			    tot_pktcount, tot_dropcount);
			PR_CHAN("%s: (channel %d) TERMINATING\n",
			    proc, channel);
			PR_CHAN("%s: (channel %d) ch_morguep = %p\n",
			    proc, channel, csp->ch_recv_morguep);

			csp->ch_recv_threadp = NULL;
#ifdef DEBUG
			for (index = 0; index < csp->ch_recv_domcount;
			    index++) {
				if ((int)((csp->ch_recv_scanset >>
				    (index*4)) & 0xf) == domid) {
					PR_DATA("%s: WARNING (channel %d) "
					    "DROPPING domid %d...\n",
					    proc, channel, domid);
				}
			}
#endif /* DEBUG */
			IDN_CHAN_RECV_DONE(csp);

			sema_v(csp->ch_recv_morguep);

			IDN_CHAN_UNLOCK_RECV(csp);

			thread_exit();
			/* not reached */
		}

		do {
			if (IDN_CHANNEL_IS_DETACHED(csp)) {
				PR_CHAN("%s: (channel %d) going to DIE...\n",
				    proc, channel);
				goto cc_die;
			}
#ifdef DEBUG
			if (IDN_CHANNEL_IS_RECV_ACTIVE(csp) &&
			    (csp->ch_recv_waittime <= IDN_NETSVR_WAIT_MAX)) {
				PR_CHAN("%s: (channel %d) going SOFT IDLE "
				    "(waittime = %d ticks)...\n",
				    proc, channel,
				    csp->ch_recv_waittime);
			} else {
				PR_CHAN("%s: (channel %d) going "
				    "HARD IDLE...\n", proc, channel);
			}
#endif /* DEBUG */
			IDN_CHAN_RECV_DONE(csp);

			/*
			 * If we're being asked to check-in then
			 * go into a hard sleep.  Want to give the
			 * thread requesting us to checkin a chance.
			 */
			while (csp->ch_recv.c_checkin)
				cv_wait(&csp->ch_recv_cv,
				    &csp->ch_recv.c_mutex);

			if (csp->ch_recv_waittime > IDN_NETSVR_WAIT_MAX)
				cv_wait(&csp->ch_recv_cv,
				    &csp->ch_recv.c_mutex);
			else
				(void) cv_reltimedwait(&csp->ch_recv_cv,
				    &csp->ch_recv.c_mutex,
				    csp->ch_recv_waittime, TR_CLOCK_TICK);

			IDN_CHAN_RECV_INPROGRESS(csp);

			IDN_KSTAT_INC(sip, si_sigsvr);

			if (csp->ch_recv_waittime <= IDN_NETSVR_WAIT_MAX)
				csp->ch_recv_waittime <<=
				    IDN_NETSVR_WAIT_SHIFT;

		} while (!IDN_CHANNEL_IS_RECV_ACTIVE(csp));

		/*
		 * Before we see the world (and touch SMR space),
		 * see if we've been told to die.
		 */
		mainhp = NULL;
		/*
		 * The world may have changed since we were
		 * asleep.  Need to resync cache and check for a
		 * new syncheader.
		 *
		 * Reset chansvr cache against any changes in
		 * mbox fields we need (mm_qiget).
		 */
		CHANSVR_SYNC_CACHE(csp, mmp, channel);
		if (csp->ch_recv_domcount <= 0) {
			/*
			 * Everybody disappeared on us.
			 * Go back to sleep.
			 */
			goto cc_die;
		}
		ASSERT(csp->ch_recv_scanset && csp->ch_recv_domset);

		mainhp = idn_chan_server_syncheader(channel);
		if (mainhp == NULL) {
			/*
			 * Bummer...we're idling...
			 */
			goto cc_die;
		}

		mainhp->mh_svr_active = 1;

		IDN_CHAN_UNLOCK_RECV(csp);
		/*
		 * Reset the domid index after sleeping.
		 */
		CHANSVR_RESET_INDEX(index);

		empty = 0;
		idleloops = 0;
	}
}

#if 0
/*
 * We maintain a separate function for flushing the STREAMs
 * queue of a channel because it must be done outside the
 * context of the idn_chan_action routine.  The streams flush
 * cannot occur inline with the idn_chan_action because
 * the act of flushing may cause IDN send functions to be called
 * directly and thus locks to be obtained which could result
 * in deadlocks.
 */
static void
idn_chan_flush(idn_chansvr_t *csp)
{
	queue_t		*rq;
	struct idn	*sip;
	int		flush_type = 0;
	idn_chaninfo_t	*csend, *crecv;
	procname_t	proc = "idn_chan_flush";

	csend = &csp->ch_send;
	crecv = &csp->ch_recv;

	mutex_enter(&crecv->c_mutex);
	mutex_enter(&csend->c_mutex);

	if (crecv->c_state & IDN_CHANSVC_STATE_FLUSH)
		flush_type |= FLUSHR;

	if (csend->c_state & IDN_CHANSVC_STATE_FLUSH)
		flush_type |= FLUSHW;

	if (flush_type) {
		rq = NULL;
		rw_enter(&idn.struprwlock, RW_READER);
		if ((sip = IDN_INST2SIP(csp->ch_id)) != NULL)
			rq = sip->si_ipq;
		rw_exit(&idn.struprwlock);
		if (rq) {
			/*
			 * Flush the STREAM if possible
			 * to get the channel server coherent
			 * enough to respond to us.
			 */
			PR_CHAN("%s: sending FLUSH (%x) to channel %d\n",
			    proc, flush_type, csp->ch_id);

			(void) putnextctl1(rq, M_FLUSH, flush_type);
		}
		crecv->c_state &= ~IDN_CHANSVC_STATE_FLUSH;
		csend->c_state &= ~IDN_CHANSVC_STATE_FLUSH;

		if (crecv->c_waiters)
			cv_broadcast(&crecv->c_cv);
	}

	mutex_exit(&csend->c_mutex);
	mutex_exit(&crecv->c_mutex);
}
#endif /* 0 */

/*
 * Locks are with respect to SEND/RECV locks (c_mutex).
 *
 * STOP/SUSPEND/DETACH
 *	- Entered with locks dropped, leave with locks held.
 *	  DETACH - lock dropped manually.
 * RESTART/RESUME
 *	- Entered with locks held, leave with locks dropped.
 * ATTACH
 *	- both enter and leave with locks dropped.
 */
static void
idn_chan_action(int channel, idn_chanaction_t chanaction, int wait)
{
	uchar_t		clr_state, set_state;
	uint_t		is_running;
	domainset_t	closed_slabwaiters = 0;
	struct idn	*sip;
	idn_chansvr_t	*csp;
	idn_chaninfo_t	*csend, *crecv;
	procname_t	proc = "idn_chan_action";

	ASSERT((channel >= 0) && (channel < IDN_MAX_NETS));
	ASSERT(idn.chan_servers);

	csp = &idn.chan_servers[channel];

	PR_CHAN("%s: requesting %s for channel %d\n",
	    proc, chanaction_str[(int)chanaction], channel);

	csend = &csp->ch_send;
	crecv = &csp->ch_recv;

	ASSERT(IDN_CHAN_GLOBAL_IS_LOCKED(csp));

	clr_state = set_state = 0;

	switch (chanaction) {
	case IDNCHAN_ACTION_DETACH:
		clr_state = IDN_CHANSVC_STATE_MASK;
		/*FALLTHROUGH*/

	case IDNCHAN_ACTION_STOP:
		clr_state |= IDN_CHANSVC_STATE_ENABLED;
		/*FALLTHROUGH*/

	case IDNCHAN_ACTION_SUSPEND:
		clr_state |= IDN_CHANSVC_STATE_ACTIVE;

		/*
		 * Must maintain this locking order.
		 * Set asynchronous check-in flags.
		 */
		crecv->c_checkin = 1;
		csend->c_checkin = 1;

		is_running = 0;
		if ((csend->c_inprogress || crecv->c_inprogress) &&
		    wait && (csp->ch_recv_threadp != curthread)) {

			rw_enter(&idn.struprwlock, RW_READER);
			if ((sip = IDN_INST2SIP(channel)) != NULL) {
				/*
				 * Temporarily turn off the STREAM
				 * to give a chance to breath.
				 */
				is_running = sip->si_flags & IDNRUNNING;
				if (is_running)
					sip->si_flags &= ~IDNRUNNING;
			}
			rw_exit(&idn.struprwlock);
		}

		mutex_enter(&crecv->c_mutex);
		crecv->c_state &= ~clr_state;

		mutex_enter(&csend->c_mutex);
		csend->c_state &= ~clr_state;

		/*
		 * It's possible the channel server could come
		 * through this flow itself due to putting data upstream
		 * that ultimately turned around and came back down for
		 * sending.  If this is the case we certainly don't
		 * want to cv_wait, otherwise we'll obviously deadlock
		 * waiting for ourself.  So, only block if somebody
		 * other than the channel server we're attempting to
		 * suspend/stop.
		 */
		if (wait && (csp->ch_recv_threadp != curthread)) {
			int	do_flush = 0;

			if (csend->c_inprogress || crecv->c_inprogress)
				do_flush++;

			if (do_flush) {
				rw_enter(&idn.struprwlock, RW_READER);
				if ((sip = IDN_INST2SIP(channel)) != NULL) {
					/*
					 * Temporarily turn off the STREAM
					 * to give a chance to breath.
					 */
					if (sip->si_flags & IDNRUNNING) {
						is_running = 1;
						sip->si_flags &= ~IDNRUNNING;
					}
				}
				rw_exit(&idn.struprwlock);
			}

			/*
			 * If we have any senders in-progress
			 * it's possible they're stuck waiting
			 * down in smr_buf_alloc which may never
			 * arrive if we're in an unlink process.
			 * Rather than wait for it to timeout
			 * let's be proactive so we can disconnect
			 * asap.
			 */
			closed_slabwaiters = csp->ch_reg_domset;
			DOMAINSET_ADD(closed_slabwaiters, idn.localid);
			if (closed_slabwaiters)
				smr_slabwaiter_close(closed_slabwaiters);

			do {
				/*
				 * It's possible due to a STREAMs
				 * loopback from read queue to write queue
				 * that receiver and sender may be same
				 * thread, i.e. receiver's inprogress
				 * flag will never clear until sender's
				 * inprogress flag clears.  So, we wait
				 * for sender's inprogress first.
				 */
				while (csend->c_inprogress) {
					mutex_exit(&crecv->c_mutex);
					while (csend->c_inprogress) {
						csend->c_waiters++;
						cv_wait(&csend->c_cv,
						    &csend->c_mutex);
						csend->c_waiters--;
					}
					/*
					 * Maintain lock ordering.
					 * Eventually we will catch
					 * him due to the flag settings.
					 */
					mutex_exit(&csend->c_mutex);
					mutex_enter(&crecv->c_mutex);
					mutex_enter(&csend->c_mutex);
				}
				if (crecv->c_inprogress) {
					mutex_exit(&csend->c_mutex);
					while (crecv->c_inprogress) {
						crecv->c_waiters++;
						cv_wait(&crecv->c_cv,
						    &crecv->c_mutex);
						crecv->c_waiters--;
					}
					mutex_enter(&csend->c_mutex);
				}
			} while (csend->c_inprogress);
		}

		if (is_running) {
			/*
			 * Restore the IDNRUNNING bit in
			 * the flags to let them know the
			 * channel is still alive.
			 */
			rw_enter(&idn.struprwlock, RW_READER);
			if ((sip = IDN_INST2SIP(channel)) != NULL)
				sip->si_flags |= IDNRUNNING;
			rw_exit(&idn.struprwlock);
		}

		if (closed_slabwaiters) {
			/*
			 * We can reopen now since at this point no new
			 * slabwaiters will attempt to come in and wait.
			 */
			smr_slabwaiter_open(csp->ch_reg_domset);
		}

		crecv->c_checkin = 0;
		csend->c_checkin = 0;

		/*
		 * ALL leave with locks held.
		 */
		PR_CHAN("%s: action (%s) for channel %d - COMPLETED\n",
		    proc, chanaction_str[(int)chanaction], channel);
		break;

	case IDNCHAN_ACTION_ATTACH:
		mutex_enter(&crecv->c_mutex);
		mutex_enter(&csend->c_mutex);
		set_state |= csp->ch_state & IDN_CHANSVC_STATE_ATTACHED;
		/*FALLTHROUGH*/

	case IDNCHAN_ACTION_RESTART:
		set_state |= csp->ch_state & IDN_CHANSVC_STATE_ENABLED;
		/*FALLTHROUGH*/

	case IDNCHAN_ACTION_RESUME:
		ASSERT(IDN_CHAN_LOCAL_IS_LOCKED(csp));
		set_state |= csp->ch_state & IDN_CHANSVC_STATE_ACTIVE;

		crecv->c_state |= set_state;
		csend->c_state |= set_state;

		/*
		 * The channel server itself could come through this
		 * flow, so obviously no point in attempting to wake
		 * ourself up!.
		 */
		if (csp->ch_recv_threadp && (csp->ch_recv_threadp != curthread))
			cv_signal(&csp->ch_recv_cv);

		PR_CHAN("%s: action (%s) for channel %d - COMPLETED\n",
		    proc, chanaction_str[(int)chanaction], channel);

		/*
		 * Leaves with lock released.
		 */
		mutex_exit(&csend->c_mutex);
		mutex_exit(&crecv->c_mutex);
		break;

	default:
		ASSERT(0);
		break;
	}
}

static void
idn_chan_addmbox(int channel, ushort_t domset)
{
	idn_chansvr_t	*csp;
	register int	d;
	procname_t	proc = "idn_chan_addmbox";

	PR_CHAN("%s: adding domset 0x%x main mailboxes to channel %d\n",
	    proc, domset, channel);

	ASSERT(idn.chan_servers);

	csp = &idn.chan_servers[channel];

	/*
	 * Adding domains to a channel can be
	 * asynchonous, so we don't bother waiting.
	 */
	IDN_CHANNEL_SUSPEND(channel, 0);

	/*
	 * Now we have the sending and receiving sides blocked
	 * for this channel.
	 */
	for (d = 0; d < MAX_DOMAINS; d++) {
		if (!DOMAIN_IN_SET(domset, d))
			continue;
		if (IDN_CHAN_DOMAIN_IS_REGISTERED(csp, d)) {
			DOMAINSET_DEL(domset, d);
			continue;
		}
		IDN_CHANSVR_SCANSET_ADD_PENDING(csp, d);
		DOMAINSET_ADD(csp->ch_recv_domset_pending, d);
		IDN_CHAN_DOMAIN_REGISTER(csp, d);

		PR_CHAN("%s: domain %d (channel %d) RECV (pending) "
		    "scanset = 0x%lx\n", proc, d, channel,
		    csp->ch_recv_scanset_pending);
		PR_CHAN("%s: domain %d (channel %d) domset = 0x%x\n",
		    proc, d, channel, (uint_t)csp->ch_reg_domset);

		CHECKPOINT_OPENED(IDNSB_CHKPT_CHAN,
		    idn_domain[d].dhw.dh_boardset, 1);
	}
	if (domset)
		csp->ch_recv_changed = 1;

	IDN_CHANNEL_RESUME(channel);
}

static void
idn_chan_delmbox(int channel, ushort_t domset)
{
	idn_chansvr_t	*csp;
	register int	d;
	procname_t	proc = "idn_chan_delmbox";

	PR_CHAN("%s: deleting domset 0x%x main mailboxes from channel %d\n",
	    proc, domset, channel);

	ASSERT(idn.chan_servers);

	csp = &idn.chan_servers[channel];

	/*
	 * Here we have to wait for the channel server
	 * as it's vital that we don't return without guaranteeing
	 * that the given domset is no longer registered.
	 */
	IDN_CHANNEL_SUSPEND(channel, 1);

	/*
	 * Now we have the sending and receiving sides blocked
	 * for this channel.
	 */
	for (d = 0; d < MAX_DOMAINS; d++) {
		if (!DOMAIN_IN_SET(domset, d))
			continue;
		if (!IDN_CHAN_DOMAIN_IS_REGISTERED(csp, d)) {
			DOMAINSET_DEL(domset, d);
			continue;
		}
		/*
		 * This domain has a mailbox hanging on this channel.
		 * Get him out.
		 *
		 * First remove him from the receive side.
		 */
		ASSERT(csp->ch_recv_domcount > 0);
		IDN_CHANSVR_SCANSET_DEL_PENDING(csp, d);
		DOMAINSET_DEL(csp->ch_recv_domset_pending, d);
		IDN_CHAN_DOMAIN_UNREGISTER(csp, d);

		PR_CHAN("%s: domain %d (channel %d) RECV (pending) "
		    "scanset = 0x%lx\n", proc, d, channel,
		    csp->ch_recv_scanset_pending);
		PR_CHAN("%s: domain %d (channel %d) domset = 0x%x\n",
		    proc, d, channel, (uint_t)csp->ch_reg_domset);

		CHECKPOINT_CLOSED(IDNSB_CHKPT_CHAN,
		    idn_domain[d].dhw.dh_boardset, 2);

	}
	if (domset)
		csp->ch_recv_changed = 1;

	IDN_CHANNEL_RESUME(channel);
}

static int
idn_valid_etherheader(struct ether_header *ehp)
{
	uchar_t	*eap;

	eap = &ehp->ether_dhost.ether_addr_octet[0];

	if ((eap[IDNETHER_ZERO] != 0) && (eap[IDNETHER_ZERO] != 0xff))
		return (0);

	if ((eap[IDNETHER_COOKIE1] != IDNETHER_COOKIE1_VAL) &&
	    (eap[IDNETHER_COOKIE1] != 0xff))
		return (0);

	if ((eap[IDNETHER_COOKIE2] != IDNETHER_COOKIE2_VAL) &&
	    (eap[IDNETHER_COOKIE2] != 0xff))
		return (0);

	if ((eap[IDNETHER_RESERVED] != IDNETHER_RESERVED_VAL) &&
	    (eap[IDNETHER_RESERVED] != 0xff))
		return (0);

	if (!VALID_UCHANNEL(eap[IDNETHER_CHANNEL]) &&
	    (eap[IDNETHER_CHANNEL] != 0xff))
		return (0);

	if (!VALID_UDOMAINID(IDN_NETID2DOMID(eap[IDNETHER_NETID])) &&
	    (eap[IDNETHER_NETID] != 0xff))
		return (0);

	return (1);
}

/*
 * Packet header has already been filled in.
 * RETURNS:	0
 *		ENOLINK
 *		EPROTO
 *		ENOSPC
 */
/*ARGSUSED*/
static int
idn_send_mboxdata(int domid, struct idn *sip, int channel, caddr_t bufp)
{
	idn_mainmbox_t	*mmp;
	idn_mboxmsg_t	*mqp;
	smr_pkthdr_t	*hdrp;
	smr_offset_t	bufoffset;
	idn_netaddr_t	dst;
	ushort_t		mbox_csum;
	int		rv = 0;
	int		pktlen, qi;
	procname_t	proc = "idn_send_mboxdata";

	mmp = idn_domain[domid].dmbox.m_send;
	if (mmp == NULL) {
		PR_DATA("%s: dmbox.m_send == NULL\n", proc);
		IDN_KSTAT_INC(sip, si_linkdown);
		return (ENOLINK);
	}

	mmp += channel;
	mutex_enter(&mmp->mm_mutex);

	if (mmp->mm_smr_mboxp == NULL) {
		PR_DATA("%s: (d %d, chn %d) mm_smr_mboxp == NULL\n",
		    proc, domid, channel);
		IDN_KSTAT_INC(sip, si_linkdown);
		rv = ENOLINK;
		goto send_err;
	}
	mbox_csum = IDN_CKSUM_MBOX(&mmp->mm_smr_mboxp->mt_header);
	if (mbox_csum != mmp->mm_smr_mboxp->mt_header.mh_cksum) {
		PR_DATA("%s: (d %d, chn %d) mbox hdr cksum (%d) "
		    "!= actual (%d)\n",
		    proc, domid, channel, mbox_csum,
		    mmp->mm_smr_mboxp->mt_header.mh_cksum);
		if ((mmp->mm_flags & IDNMMBOX_FLAG_CORRUPTED) == 0) {
			cmn_err(CE_WARN,
			    "IDN: 241: [send] (domain %d, "
			    "channel %d) SMR CORRUPTED - RELINK",
			    domid, channel);
			mmp->mm_flags |= IDNMMBOX_FLAG_CORRUPTED;
		}
		IDN_KSTAT_INC(sip, si_mboxcrc);
		IDN_KSTAT_INC(sip, si_oerrors);
		rv = EPROTO;
		goto send_err;
	}

	bufoffset = IDN_ADDR2OFFSET(bufp);
	hdrp	  = IDN_BUF2HDR(bufp);
	pktlen    = hdrp->b_length;
	dst.netaddr = hdrp->b_netaddr;
	ASSERT(dst.net.chan == (ushort_t)channel);

	mqp = &mmp->mm_smr_mboxp->mt_queue[0];
	qi  = mmp->mm_qiput;

	if (mqp[qi].ms_owner) {
		PR_DATA("%s: mailbox FULL (qiput=%d, qiget=%d)\n",
		    proc, mmp->mm_qiput, mmp->mm_qiget);
		IDN_KSTAT_INC(sip, si_txfull);
		rv = ENOSPC;
		goto send_err;
	}
	if (mqp[qi].ms_flag & IDN_MBOXMSG_FLAG_RECLAIM) {
		smr_offset_t	recl_bufoffset;
		/*
		 * Remote domain finished with mailbox entry,
		 * however it has not been reclaimed yet.  A reclaim
		 * was done before coming into this routine, however
		 * timing may have been such that the entry became
		 * free just after the reclamation, but before
		 * entry into here.  Go ahead and reclaim this entry.
		 */
		recl_bufoffset = IDN_BFRAME2OFFSET(mqp[qi].ms_bframe);

		PR_DATA("%s: attempting reclaim (domain %d) "
		    "(qiput=%d, b_off=0x%x)\n",
		    proc, domid, qi, recl_bufoffset);

		if (VALID_NWROFFSET(recl_bufoffset, IDN_SMR_BUFSIZE)) {
			int		recl;
			caddr_t		b_bufp;
			smr_pkthdr_t	*b_hdrp;

			b_bufp = IDN_OFFSET2ADDR(recl_bufoffset);
			b_hdrp = IDN_BUF2HDR(b_bufp);

			if (IDN_CKSUM_PKT(b_hdrp) != b_hdrp->b_cksum) {
				IDN_KSTAT_INC(sip, si_crc);
				IDN_KSTAT_INC(sip, si_fcs_errors);
				IDN_KSTAT_INC(sip, si_reclaim);
				IDN_KSTAT_INC(sip, si_oerrors);
			}

			recl = smr_buf_free(domid, b_bufp, b_hdrp->b_length);
#ifdef DEBUG
			if (recl == 0) {
				PR_DATA("%s: SUCCESSFULLY reclaimed buf "
				    "(domain %d)\n", proc, domid);
			} else {
				PR_DATA("%s: WARNING: reclaim failed (FREE) "
				    "(domain %d)\n", proc, domid);
			}
#endif /* DEBUG */
		} else {
			IDN_KSTAT_INC(sip, si_smraddr);
			IDN_KSTAT_INC(sip, si_reclaim);
			PR_DATA("%s: WARNING: reclaim failed (BAD OFFSET) "
			    "(domain %d)\n", proc, domid);
		}
	}

	if (*mmp->mm_smr_readyp == 0) {
		mmp->mm_qiput = qi;
		IDN_KSTAT_INC(sip, si_linkdown);
		rv = ENOLINK;
		goto send_err;
	}

	mqp[qi].ms_flag = IDN_MBOXMSG_FLAG_RECLAIM;
	mqp[qi].ms_bframe = IDN_OFFSET2BFRAME(bufoffset);
	/* membar_stst(); */
	mqp[qi].ms_owner = 1;

	IDN_MMBOXINDEX_INC(qi);

	mmp->mm_qiput = qi;

	mmp->mm_count++;

	if ((*mmp->mm_smr_readyp) && !(*mmp->mm_smr_activep)) {
		idn_msgtype_t	mt;

		mt.mt_mtype = IDNP_DATA;
		mt.mt_atype = 0;
		IDN_KSTAT_INC(sip, si_xdcall);
		(void) IDNXDC(domid, &mt, (uint_t)dst.net.chan, 0, 0, 0);
	}
	mutex_exit(&mmp->mm_mutex);
	IDN_KSTAT_INC(sip, si_opackets);
	IDN_KSTAT_INC(sip, si_opackets64);
	IDN_KSTAT_ADD(sip, si_xmtbytes, pktlen);
	IDN_KSTAT_ADD(sip, si_obytes64, (uint64_t)pktlen);

	return (0);

send_err:
	mmp->mm_dropped++;

	mutex_exit(&mmp->mm_mutex);

	return (rv);
}

static int
idn_recv_mboxdata(int channel, caddr_t bufp)
{
	smr_pkthdr_t	*hdrp;
	struct idn	*sip;
	mblk_t		*mp = nilp(mblk_t);
	int		pktlen;
	int		apktlen;
	int		rv = 0;
	smr_offset_t	bufoffset;
	ushort_t	csum;
	idn_netaddr_t	dst, daddr;
	procname_t	proc = "idn_recv_mboxdata";

	hdrp = IDN_BUF2HDR(bufp);

	csum = IDN_CKSUM_PKT(hdrp);

	sip = IDN_INST2SIP(channel);
	if (sip == NULL) {
		/*LINTED*/
		sip = IDN_INST2SIP(0);
	}
	ASSERT(sip);

	if (csum != hdrp->b_cksum) {
		PR_DATA("%s: bad checksum(%x) != expected(%x)\n",
		    proc, (uint_t)csum, (uint_t)hdrp->b_cksum);
		IDN_KSTAT_INC(sip, si_crc);
		IDN_KSTAT_INC(sip, si_fcs_errors);
		rv = -1;
		goto recv_err;
	}

	daddr.net.chan = (ushort_t)channel;
	daddr.net.netid = (ushort_t)idn.localid;

	dst.netaddr = hdrp->b_netaddr;
	bufoffset = hdrp->b_offset;

	if (dst.netaddr != daddr.netaddr) {
		PR_DATA("%s: wrong dest netaddr (0x%x), expected (0x%x)\n",
		    proc, dst.netaddr, daddr.netaddr);
		IDN_KSTAT_INC(sip, si_nolink);
		IDN_KSTAT_INC(sip, si_macrcv_errors);
		goto recv_err;
	}
	pktlen  = hdrp->b_length;
	apktlen = pktlen;

	if ((pktlen <= 0) || (pktlen > IDN_DATA_SIZE)) {
		PR_DATA("%s: invalid packet length (%d) <= 0 || > %lu\n",
		    proc, pktlen, IDN_DATA_SIZE);
		IDN_KSTAT_INC(sip, si_buff);
		IDN_KSTAT_INC(sip, si_toolong_errors);
		goto recv_err;
	}

	mp = allocb(apktlen + IDN_ALIGNSIZE, BPRI_LO);
	if (mp == nilp(mblk_t)) {
		PR_DATA("%s: allocb(pkt) failed\n", proc);
		IDN_KSTAT_INC(sip, si_allocbfail);
		IDN_KSTAT_INC(sip, si_norcvbuf);	/* MIB II */
		goto recv_err;
	}
	ASSERT(DB_TYPE(mp) == M_DATA);
	/*
	 * Copy data packet into its streams buffer.
	 * Align pointers for maximum bcopy performance.
	 */
	mp->b_rptr = (uchar_t *)IDN_ALIGNPTR(mp->b_rptr, bufoffset);
	bcopy(IDN_BUF2DATA(bufp, bufoffset), mp->b_rptr, apktlen);
	mp->b_wptr = mp->b_rptr + pktlen;

	if (IDN_CHECKSUM &&
		!idn_valid_etherheader((struct ether_header *)mp->b_rptr)) {
		freeb(mp);
		mp = nilp(mblk_t);
		PR_DATA("%s: etherheader CORRUPTED\n", proc);
		IDN_KSTAT_INC(sip, si_crc);
		IDN_KSTAT_INC(sip, si_fcs_errors);
		rv = -1;
		goto recv_err;
	}

	idndl_read(NULL, mp);

recv_err:

	if (mp == nilp(mblk_t)) {
		IDN_KSTAT_INC(sip, si_ierrors);
	}

	return (rv);
}

/*
 * When on shutdown path (idn_active_resources) must call
 * idn_mainmbox_flush() _BEFORE_ calling idn_reclaim_mboxdata()
 * for any final data.  This is necessary incase the mailboxes
 * have been unregistered.  If they have then idn_mainmbox_flush()
 * will set mm_smr_mboxp to NULL which prevents us from touching
 * poison SMR space.
 */
int
idn_reclaim_mboxdata(int domid, int channel, int nbufs)
{
	idn_mainmbox_t	*mmp;
	idn_mboxmsg_t	*mqp;
	smr_pkthdr_t	*hdrp;
	idn_domain_t	*dp;
	int		qi;
	int		mi;
	int		reclaim_cnt = 0;
	int		free_cnt;
	ushort_t	csum;
	struct idn	*sip;
	smr_offset_t	reclaim_list, curr, prev;
	procname_t	proc = "idn_reclaim_mboxdata";


	sip = IDN_INST2SIP(channel);
	if (sip == NULL) {
		/*LINTED*/
		sip = IDN_INST2SIP(0);
	}
	ASSERT(sip);

	dp = &idn_domain[domid];

	PR_DATA("%s: requested %d buffers from domain %d\n",
	    proc, nbufs, domid);

	if (lock_try(&dp->dreclaim_inprogress) == 0) {
		/*
		 * Reclaim is already in progress, don't
		 * bother.
		 */
		PR_DATA("%s: reclaim already in progress\n", proc);
		return (0);
	}

	if (dp->dmbox.m_send == NULL)
		return (0);

	reclaim_list = curr = prev = IDN_NIL_SMROFFSET;

	mi = (int)dp->dreclaim_index;
	do {
		ushort_t	mbox_csum;

		mmp = &dp->dmbox.m_send[mi];
		/* do-while continues down */
		ASSERT(mmp);
		if (mutex_tryenter(&mmp->mm_mutex) == 0) {
			/*
			 * This channel is busy, move on.
			 */
			IDN_MBOXCHAN_INC(mi);
			continue;
		}

		if (mmp->mm_smr_mboxp == NULL) {
			PR_DATA("%s: no smr pointer for domid %d, chan %d\n",
			    proc, domid, (int)mmp->mm_channel);
			ASSERT(mmp->mm_qiget == mmp->mm_qiput);
			mutex_exit(&mmp->mm_mutex);
			IDN_MBOXCHAN_INC(mi);
			continue;
		}
		mbox_csum = IDN_CKSUM_MBOX(&mmp->mm_smr_mboxp->mt_header);
		if (mbox_csum != mmp->mm_smr_mboxp->mt_header.mh_cksum) {
			PR_DATA("%s: (d %d, chn %d) mbox hdr "
			    "cksum (%d) != actual (%d)\n",
			    proc, domid, (int)mmp->mm_channel, mbox_csum,
			    mmp->mm_smr_mboxp->mt_header.mh_cksum);
			IDN_KSTAT_INC(sip, si_mboxcrc);
			IDN_KSTAT_INC(sip, si_oerrors);
			mutex_exit(&mmp->mm_mutex);
			IDN_MBOXCHAN_INC(mi);
			continue;
		}
		mqp = &mmp->mm_smr_mboxp->mt_queue[0];
		qi  = mmp->mm_qiget;

		while (!mqp[qi].ms_owner &&
		    (mqp[qi].ms_flag & IDN_MBOXMSG_FLAG_RECLAIM) &&
		    nbufs) {
			idn_mboxmsg_t	*msp;
			int		badbuf;

			badbuf = 0;
			msp = &mqp[qi];

			if (msp->ms_flag & IDN_MBOXMSG_FLAG_ERRMASK) {
				PR_DATA("%s: msg.flag ERROR(0x%x) (off=0x%x, "
				    "domid=%d, qiget=%d)\n", proc,
				    (uint_t)(msp->ms_flag &
				    IDN_MBOXMSG_FLAG_ERRMASK),
				    IDN_BFRAME2OFFSET(msp->ms_bframe),
				    domid, qi);
			}
			prev = curr;
			curr = IDN_BFRAME2OFFSET(mqp[qi].ms_bframe);

			if (!VALID_NWROFFSET(curr, IDN_SMR_BUFSIZE)) {
				badbuf = 1;
				IDN_KSTAT_INC(sip, si_reclaim);
			} else {
				/*
				 * Put the buffers onto a list that will be
				 * formally reclaimed down below.  This allows
				 * us to free up mboxq entries as fast as
				 * possible.
				 */
				hdrp = IDN_BUF2HDR(IDN_OFFSET2ADDR(curr));
				csum = IDN_CKSUM_PKT(hdrp);

				if (csum != hdrp->b_cksum) {
					badbuf = 1;
					IDN_KSTAT_INC(sip, si_crc);
					IDN_KSTAT_INC(sip, si_fcs_errors);
					IDN_KSTAT_INC(sip, si_reclaim);
					if (!(mmp->mm_flags &
					    IDNMMBOX_FLAG_CORRUPTED)) {
						cmn_err(CE_WARN,
						    "IDN: 241: [send] "
						    "(domain %d, channel "
						    "%d) SMR CORRUPTED - "
						    "RELINK",
						    domid, channel);
						mmp->mm_flags |=
						    IDNMMBOX_FLAG_CORRUPTED;
					}

				} else if (reclaim_list == IDN_NIL_SMROFFSET) {
					reclaim_list = curr;
				} else {
					caddr_t	bufp;

					bufp = IDN_OFFSET2ADDR(prev);
					hdrp = IDN_BUF2HDR(bufp);
					hdrp->b_next = curr;
				}
			}

			mqp[qi].ms_flag = 0;

			IDN_MMBOXINDEX_INC(qi);

			if (!badbuf) {
				nbufs--;
				reclaim_cnt++;
			}

			if (qi == mmp->mm_qiget)
				break;
		}
		mmp->mm_qiget = qi;

		mutex_exit(&mmp->mm_mutex);

		IDN_MBOXCHAN_INC(mi);

	} while ((mi != (int)dp->dreclaim_index) && nbufs);

	dp->dreclaim_index = (uchar_t)mi;

	if (reclaim_list != IDN_NIL_SMROFFSET) {
		hdrp = IDN_BUF2HDR(IDN_OFFSET2ADDR(curr));
		hdrp->b_next = IDN_NIL_SMROFFSET;
	}

	PR_DATA("%s: reclaimed %d buffers from domain %d\n",
	    proc, reclaim_cnt, domid);

	if (reclaim_cnt == 0) {
		lock_clear(&dp->dreclaim_inprogress);
		return (0);
	}

	/*
	 * Now actually go and reclaim (free) the buffers.
	 */
	free_cnt = 0;

	for (curr = reclaim_list; curr != IDN_NIL_SMROFFSET; ) {
		caddr_t		bufp;

		bufp = IDN_OFFSET2ADDR(curr);
		hdrp = IDN_BUF2HDR(bufp);
		csum = IDN_CKSUM_PKT(hdrp);
		if (csum != hdrp->b_cksum) {
			/*
			 * Once corruption is detected we
			 * can't trust our list any further.
			 * These buffers are effectively lost.
			 */
			cmn_err(CE_WARN,
			    "IDN: 241: [send] (domain %d, channel %d) SMR "
			    "CORRUPTED - RELINK", domid, channel);
			break;
		}

		curr = hdrp->b_next;

		if (!smr_buf_free(domid, bufp, hdrp->b_length))
			free_cnt++;
	}

	if ((dp->dio < IDN_WINDOW_EMAX) && dp->diocheck) {
		lock_clear(&dp->diocheck);
		IDN_MSGTIMER_STOP(domid, IDNP_DATA, 0);
	}

#ifdef DEBUG
	if (free_cnt != reclaim_cnt) {
		PR_DATA("%s: *** WARNING *** freecnt(%d) != reclaim_cnt (%d)\n",
		    proc, free_cnt, reclaim_cnt);
	}
#endif /* DEBUG */

	lock_clear(&dp->dreclaim_inprogress);

	return (reclaim_cnt);
}

void
idn_signal_data_server(int domid, ushort_t channel)
{
	idn_nack_t	nacktype = 0;
	idn_domain_t	*dp;
	idn_chansvr_t	*csp;
	int		c, min_chan, max_chan;
	idn_mainmbox_t	*mmp;
	procname_t	proc = "idn_signal_data_server";


	if (domid == IDN_NIL_DOMID)
		return;

	dp = &idn_domain[domid];

	if (dp->dawol.a_count > 0) {
		/*
		 * Domain was previously AWOL, but no longer.
		 */
		IDN_SYNC_LOCK();
		IDN_GLOCK_EXCL();
		idn_clear_awol(domid);
		IDN_GUNLOCK();
		IDN_SYNC_UNLOCK();
	}
	/*
	 * Do a precheck before wasting time trying to acquire the lock.
	 */
	if ((dp->dstate != IDNDS_CONNECTED) || !IDN_DLOCK_TRY_SHARED(domid)) {
		/*
		 * Either we're not connected or somebody is busy working
		 * on the domain.  Bail on the signal for now, we'll catch
		 * it on the next go around.
		 */
		return;
	}
	/*
	 * We didn't have the drwlock on the first check of dstate,
	 * but now that we do, make sure the world hasn't changed!
	 */
	if (dp->dstate != IDNDS_CONNECTED) {
		/*
		 * If we reach here, then no connection.
		 * Send no response if this is the case.
		 */
		nacktype = IDNNACK_NOCONN;
		goto send_dresp;
	}

	/*
	 * No need to worry about locking mainmbox
	 * because we're already holding reader
	 * lock on domain, plus we're just reading
	 * fields in the mainmbox which only change
	 * (or go away) when the writer lock is
	 * held on the domain.
	 */
	if ((mmp = dp->dmbox.m_recv) == NULL) {
		/*
		 * No local mailbox.
		 */
		nacktype = IDNNACK_BADCFG;
		goto send_dresp;
	}
	if ((channel != IDN_BROADCAST_ALLCHAN) && (channel >= IDN_MAX_NETS)) {
		nacktype = IDNNACK_BADCHAN;
		goto send_dresp;
	}
	if (channel == IDN_BROADCAST_ALLCHAN) {
		PR_DATA("%s: requested signal to ALL channels on domain %d\n",
		    proc, domid);
		min_chan = 0;
		max_chan = IDN_MAX_NETS - 1;
	} else {
		PR_DATA("%s: requested signal to channel %d on domain %d\n",
		    proc, channel, domid);
		min_chan = max_chan = (int)channel;
	}
	mmp += min_chan;
	for (c = min_chan; c <= max_chan; mmp++, c++) {

		/*
		 * We do a quick check for a pending channel.
		 * If pending it will need activation and we rather
		 * do that through a separate (proto) thread.
		 */
		csp = &idn.chan_servers[c];

		if (csp->ch_recv.c_checkin) {
			PR_DATA("%s: chansvr (%d) for domid %d CHECK-IN\n",
			    proc, c, domid);
			continue;
		}

		if (IDN_CHAN_TRYLOCK_RECV(csp) == 0) {
			/*
			 * Failed to grab lock, server must be active.
			 */
			PR_DATA("%s: chansvr (%d) for domid %d already actv\n",
			    proc, c, domid);
			continue;
		}

		if (IDN_CHANNEL_IS_PENDING(csp)) {
			/*
			 * Lock is pending.  Submit asynchronous
			 * job to activate and move-on.
			 */
			IDN_CHAN_UNLOCK_RECV(csp);
			idn_submit_chanactivate_job(c);
			continue;
		}

		/*
		 * If he ain't active, we ain't talkin'.
		 */
		if (IDN_CHANNEL_IS_RECV_ACTIVE(csp) == 0) {
			IDN_CHAN_UNLOCK_RECV(csp);
			PR_DATA("%s: chansvr (%d) for domid %d inactive\n",
			    proc, c, domid);
			continue;
		}

		if (mutex_tryenter(&mmp->mm_mutex) == 0) {
			IDN_CHAN_UNLOCK_RECV(csp);
			continue;
		}

		if (mmp->mm_csp != csp) {
			/*
			 * Not registered.
			 */
			mutex_exit(&mmp->mm_mutex);
			IDN_CHAN_UNLOCK_RECV(csp);
			continue;

		}
		if (mmp->mm_smr_mboxp == NULL) {
			/*
			 * No SMR mailbox.
			 */
			mutex_exit(&mmp->mm_mutex);
			IDN_CHAN_UNLOCK_RECV(csp);
			continue;
		}
		mutex_exit(&mmp->mm_mutex);

		if (csp->ch_recv.c_inprogress) {
			/*
			 * Data server is already active.
			 */
			IDN_CHAN_UNLOCK_RECV(csp);
			PR_DATA("%s: chansvr (%d) for domid %d already actv\n",
			    proc, c, domid);
			continue;
		}
		ASSERT(csp == &idn.chan_servers[c]);


		PR_DATA("%s: signaling data dispatcher for chan %d dom %d\n",
		    proc, c, domid);
		ASSERT(csp);
		cv_signal(&csp->ch_recv_cv);
		IDN_CHAN_UNLOCK_RECV(csp);
	}

	if (!nacktype || (channel == IDN_BROADCAST_ALLCHAN)) {
		/*
		 * If there were no real errors or we were
		 * handling multiple channels, then just
		 * return.
		 */
		IDN_DUNLOCK(domid);
		return;
	}

send_dresp:

	PR_DATA("%s: sending NACK (%s) back to domain %d (cpu %d)\n",
	    proc, idnnack_str[nacktype], domid, idn_domain[domid].dcpu);

	idn_send_dataresp(domid, nacktype);

	IDN_DUNLOCK(domid);
}

/*ARGSUSED*/
static int
idn_recv_data(int domid, idn_msgtype_t *mtp, idn_xdcargs_t xargs)
{
#ifdef DEBUG
	uint_t		msg = mtp ? mtp->mt_mtype : 0;
	uint_t		msgarg = mtp ? mtp->mt_atype : 0;
	procname_t	proc = "idn_recv_data";

	PR_PROTO("%s:%d: DATA message received (msg = 0x%x, msgarg = 0x%x)\n",
	    proc, domid, msg, msgarg);
	PR_PROTO("%s:%d: xargs = (0x%x, 0x%x, 0x%x, 0x%x)\n",
	    proc, domid, xargs[0], xargs[1], xargs[2], xargs[3]);
#endif /* DEBUG */

	return (0);
}

/*
 * Only used when sending a negative response.
 */
static void
idn_send_dataresp(int domid, idn_nack_t nacktype)
{
	idn_msgtype_t	mt;

	ASSERT(IDN_DLOCK_IS_HELD(domid));

	if (idn_domain[domid].dcpu == IDN_NIL_DCPU)
		return;

	mt.mt_mtype = IDNP_NACK;
	mt.mt_atype = IDNP_DATA;

	(void) IDNXDC(domid, &mt, (uint_t)nacktype, 0, 0, 0);
}

/*
 * Checksum routine used in checksum smr_pkthdr_t and idn_mboxhdr_t.
 */
static ushort_t
idn_cksum(register ushort_t *hdrp, register int count)
{
	register int		i;
	register ushort_t	sum = 0;

	for (i = 0; i < count; i++)
		sum += hdrp[i];

	sum = (sum >> 16) + (sum & 0xffff);
	sum += (sum >> 16);

	return (~sum);
}

/*
 * ------------------------------------------------
 */

int
idn_open_channel(int channel)
{
	int		masterid;
	idn_chansvr_t	*csp;
	struct idn	*sip;
	procname_t	proc = "idn_open_channel";

	if (channel >= IDN_MAX_NETS) {
		cmn_err(CE_WARN,
		    "IDN: 242: maximum channels (%d) already open",
		    IDN_MAX_NETS);
		return (-1);
	}
	IDN_GLOCK_EXCL();

	ASSERT(idn.chan_servers != NULL);

	csp = &idn.chan_servers[channel];

	IDN_CHAN_LOCK_GLOBAL(csp);

	if (IDN_CHANNEL_IS_ATTACHED(csp)) {
		PR_CHAN("%s: channel %d already open\n", proc, channel);
		IDN_CHAN_UNLOCK_GLOBAL(csp);
		IDN_GUNLOCK();
		return (0);
	}

	/*
	 * Need to zero out the kstats now that we're activating
	 * this channel.
	 */
	for (sip = idn.sip; sip; sip = sip->si_nextp) {
		if (sip->si_dip && (ddi_get_instance(sip->si_dip) == channel)) {
			bzero(&sip->si_kstat, sizeof (sip->si_kstat));
			break;
		}
	}

	IDN_CHANSVC_MARK_ATTACHED(csp);
	idn.nchannels++;
	CHANSET_ADD(idn.chanset, channel);
	IDN_CHANNEL_ATTACH(channel);

	IDN_CHAN_UNLOCK_GLOBAL(csp);

	/*
	 * We increase our window threshold each time a channel
	 * is opened.
	 */
	ASSERT(idn.nchannels > 0);
	IDN_WINDOW_EMAX = IDN_WINDOW_MAX +
	    ((idn.nchannels - 1) * IDN_WINDOW_INCR);

	PR_CHAN("%s: channel %d is OPEN (nchannels = %d)\n",
	    proc, channel, idn.nchannels);

	masterid = IDN_GET_MASTERID();
	IDN_GUNLOCK();

	/*
	 * Check if there is an active master to which
	 * we're connected.  If so, then activate channel.
	 */
	if (masterid != IDN_NIL_DOMID) {
		idn_domain_t	*dp;

		dp = &idn_domain[masterid];
		IDN_DLOCK_SHARED(masterid);
		if (dp->dvote.v.master && (dp->dstate == IDNDS_CONNECTED))
			(void) idn_activate_channel(CHANSET(channel),
			    IDNCHAN_ONLINE);
		IDN_DUNLOCK(masterid);
	}

	return (0);
}

void
idn_close_channel(int channel, idn_chanop_t chanop)
{
	idn_chansvr_t	*csp;
	procname_t	proc = "idn_close_channel";


	ASSERT(idn.chan_servers != NULL);

	csp = &idn.chan_servers[channel];

	IDN_GLOCK_EXCL();

	IDN_CHAN_LOCK_GLOBAL(csp);
	if (IDN_CHANNEL_IS_DETACHED(csp)) {
		PR_CHAN("%s: channel %d already closed\n", proc, channel);
		IDN_CHAN_UNLOCK_GLOBAL(csp);
		IDN_GUNLOCK();
		return;
	}
	IDN_CHAN_UNLOCK_GLOBAL(csp);

	idn_deactivate_channel(CHANSET(channel), chanop);

	IDN_CHAN_LOCK_GLOBAL(csp);

	if (chanop == IDNCHAN_HARD_CLOSE) {
		idn.nchannels--;
		CHANSET_DEL(idn.chanset, channel);
		/*
		 * We increase our window threshold each time a channel
		 * is opened.
		 */
		if (idn.nchannels <= 0)
			IDN_WINDOW_EMAX = 0;
		else
			IDN_WINDOW_EMAX = IDN_WINDOW_MAX +
			    ((idn.nchannels - 1) * IDN_WINDOW_INCR);
	}

	PR_CHAN("%s: channel %d is (%s) CLOSED (nchannels = %d)\n",
	    proc, channel,
	    (chanop == IDNCHAN_SOFT_CLOSE) ? "SOFT"
	    : (chanop == IDNCHAN_HARD_CLOSE) ? "HARD" : "OFFLINE",
	    idn.nchannels);

	IDN_CHAN_UNLOCK_GLOBAL(csp);
	IDN_GUNLOCK();
}

static int
idn_activate_channel(idn_chanset_t chanset, idn_chanop_t chanop)
{
	int		c, rv = 0;
	procname_t	proc = "idn_activate_channel";

	PR_CHAN("%s: chanset = 0x%x, chanop = %s\n",
	    proc, chanset, chanop_str[chanop]);

	if (idn.state != IDNGS_ONLINE) {
		/*
		 * Can't activate any channels unless local
		 * domain is connected and thus has a master.
		 */
		PR_CHAN("%s: local domain not connected.  no data servers\n",
		    proc);
		return (-1);
	}

	for (c = 0; c < IDN_MAX_NETS; c++) {
		idn_chansvr_t	*csp;
		idn_mboxhdr_t	*mainhp;
		struct idn	*sip;

		if (!CHAN_IN_SET(chanset, c))
			continue;
		csp = &idn.chan_servers[c];

		if (chanop == IDNCHAN_ONLINE) {
			IDN_CHAN_LOCK_GLOBAL(csp);
		} else {
			/*
			 * We don't wait to grab the global lock
			 * if IDNCHAN_OPEN since these occur along
			 * critical data paths and will be retried
			 * anyway if needed.
			 */
			if (IDN_CHAN_TRYLOCK_GLOBAL(csp) == 0) {
				PR_CHAN("%s: failed to acquire global "
				    "lock for channel %d\n",
				    proc, c);
				continue;
			}
		}

		if (!IDN_CHANNEL_IS_ATTACHED(csp)) {
			PR_CHAN("%s: channel %d NOT open\n", proc, c);
			IDN_CHAN_UNLOCK_GLOBAL(csp);
			continue;

		}

		if (IDN_CHANNEL_IS_ACTIVE(csp)) {

			PR_CHAN("%s: channel %d already active\n", proc, c);
			rv++;
			IDN_CHAN_UNLOCK_GLOBAL(csp);
			continue;

		}
		/*
		 * Channel activation can happen asynchronously.
		 */
		IDN_CHANNEL_SUSPEND(c, 0);

		if (IDN_CHANNEL_IS_PENDING(csp) && (chanop == IDNCHAN_OPEN)) {

			PR_CHAN("%s: ACTIVATING channel %d\n", proc, c);

			if (idn_activate_channel_services(c) >= 0) {
				PR_CHAN("%s: Setting channel %d ACTIVE\n",
				    proc, c);
				IDN_CHANSVC_MARK_ACTIVE(csp);
				rv++;
			}
		} else if (!IDN_CHANNEL_IS_PENDING(csp) &&
		    (chanop == IDNCHAN_ONLINE)) {
			PR_CHAN("%s: Setting channel %d PENDING\n", proc, c);

			IDN_CHANSVC_MARK_PENDING(csp);
		}
		/*
		 * Don't syncheader (i.e. touch SMR) unless
		 * channel is at least ENABLED.  For a DISABLED
		 * channel, the SMR may be invalid so do NOT
		 * touch it.
		 */
		if (IDN_CHANNEL_IS_ENABLED(csp) &&
		    ((mainhp = idn_chan_server_syncheader(c)) != NULL)) {
			PR_CHAN("%s: marking chansvr (mhp=0x%p) %d READY\n",
			    proc, mainhp, c);
			mainhp->mh_svr_ready = 1;
		}

		IDN_CHANNEL_RESUME(c);
		sip = IDN_INST2SIP(c);
		ASSERT(sip);
		if (sip->si_wantw) {
			mutex_enter(&idn.sipwenlock);
			idndl_wenable(sip);
			mutex_exit(&idn.sipwenlock);
		}
		IDN_CHAN_UNLOCK_GLOBAL(csp);

	}
	/*
	 * Returns "not active", i.e. value of 0 indicates
	 * no channels are activated.
	 */
	return (rv == 0);
}

static void
idn_deactivate_channel(idn_chanset_t chanset, idn_chanop_t chanop)
{
	int		c;
	procname_t	proc = "idn_deactivate_channel";

	PR_CHAN("%s: chanset = 0x%x, chanop = %s\n",
	    proc, chanset, chanop_str[chanop]);

	for (c = 0; c < IDN_MAX_NETS; c++) {
		idn_chansvr_t	*csp;
		idn_mboxhdr_t	*mainhp;

		if (!CHAN_IN_SET(chanset, c))
			continue;

		csp = &idn.chan_servers[c];

		IDN_CHAN_LOCK_GLOBAL(csp);

		if (((chanop == IDNCHAN_SOFT_CLOSE) &&
		    !IDN_CHANNEL_IS_ACTIVE(csp)) ||
		    ((chanop == IDNCHAN_HARD_CLOSE) &&
		    IDN_CHANNEL_IS_DETACHED(csp)) ||
		    ((chanop == IDNCHAN_OFFLINE) &&
		    !IDN_CHANNEL_IS_ENABLED(csp))) {

			ASSERT(!IDN_CHANNEL_IS_RECV_ACTIVE(csp));
			ASSERT(!IDN_CHANNEL_IS_SEND_ACTIVE(csp));

			PR_CHAN("%s: channel %d already deactivated\n",
			    proc, c);
			IDN_CHAN_UNLOCK_GLOBAL(csp);
			continue;
		}

		switch (chanop) {
		case IDNCHAN_OFFLINE:
			IDN_CHANSVC_MARK_IDLE(csp);
			IDN_CHANSVC_MARK_DISABLED(csp);
			IDN_CHANNEL_STOP(c, 1);
			mainhp = idn_chan_server_syncheader(c);
			if (mainhp != NULL)
				mainhp->mh_svr_ready = 0;
			break;

		case IDNCHAN_HARD_CLOSE:
			IDN_CHANSVC_MARK_DETACHED(csp);
			IDN_CHANNEL_DETACH(c, 1);
			mainhp = idn_chan_server_syncheader(c);
			if (mainhp != NULL)
				mainhp->mh_svr_ready = 0;
			break;

		default:
			IDN_CHANSVC_MARK_IDLE(csp);
			IDN_CHANNEL_SUSPEND(c, 1);
			ASSERT(IDN_CHANNEL_IS_ATTACHED(csp));
			break;
		}

		lock_clear(&csp->ch_actvlck);
		lock_clear(&csp->ch_initlck);

		PR_CHAN("%s: DEACTIVATING channel %d (%s)\n", proc, c,
		    chanop_str[chanop]);
		PR_CHAN("%s: removing chanset 0x%x data svrs for "
		    "each domain link\n", proc, chanset);

		(void) idn_deactivate_channel_services(c, chanop);
	}
	/*
	 * Returns with channels unlocked.
	 */
}

/*
 * The priority of the channel server must be less than that
 * of the protocol server since the protocol server tasks
 * are (can be) of more importance.
 *
 * Possible range: 60-99.
 */
static pri_t	idn_chansvr_pri = (7 * MAXCLSYSPRI) / 8;

static int
idn_activate_channel_services(int channel)
{
	idn_chansvr_t	*csp;
	procname_t	proc = "idn_activate_channel_services";


	ASSERT((channel >= 0) && (channel < IDN_MAX_NETS));

	csp = &idn.chan_servers[channel];

	ASSERT(IDN_CHAN_GLOBAL_IS_LOCKED(csp));
	ASSERT(IDN_CHAN_LOCAL_IS_LOCKED(csp));

	if (csp->ch_recv_threadp) {
		/*
		 * There's an existing dispatcher!
		 * Must have been idle'd during an earlier
		 * stint.
		 */
		ASSERT(csp->ch_id == (uchar_t)channel);
		PR_CHAN("%s: existing chansvr FOUND for (c=%d)\n",
		    proc, channel);

		if (IDN_CHANNEL_IS_PENDING(csp) == 0)
			return (-1);

		PR_CHAN("%s: chansvr (c=%d) Rstate = 0x%x, Sstate = 0x%x\n",
		    proc, channel, csp->ch_recv.c_state,
		    csp->ch_send.c_state);

		cv_signal(&csp->ch_recv_cv);

		return (0);
	}

	if (IDN_CHANNEL_IS_PENDING(csp) == 0)
		return (-1);

	csp->ch_id = (uchar_t)channel;

	PR_CHAN("%s: init channel %d server\n", proc, channel);

	csp->ch_recv_morguep = GETSTRUCT(ksema_t, 1);
	sema_init(csp->ch_recv_morguep, 0, NULL, SEMA_DRIVER, NULL);

	csp->ch_recv.c_inprogress = 0;
	csp->ch_recv.c_waiters = 0;
	csp->ch_recv.c_checkin = 0;
	csp->ch_recv_changed = 1;

	csp->ch_recv_domset = csp->ch_reg_domset;

	csp->ch_recv_waittime = IDN_NETSVR_WAIT_MIN;

	csp->ch_recv_threadp = thread_create(NULL, 0,
	    idn_chan_server, &csp, sizeof (csp), &p0, TS_RUN, idn_chansvr_pri);

	csp->ch_send.c_inprogress = 0;
	csp->ch_send.c_waiters = 0;
	csp->ch_send.c_checkin = 0;

	return (0);
}

/*
 * This routine can handle terminating a set of channel
 * servers all at once, however currently only used
 * for serial killing, i.e. one-at-a-time.
 *
 * Entered with RECV locks held on chanset.
 * Acquires SEND locks if needed.
 * Leaves with all RECV and SEND locks dropped.
 */
static int
idn_deactivate_channel_services(int channel, idn_chanop_t chanop)
{
	idn_chansvr_t	*csp;
	int		cs_count;
	int		c;
	idn_chanset_t	chanset;
	ksema_t		*central_morguep = NULL;
	procname_t	proc = "idn_deactivate_channel_services";


	ASSERT(idn.chan_servers);

	PR_CHAN("%s: deactivating channel %d services\n", proc, channel);

	/*
	 * XXX
	 * Old code allowed us to deactivate multiple channel
	 * servers at once.  Keep for now just in case.
	 */
	chanset = CHANSET(channel);

	/*
	 * Point all the data dispatchers to the same morgue
	 * so we can kill them all at once.
	 */
	cs_count = 0;
	for (c = 0; c < IDN_MAX_NETS; c++) {
		if (!CHAN_IN_SET(chanset, c))
			continue;

		csp = &idn.chan_servers[c];
		ASSERT(IDN_CHAN_GLOBAL_IS_LOCKED(csp));
		ASSERT(IDN_CHAN_LOCAL_IS_LOCKED(csp));

		if (csp->ch_recv_threadp == NULL) {
			/*
			 * No channel server home.
			 * But we're still holding the c_mutex.
			 * At mark him idle incase we start him up.
			 */
			PR_CHAN("%s: no channel server found for chan %d\n",
			    proc, c);
			IDN_CHAN_UNLOCK_LOCAL(csp);
			IDN_CHAN_UNLOCK_GLOBAL(csp);
			continue;
		}
		ASSERT(csp->ch_id == (uchar_t)c);

		/*
		 * Okay, now we've blocked the send and receive sides.
		 */

		if ((chanop == IDNCHAN_SOFT_CLOSE) ||
		    (chanop == IDNCHAN_OFFLINE)) {
			/*
			 * We set turned off the ACTIVE flag, but there's
			 * no guarantee he stopped because of it.  He may
			 * have already been sleeping.  We need to be
			 * sure he recognizes the IDLE, so we need to
			 * signal him and give him a chance to see it.
			 */
			cv_signal(&csp->ch_recv_cv);
			IDN_CHAN_UNLOCK_LOCAL(csp);
			IDN_CHAN_UNLOCK_GLOBAL(csp);
			cs_count++;
			continue;
		}

		PR_CHAN("%s: pointing chansvr %d to morgue (0x%p)\n",
		    proc, c, central_morguep ? central_morguep
		    : csp->ch_recv_morguep);

		if (central_morguep == NULL) {
			central_morguep = csp->ch_recv_morguep;
		} else {
			sema_destroy(csp->ch_recv_morguep);
			FREESTRUCT(csp->ch_recv_morguep, ksema_t, 1);

			csp->ch_recv_morguep = central_morguep;
		}
		cv_signal(&csp->ch_recv_cv);
		if (csp->ch_recv.c_waiters > 0)
			cv_broadcast(&csp->ch_recv.c_cv);
		/*
		 * Save any existing binding for next reincarnation.
		 * Note that we're holding the local and global
		 * locks so we're protected against others touchers
		 * of the ch_bound_cpuid fields.
		 */
		csp->ch_bound_cpuid_pending = csp->ch_bound_cpuid;
		csp->ch_bound_cpuid = -1;
		IDN_CHAN_UNLOCK_LOCAL(csp);
		IDN_CHAN_UNLOCK_GLOBAL(csp);
		cs_count++;
	}
	PR_CHAN("%s: signaled %d chansvrs for chanset 0x%x\n",
	    proc, cs_count, chanset);

	if ((chanop == IDNCHAN_SOFT_CLOSE) || (chanop == IDNCHAN_OFFLINE))
		return (cs_count);

	PR_CHAN("%s: waiting for %d (chnset=0x%x) chan svrs to term\n",
	    proc, cs_count, chanset);
	PR_CHAN("%s: morguep = 0x%p\n", proc, central_morguep);

	ASSERT((cs_count > 0) ? (central_morguep != NULL) : 1);
	while (cs_count-- > 0)
		sema_p(central_morguep);

	if (central_morguep) {
		sema_destroy(central_morguep);
		FREESTRUCT(central_morguep, ksema_t, 1);
	}

	return (cs_count);
}

int
idn_chanservers_init()
{
	int		c;
	idn_chansvr_t	*csp;


	if (idn.chan_servers)
		return (0);

	idn.chan_servers = GETSTRUCT(idn_chansvr_t, IDN_MAXMAX_NETS);

	for (c = 0; c < IDN_MAXMAX_NETS; c++) {
		csp = &idn.chan_servers[c];
		mutex_init(&csp->ch_send.c_mutex, NULL, MUTEX_DEFAULT, NULL);
		mutex_init(&csp->ch_recv.c_mutex, NULL, MUTEX_DEFAULT, NULL);
		cv_init(&csp->ch_send.c_cv, NULL, CV_DRIVER, NULL);
		cv_init(&csp->ch_recv.c_cv, NULL, CV_DRIVER, NULL);
		cv_init(&csp->ch_recv_cv, NULL, CV_DRIVER, NULL);
		csp->ch_bound_cpuid = -1;
		csp->ch_bound_cpuid_pending = -1;
	}

	return (c);
}

void
idn_chanservers_deinit()
{
	int		c;
	idn_chansvr_t	*csp;


	if (idn.chan_servers == NULL)
		return;

	for (c = 0; c < IDN_MAXMAX_NETS; c++) {
		csp = &idn.chan_servers[c];

		mutex_destroy(&csp->ch_send.c_mutex);
		mutex_destroy(&csp->ch_recv.c_mutex);
		cv_destroy(&csp->ch_send.c_cv);
		cv_destroy(&csp->ch_recv.c_cv);
		cv_destroy(&csp->ch_recv_cv);
	}

	FREESTRUCT(idn.chan_servers, idn_chansvr_t, IDN_MAXMAX_NETS);
	idn.chan_servers = NULL;
}

static void
idn_exec_chanactivate(void *chn)
{
	int		not_active, channel;
	idn_chansvr_t	*csp;

	channel = (int)(uintptr_t)chn;

	IDN_GLOCK_SHARED();
	if (idn.chan_servers == NULL) {
		IDN_GUNLOCK();
		return;
	}
	csp = &idn.chan_servers[channel];

	if (IDN_CHAN_TRYLOCK_GLOBAL(csp) == 0) {
		/*
		 * If we can't grab the global lock, then
		 * something is up, skip out.
		 */
		IDN_GUNLOCK();
		return;
	}
	IDN_GUNLOCK();

	if (IDN_CHANNEL_IS_PENDING(csp) && lock_try(&csp->ch_actvlck)) {
		IDN_CHAN_UNLOCK_GLOBAL(csp);
		not_active = idn_activate_channel(CHANSET(channel),
		    IDNCHAN_OPEN);
		if (not_active)
			lock_clear(&csp->ch_actvlck);
	} else {
		IDN_CHAN_UNLOCK_GLOBAL(csp);
	}
}

/*
 * Delayed activation of channel.  We don't want to do this within
 * idn_signal_data_server() since that's called within the context
 * of an XDC handler so we submit it as a timeout() call to be short
 * as soon as possible.
 * The ch_initlck & ch_actvlck are used to synchronize activation
 * of the channel so that we don't have multiple idn_activate_channel's
 * attempting to activate the same channel.
 */
static void
idn_submit_chanactivate_job(int channel)
{
	idn_chansvr_t	*csp;

	if (idn.chan_servers == NULL)
		return;
	csp = &idn.chan_servers[channel];

	if (lock_try(&csp->ch_initlck) == 0)
		return;

	(void) timeout(idn_exec_chanactivate, (caddr_t)(uintptr_t)channel, 1);
}

/*ARGSUSED0*/
static void
idn_xmit_monitor(void *unused)
{
	int		c, d;
	idn_chansvr_t	*csp;
	idn_chanset_t	wake_set;
	domainset_t	conset;
	smr_slab_t	*sp;
	procname_t	proc = "idn_xmit_monitor";

	CHANSET_ZERO(wake_set);

	mutex_enter(&idn.xmit_lock);
	if ((idn.xmit_tid == NULL) || !idn.xmit_chanset_wanted) {
		idn.xmit_tid = NULL;
		mutex_exit(&idn.xmit_lock);
		PR_XMON("%s: bailing out\n", proc);
		return;
	}

	/*
	 * No point in transmitting unless state
	 * is ONLINE.
	 */
	if (idn.state != IDNGS_ONLINE)
		goto retry;

	conset = idn.domset.ds_connected;

	/*
	 * Try and reclaim some buffers if possible.
	 */
	for (d = 0; d < MAX_DOMAINS; d++) {
		if (!DOMAIN_IN_SET(conset, d))
			continue;

		if (!IDN_DLOCK_TRY_SHARED(d))
			continue;

		if (idn_domain[d].dcpu != IDN_NIL_DCPU)
			(void) idn_reclaim_mboxdata(d, 0, -1);

		IDN_DUNLOCK(d);
	}

	/*
	 * Now check if we were successful in getting
	 * any buffers.
	 */
	DSLAB_LOCK_SHARED(idn.localid);
	sp = idn_domain[idn.localid].dslab;
	for (; sp; sp = sp->sl_next)
		if (sp->sl_free)
			break;
	DSLAB_UNLOCK(idn.localid);

	/*
	 * If there are no buffers available,
	 * no point in reenabling the queues.
	 */
	if (sp == NULL)
		goto retry;

	CHANSET_ZERO(wake_set);
	for (c = 0; c < IDN_MAX_NETS; c++) {
		int		pending_bits;
		struct idn	*sip;

		if (!CHAN_IN_SET(idn.xmit_chanset_wanted, c))
			continue;

		csp = &idn.chan_servers[c];
		if (!IDN_CHAN_TRYLOCK_GLOBAL(csp))
			continue;

		pending_bits = csp->ch_state & IDN_CHANSVC_PENDING_BITS;

		sip = IDN_INST2SIP(c);

		if (!csp->ch_send.c_checkin &&
		    (pending_bits == IDN_CHANSVC_PENDING_BITS) &&
		    sip && (sip->si_flags & IDNRUNNING)) {

			IDN_CHAN_UNLOCK_GLOBAL(csp);
			CHANSET_ADD(wake_set, c);

			PR_XMON("%s: QENABLE for channel %d\n", proc, c);

			rw_enter(&idn.struprwlock, RW_READER);
			mutex_enter(&idn.sipwenlock);
			idndl_wenable(sip);
			mutex_exit(&idn.sipwenlock);
			rw_exit(&idn.struprwlock);
		} else {
			IDN_CHAN_UNLOCK_GLOBAL(csp);
		}
	}

	/*
	 * Clear the channels we enabled.
	 */
	idn.xmit_chanset_wanted &= ~wake_set;

retry:

	if (idn.xmit_chanset_wanted == 0)
		idn.xmit_tid = NULL;
	else
		idn.xmit_tid = timeout(idn_xmit_monitor, NULL,
		    idn_xmit_monitor_freq);

	mutex_exit(&idn.xmit_lock);
}

void
idn_xmit_monitor_kickoff(int chan_wanted)
{
	procname_t	proc = "idn_xmit_monitor_kickoff";

	mutex_enter(&idn.xmit_lock);

	if (chan_wanted < 0) {
		/*
		 * Wants all channels.
		 */
		idn.xmit_chanset_wanted = CHANSET_ALL;
	} else {
		CHANSET_ADD(idn.xmit_chanset_wanted, chan_wanted);
	}

	if (idn.xmit_tid != (timeout_id_t)NULL) {
		/*
		 * A monitor is already running, so
		 * he will catch the new "wants" when
		 * he comes around.
		 */
		mutex_exit(&idn.xmit_lock);
		return;
	}

	PR_XMON("%s: xmit_mon kicked OFF (chanset = 0x%x)\n",
	    proc, idn.xmit_chanset_wanted);

	idn.xmit_tid = timeout(idn_xmit_monitor, NULL, idn_xmit_monitor_freq);

	mutex_exit(&idn.xmit_lock);
}