OpenSolaris_b135/uts/common/io/neti_stack.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/param.h>
#include <sys/atomic.h>
#include <sys/kmem.h>
#include <sys/rwlock.h>
#include <sys/errno.h>
#include <sys/queue.h>
#include <sys/sunddi.h>
#include <inet/common.h>
#include <inet/led.h>
#include <inet/ip.h>
#include <sys/neti.h>
#include <sys/zone.h>
#include <sys/sdt.h>


typedef boolean_t napplyfn_t(neti_stack_t *, void *);

static void *neti_stack_init(netstackid_t stackid, netstack_t *ns);
static void neti_stack_fini(netstackid_t stackid, void *arg);
static net_instance_int_t *net_instance_int_create(net_instance_t *nin,
    net_instance_int_t *parent);
static void neti_stack_shutdown(netstackid_t stackid, void *arg);
static void net_instance_int_free(net_instance_int_t *nini);

static boolean_t neti_stack_apply_create(neti_stack_t *, void *);
static boolean_t neti_stack_apply_destroy(neti_stack_t *, void *);
static boolean_t neti_stack_apply_shutdown(neti_stack_t *, void *);
static void neti_apply_all_instances(neti_stack_t *, napplyfn_t *);
static void neti_apply_all_stacks(void *, napplyfn_t *);
static boolean_t wait_for_nini_inprogress(neti_stack_t *,
    net_instance_int_t *, uint32_t);

static nini_head_t neti_instance_list;
static neti_stack_head_t neti_stack_list;
static kmutex_t neti_stack_lock;

void
neti_init()
{
	mutex_init(&neti_stack_lock, NULL, MUTEX_DRIVER, NULL);

	LIST_INIT(&neti_instance_list);
	LIST_INIT(&neti_stack_list);
	/*
	 * We want to be informed each time a netstack is created or
	 * destroyed in the kernel.
	 */
	netstack_register(NS_NETI, neti_stack_init, neti_stack_shutdown,
	    neti_stack_fini);
}

void
neti_fini()
{
	ASSERT(LIST_EMPTY(&neti_instance_list));
	ASSERT(LIST_EMPTY(&neti_stack_list));

	netstack_unregister(NS_NETI);

	mutex_destroy(&neti_stack_lock);
}

/*
 * Initialize the neti stack instance.  Because this is called out of the
 * netstack framework, it is not possible for it to be called twice with
 * the same values for (stackid,ns).  The same also applies to the other
 * two functions used with netstack_register: neti_stack_shutdown and
 * neti_stack_fini.
 */
static void *
neti_stack_init(netstackid_t stackid, netstack_t *ns)
{
	net_instance_int_t *dup;
	net_instance_int_t *n;
	neti_stack_t *nts;

	nts = kmem_zalloc(sizeof (*nts), KM_SLEEP);
	LIST_INIT(&nts->nts_instances);
	nts->nts_id = (netid_t)stackid;
	nts->nts_stackid = stackid;
	nts->nts_netstack = ns;
	nts->nts_zoneid = netstackid_to_zoneid(stackid);
	nts->nts_flags = NSF_ZONE_CREATE;
	cv_init(&nts->nts_cv, NULL, CV_DRIVER, NULL);
	mutex_init(&nts->nts_lock, NULL, MUTEX_DRIVER, NULL);

	mutex_enter(&neti_stack_lock);
	LIST_INSERT_HEAD(&neti_stack_list, nts, nts_next);

	LIST_FOREACH(n, &neti_instance_list, nini_next) {
		/*
		 * This function returns with the NSS_CREATE_NEEDED flag
		 * set in "dup", so it is adequately prepared for the
		 * upcoming apply.
		 */
		dup = net_instance_int_create(n->nini_instance, n);

		mutex_enter(&nts->nts_lock);
		LIST_INSERT_HEAD(&nts->nts_instances, dup, nini_next);
		mutex_exit(&nts->nts_lock);
	}

	neti_apply_all_instances(nts, neti_stack_apply_create);

	mutex_enter(&nts->nts_lock);
	nts->nts_flags &= ~NSF_ZONE_CREATE;
	mutex_exit(&nts->nts_lock);

	mutex_exit(&neti_stack_lock);

	return (nts);
}

/*
 * Run the shutdown for all of the hooks.
 */
/*ARGSUSED*/
static void
neti_stack_shutdown(netstackid_t stackid, void *arg)
{
	neti_stack_t *nts = arg;
	net_instance_int_t *n;
	struct net_data *nd;

	ASSERT(nts != NULL);

	mutex_enter(&neti_stack_lock);
	mutex_enter(&nts->nts_lock);
	/*
	 * Walk through all of the protocol stacks and mark them as shutting
	 * down.
	 */
	LIST_FOREACH(nd, &nts->nts_netd_head, netd_list) {
		nd->netd_condemned = 1;
	}

	/*
	 * Now proceed to see which callbacks are waiting to hear about the
	 * impending shutdown...
	 */
	LIST_FOREACH(n, &nts->nts_instances, nini_next) {
		if (n->nini_instance->nin_shutdown == NULL) {
			/*
			 * If there is no shutdown function registered,
			 * fake that we have completed it.
			 */
			n->nini_flags |= NSS_SHUTDOWN_COMPLETED;
			continue;
		}

		/*
		 * We need to ensure that we don't try and shutdown something
		 * that is already in the process of being shutdown or
		 * destroyed. If it is still being created, that's ok, the
		 * shtudown flag is added to the mix of things to do.
		 */
		if ((n->nini_flags & (NSS_DESTROY_ALL|NSS_SHUTDOWN_ALL)) == 0)
			n->nini_flags |= NSS_SHUTDOWN_NEEDED;
	}
	nts->nts_flags |= NSF_ZONE_SHUTDOWN;
	mutex_exit(&nts->nts_lock);

	neti_apply_all_instances(nts, neti_stack_apply_shutdown);

	mutex_enter(&nts->nts_lock);

	nts->nts_netstack = NULL;
	nts->nts_flags &= ~NSF_ZONE_SHUTDOWN;
	mutex_exit(&nts->nts_lock);

	mutex_exit(&neti_stack_lock);
	ASSERT(nts != NULL);
}

/*
 * Free the neti stack instance.
 * This function relies on the netstack framework only calling the _destroy
 * callback once for each stackid.  The netstack framework also provides us
 * with assurance that nobody else will be doing any work (_create, _shutdown)
 * on it, so there is no need to set and use flags to guard against
 * simultaneous execution (ie. no need to set NSF_CLOSING.)
 * What is required, however, is to make sure that we don't corrupt the
 * list of neti_stack_t's for other code that walks it.
 */
/*ARGSUSED*/
static void
neti_stack_fini(netstackid_t stackid, void *arg)
{
	neti_stack_t *nts = arg;
	net_instance_int_t *n;
	struct net_data *nd;

	mutex_enter(&neti_stack_lock);
	mutex_enter(&nts->nts_lock);

	LIST_REMOVE(nts, nts_next);

	/*
	 * Walk through all of the protocol stacks and mark them as being
	 * destroyed.
	 */
	LIST_FOREACH(nd, &nts->nts_netd_head, netd_list) {
		nd->netd_condemned = 2;
	}

	LIST_FOREACH(n, &nts->nts_instances, nini_next) {
		ASSERT((n->nini_flags & NSS_SHUTDOWN_ALL) != 0);
		if ((n->nini_flags & NSS_DESTROY_ALL) == 0)
			n->nini_flags |= NSS_DESTROY_NEEDED;
	}
	mutex_exit(&nts->nts_lock);

	neti_apply_all_instances(nts, neti_stack_apply_destroy);

	while (!LIST_EMPTY(&nts->nts_instances)) {
		n = LIST_FIRST(&nts->nts_instances);
		LIST_REMOVE(n, nini_next);

		net_instance_int_free(n);
	}
	mutex_exit(&neti_stack_lock);

	ASSERT(LIST_EMPTY(&nts->nts_netd_head));

	mutex_destroy(&nts->nts_lock);
	cv_destroy(&nts->nts_cv);

	kmem_free(nts, sizeof (*nts));
}

static net_instance_int_t *
net_instance_int_create(net_instance_t *nin, net_instance_int_t *parent)
{
	net_instance_int_t *nini;

	nini = kmem_zalloc(sizeof (net_instance_int_t), KM_SLEEP);
	nini->nini_instance = nin;
	nini->nini_parent = parent;
	if (parent != NULL) {
		/*
		 * If the parent pointer is non-NULL then we take that as
		 * an indication that the net_instance_int_t is being
		 * created for an active instance and there will expect
		 * the create function to be called.  In contrast, if
		 * parent is NULL then this code assumes the object is
		 * being prepared for insertion onto the master list of
		 * callbacks to be called when an instance is created, etc.
		 */
		parent->nini_ref++;
		nini->nini_flags |= NSS_CREATE_NEEDED;
	}

	cv_init(&nini->nini_cv, NULL, CV_DRIVER, NULL);

	return (nini);
}

/*
 * Free'ing of a net_instance_int_t is only to be done when we know nobody
 * else has is using it. For both parents and clones, this is indicated by
 * nini_ref being greater than 0, however, nini_ref is managed differently
 * for its two uses. For parents, nini_ref is increased when a new clone is
 * created and it is decremented here. For clones, nini_ref is adjusted by
 * code elsewhere (e.g. in neti_stack_apply_*) and is not changed here.
 */
static void
net_instance_int_free(net_instance_int_t *nini)
{
	/*
	 * This mutex guards the use of nini_ref.
	 */
	ASSERT(mutex_owned(&neti_stack_lock));

	/*
	 * For 'parent' structures, nini_ref will drop to 0 when
	 * the last clone has been free'd... but for clones, it
	 * is possible for nini_ref to be non-zero if we get in
	 * here when all the locks have been given up to execute
	 * a callback or wait_for_nini_inprogress. In that case,
	 * we do not want to free the structure and just indicate
	 * that it is on the "doomed" list, thus we set the
	 * condemned flag.
	 */
	if (nini->nini_parent != NULL) {
		if (nini->nini_ref > 0)
			nini->nini_condemned = B_TRUE;
		nini->nini_parent->nini_ref--;
		if (nini->nini_parent->nini_ref == 0)
			net_instance_int_free(nini->nini_parent);
		nini->nini_parent = NULL;
	}

	if (nini->nini_ref == 0) {
		cv_destroy(&nini->nini_cv);
		kmem_free(nini, sizeof (*nini));
	}
}

net_instance_t *
net_instance_alloc(const int version)
{
	net_instance_t *nin;

	if (version != NETINFO_VERSION)
		return (NULL);

	nin = kmem_zalloc(sizeof (net_instance_t), KM_SLEEP);
	nin->nin_version = version;

	return (nin);
}

void
net_instance_free(net_instance_t *nin)
{
	kmem_free(nin, sizeof (*nin));
}

int
net_instance_register(net_instance_t *nin)
{
	net_instance_int_t *parent;
	net_instance_int_t *tmp;
	neti_stack_t *nts;

	ASSERT(nin->nin_name != NULL);

	if (nin->nin_create == NULL || nin->nin_destroy == NULL)
		return (DDI_FAILURE);

	mutex_enter(&neti_stack_lock);
	/*
	 * Search for duplicate, either on the global list or on any
	 * of the known instances.
	 */
	LIST_FOREACH(tmp, &neti_instance_list, nini_next) {
		if (strcmp(nin->nin_name, tmp->nini_instance->nin_name) == 0) {
			mutex_exit(&neti_stack_lock);
			return (DDI_FAILURE);
		}
	}

	/*
	 * Now insert and activate.
	 */
	parent = net_instance_int_create(nin, NULL);
	ASSERT(parent != NULL);
	LIST_INSERT_HEAD(&neti_instance_list, parent, nini_next);

	LIST_FOREACH(nts, &neti_stack_list, nts_next) {
		mutex_enter(&nts->nts_lock);
		/*
		 * If shutdown of the zone has begun then do not add a new
		 * instance of the object being registered.
		 */
		if ((nts->nts_flags & NSF_ZONE_SHUTDOWN) ||
		    (nts->nts_netstack == NULL)) {
			mutex_exit(&nts->nts_lock);
			continue;
		}

		/*
		 * This function returns with the NSS_CREATE_NEEDED flag
		 * set in "dup", so it is adequately prepared for the
		 * upcoming apply.
		 */
		tmp = net_instance_int_create(nin, parent);
		ASSERT(tmp != NULL);
		LIST_INSERT_HEAD(&nts->nts_instances, tmp, nini_next);
		mutex_exit(&nts->nts_lock);

	}

	neti_apply_all_stacks(parent, neti_stack_apply_create);
	mutex_exit(&neti_stack_lock);

	return (DDI_SUCCESS);
}

/*
 * While net_instance_register() isn't likely to be racing against itself,
 * net_instance_unregister() can be entered from various directions that
 * can compete: shutdown of a zone, unloading of a module (and it calling
 * _unregister() as part of that) and the module doing an _unregister()
 * anyway.
 */
int
net_instance_unregister(net_instance_t *nin)
{
	net_instance_int_t *parent;
	net_instance_int_t *tmp;
	neti_stack_t *nts;

	mutex_enter(&neti_stack_lock);

	LIST_FOREACH(tmp, &neti_instance_list, nini_next) {
		if (strcmp(tmp->nini_instance->nin_name, nin->nin_name) == 0) {
			LIST_REMOVE(tmp, nini_next);
			break;
		}
	}

	if (tmp == NULL) {
		mutex_exit(&neti_stack_lock);
		return (DDI_FAILURE);
	}
	parent = tmp;

	LIST_FOREACH(nts, &neti_stack_list, nts_next) {
		mutex_enter(&nts->nts_lock);
		LIST_FOREACH(tmp, &nts->nts_instances, nini_next) {
			if (tmp->nini_parent != parent)
				continue;
			/*
			 * Netstack difference:
			 * In netstack.c, there is a check for
			 * NSS_CREATE_COMPLETED before setting the other
			 * _NEEDED flags.  If we consider that a list
			 * member must always have at least the _CREATE_NEEDED
			 * flag set and that wait_for_nini_inprogress will
			 * also wait for that flag to be cleared in both of
			 * the shutdown and destroy apply functions.
			 *
			 * It is possible to optimize out the case where
			 * all three _NEEDED flags are set to being able
			 * to pretend everything has been done and just
			 * set all three _COMPLETE flags.  This makes a
			 * special case that we then need to consider in
			 * other locations, so for the sake of simplicity,
			 * we leave it as it is.
			 */
			if ((tmp->nini_flags & NSS_SHUTDOWN_ALL) == 0)
				tmp->nini_flags |= NSS_SHUTDOWN_NEEDED;
			if ((tmp->nini_flags & NSS_DESTROY_ALL) == 0)
				tmp->nini_flags |= NSS_DESTROY_NEEDED;
			break;
		}
		mutex_exit(&nts->nts_lock);
	}

	/*
	 * Each of these functions ensures that the requisite _COMPLETED
	 * flag is present before calling the apply function. So we are
	 * guaranteed to have NSS_CREATE_COMPLETED|NSS_SHUTDOWN_COMPLETED
	 * both set after the first call here and when the second completes,
	 * NSS_DESTROY_COMPLETED is also set.
	 */
	neti_apply_all_stacks(parent, neti_stack_apply_shutdown);
	neti_apply_all_stacks(parent, neti_stack_apply_destroy);

	/*
	 * Remove the instance callback information from each stack.
	 */
	LIST_FOREACH(nts, &neti_stack_list, nts_next) {
		mutex_enter(&nts->nts_lock);
		LIST_FOREACH(tmp, &nts->nts_instances, nini_next) {
			if ((tmp->nini_parent == parent) &&
			    (tmp->nini_flags & NSS_SHUTDOWN_COMPLETED) &&
			    (tmp->nini_flags & NSS_DESTROY_COMPLETED)) {
				/*
				 * There should only be one entry that has a
				 * matching nini_parent so there is no need to
				 * worry about continuing a loop where we are
				 * free'ing the structure holding the 'next'
				 * pointer.
				 */
				LIST_REMOVE(tmp, nini_next);
				net_instance_int_free(tmp);
				break;
			}
		}
		mutex_exit(&nts->nts_lock);
	}

	mutex_exit(&neti_stack_lock);

	return (DDI_SUCCESS);
}

static void
neti_apply_all_instances(neti_stack_t *nts, napplyfn_t *applyfn)
{
	net_instance_int_t *n;

	ASSERT(mutex_owned(&neti_stack_lock));

	n = LIST_FIRST(&nts->nts_instances);
	while (n != NULL) {
		if ((applyfn)(nts, n->nini_parent)) {
			/* Lock dropped - restart at head */
			n = LIST_FIRST(&nts->nts_instances);
		} else {
			n = LIST_NEXT(n, nini_next);
		}
	}
}

static void
neti_apply_all_stacks(void *parent, napplyfn_t *applyfn)
{
	neti_stack_t *nts;

	ASSERT(mutex_owned(&neti_stack_lock));

	nts = LIST_FIRST(&neti_stack_list);
	while (nts != NULL) {
		/*
		 * This function differs, in that it doesn't have a call to
		 * a "wait_creator" call, from the zsd/netstack code.  The
		 * waiting is pushed into the apply functions which cause
		 * the waiting to be done in wait_for_nini_progress with
		 * the passing in of cmask.
		 */
		if ((applyfn)(nts, parent)) {
			/* Lock dropped - restart at head */
			nts = LIST_FIRST(&neti_stack_list);
		} else {
			nts = LIST_NEXT(nts, nts_next);
		}
	}
}

static boolean_t
neti_stack_apply_create(neti_stack_t *nts, void *parent)
{
	void *result;
	boolean_t dropped = B_FALSE;
	net_instance_int_t *tmp;
	net_instance_t *nin;

	ASSERT(parent != NULL);
	ASSERT(mutex_owned(&neti_stack_lock));

	mutex_enter(&nts->nts_lock);

	LIST_FOREACH(tmp, &nts->nts_instances, nini_next) {
		if (tmp->nini_parent == parent)
			break;
	}
	if (tmp == NULL) {
		mutex_exit(&nts->nts_lock);
		return (dropped);
	}

	tmp->nini_ref++;

	if (wait_for_nini_inprogress(nts, tmp, 0))
		dropped = B_TRUE;

	if ((tmp->nini_flags & NSS_CREATE_NEEDED) && !tmp->nini_condemned) {
		nin = tmp->nini_instance;
		tmp->nini_flags &= ~NSS_CREATE_NEEDED;
		tmp->nini_flags |= NSS_CREATE_INPROGRESS;
		DTRACE_PROBE2(neti__stack__create__inprogress,
		    neti_stack_t *, nts, net_instance_int_t *, tmp);
		mutex_exit(&nts->nts_lock);
		mutex_exit(&neti_stack_lock);
		dropped = B_TRUE;

		ASSERT(tmp->nini_created == NULL);
		ASSERT(nin->nin_create != NULL);
		DTRACE_PROBE2(neti__stack__create__start,
		    netstackid_t, nts->nts_id,
		    neti_stack_t *, nts);
		result = (nin->nin_create)(nts->nts_id);
		DTRACE_PROBE2(neti__stack__create__end,
		    void *, result, neti_stack_t *, nts);

		ASSERT(result != NULL);
		mutex_enter(&neti_stack_lock);
		mutex_enter(&nts->nts_lock);
		tmp->nini_created = result;
		tmp->nini_flags &= ~NSS_CREATE_INPROGRESS;
		tmp->nini_flags |= NSS_CREATE_COMPLETED;
		cv_broadcast(&tmp->nini_cv);
		DTRACE_PROBE2(neti__stack__create__completed,
		    neti_stack_t *, nts, net_instance_int_t *, tmp);
	}
	tmp->nini_ref--;

	if (tmp->nini_condemned) {
		net_instance_int_free(tmp);
		dropped = B_TRUE;
	}
	mutex_exit(&nts->nts_lock);
	return (dropped);
}


static boolean_t
neti_stack_apply_shutdown(neti_stack_t *nts, void *parent)
{
	boolean_t dropped = B_FALSE;
	net_instance_int_t *tmp;
	net_instance_t *nin;

	ASSERT(parent != NULL);
	ASSERT(mutex_owned(&neti_stack_lock));

	mutex_enter(&nts->nts_lock);

	LIST_FOREACH(tmp, &nts->nts_instances, nini_next) {
		if (tmp->nini_parent == parent)
			break;
	}
	if (tmp == NULL) {
		mutex_exit(&nts->nts_lock);
		return (dropped);
	}
	ASSERT((tmp->nini_flags & NSS_SHUTDOWN_ALL) != 0);

	tmp->nini_ref++;

	if (wait_for_nini_inprogress(nts, tmp, NSS_CREATE_NEEDED))
		dropped = B_TRUE;

	nin = tmp->nini_instance;
	if (nin->nin_shutdown == NULL) {
		/*
		 * If there is no shutdown function, fake having completed it.
		 */
		if (tmp->nini_flags & NSS_SHUTDOWN_NEEDED) {
			tmp->nini_flags &= ~NSS_SHUTDOWN_NEEDED;
			tmp->nini_flags |= NSS_SHUTDOWN_COMPLETED;
		}
		tmp->nini_ref--;

		if (tmp->nini_condemned) {
			net_instance_int_free(tmp);
			dropped = B_TRUE;
		}

		mutex_exit(&nts->nts_lock);
		return (dropped);
	}

	if ((tmp->nini_flags & NSS_SHUTDOWN_NEEDED) && !tmp->nini_condemned) {
		ASSERT((tmp->nini_flags & NSS_CREATE_COMPLETED) != 0);
		tmp->nini_flags &= ~NSS_SHUTDOWN_NEEDED;
		tmp->nini_flags |= NSS_SHUTDOWN_INPROGRESS;
		DTRACE_PROBE2(neti__stack__shutdown__inprogress,
		    neti_stack_t *, nts, net_instance_int_t *, tmp);
		mutex_exit(&nts->nts_lock);
		mutex_exit(&neti_stack_lock);
		dropped = B_TRUE;

		ASSERT(nin->nin_shutdown != NULL);
		DTRACE_PROBE2(neti__stack__shutdown__start,
		    netstackid_t, nts->nts_id,
		    neti_stack_t *, nts);
		(nin->nin_shutdown)(nts->nts_id, tmp->nini_created);
		DTRACE_PROBE1(neti__stack__shutdown__end,
		    neti_stack_t *, nts);

		mutex_enter(&neti_stack_lock);
		mutex_enter(&nts->nts_lock);
		tmp->nini_flags &= ~NSS_SHUTDOWN_INPROGRESS;
		tmp->nini_flags |= NSS_SHUTDOWN_COMPLETED;
		cv_broadcast(&tmp->nini_cv);
		DTRACE_PROBE2(neti__stack__shutdown__completed,
		    neti_stack_t *, nts, net_instance_int_t *, tmp);
	}
	ASSERT((tmp->nini_flags & NSS_SHUTDOWN_COMPLETED) != 0);
	tmp->nini_ref--;

	if (tmp->nini_condemned) {
		net_instance_int_free(tmp);
		dropped = B_TRUE;
	}
	mutex_exit(&nts->nts_lock);
	return (dropped);
}

static boolean_t
neti_stack_apply_destroy(neti_stack_t *nts, void *parent)
{
	boolean_t dropped = B_FALSE;
	net_instance_int_t *tmp;
	net_instance_t *nin;

	ASSERT(parent != NULL);
	ASSERT(mutex_owned(&neti_stack_lock));

	mutex_enter(&nts->nts_lock);

	LIST_FOREACH(tmp, &nts->nts_instances, nini_next) {
		if (tmp->nini_parent == parent)
			break;
	}
	if (tmp == NULL) {
		mutex_exit(&nts->nts_lock);
		return (dropped);
	}

	tmp->nini_ref++;

	/*
	 * We pause here so that when we continue we know that we're the
	 * only one doing anything active with this node.
	 */
	if (wait_for_nini_inprogress(nts, tmp,
	    NSS_CREATE_NEEDED|NSS_SHUTDOWN_NEEDED))
		dropped = B_TRUE;

	if ((tmp->nini_flags & NSS_DESTROY_NEEDED) && !tmp->nini_condemned) {
		ASSERT((tmp->nini_flags & NSS_SHUTDOWN_COMPLETED) != 0);
		nin = tmp->nini_instance;
		tmp->nini_flags &= ~NSS_DESTROY_NEEDED;
		tmp->nini_flags |= NSS_DESTROY_INPROGRESS;
		DTRACE_PROBE2(neti__stack__destroy__inprogress,
		    neti_stack_t *, nts, net_instance_int_t *, tmp);
		mutex_exit(&nts->nts_lock);
		mutex_exit(&neti_stack_lock);
		dropped = B_TRUE;

		ASSERT(nin->nin_destroy != NULL);
		DTRACE_PROBE2(neti__stack__destroy__start,
		    netstackid_t, nts->nts_id,
		    neti_stack_t *, nts);
		(nin->nin_destroy)(nts->nts_id, tmp->nini_created);
		DTRACE_PROBE1(neti__stack__destroy__end,
		    neti_stack_t *, nts);

		mutex_enter(&neti_stack_lock);
		mutex_enter(&nts->nts_lock);
		tmp->nini_flags &= ~NSS_DESTROY_INPROGRESS;
		tmp->nini_flags |= NSS_DESTROY_COMPLETED;
		cv_broadcast(&tmp->nini_cv);
		DTRACE_PROBE2(neti__stack__destroy__completed,
		    neti_stack_t *, nts, net_instance_int_t *, tmp);
	}
	tmp->nini_ref--;

	if (tmp->nini_condemned) {
		net_instance_int_free(tmp);
		dropped = B_TRUE;
	}
	mutex_exit(&nts->nts_lock);
	return (dropped);
}

static boolean_t
wait_for_nini_inprogress(neti_stack_t *nts, net_instance_int_t *nini,
    uint32_t cmask)
{
	boolean_t dropped = B_FALSE;

	ASSERT(mutex_owned(&neti_stack_lock));

	while (nini->nini_flags & (NSS_ALL_INPROGRESS|cmask)) {
		DTRACE_PROBE2(neti__wait__nini__inprogress,
		    neti_stack_t *, nts, net_instance_int_t *, nini);
		dropped = B_TRUE;
		mutex_exit(&neti_stack_lock);

		cv_wait(&nini->nini_cv, &nts->nts_lock);

		/* First drop netstack_lock to preserve order */
		mutex_exit(&nts->nts_lock);
		DTRACE_PROBE2(wait__nini__inprogress__pause,
		    neti_stack_t *, nts, net_instance_int_t *, nini);
		mutex_enter(&neti_stack_lock);
		mutex_enter(&nts->nts_lock);
	}
	DTRACE_PROBE2(neti__wait__nini__inprogress__complete,
	    neti_stack_t *, nts, net_instance_int_t *, nini);
	return (dropped);
}

/* ======================================================================= */

netid_t
net_zoneidtonetid(zoneid_t zoneid)
{

	neti_stack_t *nts;

	mutex_enter(&neti_stack_lock);
	LIST_FOREACH(nts, &neti_stack_list, nts_next) {
		if (nts->nts_zoneid == zoneid) {
			mutex_exit(&neti_stack_lock);
			return (nts->nts_id);
		}
	}
	mutex_exit(&neti_stack_lock);

	return (-1);
}

zoneid_t
net_getzoneidbynetid(netid_t netid)
{
	neti_stack_t *nts;

	mutex_enter(&neti_stack_lock);
	LIST_FOREACH(nts, &neti_stack_list, nts_next) {
		if (nts->nts_id == netid) {
			mutex_exit(&neti_stack_lock);
			return (nts->nts_zoneid);
		}
	}
	mutex_exit(&neti_stack_lock);

	return (-1);
}

netstackid_t
net_getnetstackidbynetid(netid_t netid)
{
	neti_stack_t *nts;

	mutex_enter(&neti_stack_lock);
	LIST_FOREACH(nts, &neti_stack_list, nts_next) {
		if (nts->nts_id == netid) {
			mutex_exit(&neti_stack_lock);
			return (nts->nts_stackid);
		}
	}
	mutex_exit(&neti_stack_lock);

	return (-1);
}

netid_t
net_getnetidbynetstackid(netstackid_t netstackid)
{
	neti_stack_t *nts;

	mutex_enter(&neti_stack_lock);
	LIST_FOREACH(nts, &neti_stack_list, nts_next) {
		if (nts->nts_stackid == netstackid) {
			mutex_exit(&neti_stack_lock);
			return (nts->nts_id);
		}
	}
	mutex_exit(&neti_stack_lock);

	return (-1);
}

neti_stack_t *
net_getnetistackbyid(netid_t netid)
{
	neti_stack_t *nts;

	mutex_enter(&neti_stack_lock);
	LIST_FOREACH(nts, &neti_stack_list, nts_next) {
		if (nts->nts_id == netid) {
			mutex_exit(&neti_stack_lock);
			return (nts);
		}
	}
	mutex_exit(&neti_stack_lock);

	return (NULL);
}

int
net_instance_notify_register(netid_t netid, hook_notify_fn_t callback,
    void *arg)
{

	return (hook_stack_notify_register(net_getnetstackidbynetid(netid),
	    callback, arg));
}

int
net_instance_notify_unregister(netid_t netid, hook_notify_fn_t callback)
{

	return (hook_stack_notify_unregister(net_getnetstackidbynetid(netid),
	    callback));
}