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

#include <sys/note.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/kmem.h>
#include <sys/cmn_err.h>
#include <sys/debug.h>
#include <sys/sunndi.h>
#include <sys/kstat.h>
#include <sys/conf.h>
#include <sys/ddi_timer.h>
#include <sys/devctl.h>
#include <sys/callb.h>
#include <sys/sysevent.h>
#include <sys/taskq.h>
#include <sys/ddi.h>
#include <sys/bitset.h>
#include <sys/damap.h>
#include <sys/damap_impl.h>

#ifdef DEBUG
static int damap_debug = 0;
#endif /* DEBUG */

static void dam_addrset_activate(dam_t *, bitset_t *);
static void dam_addrset_release(dam_t *, bitset_t *);
static void dam_activate_taskq(void *);
static void dam_addr_stable_cb(void *);
static void dam_set_stable_cb(void *);
static void dam_sched_tmo(dam_t *, clock_t, void (*tmo_cb)());
static void dam_add_report(dam_t *, dam_da_t *, id_t, int);
static void dam_release(dam_t *, id_t);
static void dam_release_report(dam_t *, id_t);
static void dam_deactivate_addr(dam_t *, id_t);
static id_t dam_get_addrid(dam_t *, char *);
static int dam_kstat_create(dam_t *);
static void dam_kstat_destroy(dam_t *);

#define	DAM_INCR_STAT(mapp, stat)				\
	if ((mapp)->dam_kstatsp) {				\
		struct dam_kstats *stp = (mapp)->dam_kstatsp->ks_data;	\
		stp->stat.value.ui32++;				\
	}

#define	DAM_SET_STAT(mapp, stat, val)				\
	if ((mapp)->dam_kstatsp) {				\
		struct dam_kstats *stp = (mapp)->dam_kstatsp->ks_data;	\
		stp->stat.value.ui32 = (val);			\
	}

/*
 * Create new device address map
 *
 * ident:		map name (kstat)
 * size:		max # of map entries
 * rptmode:		type or mode of reporting
 * stable_usec:		# of quiescent microseconds before report/map is stable
 *
 * activate_arg:	address provider activation-callout private
 * activate_cb:		address provider activation callback handler
 * deactivate_cb:	address provider deactivation callback handler
 *
 * config_arg:		configuration-callout private
 * config_cb:		class configuration callout
 * unconfig_cb:		class unconfiguration callout
 *
 * damapp:		pointer to map handle (return)
 *
 * Returns:	DAM_SUCCESS
 *		DAM_EINVAL	Invalid argument(s)
 *		DAM_FAILURE	General failure
 */
int
damap_create(char *ident, size_t size, damap_rptmode_t rptmode,
    clock_t stable_usec,
    void *activate_arg, damap_activate_cb_t activate_cb,
    damap_deactivate_cb_t deactivate_cb,
    void *config_arg, damap_configure_cb_t configure_cb,
    damap_unconfig_cb_t unconfig_cb,
    damap_t **damapp)
{
	dam_t *mapp;
	void *softstate_p;

	DTRACE_PROBE1(damap__create__entry, char *, ident);
	if ((configure_cb == NULL) || (unconfig_cb == NULL))
		return (DAM_EINVAL);

	if (ddi_soft_state_init(&softstate_p, sizeof (dam_da_t), size) !=
	    DDI_SUCCESS)
		return (DAM_FAILURE);

	mapp = kmem_zalloc(sizeof (*mapp), KM_SLEEP);
	if (ddi_strid_init(&mapp->dam_addr_hash, size) != DDI_SUCCESS) {
		ddi_soft_state_fini(&softstate_p);
		kmem_free(mapp, sizeof (*mapp));
		return (DAM_FAILURE);
	}

	mapp->dam_da = softstate_p;
	mapp->dam_stabletmo = drv_usectohz(stable_usec);
	mapp->dam_size = size;
	mapp->dam_high = 1;
	mapp->dam_rptmode = rptmode;

	mapp->dam_activate_arg = activate_arg;
	mapp->dam_activate_cb = (activate_cb_t)activate_cb;
	mapp->dam_deactivate_cb = (deactivate_cb_t)deactivate_cb;

	mapp->dam_config_arg = config_arg;
	mapp->dam_configure_cb = (configure_cb_t)configure_cb;
	mapp->dam_unconfig_cb = (unconfig_cb_t)unconfig_cb;

	if (ident)
		mapp->dam_name = i_ddi_strdup(ident, KM_SLEEP);

	bitset_init(&mapp->dam_active_set);
	bitset_resize(&mapp->dam_active_set, size);
	bitset_init(&mapp->dam_stable_set);
	bitset_resize(&mapp->dam_stable_set, size);
	bitset_init(&mapp->dam_report_set);
	bitset_resize(&mapp->dam_report_set, size);
	mutex_init(&mapp->dam_lock, NULL, MUTEX_DRIVER, NULL);
	cv_init(&mapp->dam_cv, NULL, CV_DRIVER, NULL);
	mapp->dam_taskqp = ddi_taskq_create(NULL, ident, 1, TASKQ_DEFAULTPRI,
	    0);
	*damapp = (damap_t *)mapp;
	if (dam_kstat_create(mapp) != DDI_SUCCESS) {
		damap_destroy((damap_t *)mapp);
		return (DAM_FAILURE);
	}

	DTRACE_PROBE1(damap__create__exit, dam_t *, mapp);
	return (DAM_SUCCESS);
}

/*
 * Destroy device address map
 *
 * damapp:	address map
 *
 * Returns:	DAM_SUCCESS
 *		DAM_EINVAL	Invalid argument(s)
 *		DAM_FAILURE	General failure
 */
void
damap_destroy(damap_t *damapp)
{
	int i;
	dam_t *mapp = (dam_t *)damapp;

	ASSERT(mapp);

	DTRACE_PROBE2(damap__destroy__entry, dam_t *, mapp, char *,
	    mapp->dam_name);

	DAM_FLAG_SET(mapp, DAM_DESTROYPEND);
	(void) damap_sync(damapp);

	/*
	 * cancel pending timeouts and kill off the taskq
	 */
	dam_sched_tmo(mapp, 0, NULL);
	ddi_taskq_wait(mapp->dam_taskqp);
	ddi_taskq_destroy(mapp->dam_taskqp);

	for (i = 1; i < mapp->dam_high; i++) {
		if (ddi_get_soft_state(mapp->dam_da, i) == NULL)
			continue;
		if (DAM_IN_REPORT(mapp, i))
			dam_release_report(mapp, i);
		if (DAM_IS_STABLE(mapp, i))
			dam_deactivate_addr(mapp, i);
		ddi_strid_free(mapp->dam_addr_hash, i);
		ddi_soft_state_free(mapp->dam_da, i);
	}
	ddi_strid_fini(&mapp->dam_addr_hash);
	ddi_soft_state_fini(&mapp->dam_da);
	bitset_fini(&mapp->dam_active_set);
	bitset_fini(&mapp->dam_stable_set);
	bitset_fini(&mapp->dam_report_set);
	dam_kstat_destroy(mapp);
	mutex_destroy(&mapp->dam_lock);
	cv_destroy(&mapp->dam_cv);
	if (mapp->dam_name)
		kmem_free(mapp->dam_name, strlen(mapp->dam_name) + 1);
	kmem_free(mapp, sizeof (*mapp));
	DTRACE_PROBE(damap__destroy__exit);
}

/*
 * Wait for map stability.
 *
 * damapp:	address map
 */
int
damap_sync(damap_t *damapp)
{

#define	WAITFOR_FLAGS (DAM_SETADD | DAM_SPEND | MAP_LOCK)

	dam_t *mapp = (dam_t *)damapp;
	int   none_active;

	ASSERT(mapp);

	DTRACE_PROBE1(damap__sync__entry, dam_t *, mapp);

	mutex_enter(&mapp->dam_lock);
	while ((mapp->dam_flags & WAITFOR_FLAGS) ||
	    (!bitset_is_null(&mapp->dam_report_set)) || (mapp->dam_tid != 0)) {
		cv_wait(&mapp->dam_cv, &mapp->dam_lock);
	}

	none_active = bitset_is_null(&mapp->dam_active_set);

	mutex_exit(&mapp->dam_lock);
	DTRACE_PROBE2(damap__sync__exit, dam_t *, mapp, int, none_active);

	return (none_active);
}

/*
 * Get the name of a device address map
 *
 * damapp:	address map
 *
 * Returns:	name
 */
char *
damap_name(damap_t *damapp)
{
	dam_t *mapp = (dam_t *)damapp;

	return (mapp ? mapp->dam_name : "UNKNOWN_damap");
}

/*
 * Report an address to per-address report
 *
 * damapp:	address map handle
 * address:	address in ascii string representation
 * rindx:	index if address stabilizes
 * nvl:		optional nvlist of configuration-private data
 * addr_priv:	optional provider-private (passed to activate/deactivate cb)
 *
 * Returns:	DAM_SUCCESS
 *		DAM_EINVAL	Invalid argument(s)
 *		DAM_MAPFULL	address map exhausted
 */
int
damap_addr_add(damap_t *damapp, char *address, damap_id_t *ridx, nvlist_t *nvl,
    void *addr_priv)
{
	dam_t *mapp = (dam_t *)damapp;
	id_t addrid;
	dam_da_t *passp;

	DTRACE_PROBE2(damap__addr__add__entry, dam_t *, mapp,
	    char *, address);
	if (!mapp || !address || (mapp->dam_rptmode != DAMAP_REPORT_PERADDR) ||
	    (mapp->dam_flags & DAM_DESTROYPEND))
		return (DAM_EINVAL);

	DAM_LOCK(mapp, ADDR_LOCK);
	if ((addrid = dam_get_addrid(mapp, address)) == 0) {
		DAM_UNLOCK(mapp, ADDR_LOCK);
		return (DAM_MAPFULL);
	}

	passp = ddi_get_soft_state(mapp->dam_da, addrid);
	ASSERT(passp != NULL);

	/*
	 * If re-reporting the same address (add or remove) clear
	 * the existing report
	 */
	if (DAM_IN_REPORT(mapp, addrid)) {
		DAM_INCR_STAT(mapp, dam_rereport);
		dam_release_report(mapp, addrid);
		passp->da_jitter++;
	}
	passp->da_ppriv_rpt = addr_priv;
	if (nvl)
		(void) nvlist_dup(nvl, &passp->da_nvl_rpt, KM_SLEEP);

	dam_add_report(mapp, passp, addrid, RPT_ADDR_ADD);
	if (ridx != NULL)
		*ridx = (damap_id_t)addrid;
	DAM_UNLOCK(mapp, ADDR_LOCK);
	DTRACE_PROBE3(damap__addr__add__exit, dam_t *, mapp, char *,
	    address, int, addrid);
	return (DAM_SUCCESS);
}

/*
 * Report removal of address from per-address report
 *
 * damapp:	address map
 * address:	address in ascii string representation
 *
 * Returns:	DAM_SUCCESS
 *		DAM_EINVAL	Invalid argument(s)
 *		DAM_FAILURE	General failure
 */
int
damap_addr_del(damap_t *damapp, char *address)
{
	dam_t *mapp = (dam_t *)damapp;
	id_t addrid;
	dam_da_t *passp;

	DTRACE_PROBE2(damap__addr__del__entry, dam_t *, mapp,
	    char *, address);
	if (!mapp || !address || (mapp->dam_rptmode != DAMAP_REPORT_PERADDR) ||
	    (mapp->dam_flags & DAM_DESTROYPEND))
		return (DAM_EINVAL);

	DAM_LOCK(mapp, ADDR_LOCK);
	if (!(addrid = ddi_strid_str2id(mapp->dam_addr_hash, address))) {
		DAM_UNLOCK(mapp, ADDR_LOCK);
		return (DAM_SUCCESS);
	}
	passp = ddi_get_soft_state(mapp->dam_da, addrid);
	ASSERT(passp);
	if (DAM_IN_REPORT(mapp, addrid)) {
		DAM_INCR_STAT(mapp, dam_rereport);
		dam_release_report(mapp, addrid);
		passp->da_jitter++;
	}
	dam_add_report(mapp, passp, addrid, RPT_ADDR_DEL);
	DAM_UNLOCK(mapp, ADDR_LOCK);
	DTRACE_PROBE3(damap__addr__del__exit, dam_t *, mapp,
	    char *, address, int, addrid);
	return (DAM_SUCCESS);
}

/*
 * Initiate full-set report
 *
 * damapp:	address map
 *
 * Returns:	DAM_SUCCESS
 *		DAM_EINVAL	Invalid argument(s)
 */
int
damap_addrset_begin(damap_t *damapp)
{
	dam_t *mapp = (dam_t *)damapp;
	int i;

	DTRACE_PROBE1(damap__addrset__begin__entry, dam_t *, mapp);

	if ((mapp->dam_rptmode != DAMAP_REPORT_FULLSET) ||
	    (mapp->dam_flags & DAM_DESTROYPEND))
		return (DAM_EINVAL);

	DAM_LOCK(mapp, MAP_LOCK);
	/*
	 * reset any pending reports
	 */
	if (mapp->dam_flags & DAM_SETADD) {
		/*
		 * cancel stabilization timeout
		 */
		dam_sched_tmo(mapp, 0, NULL);
		DAM_INCR_STAT(mapp, dam_rereport);
		DAM_UNLOCK(mapp, MAP_LOCK);
		DAM_LOCK(mapp, ADDR_LOCK);
		for (i = 1; i < mapp->dam_high; i++) {
			if (DAM_IN_REPORT(mapp, i))
				dam_release_report(mapp, i);
		}
		DAM_UNLOCK(mapp, ADDR_LOCK);
		DAM_LOCK(mapp, MAP_LOCK);
	}
	DAM_FLAG_SET(mapp, DAM_SETADD);
	bitset_zero(&mapp->dam_report_set);
	DAM_UNLOCK(mapp, MAP_LOCK);
	DTRACE_PROBE(damap__addrset__begin__exit);
	return (DAM_SUCCESS);
}

/*
 * Report address to full-set report
 *
 * damapp:	address map handle
 * address:	address in ascii string representation
 * rindx:	index if address stabilizes
 * nvl:		optional nvlist of configuration-private data
 * addr_priv:	optional provider-private data (passed to activate/release cb)
 *
 * Returns:	DAM_SUCCESS
 *		DAM_EINVAL	Invalid argument(s)
 *		DAM_MAPFULL	address map exhausted
 *		DAM_FAILURE	General failure
 */
int
damap_addrset_add(damap_t *damapp, char *address, damap_id_t *ridx,
    nvlist_t *nvl, void *addr_priv)
{
	dam_t *mapp = (dam_t *)damapp;
	id_t addrid;
	dam_da_t *passp;

	DTRACE_PROBE2(damap__addrset__add__entry, dam_t *, mapp,
	    char *, address);

	if (!mapp || !address || (mapp->dam_rptmode != DAMAP_REPORT_FULLSET) ||
	    (mapp->dam_flags & DAM_DESTROYPEND))
		return (DAM_EINVAL);

	if (!(mapp->dam_flags & DAM_SETADD))
		return (DAM_FAILURE);

	DAM_LOCK(mapp, ADDR_LOCK);
	if ((addrid = dam_get_addrid(mapp, address)) == 0) {
		DAM_UNLOCK(mapp, ADDR_LOCK);
		return (DAM_MAPFULL);
	}

	passp = ddi_get_soft_state(mapp->dam_da, addrid);
	ASSERT(passp);
	if (DAM_IN_REPORT(mapp, addrid)) {
		dam_release_report(mapp, addrid);
		passp->da_jitter++;
	}
	passp->da_ppriv_rpt = addr_priv;
	if (nvl)
		(void) nvlist_dup(nvl, &passp->da_nvl_rpt, KM_SLEEP);
	DAM_LOCK(mapp, MAP_LOCK);
	bitset_add(&mapp->dam_report_set, addrid);
	DAM_UNLOCK(mapp, MAP_LOCK);
	if (ridx)
		*ridx = (damap_id_t)addrid;
	DAM_UNLOCK(mapp, ADDR_LOCK);
	DTRACE_PROBE3(damap__addr__addset__exit, dam_t *, mapp, char *,
	    address, int, addrid);
	return (DAM_SUCCESS);
}

/*
 * Commit full-set report for stabilization
 *
 * damapp:	address map handle
 * flags:	(currently 0)
 *
 * Returns:	DAM_SUCCESS
 *		DAM_EINVAL	Invalid argument(s)
 *		DAM_FAILURE	General failure
 */
int
damap_addrset_end(damap_t *damapp, int flags)
{
	dam_t *mapp = (dam_t *)damapp;
	int i;

	DTRACE_PROBE1(damap__addrset__end__entry, dam_t *, mapp);

	if (!mapp || (mapp->dam_rptmode != DAMAP_REPORT_FULLSET) ||
	    (mapp->dam_flags & DAM_DESTROYPEND))
		return (DAM_EINVAL);

	if (!(mapp->dam_flags & DAM_SETADD))
		return (DAM_FAILURE);

	if (flags & DAMAP_RESET) {
		DAM_LOCK(mapp, MAP_LOCK);
		dam_sched_tmo(mapp, 0, NULL);
		DAM_UNLOCK(mapp, MAP_LOCK);
		DAM_LOCK(mapp, ADDR_LOCK);
		for (i = 1; i < mapp->dam_high; i++)
			if (DAM_IN_REPORT(mapp, i))
				dam_release_report(mapp, i);
		DAM_UNLOCK(mapp, ADDR_LOCK);
	} else {
		mapp->dam_last_update = gethrtime();
		DAM_LOCK(mapp, MAP_LOCK);
		dam_sched_tmo(mapp, mapp->dam_stabletmo, dam_set_stable_cb);
		DAM_UNLOCK(mapp, MAP_LOCK);
	}
	DTRACE_PROBE(damap__addrset__end__exit);
	return (DAM_SUCCESS);
}

/*
 * Return nvlist registered with reported address
 *
 * damapp:	address map handle
 * aid:		address ID
 *
 * Returns:	nvlist_t *	provider supplied via damap_addr{set}_add())
 *		NULL
 */
nvlist_t *
damap_id2nvlist(damap_t *damapp, damap_id_t addrid)
{
	dam_t *mapp = (dam_t *)damapp;
	id_t aid = (id_t)addrid;
	dam_da_t *pass;

	if (ddi_strid_id2str(mapp->dam_addr_hash, aid)) {
		if (pass = ddi_get_soft_state(mapp->dam_da, aid))
			return (pass->da_nvl);
	}
	return (NULL);
}

/*
 * Return address string
 *
 * damapp:	address map handle
 * aid:		address ID
 *
 * Returns:	char *		Address string
 *		NULL
 */
char *
damap_id2addr(damap_t *damapp, damap_id_t aid)
{
	dam_t *mapp = (dam_t *)damapp;

	return (ddi_strid_id2str(mapp->dam_addr_hash, (id_t)aid));
}

/*
 * Hold address reference in map
 *
 * damapp:	address map handle
 * aid:		address ID
 *
 * Returns:	DAM_SUCCESS
 *		DAM_FAILURE
 */
int
damap_id_hold(damap_t *damapp, damap_id_t aid)
{
	dam_t *mapp = (dam_t *)damapp;
	dam_da_t *passp;


	DAM_LOCK(mapp, ADDR_LOCK);
	passp = ddi_get_soft_state(mapp->dam_da, (id_t)aid);
	if (!passp) {
		DAM_UNLOCK(mapp, ADDR_LOCK);
		return (DAM_FAILURE);
	}
	passp->da_ref++;
	DAM_UNLOCK(mapp, ADDR_LOCK);
	return (DAM_SUCCESS);
}

/*
 * Release address reference in map
 *
 * damapp:	address map handle
 * aid:		address ID
 */
void
damap_id_rele(damap_t *damapp, damap_id_t addrid)
{
	dam_t *mapp = (dam_t *)damapp;

	DAM_LOCK(mapp, ADDR_LOCK);
	dam_release(mapp, (id_t)addrid);
	DAM_UNLOCK(mapp, ADDR_LOCK);
}

/*
 * Return current reference count on address reference in map
 *
 * damapp:	address map handle
 * aid:		address ID
 *
 * Returns:	DAM_SUCCESS
 *		DAM_FAILURE
 */
int
damap_id_ref(damap_t *damapp, damap_id_t aid)
{
	dam_t *mapp = (dam_t *)damapp;
	dam_da_t *passp;
	int ref = -1;

	DAM_LOCK(mapp, ADDR_LOCK);
	passp = ddi_get_soft_state(mapp->dam_da, (id_t)aid);
	if (passp)
		ref = passp->da_ref;
	DAM_UNLOCK(mapp, ADDR_LOCK);
	return (ref);
}

/*
 * Return next address ID in list
 *
 * damapp:	address map handle
 * damap_list:	address ID list passed to config|unconfig
 *		returned by look by lookup_all
 * last:	last ID returned, 0 is start of list
 *
 * Returns:	addrid		Next ID from the list
 *		0		End of the list
 */
damap_id_t
damap_id_next(damap_t *damapp, damap_id_list_t damap_list, damap_id_t last)
{
	int i, start;
	dam_t *mapp = (dam_t *)damapp;
	bitset_t *dam_list = (bitset_t *)damap_list;

	if (!mapp || !dam_list)
		return ((damap_id_t)0);

	start = (int)last + 1;
	for (i = start; i < mapp->dam_high; i++)
		if (bitset_in_set(dam_list, i))
			return ((damap_id_t)i);
	return ((damap_id_t)0);
}

/*
 * Set config private data
 *
 * damapp:	address map handle
 * aid:		address ID
 * cfg_priv:	configuration private data
 *
 */
void
damap_id_priv_set(damap_t *damapp, damap_id_t aid, void *cfg_priv)
{
	dam_t *mapp = (dam_t *)damapp;
	dam_da_t *passp;


	DAM_LOCK(mapp, ADDR_LOCK);
	passp = ddi_get_soft_state(mapp->dam_da, (id_t)aid);
	if (!passp) {
		DAM_UNLOCK(mapp, ADDR_LOCK);
		return;
	}
	passp->da_cfg_priv = cfg_priv;
	DAM_UNLOCK(mapp, ADDR_LOCK);
}

/*
 * Get config private data
 *
 * damapp:	address map handle
 * aid:		address ID
 *
 * Returns:	configuration private data
 */
void *
damap_id_priv_get(damap_t *damapp, damap_id_t aid)
{
	dam_t *mapp = (dam_t *)damapp;
	dam_da_t *passp;
	void *rv;


	DAM_LOCK(mapp, ADDR_LOCK);
	passp = ddi_get_soft_state(mapp->dam_da, (id_t)aid);
	if (!passp) {
		DAM_UNLOCK(mapp, ADDR_LOCK);
		return (NULL);
	}
	rv = passp->da_cfg_priv;
	DAM_UNLOCK(mapp, ADDR_LOCK);
	return (rv);
}

/*
 * Lookup a single address in the active address map
 *
 * damapp:	address map handle
 * address:	address string
 *
 * Returns:	ID of active/stable address
 *		0	Address not in stable set
 *
 * Future: Allow the caller to wait for stabilize before returning not found.
 */
damap_id_t
damap_lookup(damap_t *damapp, char *address)
{
	dam_t *mapp = (dam_t *)damapp;
	id_t addrid = 0;
	dam_da_t *passp = NULL;

	DAM_LOCK(mapp, ADDR_LOCK);
	addrid = ddi_strid_str2id(mapp->dam_addr_hash, address);
	if (addrid) {
		DAM_LOCK(mapp, MAP_LOCK);
		if (DAM_IS_STABLE(mapp, addrid)) {
			passp = ddi_get_soft_state(mapp->dam_da, addrid);
			ASSERT(passp);
			if (passp) {
				passp->da_ref++;
			} else {
				addrid = 0;
			}
		} else {
			addrid = 0;
		}
		DAM_UNLOCK(mapp, MAP_LOCK);
	}
	DAM_UNLOCK(mapp, ADDR_LOCK);
	return ((damap_id_t)addrid);
}


/*
 * Return the list of stable addresses in the map
 *
 * damapp:	address map handle
 * id_listp:	pointer to list of address IDs in stable map (returned)
 *
 * Returns:	# of entries returned in alist
 */
int
damap_lookup_all(damap_t *damapp, damap_id_list_t *id_listp)
{
	dam_t *mapp = (dam_t *)damapp;
	int mapsz = mapp->dam_size;
	int n_ids, i;
	bitset_t *bsp;
	dam_da_t *passp;

	bsp = kmem_alloc(sizeof (*bsp), KM_SLEEP);
	bitset_init(bsp);
	bitset_resize(bsp, mapsz);
	DAM_LOCK(mapp, MAP_LOCK);
	bitset_copy(&mapp->dam_active_set, bsp);
	DAM_UNLOCK(mapp, MAP_LOCK);
	DAM_LOCK(mapp, ADDR_LOCK);
	for (n_ids = 0, i = 1; i < mapsz; i++) {
		if (bitset_in_set(bsp, i)) {
			passp = ddi_get_soft_state(mapp->dam_da, i);
			ASSERT(passp);
			if (passp) {
				passp->da_ref++;
				n_ids++;
			}
		}
	}
	DAM_UNLOCK(mapp, ADDR_LOCK);
	if (n_ids) {
		*id_listp = (damap_id_list_t)bsp;
		return (n_ids);
	} else {
		*id_listp = (damap_id_list_t)NULL;
		bitset_fini(bsp);
		kmem_free(bsp, sizeof (*bsp));
		return (0);
	}
}

/*
 * Release the address list returned by damap_lookup_all()
 *
 * mapp:	address map handle
 * id_list:	list of address IDs returned in damap_lookup_all()
 */
void
damap_id_list_rele(damap_t *damapp, damap_id_list_t id_list)
{
	dam_t *mapp = (dam_t *)damapp;
	int i;

	if (id_list == NULL)
		return;

	DAM_LOCK(mapp, ADDR_LOCK);
	for (i = 1; i < mapp->dam_high; i++) {
		if (bitset_in_set((bitset_t *)id_list, i))
			(void) dam_release(mapp, i);
	}
	DAM_UNLOCK(mapp, ADDR_LOCK);
	bitset_fini((bitset_t *)id_list);
	kmem_free((void *)id_list, sizeof (bitset_t));
}

/*
 * Activate a set of stabilized addresses
 */
static void
dam_addrset_activate(dam_t *mapp, bitset_t *active_set)
{
	dam_da_t *passp;
	char *addrstr;
	int i;
	uint32_t n_active = 0;

	for (i = 1; i < mapp->dam_high; i++) {
		if (bitset_in_set(&mapp->dam_active_set, i))
			n_active++;
		if (!bitset_in_set(active_set, i))
			continue;
		n_active++;
		passp = ddi_get_soft_state(mapp->dam_da, i);
		ASSERT(passp);
		if (mapp->dam_activate_cb) {
			addrstr = ddi_strid_id2str(mapp->dam_addr_hash, i);
			(*mapp->dam_activate_cb)(
			    mapp->dam_activate_arg, addrstr, i,
			    &passp->da_ppriv_rpt);
		}
		DTRACE_PROBE2(damap__addrset__activate, dam_t *, mapp, int, i);
		DAM_LOCK(mapp, MAP_LOCK);
		bitset_add(&mapp->dam_active_set, i);
		/*
		 * copy the reported nvlist and provider private data
		 */
		passp->da_nvl = passp->da_nvl_rpt;
		passp->da_ppriv = passp->da_ppriv_rpt;
		passp->da_ppriv_rpt = NULL;
		passp->da_nvl_rpt = NULL;
		passp->da_last_stable = gethrtime();
		passp->da_stable_cnt++;
		DAM_UNLOCK(mapp, MAP_LOCK);
		DAM_SET_STAT(mapp, dam_numstable, n_active);
	}
}

/*
 * Release a set of stabilized addresses
 */
static void
dam_addrset_release(dam_t *mapp, bitset_t *release_set)
{
	int i;

	DAM_LOCK(mapp, ADDR_LOCK);
	for (i = 1; i < mapp->dam_high; i++) {
		if (bitset_in_set(release_set, i)) {
			DTRACE_PROBE2(damap__addrset__release, dam_t *, mapp,
			    int, i);
			DAM_LOCK(mapp, MAP_LOCK);
			bitset_del(&mapp->dam_active_set, i);
			DAM_UNLOCK(mapp, MAP_LOCK);
			(void) dam_release(mapp, i);
		}
	}
	DAM_UNLOCK(mapp, ADDR_LOCK);
}

/*
 * release a previously activated address
 */
static void
dam_release(dam_t *mapp, id_t addrid)
{
	dam_da_t *passp;

	DAM_ASSERT_LOCKED(mapp, ADDR_LOCK);
	passp = ddi_get_soft_state(mapp->dam_da, addrid);
	ASSERT(passp);

	/*
	 * invoke the deactivation callback to notify
	 * this address is no longer active
	 */
	dam_deactivate_addr(mapp, addrid);

	/*
	 * allow pending reports for this address to stabilize
	 */
	if (DAM_IN_REPORT(mapp, addrid))
		return;

	/*
	 * defer teardown until outstanding references are released
	 */
	if (--passp->da_ref) {
		passp->da_flags |= DA_RELE;
		return;
	}
	ddi_strid_free(mapp->dam_addr_hash, addrid);
	ddi_soft_state_free(mapp->dam_da, addrid);
}

/*
 * process stabilized address reports
 */
static void
dam_activate_taskq(void *arg)
{
	dam_t *mapp = (dam_t *)arg;
	bitset_t delta;
	bitset_t cfg;
	bitset_t uncfg;
	int has_cfg, has_uncfg;

	bitset_init(&delta);
	bitset_resize(&delta, mapp->dam_size);
	bitset_init(&cfg);
	bitset_resize(&cfg, mapp->dam_size);
	bitset_init(&uncfg);
	bitset_resize(&uncfg, mapp->dam_size);

	DTRACE_PROBE1(damap__activate__taskq__entry, dam_t, mapp);
	DAM_LOCK(mapp, MAP_LOCK);
	if (!bitset_xor(&mapp->dam_active_set, &mapp->dam_stable_set,
	    &delta)) {
		bitset_zero(&mapp->dam_stable_set);
		DAM_FLAG_CLR(mapp, DAM_SPEND);
		DAM_UNLOCK(mapp, MAP_LOCK);
		bitset_fini(&uncfg);
		bitset_fini(&cfg);
		bitset_fini(&delta);
		return;
	}
	has_cfg = bitset_and(&delta, &mapp->dam_stable_set, &cfg);
	has_uncfg = bitset_and(&delta, &mapp->dam_active_set, &uncfg);
	DAM_UNLOCK(mapp, MAP_LOCK);
	if (has_cfg) {
		dam_addrset_activate(mapp, &cfg);
		(*mapp->dam_configure_cb)(mapp->dam_config_arg, mapp, &cfg);
	}
	if (has_uncfg) {
		(*mapp->dam_unconfig_cb)(mapp->dam_config_arg, mapp, &uncfg);
		dam_addrset_release(mapp, &uncfg);
	}
	DAM_LOCK(mapp, MAP_LOCK);
	bitset_zero(&mapp->dam_stable_set);
	DAM_FLAG_CLR(mapp, DAM_SPEND);
	mapp->dam_last_stable = gethrtime();
	mapp->dam_stable_cnt++;
	DAM_INCR_STAT(mapp, dam_stable);
	DAM_UNLOCK(mapp, MAP_LOCK);
	bitset_fini(&uncfg);
	bitset_fini(&cfg);
	bitset_fini(&delta);
	DTRACE_PROBE1(damap__activate__taskq__exit, dam_t, mapp);
}

/*
 * per-address stabilization timeout
 */
static void
dam_addr_stable_cb(void *arg)
{
	dam_t *mapp = (dam_t *)arg;
	int i;
	dam_da_t *passp;
	int spend = 0;
	int tpend = 0;
	int64_t	next_tmov = mapp->dam_stabletmo;
	int64_t tmo_delta;
	int64_t ts = lbolt64;

	DTRACE_PROBE1(damap__addr__stable__cb__entry, dam_t *, mapp);
	DAM_LOCK(mapp, MAP_LOCK);
	if (mapp->dam_tid == 0) {
		DAM_UNLOCK(mapp, MAP_LOCK);
		return;
	}
	mapp->dam_tid = 0;
	/*
	 * If still under stabilization, reschedule timeout,
	 * else dispatch the task to activate & deactivate the stable
	 * set.
	 */
	if (mapp->dam_flags & DAM_SPEND) {
		DAM_INCR_STAT(mapp, dam_stable_blocked);
		mapp->dam_stable_overrun++;
		dam_sched_tmo(mapp, mapp->dam_stabletmo, dam_addr_stable_cb);
		DAM_UNLOCK(mapp, MAP_LOCK);
		DTRACE_PROBE1(damap__addr__stable__cb__overrun,
		    dam_t *, mapp);
		return;
	}

	bitset_copy(&mapp->dam_active_set, &mapp->dam_stable_set);
	for (i = 1; i < mapp->dam_high; i++) {
		if (!bitset_in_set(&mapp->dam_report_set, i))
			continue;
		/*
		 * Stabilize each address
		 */
		passp = ddi_get_soft_state(mapp->dam_da, i);
		ASSERT(passp);
		if (!passp) {
			cmn_err(CE_WARN, "Clearing report no softstate %d", i);
			bitset_del(&mapp->dam_report_set, i);
			continue;
		}

		/* report has stabilized */
		if (passp->da_deadline <= ts) {
			bitset_del(&mapp->dam_report_set, i);
			if (passp->da_flags & DA_RELE) {
				DTRACE_PROBE2(damap__addr__stable__del,
				    dam_t *, mapp, int, i);
				bitset_del(&mapp->dam_stable_set, i);
			} else {
				DTRACE_PROBE2(damap__addr__stable__add,
				    dam_t *, mapp, int, i);
				bitset_add(&mapp->dam_stable_set, i);
			}
			spend++;
			continue;
		}

		/*
		 * not stabilized, determine next (future) map timeout
		 */
		tpend++;
		tmo_delta = passp->da_deadline - ts;
		if (tmo_delta < next_tmov)
			next_tmov = tmo_delta;
	}

	/*
	 * schedule taskq activation of stabilized reports
	 */
	if (spend) {
		if (ddi_taskq_dispatch(mapp->dam_taskqp, dam_activate_taskq,
		    mapp, DDI_NOSLEEP) == DDI_SUCCESS) {
			DAM_FLAG_SET(mapp, DAM_SPEND);
		} else
			tpend++;
	}

	/*
	 * schedule timeout to handle future stabalization of active reports
	 */
	if (tpend)
		dam_sched_tmo(mapp, (clock_t)next_tmov, dam_addr_stable_cb);
	DAM_UNLOCK(mapp, MAP_LOCK);
	DTRACE_PROBE1(damap__addr__stable__cb__exit, dam_t *, mapp);
}

/*
 * fullset stabilization timeout
 */
static void
dam_set_stable_cb(void *arg)
{
	dam_t *mapp = (dam_t *)arg;

	DTRACE_PROBE1(damap__set__stable__cb__enter, dam_t *, mapp);

	DAM_LOCK(mapp, MAP_LOCK);
	if (mapp->dam_tid == 0) {
		DAM_UNLOCK(mapp, MAP_LOCK);
		return;
	}
	mapp->dam_tid = 0;

	/*
	 * If still under stabilization, reschedule timeout,
	 * else dispatch the task to activate & deactivate the stable
	 * set.
	 */
	if (mapp->dam_flags & DAM_SPEND) {
		DAM_INCR_STAT(mapp, dam_stable_blocked);
		mapp->dam_stable_overrun++;
		dam_sched_tmo(mapp, mapp->dam_stabletmo, dam_set_stable_cb);
		DTRACE_PROBE1(damap__set__stable__cb__overrun,
		    dam_t *, mapp);
	} else if (ddi_taskq_dispatch(mapp->dam_taskqp, dam_activate_taskq,
	    mapp, DDI_NOSLEEP) == DDI_FAILURE) {
		dam_sched_tmo(mapp, mapp->dam_stabletmo, dam_set_stable_cb);
	} else {
		bitset_copy(&mapp->dam_report_set, &mapp->dam_stable_set);
		bitset_zero(&mapp->dam_report_set);
		DAM_FLAG_CLR(mapp, DAM_SETADD);
		DAM_FLAG_SET(mapp, DAM_SPEND);
	}
	DAM_UNLOCK(mapp, MAP_LOCK);
	DTRACE_PROBE1(damap__set__stable__cb__exit, dam_t *, mapp);
}

/*
 * reschedule map timeout 'tmo_ms' ticks
 */
static void
dam_sched_tmo(dam_t *mapp, clock_t tmo_ms, void (*tmo_cb)())
{
	timeout_id_t tid;

	if ((tid = mapp->dam_tid) != 0) {
		mapp->dam_tid = 0;
		DAM_UNLOCK(mapp, MAP_LOCK);
		(void) untimeout(tid);
		DAM_LOCK(mapp, MAP_LOCK);
	}

	if (tmo_cb && (tmo_ms != 0))
		mapp->dam_tid = timeout(tmo_cb, mapp, tmo_ms);
}

/*
 * record report addition or removal of an address
 */
static void
dam_add_report(dam_t *mapp, dam_da_t *passp, id_t addrid, int report)
{
	ASSERT(!DAM_IN_REPORT(mapp, addrid));
	passp->da_last_report = gethrtime();
	mapp->dam_last_update = gethrtime();
	passp->da_report_cnt++;
	passp->da_deadline = lbolt64 + mapp->dam_stabletmo;
	if (report == RPT_ADDR_DEL)
		passp->da_flags |= DA_RELE;
	else if (report == RPT_ADDR_ADD)
		passp->da_flags &= ~DA_RELE;
	DAM_LOCK(mapp, MAP_LOCK);
	bitset_add(&mapp->dam_report_set, addrid);
	dam_sched_tmo(mapp, mapp->dam_stabletmo, dam_addr_stable_cb);
	DAM_UNLOCK(mapp, MAP_LOCK);

}

/*
 * release an address report
 */
static void
dam_release_report(dam_t *mapp, id_t addrid)
{
	dam_da_t *passp;

	passp = ddi_get_soft_state(mapp->dam_da, addrid);
	ASSERT(passp);
	passp->da_ppriv_rpt = NULL;
	if (passp->da_nvl_rpt)
		nvlist_free(passp->da_nvl_rpt);
	passp->da_nvl_rpt = NULL;
	DAM_LOCK(mapp, MAP_LOCK);
	bitset_del(&mapp->dam_report_set, addrid);
	DAM_UNLOCK(mapp, MAP_LOCK);
}

/*
 * deactivate a previously stable address
 */
static void
dam_deactivate_addr(dam_t *mapp, id_t addrid)
{
	dam_da_t *passp;

	passp = ddi_get_soft_state(mapp->dam_da, addrid);
	ASSERT(passp);
	if (passp == NULL)
		return;
	DAM_UNLOCK(mapp, ADDR_LOCK);
	if (mapp->dam_deactivate_cb)
		(*mapp->dam_deactivate_cb)(
		    mapp->dam_activate_arg,
		    ddi_strid_id2str(mapp->dam_addr_hash,
		    addrid), addrid, passp->da_ppriv);
	DAM_LOCK(mapp, ADDR_LOCK);
	passp->da_ppriv = NULL;
	if (passp->da_nvl)
		nvlist_free(passp->da_nvl);
	passp->da_nvl = NULL;
}

/*
 * return the map ID of an address
 */
static id_t
dam_get_addrid(dam_t *mapp, char *address)
{
	damap_id_t addrid;
	dam_da_t *passp;

	if ((addrid = ddi_strid_str2id(mapp->dam_addr_hash, address)) == 0) {
		if ((addrid = ddi_strid_fixed_alloc(mapp->dam_addr_hash,
		    address)) == (damap_id_t)0) {
			return (0);
		}
		if (ddi_soft_state_zalloc(mapp->dam_da, addrid) !=
		    DDI_SUCCESS) {
			ddi_strid_free(mapp->dam_addr_hash, addrid);
			return (0);
		}
		if (addrid >= mapp->dam_high)
			mapp->dam_high = addrid + 1;
	}
	passp = ddi_get_soft_state(mapp->dam_da, addrid);
	if (passp == NULL)
		return (0);
	passp->da_ref++;
	if (passp->da_addr == NULL)
		passp->da_addr = ddi_strid_id2str(
		    mapp->dam_addr_hash, addrid); /* for mdb */
	return (addrid);
}

/*
 * create and install map statistics
 */
static int
dam_kstat_create(dam_t *mapp)
{
	kstat_t			*mapsp;
	struct dam_kstats	*statsp;

	mapsp = kstat_create("dam", 0, mapp->dam_name, "damap",
	    KSTAT_TYPE_NAMED,
	    sizeof (struct dam_kstats) / sizeof (kstat_named_t), 0);
	if (mapsp == NULL) {
		return (DDI_FAILURE);
	}

	statsp = (struct dam_kstats *)mapsp->ks_data;
	kstat_named_init(&statsp->dam_stable, "stable cycles",
	    KSTAT_DATA_UINT32);
	kstat_named_init(&statsp->dam_stable_blocked,
	    "stable cycle overrun", KSTAT_DATA_UINT32);
	kstat_named_init(&statsp->dam_rereport,
	    "restarted reports", KSTAT_DATA_UINT32);
	kstat_named_init(&statsp->dam_numstable,
	    "# of stable map entries", KSTAT_DATA_UINT32);
	kstat_install(mapsp);
	mapp->dam_kstatsp = mapsp;
	return (DDI_SUCCESS);
}

/*
 * destroy map stats
 */
static void
dam_kstat_destroy(dam_t *mapp)
{

	kstat_delete(mapp->dam_kstatsp);
}