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

#include <sys/types.h>
#include <sys/param.h>
#include <sys/modctl.h>
#include <sys/sysmacros.h>
#include <sys/kmem.h>
#include <sys/cmn_err.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/spl.h>
#include <sys/time.h>
#include <sys/varargs.h>
#include <ipp/ipp.h>
#include <ipp/ipp_impl.h>
#include <ipp/ipgpc/ipgpc.h>

/*
 * Debug switch.
 */

#if	defined(DEBUG)
#define	IPP_DBG
#endif

/*
 * Globals
 */

/*
 * ipp_action_count is not static because it is imported by inet/ipp_common.h
 */
uint32_t		ipp_action_count = 0;

static kmem_cache_t	*ipp_mod_cache = NULL;
static uint32_t		ipp_mod_count = 0;
static uint32_t		ipp_max_mod = IPP_NMOD;
static ipp_mod_t	**ipp_mod_byid;
static krwlock_t	ipp_mod_byid_lock[1];

static ipp_mod_id_t	ipp_next_mid = IPP_MOD_RESERVED + 1;
static ipp_mod_id_t	ipp_mid_limit;

static ipp_ref_t	*ipp_mod_byname[IPP_NBUCKET];
static krwlock_t	ipp_mod_byname_lock[1];

static kmem_cache_t	*ipp_action_cache = NULL;
static uint32_t		ipp_max_action = IPP_NACTION;
static ipp_action_t	**ipp_action_byid;
static krwlock_t	ipp_action_byid_lock[1];

static ipp_action_id_t	ipp_next_aid = IPP_ACTION_RESERVED + 1;
static ipp_action_id_t	ipp_aid_limit;

static ipp_ref_t	*ipp_action_byname[IPP_NBUCKET];
static krwlock_t	ipp_action_byname_lock[1];
static ipp_ref_t	*ipp_action_noname;

static kmem_cache_t	*ipp_packet_cache = NULL;
static uint_t		ipp_packet_classes = IPP_NCLASS;
static uint_t		ipp_packet_logging = 0;
static uint_t		ipp_packet_log_entries = IPP_NLOG;

/*
 * Prototypes
 */

void			ipp_init(void);

int			ipp_list_mods(ipp_mod_id_t **, int *);

ipp_mod_id_t		ipp_mod_lookup(const char *);
int			ipp_mod_name(ipp_mod_id_t, char **);
int			ipp_mod_register(const char *, ipp_ops_t *);
int			ipp_mod_unregister(ipp_mod_id_t);
int			ipp_mod_list_actions(ipp_mod_id_t, ipp_action_id_t **,
    int *);

ipp_action_id_t		ipp_action_lookup(const char *);
int			ipp_action_name(ipp_action_id_t, char **);
int			ipp_action_mod(ipp_action_id_t, ipp_mod_id_t *);
int			ipp_action_create(ipp_mod_id_t, const char *,
    nvlist_t **, ipp_flags_t, ipp_action_id_t *);
int			ipp_action_modify(ipp_action_id_t, nvlist_t **,
    ipp_flags_t);
int			ipp_action_destroy(ipp_action_id_t, ipp_flags_t);
int			ipp_action_info(ipp_action_id_t, int (*)(nvlist_t *,
    void *), void *, ipp_flags_t);
void			ipp_action_set_ptr(ipp_action_id_t, void *);
void			*ipp_action_get_ptr(ipp_action_id_t);
int			ipp_action_ref(ipp_action_id_t,	ipp_action_id_t,
    ipp_flags_t);
int			ipp_action_unref(ipp_action_id_t, ipp_action_id_t,
    ipp_flags_t);

int			ipp_packet_alloc(ipp_packet_t **, const char *,
    ipp_action_id_t);
void			ipp_packet_free(ipp_packet_t *);
int			ipp_packet_add_class(ipp_packet_t *, const char *,
    ipp_action_id_t);
int			ipp_packet_process(ipp_packet_t **);
int			ipp_packet_next(ipp_packet_t *, ipp_action_id_t);
void			ipp_packet_set_data(ipp_packet_t *, mblk_t *);
mblk_t			*ipp_packet_get_data(ipp_packet_t *);
void			ipp_packet_set_private(ipp_packet_t *, void *,
    void (*)(void *));
void			*ipp_packet_get_private(ipp_packet_t *);

int			ipp_stat_create(ipp_action_id_t, const char *, int,
    int (*)(ipp_stat_t *, void *, int), void *, ipp_stat_t **);
void			ipp_stat_install(ipp_stat_t *);
void			ipp_stat_destroy(ipp_stat_t *);
int			ipp_stat_named_init(ipp_stat_t *, const char *, uchar_t,
    ipp_named_t	*);
int			ipp_stat_named_op(ipp_named_t *, void *, int);

static int		ref_mod(ipp_action_t *, ipp_mod_t *);
static void		unref_mod(ipp_action_t *, ipp_mod_t *);
static int		is_mod_busy(ipp_mod_t *);
static int		get_mod_ref(ipp_mod_t *, ipp_action_id_t **, int *);
static int		get_mods(ipp_mod_id_t **bufp, int *);
static ipp_mod_id_t	find_mod(const char *);
static int		alloc_mod(const char *, ipp_mod_id_t *);
static void		free_mod(ipp_mod_t *);
static ipp_mod_t	*hold_mod(ipp_mod_id_t);
static void		rele_mod(ipp_mod_t *);
static ipp_mod_id_t	get_mid(void);

static int		condemn_action(ipp_ref_t **, ipp_action_t *);
static int		destroy_action(ipp_action_t *, ipp_flags_t);
static int		ref_action(ipp_action_t *, ipp_action_t *);
static int		unref_action(ipp_action_t *, ipp_action_t *);
static int		is_action_refd(ipp_action_t *);
static ipp_action_id_t	find_action(const char *);
static int		alloc_action(const char *, ipp_action_id_t *);
static void		free_action(ipp_action_t *);
static ipp_action_t	*hold_action(ipp_action_id_t);
static void		rele_action(ipp_action_t *);
static ipp_action_id_t	get_aid(void);

static int		alloc_packet(const char *, ipp_action_id_t,
    ipp_packet_t **);
static int		realloc_packet(ipp_packet_t *);
static void		free_packet(ipp_packet_t *);

static int		hash(const char *);
static int		update_stats(kstat_t *, int);
static void		init_mods(void);
static void		init_actions(void);
static void		init_packets(void);
static int		mod_constructor(void *, void *, int);
static void		mod_destructor(void *, void *);
static int		action_constructor(void *, void *, int);
static void		action_destructor(void *, void *);
static int		packet_constructor(void *, void *, int);
static void		packet_destructor(void *, void *);

/*
 * Debug message macros
 */

#ifdef	IPP_DBG

#define	DBG_MOD		0x00000001ull
#define	DBG_ACTION	0x00000002ull
#define	DBG_PACKET	0x00000004ull
#define	DBG_STATS	0x00000008ull
#define	DBG_LIST	0x00000010ull

static uint64_t		ipp_debug_flags =
/*
 * DBG_PACKET |
 * DBG_STATS |
 * DBG_LIST |
 * DBG_MOD |
 * DBG_ACTION |
 */
0;

static kmutex_t	debug_mutex[1];

/*PRINTFLIKE3*/
static void ipp_debug(uint64_t, const char *, char *, ...)
	__KPRINTFLIKE(3);

#define	DBG0(_type, _fmt)		    			\
	ipp_debug((_type), __FN__, (_fmt));

#define	DBG1(_type, _fmt, _a1) 					\
	ipp_debug((_type), __FN__, (_fmt), (_a1));

#define	DBG2(_type, _fmt, _a1, _a2)				\
	ipp_debug((_type), __FN__, (_fmt), (_a1), (_a2));

#define	DBG3(_type, _fmt, _a1, _a2, _a3)			\
	ipp_debug((_type), __FN__, (_fmt), (_a1), (_a2),	\
	    (_a3));

#define	DBG4(_type, _fmt, _a1, _a2, _a3, _a4)			\
	ipp_debug((_type), __FN__, (_fmt), (_a1), (_a2),	\
	    (_a3), (_a4));

#define	DBG5(_type, _fmt, _a1, _a2, _a3, _a4, _a5)		\
	ipp_debug((_type), __FN__, (_fmt), (_a1), (_a2),	\
	    (_a3), (_a4), (_a5));

#else	/* IPP_DBG */

#define	DBG0(_type, _fmt)
#define	DBG1(_type, _fmt, _a1)
#define	DBG2(_type, _fmt, _a1, _a2)
#define	DBG3(_type, _fmt, _a1, _a2, _a3)
#define	DBG4(_type, _fmt, _a1, _a2, _a3, _a4)
#define	DBG5(_type, _fmt, _a1, _a2, _a3, _a4, _a5)

#endif	/* IPP_DBG */

/*
 * Lock macros
 */

#define	LOCK_MOD(_imp, _rw)						\
	rw_enter((_imp)->ippm_lock, (_rw))
#define	UNLOCK_MOD(_imp)						\
	rw_exit((_imp)->ippm_lock)

#define	LOCK_ACTION(_ap, _rw)						\
	rw_enter((_ap)->ippa_lock, (_rw))
#define	UNLOCK_ACTION(_imp)						\
	rw_exit((_imp)->ippa_lock)

#define	CONFIG_WRITE_START(_ap)						\
	CONFIG_LOCK_ENTER((_ap)->ippa_config_lock, CL_WRITE)

#define	CONFIG_WRITE_END(_ap)						\
	CONFIG_LOCK_EXIT((_ap)->ippa_config_lock)

#define	CONFIG_READ_START(_ap)						\
	CONFIG_LOCK_ENTER((_ap)->ippa_config_lock, CL_READ)

#define	CONFIG_READ_END(_ap)						\
	CONFIG_LOCK_EXIT((_ap)->ippa_config_lock)

/*
 * Exported functions
 */

#define	__FN__	"ipp_init"
void
ipp_init(
	void)
{
#ifdef	IPP_DBG
	mutex_init(debug_mutex, NULL, MUTEX_ADAPTIVE,
	    (void *)ipltospl(LOCK_LEVEL));
#endif	/* IPP_DBG */

	/*
	 * Initialize module and action structure caches and associated locks.
	 */

	init_mods();
	init_actions();
	init_packets();
}
#undef	__FN__

#define	__FN__	"ipp_list_mods"
int
ipp_list_mods(
	ipp_mod_id_t	**bufp,
	int		*neltp)
{
	ASSERT(bufp != NULL);
	ASSERT(neltp != NULL);

	return (get_mods(bufp, neltp));
}
#undef	__FN__

/*
 * Module manipulation interface.
 */

#define	__FN__	"ipp_mod_lookup"
ipp_mod_id_t
ipp_mod_lookup(
	const char	*modname)
{
	ipp_mod_id_t	mid;
#define	FIRST_TIME	0
	int		try = FIRST_TIME;

	/*
	 * Sanity check the module name.
	 */

	if (modname == NULL || strlen(modname) > MAXNAMELEN - 1)
		return (IPP_MOD_INVAL);

try_again:
	if ((mid = find_mod(modname)) == IPP_MOD_INVAL) {

		/*
		 * Module not installed.
		 */

		if (try++ == FIRST_TIME) {

			/*
			 * This is the first attempt to find the module so
			 * try to 'demand load' it.
			 */

			DBG1(DBG_MOD, "loading module '%s'\n", modname);
			(void) modload("ipp", (char *)modname);
			goto try_again;
		}
	}

	return (mid);

#undef	FIRST_TIME
}
#undef	__FN__

#define	__FN__	"ipp_mod_name"
int
ipp_mod_name(
	ipp_mod_id_t	mid,
	char		**modnamep)
{
	ipp_mod_t	*imp;
	char		*modname;
	char		*buf;

	ASSERT(modnamep != NULL);

	/*
	 * Translate the module id into the module pointer.
	 */

	if ((imp = hold_mod(mid)) == NULL)
		return (ENOENT);

	LOCK_MOD(imp, RW_READER);
	modname = imp->ippm_name;

	/*
	 * Allocate a buffer to pass back to the caller.
	 */

	if ((buf = kmem_zalloc(strlen(modname) + 1, KM_NOSLEEP)) == NULL) {
		UNLOCK_MOD(imp);
		rele_mod(imp);
		return (ENOMEM);
	}

	/*
	 * Copy the module name into the buffer.
	 */

	(void) strcpy(buf, modname);
	UNLOCK_MOD(imp);

	*modnamep = buf;

	rele_mod(imp);
	return (0);
}
#undef	__FN__

#define	__FN__	"ipp_mod_register"
int
ipp_mod_register(
	const char	*modname,
	ipp_ops_t	*ipp_ops)
{
	ipp_mod_id_t	mid;
	ipp_mod_t	*imp;
	int		rc;

	ASSERT(ipp_ops != NULL);

	/*
	 * Sanity check the module name.
	 */

	if (modname == NULL || strlen(modname) > MAXNAMELEN - 1)
		return (EINVAL);

	/*
	 * Allocate a module structure.
	 */

	if ((rc = alloc_mod(modname, &mid)) != 0)
		return (rc);

	imp = hold_mod(mid);
	ASSERT(imp != NULL);

	/*
	 * Make module available for use.
	 */

	LOCK_MOD(imp, RW_WRITER);
	DBG1(DBG_MOD, "registering module '%s'\n", imp->ippm_name);
	imp->ippm_ops = ipp_ops;
	imp->ippm_state = IPP_MODSTATE_AVAILABLE;
	UNLOCK_MOD(imp);

	rele_mod(imp);
	return (0);
}
#undef	__FN__

#define	__FN__	"ipp_mod_unregister"
int
ipp_mod_unregister(
	ipp_mod_id_t	mid)
{
	ipp_mod_t	*imp;

	/*
	 * Translate the module id into the module pointer.
	 */

	if ((imp = hold_mod(mid)) == NULL)
		return (ENOENT);

	LOCK_MOD(imp, RW_WRITER);
	ASSERT(imp->ippm_state == IPP_MODSTATE_AVAILABLE);

	/*
	 * Check to see if there are any actions that reference the module.
	 */

	if (is_mod_busy(imp)) {
		UNLOCK_MOD(imp);
		rele_mod(imp);
		return (EBUSY);
	}

	/*
	 * Prevent further use of the module.
	 */

	DBG1(DBG_MOD, "unregistering module '%s'\n", imp->ippm_name);
	imp->ippm_state = IPP_MODSTATE_PROTO;
	imp->ippm_ops = NULL;
	UNLOCK_MOD(imp);

	/*
	 * Free the module structure.
	 */

	free_mod(imp);
	rele_mod(imp);

	return (0);
}
#undef	__FN__

#define	__FN__	"ipp_mod_list_actions"
int
ipp_mod_list_actions(
	ipp_mod_id_t	mid,
	ipp_action_id_t	**bufp,
	int		*neltp)
{
	ipp_mod_t	*imp;
	int		rc;

	ASSERT(bufp != NULL);
	ASSERT(neltp != NULL);

	/*
	 * Translate the module id into the module pointer.
	 */

	if ((imp = hold_mod(mid)) == NULL)
		return (ENOENT);

	/*
	 * Get the list of actions referencing the module.
	 */

	LOCK_MOD(imp, RW_READER);
	rc = get_mod_ref(imp, bufp, neltp);
	UNLOCK_MOD(imp);

	rele_mod(imp);
	return (rc);
}
#undef	__FN__

/*
 * Action manipulation interface.
 */

#define	__FN__	"ipp_action_lookup"
ipp_action_id_t
ipp_action_lookup(
	const char	*aname)
{
	if (aname == NULL)
		return (IPP_ACTION_INVAL);

	/*
	 * Check for special case 'virtual action' names.
	 */

	if (strcmp(aname, IPP_ANAME_CONT) == 0)
		return (IPP_ACTION_CONT);
	else if (strcmp(aname, IPP_ANAME_DEFER) == 0)
		return (IPP_ACTION_DEFER);
	else if (strcmp(aname, IPP_ANAME_DROP) == 0)
		return (IPP_ACTION_DROP);

	/*
	 * Now check real actions.
	 */

	return (find_action(aname));
}
#undef	__FN__

#define	__FN__	"ipp_action_name"
int
ipp_action_name(
	ipp_action_id_t	aid,
	char		**anamep)
{
	ipp_action_t	*ap;
	char		*aname;
	char		*buf;
	int		rc;

	ASSERT(anamep != NULL);

	/*
	 * Check for special case 'virtual action' ids.
	 */

	switch (aid) {
	case IPP_ACTION_CONT:
		ap = NULL;
		aname = IPP_ANAME_CONT;
		break;
	case IPP_ACTION_DEFER:
		ap = NULL;
		aname = IPP_ANAME_DEFER;
		break;
	case IPP_ACTION_DROP:
		ap = NULL;
		aname = IPP_ANAME_DROP;
		break;
	default:

		/*
		 * Not a special case. Check for a real action.
		 */

		if ((ap = hold_action(aid)) == NULL)
			return (ENOENT);

		LOCK_ACTION(ap, RW_READER);
		aname = ap->ippa_name;
		break;
	}

	/*
	 * Allocate a buffer to pass back to the caller.
	 */

	if ((buf = kmem_zalloc(strlen(aname) + 1, KM_NOSLEEP)) == NULL) {
		rc = ENOMEM;
		goto done;
	}

	/*
	 * Copy the action name into the buffer.
	 */

	(void) strcpy(buf, aname);
	*anamep = buf;
	rc = 0;
done:
	/*
	 * Unlock the action if necessary (i.e. it wasn't a virtual action).
	 */

	if (ap != NULL) {
		UNLOCK_ACTION(ap);
		rele_action(ap);
	}

	return (rc);
}
#undef	__FN__

#define	__FN__	"ipp_action_mod"
int
ipp_action_mod(
	ipp_action_id_t	aid,
	ipp_mod_id_t	*midp)
{
	ipp_action_t	*ap;
	ipp_mod_t	*imp;

	ASSERT(midp != NULL);

	/*
	 * Return an error for  'virtual action' ids.
	 */

	switch (aid) {
	case IPP_ACTION_CONT:
	/*FALLTHRU*/
	case IPP_ACTION_DEFER:
	/*FALLTHRU*/
	case IPP_ACTION_DROP:
		return (EINVAL);
	default:
		break;
	}

	/*
	 * This is a real action.
	 */

	if ((ap = hold_action(aid)) == NULL)
		return (ENOENT);

	/*
	 * Check that the action is not in prototype state.
	 */

	LOCK_ACTION(ap, RW_READER);
	if (ap->ippa_state == IPP_ASTATE_PROTO) {
		UNLOCK_ACTION(ap);
		rele_action(ap);
		return (ENOENT);
	}

	imp = ap->ippa_mod;
	ASSERT(imp != NULL);
	UNLOCK_ACTION(ap);

	*midp = imp->ippm_id;

	rele_action(ap);
	return (0);
}
#undef	__FN__

#define	__FN__	"ipp_action_create"
int
ipp_action_create(
	ipp_mod_id_t	mid,
	const char	*aname,
	nvlist_t	**nvlpp,
	ipp_flags_t	flags,
	ipp_action_id_t	*aidp)
{
	ipp_ops_t	*ippo;
	ipp_mod_t	*imp;
	ipp_action_id_t	aid;
	ipp_action_t	*ap;
	int		rc;

	ASSERT(nvlpp != NULL);
	ASSERT(*nvlpp != NULL);

	/*
	 * Sanity check the action name (NULL means the framework chooses the
	 * name).
	 */

	if (aname != NULL && strlen(aname) > MAXNAMELEN - 1)
		return (EINVAL);

	/*
	 * Translate the module id into the module pointer.
	 */

	if ((imp = hold_mod(mid)) == NULL)
		return (ENOENT);

	/*
	 * Allocate an action.
	 */

	if ((rc = alloc_action(aname, &aid)) != 0) {
		rele_mod(imp);
		return (rc);
	}

	ap = hold_action(aid);
	ASSERT(ap != NULL);

	/*
	 * Note that the action is in the process of creation/destruction.
	 */

	LOCK_ACTION(ap, RW_WRITER);
	ap->ippa_state = IPP_ASTATE_CONFIG_PENDING;

	/*
	 * Reference the module for which the action is being created.
	 */

	LOCK_MOD(imp, RW_WRITER);
	if ((rc = ref_mod(ap, imp)) != 0) {
		UNLOCK_MOD(imp);
		ap->ippa_state = IPP_ASTATE_PROTO;
		UNLOCK_ACTION(ap);

		free_action(ap);
		rele_action(ap);
		rele_mod(imp);
		return (rc);
	}

	UNLOCK_ACTION(ap);

	ippo = imp->ippm_ops;
	ASSERT(ippo != NULL);
	UNLOCK_MOD(imp);

	/*
	 * Call into the module to create the action context.
	 */

	CONFIG_WRITE_START(ap);
	DBG2(DBG_ACTION, "creating action '%s' in module '%s'\n",
	    ap->ippa_name, imp->ippm_name);
	if ((rc = ippo->ippo_action_create(ap->ippa_id, nvlpp, flags)) != 0) {
		LOCK_ACTION(ap, RW_WRITER);
		LOCK_MOD(imp, RW_WRITER);
		unref_mod(ap, imp);
		UNLOCK_MOD(imp);
		ap->ippa_state = IPP_ASTATE_PROTO;
		UNLOCK_ACTION(ap);

		CONFIG_WRITE_END(ap);

		free_action(ap);
		rele_action(ap);
		rele_mod(imp);
		return (rc);
	}
	CONFIG_WRITE_END(ap);

	/*
	 * Make the action available for use.
	 */

	LOCK_ACTION(ap, RW_WRITER);
	ap->ippa_state = IPP_ASTATE_AVAILABLE;
	if (aidp != NULL)
		*aidp = ap->ippa_id;
	UNLOCK_ACTION(ap);

	rele_action(ap);
	rele_mod(imp);
	return (0);
}
#undef	__FN__

#define	__FN__	"ipp_action_destroy"
int
ipp_action_destroy(
	ipp_action_id_t	aid,
	ipp_flags_t	flags)
{
	ipp_ref_t	*rp = NULL;
	ipp_ref_t	*tmp;
	ipp_action_t	*ap;
	int		rc;

	/*
	 * Translate the action id into the action pointer.
	 */

	if ((ap = hold_action(aid)) == NULL)
		return (ENOENT);

	/*
	 * Set the condemned action list pointer and destroy the action.
	 */

	ap->ippa_condemned = &rp;
	if ((rc = destroy_action(ap, flags)) == 0) {

		/*
		 * Destroy any other actions condemned by the destruction of
		 * the first action.
		 */

		for (tmp = rp; tmp != NULL; tmp = tmp->ippr_nextp) {
			ap = tmp->ippr_action;
			ap->ippa_condemned = &rp;
			(void) destroy_action(ap, flags);
		}
	} else {

		/*
		 * Unreference any condemned actions since the destruction of
		 * the first action failed.
		 */

		for (tmp = rp; tmp != NULL; tmp = tmp->ippr_nextp) {
			ap = tmp->ippr_action;
			rele_action(ap);
		}
	}

	/*
	 * Clean up the condemned list.
	 */

	while (rp != NULL) {
		tmp = rp;
		rp = rp->ippr_nextp;
		kmem_free(tmp, sizeof (ipp_ref_t));
	}

	return (rc);
}
#undef	__FN__

#define	__FN__	"ipp_action_modify"
int
ipp_action_modify(
	ipp_action_id_t	aid,
	nvlist_t	**nvlpp,
	ipp_flags_t	flags)
{
	ipp_action_t	*ap;
	ipp_ops_t	*ippo;
	ipp_mod_t	*imp;
	int		rc;

	ASSERT(nvlpp != NULL);
	ASSERT(*nvlpp != NULL);

	/*
	 * Translate the action id into the action pointer.
	 */

	if ((ap = hold_action(aid)) == NULL)
		return (ENOENT);

	/*
	 * Check that the action is either available for use or is in the
	 * process of creation/destruction.
	 *
	 * NOTE: It is up to the module to lock multiple configuration
	 *	 operations against each other if necessary.
	 */

	LOCK_ACTION(ap, RW_READER);
	if (ap->ippa_state != IPP_ASTATE_AVAILABLE &&
	    ap->ippa_state != IPP_ASTATE_CONFIG_PENDING) {
		UNLOCK_ACTION(ap);
		rele_action(ap);
		return (EPROTO);
	}

	imp = ap->ippa_mod;
	ASSERT(imp != NULL);
	UNLOCK_ACTION(ap);

	ippo = imp->ippm_ops;
	ASSERT(ippo != NULL);

	/*
	 * Call into the module to modify the action context.
	 */

	DBG1(DBG_ACTION, "modifying action '%s'\n", ap->ippa_name);
	CONFIG_WRITE_START(ap);
	rc = ippo->ippo_action_modify(aid, nvlpp, flags);
	CONFIG_WRITE_END(ap);

	rele_action(ap);
	return (rc);
}
#undef	__FN__

#define	__FN__	"ipp_action_info"
int
ipp_action_info(
	ipp_action_id_t	aid,
	int		(*fn)(nvlist_t *, void *),
	void		*arg,
	ipp_flags_t    	flags)
{
	ipp_action_t	*ap;
	ipp_mod_t	*imp;
	ipp_ops_t	*ippo;
	int		rc;

	/*
	 * Translate the action id into the action pointer.
	 */

	if ((ap = hold_action(aid)) == NULL)
		return (ENOENT);

	/*
	 * Check that the action is available for use. We don't want to
	 * read back parameters while the action is in the process of
	 * creation/destruction.
	 */

	LOCK_ACTION(ap, RW_READER);
	if (ap->ippa_state != IPP_ASTATE_AVAILABLE) {
		UNLOCK_ACTION(ap);
		rele_action(ap);
		return (EPROTO);
	}

	imp = ap->ippa_mod;
	ASSERT(imp != NULL);
	UNLOCK_ACTION(ap);

	ippo = imp->ippm_ops;
	ASSERT(ippo != NULL);

	/*
	 * Call into the module to get the action configuration information.
	 */

	DBG1(DBG_ACTION,
	    "getting configuration information from action '%s'\n",
	    ap->ippa_name);
	CONFIG_READ_START(ap);
	if ((rc = ippo->ippo_action_info(aid, fn, arg, flags)) != 0) {
		CONFIG_READ_END(ap);
		rele_action(ap);
		return (rc);
	}
	CONFIG_READ_END(ap);

	rele_action(ap);
	return (0);
}
#undef	__FN__

#define	__FN__	"ipp_action_set_ptr"
void
ipp_action_set_ptr(
	ipp_action_id_t	aid,
	void		*ptr)
{
	ipp_action_t	*ap;

	/*
	 * Translate the action id into the action pointer.
	 */

	ap = hold_action(aid);
	ASSERT(ap != NULL);

	/*
	 * Set the private data pointer.
	 */

	ap->ippa_ptr = ptr;
	rele_action(ap);
}
#undef	__FN__

#define	__FN__	"ipp_action_get_ptr"
void *
ipp_action_get_ptr(
	ipp_action_id_t	aid)
{
	ipp_action_t	*ap;
	void		*ptr;

	/*
	 * Translate the action id into the action pointer.
	 */

	ap = hold_action(aid);
	ASSERT(ap != NULL);

	/*
	 * Return the private data pointer.
	 */

	ptr = ap->ippa_ptr;
	rele_action(ap);

	return (ptr);
}
#undef	__FN__

#define	__FN__	"ipp_action_ref"
/*ARGSUSED*/
int
ipp_action_ref(
	ipp_action_id_t	aid,
	ipp_action_id_t	ref_aid,
	ipp_flags_t	flags)
{
	ipp_action_t	*ap;
	ipp_action_t	*ref_ap;
	int		rc;

	/*
	 * Actions are not allowed to reference themselves.
	 */

	if (aid == ref_aid)
		return (EINVAL);

	/*
	 * Check for a special case 'virtual action' id.
	 */

	switch (ref_aid) {
	case IPP_ACTION_CONT:
	/*FALLTHRU*/
	case IPP_ACTION_DEFER:
	/*FALLTHRU*/
	case IPP_ACTION_DROP:
		return (0);
	default:
		break;
	}

	/*
	 * Translate the action ids into action pointers.
	 */

	if ((ap = hold_action(aid)) == NULL)
		return (ENOENT);

	if ((ref_ap = hold_action(ref_aid)) == NULL) {
		rele_action(ap);
		return (ENOENT);
	}

	LOCK_ACTION(ap, RW_WRITER);
	LOCK_ACTION(ref_ap, RW_WRITER);

	if (ref_ap->ippa_state != IPP_ASTATE_AVAILABLE) {
		UNLOCK_ACTION(ref_ap);
		UNLOCK_ACTION(ap);

		rele_action(ref_ap);
		rele_action(ap);
		return (EPROTO);
	}

	/*
	 * Create references between the two actions.
	 */

	rc = ref_action(ap, ref_ap);
	UNLOCK_ACTION(ref_ap);
	UNLOCK_ACTION(ap);

	rele_action(ref_ap);
	rele_action(ap);
	return (rc);
}
#undef	__FN__

#define	__FN__	"ipp_action_unref"
int
ipp_action_unref(
	ipp_action_id_t	aid,
	ipp_action_id_t	ref_aid,
	ipp_flags_t	flags)
{
	ipp_action_t	*ap;
	ipp_action_t	*ref_ap;
	int		ref_is_busy;
	int		rc;

	if (aid == ref_aid)
		return (EINVAL);

	/*
	 * Check for a special case 'virtual action' id.
	 */

	switch (ref_aid) {
	case IPP_ACTION_CONT:
	/*FALLTHRU*/
	case IPP_ACTION_DEFER:
	/*FALLTHRU*/
	case IPP_ACTION_DROP:
		return (0);
	default:
		break;
	}

	/*
	 * Translate the action ids into action pointers.
	 */

	if ((ap = hold_action(aid)) == NULL)
		return (ENOENT);

	if ((ref_ap = hold_action(ref_aid)) == NULL) {
		rele_action(ap);
		return (ENOENT);
	}

	LOCK_ACTION(ap, RW_WRITER);
	LOCK_ACTION(ref_ap, RW_WRITER);

	/*
	 * Remove the reference between the actions.
	 */

	if ((rc = unref_action(ap, ref_ap)) != 0) {
		UNLOCK_ACTION(ref_ap);
		UNLOCK_ACTION(ap);
		rele_action(ref_ap);
		rele_action(ap);
		return (rc);
	}

	ref_is_busy = is_action_refd(ref_ap);

	UNLOCK_ACTION(ref_ap);
	UNLOCK_ACTION(ap);

	if (flags & IPP_DESTROY_REF) {
		if (!ref_is_busy) {

			/*
			 * Condemn the action so that it will be destroyed.
			 */

			(void) condemn_action(ap->ippa_condemned, ref_ap);
			return (0);
		}
	}

	rele_action(ref_ap);
	rele_action(ap);
	return (0);
}
#undef	__FN__

/*
 * Packet manipulation interface.
 */

#define	__FN__	"ipp_packet_alloc"
int
ipp_packet_alloc(
	ipp_packet_t	**ppp,
	const char	*name,
	ipp_action_id_t	aid)
{
	ipp_packet_t	*pp;
	int		rc;

	ASSERT(ppp != NULL);

	/*
	 * A name is required.
	 */

	if (name == NULL || strlen(name) > MAXNAMELEN - 1)
		return (EINVAL);

	/*
	 * Allocate a packet structure from the cache.
	 */

	if ((rc = alloc_packet(name, aid, &pp)) != 0)
		return (rc);

	if (ipp_packet_logging != 0 && pp->ippp_log == NULL) {

		/*
		 * Logging is turned on but there's no log buffer. We need
		 * to allocate one.
		 */
		if ((pp->ippp_log = kmem_alloc(
		    ipp_packet_log_entries * sizeof (ipp_log_t),
		    KM_NOSLEEP)) != NULL) {
			pp->ippp_log_limit = ipp_packet_log_entries - 1;
			pp->ippp_log_windex = 0;
		}
	} else if (ipp_packet_logging == 0 && pp->ippp_log != NULL) {

		/*
		 * A log buffer is present but logging has been turned off.
		 * Free the buffer now,
		 */

		kmem_free(pp->ippp_log,
		    (pp->ippp_log_limit + 1) * sizeof (ipp_log_t));
		pp->ippp_log = NULL;
		pp->ippp_log_limit = 0;
		pp->ippp_log_windex = 0;
	}

	*ppp = pp;
	return (0);
}
#undef	__FN__

#define	__FN__	"ipp_packet_free"
void
ipp_packet_free(
	ipp_packet_t	*pp)
{

	ASSERT(pp != NULL);

	/*
	 * If there is a private structure pointer set, call its free
	 * function.
	 */

	if (pp->ippp_private) {
		pp->ippp_private_free(pp->ippp_private);
		pp->ippp_private = NULL;
		pp->ippp_private_free = NULL;
	}

	/*
	 * Free the packet structure back to the cache.
	 */

	free_packet(pp);
}
#undef	__FN__

#define	__FN__	"ipp_packet_add_class"
int
ipp_packet_add_class(
	ipp_packet_t	*pp,
	const char	*name,
	ipp_action_id_t	aid)
{
	ipp_class_t	*cp;
	int		rc;

	ASSERT(pp != NULL);

	/*
	 * A name is required.
	 */

	if (name == NULL || strlen(name) > MAXNAMELEN - 1)
		return (EINVAL);

	/*
	 * Check if there is an available class structure.
	 */

	if (pp->ippp_class_windex == pp->ippp_class_limit) {

		/*
		 * No more structures. Re-allocate the array.
		 */

		if ((rc = realloc_packet(pp)) != 0)
			return (rc);
	}
	ASSERT(pp->ippp_class_windex < pp->ippp_class_limit);

	/*
	 * Set up a new class structure.
	 */

	cp = &(pp->ippp_class_array[pp->ippp_class_windex++]);
	(void) strcpy(cp->ippc_name, name);
	cp->ippc_aid = aid;

	return (0);
}
#undef	__FN__

#define	__FN__	"ipp_packet_process"
int
ipp_packet_process(
	ipp_packet_t	**ppp)
{
	ipp_packet_t	*pp;
	ipp_action_id_t	aid;
	ipp_class_t	*cp;
	ipp_log_t	*lp;
	ipp_action_t	*ap;
	ipp_mod_t	*imp;
	ipp_ops_t	*ippo;
	int		rc;

	ASSERT(ppp != NULL);
	pp = *ppp;
	ASSERT(pp != NULL);

	/*
	 * Walk the class list.
	 */

	while (pp->ippp_class_rindex < pp->ippp_class_windex) {
		cp = &(pp->ippp_class_array[pp->ippp_class_rindex]);

		/*
		 * While there is a real action to invoke...
		 */

		aid = cp->ippc_aid;
		while (aid != IPP_ACTION_CONT &&
		    aid != IPP_ACTION_DEFER &&
		    aid != IPP_ACTION_DROP) {

			ASSERT(aid != IPP_ACTION_INVAL);

			/*
			 * Translate the action id to the action pointer.
			 */

			if ((ap = hold_action(aid)) == NULL) {
				DBG1(DBG_PACKET,
				    "action id '%d' not found\n", aid);
				return (ENOENT);
			}

			/*
			 * Check that the action is available for use...
			 */
			LOCK_ACTION(ap, RW_READER);
			if (ap->ippa_state != IPP_ASTATE_AVAILABLE) {
				UNLOCK_ACTION(ap);
				rele_action(ap);
				return (EPROTO);
			}

			/*
			 * Increment the action's packet count to note that
			 * it's being used.
			 *
			 * NOTE: We only have a read lock, so we need to use
			 *	 atomic_add_32(). The read lock is still
			 *	 important though as it is crucial to block
			 *	 out a destroy operation between the action
			 *	 state being checked and the packet count
			 *	 being incremented.
			 */

			atomic_inc_32(&(ap->ippa_packets));

			imp = ap->ippa_mod;
			ASSERT(imp != NULL);
			UNLOCK_ACTION(ap);

			ippo = imp->ippm_ops;
			ASSERT(ippo != NULL);

			/*
			 * If there's a log, grab the next entry and fill it
			 * in.
			 */

			if (pp->ippp_log != NULL &&
			    pp->ippp_log_windex <= pp->ippp_log_limit) {
				lp = &(pp->ippp_log[pp->ippp_log_windex++]);
				lp->ippl_aid = aid;
				(void) strcpy(lp->ippl_name, cp->ippc_name);
				gethrestime(&lp->ippl_begin);
			} else {
				lp = NULL;
			}

			/*
			 * Invoke the action.
			 */

			rc = ippo->ippo_action_invoke(aid, pp);

			/*
			 * Also log the time that the action finished
			 * processing.
			 */

			if (lp != NULL)
				gethrestime(&lp->ippl_end);

			/*
			 * Decrement the packet count.
			 */

			atomic_dec_32(&(ap->ippa_packets));

			/*
			 * If the class' action id is the same now as it was
			 * before then clearly no 'next action' has been set.
			 * This is a protocol error.
			 */

			if (cp->ippc_aid == aid) {
				DBG1(DBG_PACKET,
				    "action '%s' did not set next action\n",
				    ap->ippa_name);
				rele_action(ap);
				return (EPROTO);
			}

			/*
			 * The action did not complete successfully. Terminate
			 * packet processing.
			 */

			if (rc != 0) {
				DBG2(DBG_PACKET,
				    "action error '%d' from action '%s'\n",
				    rc, ap->ippa_name);
				rele_action(ap);
				return (rc);
			}

			rele_action(ap);

			/*
			 * Look at the next action.
			 */

			aid = cp->ippc_aid;
		}

		/*
		 * No more real actions to invoke, check for 'virtual' ones.
		 */

		/*
		 * Packet deferred: module has held onto packet for processing
		 * later.
		 */

		if (cp->ippc_aid == IPP_ACTION_DEFER) {
			*ppp = NULL;
			return (0);
		}

		/*
		 * Packet dropped: free the packet and discontinue processing.
		 */

		if (cp->ippc_aid == IPP_ACTION_DROP) {
			freemsg(pp->ippp_data);
			ipp_packet_free(pp);
			*ppp = NULL;
			return (0);
		}

		/*
		 * Must be 'continue processing': move onto the next class.
		 */

		ASSERT(cp->ippc_aid == IPP_ACTION_CONT);
		pp->ippp_class_rindex++;
	}

	return (0);
}
#undef	__FN__

#define	__FN__	"ipp_packet_next"
int
ipp_packet_next(
	ipp_packet_t	*pp,
	ipp_action_id_t	aid)
{
	ipp_action_t	*ap;
	ipp_class_t	*cp;

	ASSERT(pp != NULL);

	cp = &(pp->ippp_class_array[pp->ippp_class_rindex]);
	ASSERT(cp != NULL);

	/*
	 * Check for a special case 'virtual action' id.
	 */

	switch (aid) {
	case IPP_ACTION_INVAL:
		return (EINVAL);
	case IPP_ACTION_DEFER:
	/*FALLTHRU*/
	case IPP_ACTION_CONT:
	/*FALLTHRU*/
	case IPP_ACTION_DROP:
		break;
	default:

		/*
		 * Not a virtual action so try to translate the action id
		 * into the action pointer to confirm the actions existence.
		 */

		if ((ap = hold_action(aid)) == NULL) {
			DBG0(DBG_PACKET, "invalid action\n");
			return (ENOENT);
		}
		rele_action(ap);

		break;
	}

	/*
	 * Set the class' new action id.
	 */

	cp->ippc_aid = aid;

	return (0);
}
#undef	__FN__

#define	__FN__	"ipp_packet_set_data"
void
ipp_packet_set_data(
	ipp_packet_t	*pp,
	mblk_t		*data)
{
	ASSERT(pp != NULL);
	pp->ippp_data = data;
}
#undef	__FN__

#define	__FN__	"ipp_packet_get_data"
mblk_t *
ipp_packet_get_data(
	ipp_packet_t	*pp)
{
	ASSERT(pp != NULL);
	return (pp->ippp_data);
}
#undef	__FN__

#define	__FN__	"ipp_packet_set_private"
void
ipp_packet_set_private(
	ipp_packet_t	*pp,
	void		*buf,
	void		(*free_func)(void *))
{
	ASSERT(pp != NULL);
	ASSERT(free_func != NULL);

	pp->ippp_private = buf;
	pp->ippp_private_free = free_func;
}
#undef	__FN__

#define	__FN__	"ipp_packet_get_private"
void *
ipp_packet_get_private(
	ipp_packet_t	*pp)
{
	ASSERT(pp != NULL);
	return (pp->ippp_private);
}
#undef	__FN__

/*
 * Statistics interface.
 */

#define	__FN__	"ipp_stat_create"
int
ipp_stat_create(
	ipp_action_id_t	aid,
	const char	*name,
	int		nstat,
	int		(*update)(ipp_stat_t *, void *, int),
	void		*arg,
	ipp_stat_t	**spp)
{
	ipp_action_t	*ap;
	ipp_mod_t	*imp;
	ipp_stat_impl_t	*sip;
	ipp_stat_t	*sp;
	kstat_t		*ksp;
	char		*class;
	char		*modname;
	int		instance;

	ASSERT(spp != NULL);

	/*
	 * Sanity check the arguments.
	 */

	if (name == NULL || nstat <= 0 || update == NULL)
		return (EINVAL);

	/*
	 * Translate the action id into the action pointer.
	 */

	if ((ap = hold_action(aid)) == NULL)
		return (ENOENT);

	/*
	 * Grab relevant action and module information.
	 */

	LOCK_ACTION(ap, RW_READER);
	class = ap->ippa_name;
	instance = (int)ap->ippa_id;

	imp = ap->ippa_mod;
	ASSERT(imp != NULL);

	LOCK_MOD(imp, RW_READER);
	modname = imp->ippm_name;

	/*
	 * Allocate a stats info structure.
	 */

	if ((sip = kmem_alloc(sizeof (ipp_stat_impl_t), KM_NOSLEEP)) == NULL)
		return (ENOMEM);

	/*
	 * Create a set of kstats.
	 */

	DBG2(DBG_STATS, "creating stat set '%s' for action '%s'\n",
	    name, class);
	if ((ksp = kstat_create(modname, instance, name, class,
	    KSTAT_TYPE_NAMED, nstat, KSTAT_FLAG_WRITABLE)) == NULL) {
		kmem_free(sip, sizeof (ipp_stat_impl_t));
		UNLOCK_ACTION(ap);
		UNLOCK_MOD(imp);
		return (EINVAL);	/* Assume EINVAL was the cause */
	}

	UNLOCK_ACTION(ap);
	UNLOCK_MOD(imp);

	DBG1(DBG_STATS, "ks_data = %p\n", ksp->ks_data);

	/*
	 * Set up the kstats structure with a private data pointer and an
	 * 'update' function.
	 */

	ksp->ks_update = update_stats;
	ksp->ks_private = (void *)sip;

	/*
	 * Keep a reference to the kstats structure in our own stats info
	 * structure.
	 */

	sip->ippsi_ksp = ksp;
	sip->ippsi_data = ksp->ks_data;

	/*
	 * Fill in the rest of the stats info structure.
	 */

	(void) strcpy(sip->ippsi_name, name);
	sip->ippsi_arg = arg;
	sip->ippsi_update = update;
	sip->ippsi_limit = nstat;
	sip->ippsi_count = 0;
	mutex_init(sip->ippsi_lock, NULL, MUTEX_ADAPTIVE,
	    (void *)ipltospl(LOCK_LEVEL));

	/*
	 * Case the stats info structure to a semi-opaque structure that
	 * we pass back to the caller.
	 */

	sp = (ipp_stat_t *)sip;
	ASSERT(sp->ipps_data == sip->ippsi_data);
	*spp = sp;

	rele_action(ap);
	return (0);
}
#undef __FN__

#define	__FN__	"ipp_stat_install"
void
ipp_stat_install(
	ipp_stat_t	*sp)
{
	ipp_stat_impl_t	*sip = (ipp_stat_impl_t *)sp;

	ASSERT(sp != NULL);

	/*
	 * Install the set of kstats referenced by the stats info structure.
	 */

	DBG1(DBG_STATS, "installing stat set '%s'\n", sip->ippsi_name);
	kstat_install(sip->ippsi_ksp);
}
#undef	__FN__

#define	__FN__	"ipp_stat_destroy"
void
ipp_stat_destroy(
	ipp_stat_t	*sp)
{
	ipp_stat_impl_t	*sip = (ipp_stat_impl_t *)sp;

	ASSERT(sp != NULL);

	/*
	 * Destroy the set of kstats referenced by the stats info structure.
	 */

	DBG1(DBG_STATS, "destroying stat set '%s'\n", sip->ippsi_name);
	kstat_delete(sip->ippsi_ksp);

	/*
	 * Destroy the stats info structure itself.
	 */

	mutex_destroy(sip->ippsi_lock);
	kmem_free(sip, sizeof (ipp_stat_impl_t));
}
#undef	__FN__

#define	__FN__	"ipp_stat_named_init"
int
ipp_stat_named_init(
	ipp_stat_t	*sp,
	const char	*name,
	uchar_t		type,
	ipp_named_t	*np)
{
	ipp_stat_impl_t	*sip = (ipp_stat_impl_t *)sp;
	uchar_t		ktype;

	ASSERT(sp != NULL);
	ASSERT(np != NULL);

	if (name == NULL)
		return (EINVAL);

	if ((type & IPP_STAT_TAG) == 0)
		return (EINVAL);
	ktype = type & ~IPP_STAT_TAG;

	/*
	 * Check we will not exceed the maximum number of a stats that was
	 * indicated during set creation.
	 */

	mutex_enter(sip->ippsi_lock);
	if (sip->ippsi_count >= sip->ippsi_limit) {
		mutex_exit(sip->ippsi_lock);
		return (ENOSPC);
	}

	/*
	 * Bump the count.
	 */

	sip->ippsi_count++;

	/*
	 * Create a new named kstat.
	 */

	DBG3(DBG_STATS, "%s.%s: knp = %p\n", sip->ippsi_name, name, np);
	kstat_named_init(np, name, ktype);
	mutex_exit(sip->ippsi_lock);

	return (0);
}
#undef	__FN__

#define	__FN__	"ipp_stat_named_op"
int
ipp_stat_named_op(
	ipp_named_t	*np,
	void		*valp,
	int		rw)
{
	kstat_named_t	*knp;
	uchar_t		type;
	int		rc = 0;

	ASSERT(np != NULL);
	ASSERT(valp != NULL);

	knp = np;
	type = knp->data_type | IPP_STAT_TAG;

	/*
	 * Copy data to or from the named kstat, depending on the specified
	 * opcode.
	 */

	switch (rw) {
	case IPP_STAT_WRITE:
		switch (type) {
		case IPP_STAT_INT32:
			*(int32_t *)valp = knp->value.i32;
			break;
		case IPP_STAT_UINT32:
			*(uint32_t *)valp = knp->value.ui32;
			break;
		case IPP_STAT_INT64:
			*(int64_t *)valp = knp->value.i64;
			break;
		case IPP_STAT_UINT64:
			*(uint64_t *)valp = knp->value.ui64;
			break;
		case IPP_STAT_STRING:
			(void) strncpy(valp, knp->value.c, 16);
			break;
		default:
			ASSERT(0);	/* should not reach here */
			break;
		}

		break;
	case IPP_STAT_READ:
		switch (type) {
		case IPP_STAT_INT32:
			knp->value.i32 = *(int32_t *)valp;
			break;
		case IPP_STAT_UINT32:
			knp->value.ui32 = *(uint32_t *)valp;
			break;
		case IPP_STAT_INT64:
			knp->value.i64 = *(int64_t *)valp;
			break;
		case IPP_STAT_UINT64:
			knp->value.ui64 = *(uint64_t *)valp;
			break;
		case IPP_STAT_STRING:
			(void) strncpy(knp->value.c, valp, 16);
			break;
		default:
			ASSERT(0);	/* should not reach here */
			break;
		}

		break;
	default:
		rc = EINVAL;
	}

	return (rc);
}
#undef	__FN__

/*
 * Local functions (for local people. There's nothing for you here!)
 */

#define	__FN__	"ref_mod"
static int
ref_mod(
	ipp_action_t	*ap,
	ipp_mod_t	*imp)
{
	ipp_ref_t	**rpp;
	ipp_ref_t	*rp;

	ASSERT(rw_write_held(ap->ippa_lock));
	ASSERT(rw_write_held(imp->ippm_lock));

	/*
	 * Add the new reference at the end of the module's list.
	 */

	rpp = &(imp->ippm_action);
	while ((rp = *rpp) != NULL) {
		ASSERT(rp->ippr_action != ap);
		rpp = &(rp->ippr_nextp);
	}

	/*
	 * Allocate a reference structure.
	 */

	if ((rp = kmem_zalloc(sizeof (ipp_ref_t), KM_NOSLEEP)) == NULL)
		return (ENOMEM);

	/*
	 * Set the reference to the action and link it onto the module's list.
	 */

	rp->ippr_action = ap;
	*rpp = rp;

	/*
	 * Keep a 'back pointer' from the action structure to the module
	 * structure.
	 */

	ap->ippa_mod = imp;

	return (0);
}
#undef	__FN__

#define	__FN__	"unref_mod"
static void
unref_mod(
	ipp_action_t	*ap,
	ipp_mod_t	*imp)
{
	ipp_ref_t	**rpp;
	ipp_ref_t	*rp;

	ASSERT(rw_write_held(ap->ippa_lock));
	ASSERT(rw_write_held(imp->ippm_lock));

	/*
	 * Scan the module's list for the reference to the action.
	 */

	rpp = &(imp->ippm_action);
	while ((rp = *rpp) != NULL) {
		if (rp->ippr_action == ap)
			break;
		rpp = &(rp->ippr_nextp);
	}
	ASSERT(rp != NULL);

	/*
	 * Unlink the reference structure and free it.
	 */

	*rpp = rp->ippr_nextp;
	kmem_free(rp, sizeof (ipp_ref_t));

	/*
	 * NULL the 'back pointer'.
	 */

	ap->ippa_mod = NULL;
}
#undef	__FN__

#define	__FN__	"is_mod_busy"
static int
is_mod_busy(
	ipp_mod_t	*imp)
{
	/*
	 * Return a value which is true (non-zero) iff the module refers
	 * to no actions.
	 */

	return (imp->ippm_action != NULL);
}
#undef	__FN__

#define	__FN__	"get_mod_ref"
static int
get_mod_ref(
	ipp_mod_t	*imp,
	ipp_action_id_t	**bufp,
	int		*neltp)
{
	ipp_ref_t	*rp;
	int		nelt;
	ipp_action_t	*ap;
	ipp_action_id_t	*buf;
	int		length;

	ASSERT(rw_lock_held(imp->ippm_lock));

	/*
	 * Count the number of actions referred to from the module structure.
	 */

	nelt = 0;
	for (rp = imp->ippm_action; rp != NULL; rp = rp->ippr_nextp) {
		nelt++;
	}
	DBG1(DBG_LIST, "%d actions found\n", nelt);

	/*
	 * If there are no actions referred to then there's nothing to do.
	 */

	if (nelt == 0) {
		*bufp = NULL;
		*neltp = 0;
		return (0);
	}

	/*
	 * Allocate a buffer to pass back to the caller.
	 */

	length = nelt * sizeof (ipp_action_id_t);
	if ((buf = kmem_alloc(length, KM_NOSLEEP)) == NULL)
		return (ENOMEM);

	/*
	 * Fill the buffer with an array of action ids.
	 */

	*bufp = buf;
	*neltp = nelt;

	for (rp = imp->ippm_action; rp != NULL; rp = rp->ippr_nextp) {
		ap = rp->ippr_action;
		*buf++ = ap->ippa_id;
	}

	ASSERT((uintptr_t)buf == (uintptr_t)*bufp + length);
	return (0);
}
#undef	__FN__

#define	__FN__	"get_mods"
static int
get_mods(
	ipp_mod_id_t	**bufp,
	int		*neltp)
{
	ipp_mod_id_t	*buf;
	int		length;
	ipp_mod_id_t	mid;
	ipp_mod_t	*imp;


	rw_enter(ipp_mod_byname_lock, RW_READER);

	/*
	 * If there are no modules registered then there's nothing to do.
	 */

	if (ipp_mod_count == 0) {
		DBG0(DBG_LIST, "no modules registered\n");
		*bufp = NULL;
		*neltp = 0;
		rw_exit(ipp_mod_byname_lock);
		return (0);
	}

	/*
	 * Allocate a buffer to pass back to the caller.
	 */

	DBG1(DBG_LIST, "%d modules registered\n", ipp_mod_count);
	length = ipp_mod_count * sizeof (ipp_mod_id_t);
	if ((buf = kmem_alloc(length, KM_NOSLEEP)) == NULL) {
		rw_exit(ipp_mod_byname_lock);
		return (ENOMEM);
	}

	rw_enter(ipp_mod_byid_lock, RW_READER);

	/*
	 * Search the array of all modules.
	 */

	*bufp = buf;
	*neltp = ipp_mod_count;

	for (mid = IPP_MOD_RESERVED + 1; mid <= ipp_mid_limit; mid++) {
		if ((imp = ipp_mod_byid[mid]) == NULL)
			continue;

		/*
		 * If the module has 'destruct pending' set then it means it
		 * is either still in the cache (i.e not allocated) or in the
		 * process of being set up by alloc_mod().
		 */

		LOCK_MOD(imp, RW_READER);
		ASSERT(imp->ippm_id == mid);

		if (imp->ippm_destruct_pending) {
			UNLOCK_MOD(imp);
			continue;
		}
		UNLOCK_MOD(imp);

		*buf++ = mid;
	}

	rw_exit(ipp_mod_byid_lock);
	rw_exit(ipp_mod_byname_lock);

	ASSERT((uintptr_t)buf == (uintptr_t)*bufp + length);
	return (0);
}
#undef	__FN__

#define	__FN__	"find_mod"
static ipp_mod_id_t
find_mod(
	const char	*modname)
{
	ipp_mod_id_t	mid;
	ipp_mod_t	*imp;
	ipp_ref_t	*rp;
	int		hb;

	ASSERT(modname != NULL);

	rw_enter(ipp_mod_byname_lock, RW_READER);

	/*
	 * Quick return if no modules are registered.
	 */

	if (ipp_mod_count == 0) {
		rw_exit(ipp_mod_byname_lock);
		return (IPP_MOD_INVAL);
	}

	/*
	 * Find the hash bucket where the module structure should be.
	 */

	hb = hash(modname);
	rp = ipp_mod_byname[hb];

	/*
	 * Scan the bucket for a match.
	 */

	while (rp != NULL) {
		imp = rp->ippr_mod;
		if (strcmp(imp->ippm_name, modname) == 0)
			break;
		rp = rp->ippr_nextp;
	}

	if (rp == NULL) {
		rw_exit(ipp_mod_byname_lock);
		return (IPP_MOD_INVAL);
	}

	if (imp->ippm_state == IPP_MODSTATE_PROTO) {
		rw_exit(ipp_mod_byname_lock);
		return (IPP_MOD_INVAL);
	}

	mid = imp->ippm_id;
	rw_exit(ipp_mod_byname_lock);

	return (mid);
}
#undef __FN__

#define	__FN__	"alloc_mod"
static int
alloc_mod(
	const char	*modname,
	ipp_mod_id_t	*midp)
{
	ipp_mod_t	*imp;
	ipp_ref_t	**rpp;
	ipp_ref_t	*rp;
	int		hb;

	ASSERT(modname != NULL);
	ASSERT(midp != NULL);

	rw_enter(ipp_mod_byname_lock, RW_WRITER);

	/*
	 * Find the right hash bucket for a module of the given name.
	 */

	hb = hash(modname);
	rpp = &ipp_mod_byname[hb];

	/*
	 * Scan the bucket making sure the module isn't already
	 * registered.
	 */

	while ((rp = *rpp) != NULL) {
		imp = rp->ippr_mod;
		if (strcmp(imp->ippm_name, modname) == 0) {
			DBG1(DBG_MOD, "module '%s' already exists\n", modname);
			rw_exit(ipp_mod_byname_lock);
			return (EEXIST);
		}
		rpp = &(rp->ippr_nextp);
	}

	/*
	 * Allocate a new reference structure and a new module structure.
	 */

	if ((rp = kmem_zalloc(sizeof (ipp_ref_t), KM_NOSLEEP)) == NULL) {
		rw_exit(ipp_mod_byname_lock);
		return (ENOMEM);
	}

	if ((imp = kmem_cache_alloc(ipp_mod_cache, KM_NOSLEEP)) == NULL) {
		kmem_free(rp, sizeof (ipp_ref_t));
		rw_exit(ipp_mod_byname_lock);
		return (ENOMEM);
	}

	/*
	 * Set up the name of the new structure.
	 */

	(void) strcpy(imp->ippm_name, modname);

	/*
	 * Make sure the 'destruct pending' flag is clear. This indicates
	 * that the structure is no longer part of the cache.
	 */

	LOCK_MOD(imp, RW_WRITER);
	imp->ippm_destruct_pending = B_FALSE;
	UNLOCK_MOD(imp);

	/*
	 * Set the reference and link it into the hash bucket.
	 */

	rp->ippr_mod = imp;
	*rpp = rp;

	/*
	 * Increment the module count.
	 */

	ipp_mod_count++;

	*midp = imp->ippm_id;
	rw_exit(ipp_mod_byname_lock);
	return (0);
}
#undef	__FN__

#define	__FN__	"free_mod"
static void
free_mod(
	ipp_mod_t	*imp)
{
	ipp_ref_t	**rpp;
	ipp_ref_t	*rp;
	int		hb;

	rw_enter(ipp_mod_byname_lock, RW_WRITER);

	/*
	 * Find the hash bucket where the module structure should be.
	 */

	hb = hash(imp->ippm_name);
	rpp = &ipp_mod_byname[hb];

	/*
	 * Scan the bucket for a match.
	 */

	while ((rp = *rpp) != NULL) {
		if (rp->ippr_mod == imp)
			break;
		rpp = &(rp->ippr_nextp);
	}
	ASSERT(rp != NULL);

	/*
	 * Unlink the reference structure and free it.
	 */

	*rpp = rp->ippr_nextp;
	kmem_free(rp, sizeof (ipp_ref_t));

	/*
	 * Decrement the module count.
	 */

	ipp_mod_count--;

	/*
	 * Empty the name.
	 */

	*imp->ippm_name = '\0';

	/*
	 * If the hold count is zero then we can free the structure
	 * immediately, otherwise we defer to rele_mod().
	 */

	LOCK_MOD(imp, RW_WRITER);
	imp->ippm_destruct_pending = B_TRUE;
	if (imp->ippm_hold_count == 0) {
		UNLOCK_MOD(imp);
		kmem_cache_free(ipp_mod_cache, imp);
		rw_exit(ipp_mod_byname_lock);
		return;
	}
	UNLOCK_MOD(imp);

	rw_exit(ipp_mod_byname_lock);
}
#undef __FN__

#define	__FN__	"hold_mod"
static ipp_mod_t *
hold_mod(
	ipp_mod_id_t	mid)
{
	ipp_mod_t	*imp;

	if (mid < 0)
		return (NULL);

	/*
	 * Use the module id as an index into the array of all module
	 * structures.
	 */

	rw_enter(ipp_mod_byid_lock, RW_READER);
	if ((imp = ipp_mod_byid[mid]) == NULL) {
		rw_exit(ipp_mod_byid_lock);
		return (NULL);
	}

	ASSERT(imp->ippm_id == mid);

	/*
	 * If the modul has 'destruct pending' set then it means it is either
	 * still in the cache (i.e not allocated) or in the process of
	 * being set up by alloc_mod().
	 */

	LOCK_MOD(imp, RW_READER);
	if (imp->ippm_destruct_pending) {
		UNLOCK_MOD(imp);
		rw_exit(ipp_mod_byid_lock);
		return (NULL);
	}
	UNLOCK_MOD(imp);

	/*
	 * Increment the hold count to prevent the structure from being
	 * freed.
	 */

	atomic_inc_32(&(imp->ippm_hold_count));
	rw_exit(ipp_mod_byid_lock);

	return (imp);
}
#undef	__FN__

#define	__FN__	"rele_mod"
static void
rele_mod(
	ipp_mod_t	*imp)
{
	/*
	 * This call means we're done with the pointer so we can drop the
	 * hold count.
	 */

	ASSERT(imp->ippm_hold_count != 0);
	atomic_dec_32(&(imp->ippm_hold_count));

	/*
	 * If the structure has 'destruct pending' set then we tried to free
	 * it but couldn't, so do it now.
	 */

	LOCK_MOD(imp, RW_READER);
	if (imp->ippm_destruct_pending && imp->ippm_hold_count == 0) {
		UNLOCK_MOD(imp);
		kmem_cache_free(ipp_mod_cache, imp);
		return;
	}

	UNLOCK_MOD(imp);
}
#undef	__FN__

#define	__FN__	"get_mid"
static ipp_mod_id_t
get_mid(
	void)
{
	int	index;
	int	start;
	int	limit;

	ASSERT(rw_write_held(ipp_mod_byid_lock));

	/*
	 * Start searching after the last module id we allocated.
	 */

	start = (int)ipp_next_mid;
	limit = (int)ipp_mid_limit;

	/*
	 * Look for a spare slot in the array.
	 */

	index = start;
	while (ipp_mod_byid[index] != NULL) {
		index++;
		if (index > limit)
			index = IPP_MOD_RESERVED + 1;
		if (index == start)
			return (IPP_MOD_INVAL);
	}

	/*
	 * Note that we've just allocated a new module id so that we can
	 * start our search there next time.
	 */

	index++;
	if (index > limit) {
		ipp_next_mid = IPP_MOD_RESERVED + 1;
	} else
		ipp_next_mid = (ipp_mod_id_t)index;

	return ((ipp_mod_id_t)(--index));
}
#undef	__FN__

#define	__FN__	"condemn_action"
static int
condemn_action(
	ipp_ref_t	**rpp,
	ipp_action_t	*ap)
{
	ipp_ref_t	*rp;

	DBG1(DBG_ACTION, "condemning action '%s'\n", ap->ippa_name);

	/*
	 * Check to see if the action is already condemned.
	 */

	while ((rp = *rpp) != NULL) {
		if (rp->ippr_action == ap)
			break;
		rpp = &(rp->ippr_nextp);
	}

	/*
	 * Create a new entry for the action.
	 */

	if (rp == NULL) {
		if ((rp = kmem_zalloc(sizeof (ipp_ref_t), KM_NOSLEEP)) == NULL)
			return (ENOMEM);

		rp->ippr_action = ap;
		*rpp = rp;
	}

	return (0);
}
#undef	__FN__

#define	__FN__	"destroy_action"
static int
destroy_action(
	ipp_action_t	*ap,
	ipp_flags_t	flags)
{
	ipp_ops_t	*ippo;
	ipp_mod_t	*imp;
#define	MAXWAIT		10
	uint32_t	wait;
	int		rc;

	/*
	 * Check that the action is available.
	 */

	LOCK_ACTION(ap, RW_WRITER);
	if (ap->ippa_state != IPP_ASTATE_AVAILABLE) {
		UNLOCK_ACTION(ap);
		rele_action(ap);
		return (EPROTO);
	}

	/*
	 * Note that the action is in the process of creation/destruction.
	 */

	ap->ippa_state = IPP_ASTATE_CONFIG_PENDING;

	/*
	 * Wait for the in-transit packet count for this action to fall to
	 * zero (checking at millisecond intervals).
	 *
	 * NOTE: no new packets will enter the action now that the
	 *	 state has been changed.
	 */

	for (wait = 0; ap->ippa_packets > 0 && wait < (MAXWAIT * 1000000);
	    wait += 1000) {

		/*
		 * NOTE: We can hang onto the lock because the packet count is
		 *	 decremented without needing to take the lock.
		 */

		drv_usecwait(1000);
	}

	/*
	 * The packet count did not fall to zero.
	 */
	if (ap->ippa_packets > 0) {
		ap->ippa_state = IPP_ASTATE_AVAILABLE;
		UNLOCK_ACTION(ap);
		rele_action(ap);
		return (EAGAIN);
	}

	/*
	 * Check to see if any other action has a dependency on this one.
	 */

	if (is_action_refd(ap)) {
		ap->ippa_state = IPP_ASTATE_AVAILABLE;
		UNLOCK_ACTION(ap);
		rele_action(ap);
		return (EBUSY);
	}

	imp = ap->ippa_mod;
	ASSERT(imp != NULL);
	UNLOCK_ACTION(ap);

	ippo = imp->ippm_ops;
	ASSERT(ippo != NULL);

	/*
	 * Call into the module to destroy the action context.
	 */

	CONFIG_WRITE_START(ap);
	DBG1(DBG_ACTION, "destroying action '%s'\n", ap->ippa_name);
	if ((rc = ippo->ippo_action_destroy(ap->ippa_id, flags)) != 0) {
		LOCK_ACTION(ap, RW_WRITER);
		ap->ippa_state = IPP_ASTATE_AVAILABLE;
		UNLOCK_ACTION(ap);

		CONFIG_WRITE_END(ap);

		rele_action(ap);
		return (rc);
	}
	CONFIG_WRITE_END(ap);

	LOCK_ACTION(ap, RW_WRITER);
	LOCK_MOD(imp, RW_WRITER);
	unref_mod(ap, imp);
	UNLOCK_MOD(imp);
	ap->ippa_state = IPP_ASTATE_PROTO;
	UNLOCK_ACTION(ap);

	/*
	 * Free the action structure.
	 */

	ASSERT(ap->ippa_ref == NULL);
	free_action(ap);
	rele_action(ap);
	return (0);
#undef	MAXWAIT
}
#undef	__FN__

#define	__FN__	"ref_action"
static int
ref_action(
	ipp_action_t	*refby_ap,
	ipp_action_t	*ref_ap)
{
	ipp_ref_t	**rpp;
	ipp_ref_t	**save_rpp;
	ipp_ref_t	*rp;

	ASSERT(rw_write_held(refby_ap->ippa_lock));
	ASSERT(rw_write_held(ref_ap->ippa_lock));

	/*
	 * We want to add the new reference at the end of the refering
	 * action's list.
	 */

	rpp = &(refby_ap->ippa_ref);
	while ((rp = *rpp) != NULL) {
		if (rp->ippr_action == ref_ap)
			break;
		rpp = &(rp->ippr_nextp);
	}

	if ((rp = *rpp) != NULL) {

		/*
		 * There is an existing reference so increment its counter.
		 */

		rp->ippr_count++;

		/*
		 * Find the 'back pointer' and increment its counter too.
		 */

		rp = ref_ap->ippa_refby;
		while (rp != NULL) {
			if (rp->ippr_action == refby_ap)
				break;
			rp = rp->ippr_nextp;
		}
		ASSERT(rp != NULL);

		rp->ippr_count++;
	} else {

		/*
		 * Allocate, fill in and link a new reference structure.
		 */

		if ((rp = kmem_zalloc(sizeof (ipp_ref_t), KM_NOSLEEP)) == NULL)
			return (ENOMEM);

		rp->ippr_action = ref_ap;
		rp->ippr_count = 1;
		*rpp = rp;
		save_rpp = rpp;

		/*
		 * We keep a 'back pointer' which we want to add at the end of
		 * a list in the referred action's structure.
		 */

		rpp = &(ref_ap->ippa_refby);
		while ((rp = *rpp) != NULL) {
			ASSERT(rp->ippr_action != refby_ap);
			rpp = &(rp->ippr_nextp);
		}

		/*
		 * Allocate another reference structure and, if this fails,
		 * remember to clean up the first reference structure we
		 * allocated.
		 */

		if ((rp = kmem_zalloc(sizeof (ipp_ref_t),
		    KM_NOSLEEP)) == NULL) {
			rpp = save_rpp;
			rp = *rpp;
			*rpp = NULL;
			kmem_free(rp, sizeof (ipp_ref_t));

			return (ENOMEM);
		}

		/*
		 * Fill in the reference structure with the 'back pointer' and
		 * link it into the list.
		 */

		rp->ippr_action = refby_ap;
		rp->ippr_count = 1;
		*rpp = rp;
	}

	return (0);
}
#undef	__FN__

#define	__FN__	"unref_action"
static int
unref_action(
	ipp_action_t	*refby_ap,
	ipp_action_t	*ref_ap)
{
	ipp_ref_t	**rpp;
	ipp_ref_t	*rp;

	ASSERT(rw_write_held(refby_ap->ippa_lock));
	ASSERT(rw_write_held(ref_ap->ippa_lock));

	/*
	 * Scan for the reference in the referring action's list.
	 */

	rpp = &(refby_ap->ippa_ref);
	while ((rp = *rpp) != NULL) {
		if (rp->ippr_action == ref_ap)
			break;
		rpp = &(rp->ippr_nextp);
	}

	if (rp == NULL)
		return (ENOENT);

	if (rp->ippr_count > 1) {

		/*
		 * There are currently multiple references so decrement the
		 * count.
		 */

		rp->ippr_count--;

		/*
		 * Find the 'back pointer' and decrement its counter too.
		 */

		rp = ref_ap->ippa_refby;
		while (rp != NULL) {
			if (rp->ippr_action == refby_ap)
				break;
			rp = rp->ippr_nextp;
		}
		ASSERT(rp != NULL);

		rp->ippr_count--;
	} else {

		/*
		 * There is currently only a single reference, so unlink and
		 * free the reference structure.
		 */

		*rpp = rp->ippr_nextp;
		kmem_free(rp, sizeof (ipp_ref_t));

		/*
		 * Scan for the 'back pointer' in the referred action's list.
		 */

		rpp = &(ref_ap->ippa_refby);
		while ((rp = *rpp) != NULL) {
			if (rp->ippr_action == refby_ap)
				break;
			rpp = &(rp->ippr_nextp);
		}
		ASSERT(rp != NULL);

		/*
		 * Unlink and free this reference structure too.
		 */

		*rpp = rp->ippr_nextp;
		kmem_free(rp, sizeof (ipp_ref_t));
	}

	return (0);
}
#undef	__FN__

#define	__FN__	"is_action_refd"
static int
is_action_refd(
	ipp_action_t	*ap)
{
	/*
	 * Return a value which is true (non-zero) iff the action is not
	 * referred to by any other actions.
	 */

	return (ap->ippa_refby != NULL);
}
#undef	__FN__

#define	__FN__	"find_action"
static ipp_action_id_t
find_action(
	const char	*aname)
{
	ipp_action_id_t	aid;
	ipp_action_t	*ap;
	ipp_ref_t	*rp;
	int		hb;

	ASSERT(aname != NULL);

	rw_enter(ipp_action_byname_lock, RW_READER);

	/*
	 * Quick return if there are no actions defined at all.
	 */

	if (ipp_action_count == 0) {
		rw_exit(ipp_action_byname_lock);
		return (IPP_ACTION_INVAL);
	}

	/*
	 * Find the hash bucket where the action structure should be.
	 */

	hb = hash(aname);
	rp = ipp_action_byname[hb];

	/*
	 * Scan the bucket looking for a match.
	 */

	while (rp != NULL) {
		ap = rp->ippr_action;
		if (strcmp(ap->ippa_name, aname) == 0)
			break;
		rp = rp->ippr_nextp;
	}

	if (rp == NULL) {
		rw_exit(ipp_action_byname_lock);
		return (IPP_ACTION_INVAL);
	}

	if (ap->ippa_state == IPP_ASTATE_PROTO) {
		rw_exit(ipp_action_byname_lock);
		return (IPP_ACTION_INVAL);
	}

	aid = ap->ippa_id;
	rw_exit(ipp_action_byname_lock);

	return (aid);
}
#undef __FN__

#define	__FN__	"alloc_action"
static int
alloc_action(
	const char	*aname,
	ipp_action_id_t	*aidp)
{
	ipp_action_t	*ap;
	ipp_ref_t	**rpp;
	ipp_ref_t	*rp;
	int		hb;

	ASSERT(aidp != NULL);

	rw_enter(ipp_action_byname_lock, RW_WRITER);

	/*
	 * Find the right hash bucket for an action of the given name.
	 * (Nameless actions always go in a special bucket).
	 */

	if (aname != NULL) {
		hb = hash(aname);
		rpp = &ipp_action_byname[hb];
	} else
		rpp = &ipp_action_noname;

	/*
	 * Scan the bucket to make sure that an action with the given name
	 * does not already exist.
	 */

	while ((rp = *rpp) != NULL) {
		ap = rp->ippr_action;
		if (aname != NULL && strcmp(ap->ippa_name, aname) == 0) {
			DBG1(DBG_ACTION, "action '%s' already exists\n",
			    aname);
			rw_exit(ipp_action_byname_lock);
			return (EEXIST);
		}
		rpp = &(rp->ippr_nextp);
	}

	/*
	 * Allocate a new reference structure and a new action structure.
	 */

	if ((rp = kmem_zalloc(sizeof (ipp_ref_t), KM_NOSLEEP)) == NULL) {
		rw_exit(ipp_action_byname_lock);
		return (ENOMEM);
	}

	if ((ap = kmem_cache_alloc(ipp_action_cache, KM_NOSLEEP)) == NULL) {
		kmem_free(rp, sizeof (ipp_ref_t));
		rw_exit(ipp_action_byname_lock);
		return (ENOMEM);
	}

	/*
	 * Dream up a name if there isn't a real one and note that the action is
	 * really nameless.
	 */

	if (aname == NULL) {
		(void) sprintf(ap->ippa_name, "$%08X", ap->ippa_id);
		ap->ippa_nameless = B_TRUE;
	} else
		(void) strcpy(ap->ippa_name, aname);

	/*
	 * Make sure the 'destruct pending' flag is clear. This indicates that
	 * the structure is no longer part of the cache.
	 */

	LOCK_ACTION(ap, RW_WRITER);
	ap->ippa_destruct_pending = B_FALSE;
	UNLOCK_ACTION(ap);

	/*
	 * Fill in the reference structure and lint it onto the list.
	 */

	rp->ippr_action = ap;
	*rpp = rp;

	/*
	 * Increment the action count.
	 */

	ipp_action_count++;

	*aidp = ap->ippa_id;
	rw_exit(ipp_action_byname_lock);
	return (0);
}
#undef	__FN__

#define	__FN__	"free_action"
static void
free_action(
	ipp_action_t	*ap)
{
	ipp_ref_t	**rpp;
	ipp_ref_t	*rp;
	int		hb;

	rw_enter(ipp_action_byname_lock, RW_WRITER);

	/*
	 * Find the hash bucket where the action structure should be.
	 */

	if (!ap->ippa_nameless) {
		hb = hash(ap->ippa_name);
		rpp = &ipp_action_byname[hb];
	} else
		rpp = &ipp_action_noname;

	/*
	 * Scan the bucket for a match.
	 */

	while ((rp = *rpp) != NULL) {
		if (rp->ippr_action == ap)
			break;
		rpp = &(rp->ippr_nextp);
	}
	ASSERT(rp != NULL);

	/*
	 * Unlink and free the reference structure.
	 */

	*rpp = rp->ippr_nextp;
	kmem_free(rp, sizeof (ipp_ref_t));

	/*
	 * Decrement the action count.
	 */

	ipp_action_count--;

	/*
	 * Empty the name.
	 */

	*ap->ippa_name = '\0';

	/*
	 * If the hold count is zero then we can free the structure
	 * immediately, otherwise we defer to rele_action().
	 */

	LOCK_ACTION(ap, RW_WRITER);
	ap->ippa_destruct_pending = B_TRUE;
	if (ap->ippa_hold_count == 0) {
		UNLOCK_ACTION(ap);
		kmem_cache_free(ipp_action_cache, ap);
		rw_exit(ipp_action_byname_lock);
		return;
	}
	UNLOCK_ACTION(ap);

	rw_exit(ipp_action_byname_lock);
}
#undef __FN__

#define	__FN__	"hold_action"
static ipp_action_t *
hold_action(
	ipp_action_id_t	aid)
{
	ipp_action_t	*ap;

	if (aid < 0)
		return (NULL);

	/*
	 * Use the action id as an index into the array of all action
	 * structures.
	 */

	rw_enter(ipp_action_byid_lock, RW_READER);
	if ((ap = ipp_action_byid[aid]) == NULL) {
		rw_exit(ipp_action_byid_lock);
		return (NULL);
	}

	/*
	 * If the action has 'destruct pending' set then it means it is either
	 * still in the cache (i.e not allocated) or in the process of
	 * being set up by alloc_action().
	 */

	LOCK_ACTION(ap, RW_READER);
	if (ap->ippa_destruct_pending) {
		UNLOCK_ACTION(ap);
		rw_exit(ipp_action_byid_lock);
		return (NULL);
	}
	UNLOCK_ACTION(ap);

	/*
	 * Increment the hold count to prevent the structure from being
	 * freed.
	 */

	atomic_inc_32(&(ap->ippa_hold_count));
	rw_exit(ipp_action_byid_lock);

	return (ap);
}
#undef	__FN__

#define	__FN__	"rele_action"
static void
rele_action(
	ipp_action_t	*ap)
{
	/*
	 * This call means we're done with the pointer so we can drop the
	 * hold count.
	 */

	ASSERT(ap->ippa_hold_count != 0);
	atomic_dec_32(&(ap->ippa_hold_count));

	/*
	 * If the structure has 'destruct pending' set then we tried to free
	 * it but couldn't, so do it now.
	 */

	LOCK_ACTION(ap, RW_READER);
	if (ap->ippa_destruct_pending && ap->ippa_hold_count == 0) {
		UNLOCK_ACTION(ap);
		kmem_cache_free(ipp_action_cache, ap);
		return;
	}
	UNLOCK_ACTION(ap);
}
#undef	__FN__

#define	__FN__	"get_aid"
static ipp_action_id_t
get_aid(
	void)
{
	int	index;
	int	start;
	int	limit;

	ASSERT(rw_write_held(ipp_action_byid_lock));

	/*
	 * Start searching after the last action id that we allocated.
	 */

	start = (int)ipp_next_aid;
	limit = (int)ipp_aid_limit;

	/*
	 * Look for a spare slot in the array.
	 */

	index = start;
	while (ipp_action_byid[index] != NULL) {
		index++;
		if (index > limit)
			index = IPP_ACTION_RESERVED + 1;
		if (index == start)
			return (IPP_ACTION_INVAL);
	}

	/*
	 * Note that we've just allocated a new action id so that we can
	 * start our search there next time.
	 */

	index++;
	if (index > limit)
		ipp_next_aid = IPP_ACTION_RESERVED + 1;
	else
		ipp_next_aid = (ipp_action_id_t)index;

	return ((ipp_action_id_t)(--index));
}
#undef	__FN__

#define	__FN__	"alloc_packet"
static int
alloc_packet(
	const char	*name,
	ipp_action_id_t	aid,
	ipp_packet_t	**ppp)
{
	ipp_packet_t	*pp;
	ipp_class_t	*cp;

	if ((pp = kmem_cache_alloc(ipp_packet_cache, KM_NOSLEEP)) == NULL)
		return (ENOMEM);

	/*
	 * Set the packet up with a single class.
	 */

	cp = &(pp->ippp_class_array[0]);
	pp->ippp_class_windex = 1;

	(void) strcpy(cp->ippc_name, name);
	cp->ippc_aid = aid;

	*ppp = pp;
	return (0);
}
#undef	__FN__

#define	__FN__	"realloc_packet"
static int
realloc_packet(
	ipp_packet_t	*pp)
{
	uint_t		length;
	ipp_class_t	*array;

	length = (pp->ippp_class_limit + 1) << 1;
	if ((array = kmem_alloc(length * sizeof (ipp_class_t),
	    KM_NOSLEEP)) == NULL)
		return (ENOMEM);

	bcopy(pp->ippp_class_array, array,
	    (length >> 1) * sizeof (ipp_class_t));

	kmem_free(pp->ippp_class_array,
	    (length >> 1) * sizeof (ipp_class_t));

	pp->ippp_class_array = array;
	pp->ippp_class_limit = length - 1;

	return (0);
}
#undef	__FN__

#define	__FN__	"free_packet"
static void
free_packet(
	ipp_packet_t	*pp)
{
	pp->ippp_class_windex = 0;
	pp->ippp_class_rindex = 0;

	pp->ippp_data = NULL;
	pp->ippp_private = NULL;

	kmem_cache_free(ipp_packet_cache, pp);
}
#undef	__FN__

#define	__FN__ 	"hash"
static int
hash(
	const char	*name)
{
	int		val = 0;
	char		*ptr;

	/*
	 * Make a hash value by XORing all the ascii codes in the text string.
	 */

	for (ptr = (char *)name; *ptr != NULL; ptr++) {
		val ^= *ptr;
	}

	/*
	 * Return the value modulo the number of hash buckets we allow.
	 */

	return (val % IPP_NBUCKET);
}
#undef	__FN__

#define	__FN__	"update_stats"
static int
update_stats(
	kstat_t		*ksp,
	int		rw)
{
	ipp_stat_impl_t	*sip;

	ASSERT(ksp->ks_private != NULL);
	sip = (ipp_stat_impl_t *)ksp->ks_private;

	/*
	 * Call the update function passed to ipp_stat_create() for the given
	 * set of kstats.
	 */

	return (sip->ippsi_update((ipp_stat_t *)sip, sip->ippsi_arg, rw));
}
#undef	__FN__

#define	__FN__	"init_mods"
static void
init_mods(
	void)
{
	/*
	 * Initialise the array of all module structures and the module
	 * structure kmem cache.
	 */

	rw_init(ipp_mod_byid_lock, NULL, RW_DEFAULT,
	    (void *)ipltospl(LOCK_LEVEL));
	ipp_mod_byid = kmem_zalloc(sizeof (ipp_mod_t *) * (ipp_max_mod + 1),
	    KM_SLEEP);
	ipp_mod_byid[ipp_max_mod] = (ipp_mod_t *)-1;
	ipp_mid_limit = (ipp_mod_id_t)(ipp_max_mod - 1);

	ipp_mod_cache = kmem_cache_create("ipp_mod", sizeof (ipp_mod_t),
	    IPP_ALIGN, mod_constructor, mod_destructor, NULL, NULL, NULL, 0);
	ASSERT(ipp_mod_cache != NULL);

	/*
	 * Initialize the 'module by name' hash bucket array.
	 */

	rw_init(ipp_mod_byname_lock, NULL, RW_DEFAULT,
	    (void *)ipltospl(LOCK_LEVEL));
	bzero(ipp_mod_byname, IPP_NBUCKET * sizeof (ipp_ref_t *));
}
#undef	__FN__

#define	__FN__	"init_actions"
static void
init_actions(
	void)
{
	/*
	 * Initialise the array of all action structures and the action
	 * structure cache.
	 */

	rw_init(ipp_action_byid_lock, NULL, RW_DEFAULT,
	    (void *)ipltospl(LOCK_LEVEL));
	ipp_action_byid = kmem_zalloc(sizeof (ipp_action_t *) *
	    (ipp_max_action + 1), KM_SLEEP);
	ipp_action_byid[ipp_max_action] = (ipp_action_t *)-1;
	ipp_aid_limit = (ipp_action_id_t)(ipp_max_action - 1);

	ipp_action_cache = kmem_cache_create("ipp_action",
	    sizeof (ipp_action_t), IPP_ALIGN, action_constructor,
	    action_destructor, NULL, NULL, NULL, 0);
	ASSERT(ipp_action_cache != NULL);

	/*
	 * Initialize the 'action by name' hash bucket array (and the special
	 * 'hash' bucket for nameless actions).
	 */

	rw_init(ipp_action_byname_lock, NULL, RW_DEFAULT,
	    (void *)ipltospl(LOCK_LEVEL));
	bzero(ipp_action_byname, IPP_NBUCKET * sizeof (ipp_ref_t *));
	ipp_action_noname = NULL;
}
#undef	__FN__

#define	__FN__	"init_packets"
static void
init_packets(
	void)
{
	/*
	 * Initialise the packet structure cache.
	 */

	ipp_packet_cache = kmem_cache_create("ipp_packet",
	    sizeof (ipp_packet_t), IPP_ALIGN, packet_constructor,
	    packet_destructor, NULL, NULL, NULL, 0);
	ASSERT(ipp_packet_cache != NULL);
}
#undef	__FN__

/*
 * Kmem cache constructor/destructor functions.
 */

#define	__FN__	"mod_constructor"
/*ARGSUSED*/
static int
mod_constructor(
	void		*buf,
	void		*cdrarg,
	int		kmflags)
{
	ipp_mod_t	*imp;
	ipp_mod_id_t	mid;

	ASSERT(buf != NULL);
	bzero(buf, sizeof (ipp_mod_t));
	imp = (ipp_mod_t *)buf;

	rw_enter(ipp_mod_byid_lock, RW_WRITER);

	/*
	 * Get a new module id.
	 */

	if ((mid = get_mid()) <= IPP_MOD_RESERVED) {
		rw_exit(ipp_mod_byid_lock);
		return (-1);
	}

	/*
	 * Initialize the buffer as a module structure in PROTO form.
	 */

	imp->ippm_destruct_pending = B_TRUE;
	imp->ippm_state = IPP_MODSTATE_PROTO;
	rw_init(imp->ippm_lock, NULL, RW_DEFAULT,
	    (void *)ipltospl(LOCK_LEVEL));

	/*
	 * Insert it into the array of all module structures.
	 */

	imp->ippm_id = mid;
	ipp_mod_byid[mid] = imp;

	rw_exit(ipp_mod_byid_lock);

	return (0);
}
#undef	__FN__

#define	__FN__	"mod_destructor"
/*ARGSUSED*/
static void
mod_destructor(
	void		*buf,
	void		*cdrarg)
{
	ipp_mod_t	*imp;

	ASSERT(buf != NULL);
	imp = (ipp_mod_t *)buf;

	ASSERT(imp->ippm_state == IPP_MODSTATE_PROTO);
	ASSERT(imp->ippm_action == NULL);
	ASSERT(*imp->ippm_name == '\0');
	ASSERT(imp->ippm_destruct_pending);

	rw_enter(ipp_mod_byid_lock, RW_WRITER);
	ASSERT(imp->ippm_hold_count == 0);

	/*
	 * NULL the entry in the array of all module structures.
	 */

	ipp_mod_byid[imp->ippm_id] = NULL;

	/*
	 * Clean up any remnants of the module structure as the buffer is
	 * about to disappear.
	 */

	rw_destroy(imp->ippm_lock);
	rw_exit(ipp_mod_byid_lock);
}
#undef	__FN__

#define	__FN__	"action_constructor"
/*ARGSUSED*/
static int
action_constructor(
	void		*buf,
	void		*cdrarg,
	int		kmflags)
{
	ipp_action_t	*ap;
	ipp_action_id_t	aid;

	ASSERT(buf != NULL);
	bzero(buf, sizeof (ipp_action_t));
	ap = (ipp_action_t *)buf;

	rw_enter(ipp_action_byid_lock, RW_WRITER);

	/*
	 * Get a new action id.
	 */

	if ((aid = get_aid()) <= IPP_ACTION_RESERVED) {
		rw_exit(ipp_action_byid_lock);
		return (-1);
	}

	/*
	 * Initialize the buffer as an action structure in PROTO form.
	 */

	ap->ippa_state = IPP_ASTATE_PROTO;
	ap->ippa_destruct_pending = B_TRUE;
	rw_init(ap->ippa_lock, NULL, RW_DEFAULT,
	    (void *)ipltospl(LOCK_LEVEL));
	CONFIG_LOCK_INIT(ap->ippa_config_lock);

	/*
	 * Insert it into the array of all action structures.
	 */

	ap->ippa_id = aid;
	ipp_action_byid[aid] = ap;

	rw_exit(ipp_action_byid_lock);
	return (0);
}
#undef	__FN__

#define	__FN__	"action_destructor"
/*ARGSUSED*/
static void
action_destructor(
	void		*buf,
	void		*cdrarg)
{
	ipp_action_t	*ap;

	ASSERT(buf != NULL);
	ap = (ipp_action_t *)buf;

	ASSERT(ap->ippa_state == IPP_ASTATE_PROTO);
	ASSERT(ap->ippa_ref == NULL);
	ASSERT(ap->ippa_refby == NULL);
	ASSERT(ap->ippa_packets == 0);
	ASSERT(*ap->ippa_name == '\0');
	ASSERT(ap->ippa_destruct_pending);

	rw_enter(ipp_action_byid_lock, RW_WRITER);
	ASSERT(ap->ippa_hold_count == 0);

	/*
	 * NULL the entry in the array of all action structures.
	 */

	ipp_action_byid[ap->ippa_id] = NULL;

	/*
	 * Clean up any remnants of the action structure as the buffer is
	 * about to disappear.
	 */

	CONFIG_LOCK_FINI(ap->ippa_config_lock);
	rw_destroy(ap->ippa_lock);

	rw_exit(ipp_action_byid_lock);
}
#undef	__FN__

#define	__FN__	"packet_constructor"
/*ARGSUSED*/
static int
packet_constructor(
	void		*buf,
	void		*cdrarg,
	int		kmflags)
{
	ipp_packet_t	*pp;
	ipp_class_t	*cp;

	ASSERT(buf != NULL);
	bzero(buf, sizeof (ipp_packet_t));
	pp = (ipp_packet_t *)buf;

	if ((cp = kmem_alloc(ipp_packet_classes * sizeof (ipp_class_t),
	    KM_NOSLEEP)) == NULL)
		return (ENOMEM);

	pp->ippp_class_array = cp;
	pp->ippp_class_windex = 0;
	pp->ippp_class_rindex = 0;
	pp->ippp_class_limit = ipp_packet_classes - 1;

	return (0);
}
#undef	__FN__

#define	__FN__	"packet_destructor"
/*ARGSUSED*/
static void
packet_destructor(
	void		*buf,
	void		*cdrarg)
{
	ipp_packet_t	*pp;

	ASSERT(buf != NULL);
	pp = (ipp_packet_t *)buf;

	ASSERT(pp->ippp_data == NULL);
	ASSERT(pp->ippp_class_windex == 0);
	ASSERT(pp->ippp_class_rindex == 0);
	ASSERT(pp->ippp_private == NULL);
	ASSERT(pp->ippp_private_free == NULL);

	kmem_free(pp->ippp_class_array,
	    (pp->ippp_class_limit + 1) * sizeof (ipp_class_t));

	if (pp->ippp_log != NULL) {
		kmem_free(pp->ippp_log,
		    (pp->ippp_log_limit + 1) * sizeof (ipp_log_t));
	}
}
#undef	__FN__

/*
 * Debug message printout code.
 */

#ifdef	IPP_DBG
static void
ipp_debug(
	uint64_t	type,
	const char	*fn,
	char		*fmt,
			...)
{
	char		buf[255];
	va_list		adx;

	if ((type & ipp_debug_flags) == 0)
		return;

	mutex_enter(debug_mutex);
	va_start(adx, fmt);
	(void) vsnprintf(buf, 255, fmt, adx);
	va_end(adx);

	printf("(%llx) %s: %s", (unsigned long long)curthread->t_did, fn,
	    buf);
	mutex_exit(debug_mutex);
}
#endif	/* IPP_DBG */