OpenSolaris_b135/uts/sun4v/io/ds_snmp.c

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

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


/*
 * sun4v domain services SNMP driver
 */

#include <sys/types.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/open.h>
#include <sys/cred.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <sys/ksynch.h>
#include <sys/modctl.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/debug.h>
#include <sys/cmn_err.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/ds.h>
#include <sys/ds_snmp.h>

#define	DS_SNMP_NAME		"ds_snmp"
#define	DS_SNMP_MAX_OPENS	256
#define	DS_BITS_IN_UINT64	64
#define	DS_MINOR_POOL_SZ	(DS_SNMP_MAX_OPENS / DS_BITS_IN_UINT64)
#define	DS_SNMP_MINOR_SHIFT	56
#define	DS_SNMP_DBG		if (ds_snmp_debug) printf

typedef	struct {
	uint64_t	seq_num;
	uint64_t	type;
} ds_snmp_msg_t;

typedef	enum {
	DS_SNMP_REQUEST	= 0,
	DS_SNMP_REPLY	= 1,
	DS_SNMP_ERROR = 2
} ds_snmp_msg_type_t;

typedef enum {
	DS_SNMP_READY = 0x0,
	DS_SNMP_REQUESTED = 0x1,
	DS_SNMP_DATA_AVL = 0x2,
	DS_SNMP_DATA_ERR = 0x3
} ds_snmp_flags_t;

/*
 * The single mutex 'lock' protects all the SNMP/DS variables in the state
 * structure.
 *
 * The condition variable 'state_cv' helps serialize write() calls for a
 * single descriptor. When write() is called, it sets a flag to indicate
 * that an SNMP request has been made to the agent. No more write()'s on
 * the same open descriptor will be allowed until this flag is cleared via
 * a matching read(), where the requested packet is consumed on arrival.
 * Read() then wakes up any waiters blocked in write() for sending the next
 * SNMP request to the agent.
 */
typedef struct ds_snmp_state {
	dev_info_t	*dip;
	int		instance;
	dev_t		dev;

	/* SNMP/DS */
	kmutex_t	lock;
	kcondvar_t	state_cv;
	ds_snmp_flags_t	state;
	void		*data;
	size_t		data_len;
	uint64_t	req_id;
	uint64_t	last_req_id;
	uint64_t	gencount;
	boolean_t	sc_reset;
} ds_snmp_state_t;


static uint_t		ds_snmp_debug = 0;
static void		*ds_snmp_statep = NULL;
static int		ds_snmp_instance = -1;
static dev_info_t	*ds_snmp_devi = NULL;

/*
 * The ds_snmp_lock mutex protects the following data global to the
 * driver.
 *
 * The ds_snmp_service_cv condition variable is used to resolve the
 * potential race between the registration of snmp service via a
 * ds_cap_init() in attach(), the acknowledgement of this registration
 * at a later time in ds_snmp_reg_handler(), and a possible open() at
 * a time inbetween. The ds_snmp_has_service and ds_snmp_handle are
 * used to indicate whether the registration acknowledgement has happened
 * or not.
 *
 * The ds_snmp_minor_pool[] is a bitmask to allocate and keep track of
 * minor numbers dynamically.
 */
static kmutex_t		ds_snmp_lock;
static kcondvar_t	ds_snmp_service_cv;
static int		ds_snmp_has_service = B_FALSE;
static ds_svc_hdl_t	ds_snmp_handle = DS_INVALID_HDL;
static uint64_t		ds_snmp_minor_pool[DS_MINOR_POOL_SZ];	/* bitmask */
static int		ds_snmp_num_opens = 0;

static int ds_snmp_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int ds_snmp_attach(dev_info_t *, ddi_attach_cmd_t);
static int ds_snmp_detach(dev_info_t *, ddi_detach_cmd_t);
static int ds_snmp_open(dev_t *, int, int, cred_t *);
static int ds_snmp_close(dev_t, int, int, cred_t *);
static int ds_snmp_read(dev_t, struct uio *, cred_t *);
static int ds_snmp_write(dev_t, struct uio *, cred_t *);
static int ds_snmp_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);

/*
 * DS Callbacks
 */
static void ds_snmp_reg_handler(ds_cb_arg_t, ds_ver_t *, ds_svc_hdl_t);
static void ds_snmp_unreg_handler(ds_cb_arg_t arg);
static void ds_snmp_data_handler(ds_cb_arg_t arg, void *buf, size_t buflen);

/*
 * SNMP DS capability registration
 */
static ds_ver_t ds_snmp_ver_1_0 = { 1, 0 };
static ds_capability_t ds_snmp_cap = {
	"snmp",
	&ds_snmp_ver_1_0,
	1
};

/*
 * SNMP DS Client callback vector
 */
static ds_clnt_ops_t ds_snmp_ops = {
	ds_snmp_reg_handler,	/* ds_reg_cb */
	ds_snmp_unreg_handler,	/* ds_unreg_cb */
	ds_snmp_data_handler,	/* ds_data_cb */
	NULL			/* cb_arg */
};

/*
 * DS SNMP driver Ops Vector
 */
static struct cb_ops ds_snmp_cb_ops = {
	ds_snmp_open,		/* cb_open */
	ds_snmp_close,		/* cb_close */
	nodev,			/* cb_strategy */
	nodev,			/* cb_print */
	nodev,			/* cb_dump */
	ds_snmp_read,		/* cb_read */
	ds_snmp_write,		/* cb_write */
	ds_snmp_ioctl,		/* cb_ioctl */
	nodev,			/* cb_devmap */
	nodev,			/* cb_mmap */
	nodev,			/* cb_segmap */
	nochpoll,		/* cb_chpoll */
	ddi_prop_op,		/* cb_prop_op */
	(struct streamtab *)NULL, /* cb_str */
	D_MP | D_64BIT,		/* cb_flag */
	CB_REV,			/* cb_rev */
	nodev,			/* cb_aread */
	nodev			/* cb_awrite */
};

static struct dev_ops ds_snmp_dev_ops = {
	DEVO_REV,		/* devo_rev */
	0,			/* devo_refcnt */
	ds_snmp_getinfo,	/* devo_getinfo */
	nulldev,		/* devo_identify */
	nulldev,		/* devo_probe */
	ds_snmp_attach,		/* devo_attach */
	ds_snmp_detach,		/* devo_detach */
	nodev,			/* devo_reset */
	&ds_snmp_cb_ops,	/* devo_cb_ops */
	(struct bus_ops *)NULL,	/* devo_bus_ops */
	nulldev,		/* devo_power */
	ddi_quiesce_not_needed,		/* devo_quiesce */
};

static struct modldrv modldrv = {
	&mod_driverops,
	"Domain Services SNMP Driver",
	&ds_snmp_dev_ops
};

static struct modlinkage modlinkage = {
	MODREV_1,
	(void *)&modldrv,
	NULL
};

int
_init(void)
{
	int retval;

	mutex_init(&ds_snmp_lock, NULL, MUTEX_DRIVER, NULL);
	cv_init(&ds_snmp_service_cv, NULL, CV_DRIVER, NULL);

	retval = ddi_soft_state_init(&ds_snmp_statep,
	    sizeof (ds_snmp_state_t), DS_SNMP_MAX_OPENS);
	if (retval != 0) {
		cv_destroy(&ds_snmp_service_cv);
		mutex_destroy(&ds_snmp_lock);
		return (retval);
	}

	retval = mod_install(&modlinkage);
	if (retval != 0) {
		ddi_soft_state_fini(&ds_snmp_statep);
		cv_destroy(&ds_snmp_service_cv);
		mutex_destroy(&ds_snmp_lock);
	}

	return (retval);
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}

int
_fini(void)
{
	int retval;

	if ((retval = mod_remove(&modlinkage)) != 0)
		return (retval);

	ddi_soft_state_fini(&ds_snmp_statep);

	cv_destroy(&ds_snmp_service_cv);
	mutex_destroy(&ds_snmp_lock);

	return (retval);
}

/*ARGSUSED*/
static int
ds_snmp_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp)
{
	ds_snmp_state_t *sp;
	int retval = DDI_FAILURE;

	ASSERT(resultp != NULL);

	switch (cmd) {
	case DDI_INFO_DEVT2DEVINFO:
		sp = ddi_get_soft_state(ds_snmp_statep, getminor((dev_t)arg));
		if (sp != NULL) {
			*resultp = sp->dip;
			retval = DDI_SUCCESS;
		} else
			*resultp = NULL;
		break;

	case DDI_INFO_DEVT2INSTANCE:
		*resultp = (void *)(uintptr_t)getminor((dev_t)arg);
		retval = DDI_SUCCESS;
		break;
	}

	return (retval);
}

static int
ds_snmp_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	int	rv;

	switch (cmd) {
	case DDI_ATTACH:
		if (ds_snmp_instance != -1)
			return (DDI_FAILURE);
		break;

	case DDI_RESUME:
		return (DDI_SUCCESS);

	default:
		return (DDI_FAILURE);
	}

	ds_snmp_instance = ddi_get_instance(dip);
	if (ddi_create_minor_node(dip, DS_SNMP_NAME, S_IFCHR, ds_snmp_instance,
	    DDI_PSEUDO, 0) != DDI_SUCCESS) {
		cmn_err(CE_WARN, "%s@%d: Unable to create minor node",
		    DS_SNMP_NAME, ds_snmp_instance);
		return (DDI_FAILURE);
	}

	bzero(ds_snmp_minor_pool, DS_MINOR_POOL_SZ * sizeof (uint64_t));

	ds_snmp_ops.cb_arg = dip;
	if ((rv = ds_cap_init(&ds_snmp_cap, &ds_snmp_ops)) != 0) {
		cmn_err(CE_NOTE, "ds_cap_init failed: %d", rv);
		ddi_remove_minor_node(dip, NULL);
		ds_snmp_instance = -1;
		return (DDI_FAILURE);
	}

	ds_snmp_devi = dip;
	ddi_report_dev(dip);

	return (DDI_SUCCESS);
}

/*ARGSUSED*/
static int
ds_snmp_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	switch (cmd) {
	case DDI_DETACH:
		if (ds_snmp_instance == -1)
			return (DDI_FAILURE);
		break;

	case DDI_SUSPEND:
		return (DDI_SUCCESS);

	default:
		return (DDI_FAILURE);
	}

	(void) ds_cap_fini(&ds_snmp_cap);

	ddi_remove_minor_node(ds_snmp_devi, NULL);
	bzero(ds_snmp_minor_pool, DS_MINOR_POOL_SZ * sizeof (uint64_t));

	ds_snmp_instance = -1;
	ds_snmp_devi = NULL;

	return (DDI_SUCCESS);
}

static minor_t
ds_snmp_get_minor(void)
{
	uint64_t	val;
	int		i, ndx;
	minor_t		minor;

	mutex_enter(&ds_snmp_lock);
	for (ndx = 0; ndx < DS_MINOR_POOL_SZ; ndx++) {
		val = ds_snmp_minor_pool[ndx];
		for (i = 0; i < DS_BITS_IN_UINT64; i++) {
			if ((val & 0x1) == 0) {
				ds_snmp_minor_pool[ndx] |= ((uint64_t)1 << i);
				ds_snmp_num_opens++;
				mutex_exit(&ds_snmp_lock);

				minor = ndx * DS_BITS_IN_UINT64 + i + 1;

				return (minor);
			}
			val >>= 1;
		}
	}
	mutex_exit(&ds_snmp_lock);

	return (0);
}

static void
ds_snmp_rel_minor(minor_t minor)
{
	int	i, ndx;

	ndx = (minor - 1) / DS_BITS_IN_UINT64;
	i = (minor - 1) % DS_BITS_IN_UINT64;

	ASSERT(ndx < DS_MINOR_POOL_SZ);

	mutex_enter(&ds_snmp_lock);

	ds_snmp_num_opens--;
	ds_snmp_minor_pool[ndx] &= ~((uint64_t)1 << i);

	mutex_exit(&ds_snmp_lock);
}

static boolean_t
ds_snmp_is_open(minor_t minor)
{
	uint64_t	val;
	int		i, ndx;

	ndx = (minor - 1) / DS_BITS_IN_UINT64;
	i = (minor - 1) % DS_BITS_IN_UINT64;

	val = ((uint64_t)1 << i);
	if (ds_snmp_minor_pool[ndx] & val)
		return (B_TRUE);
	else
		return (B_FALSE);
}

static int
ds_snmp_create_state(dev_t *devp)
{
	major_t	major;
	minor_t	minor;
	ds_snmp_state_t	*sp;

	if ((minor = ds_snmp_get_minor()) == 0)
		return (EMFILE);

	if (ddi_soft_state_zalloc(ds_snmp_statep, minor) != DDI_SUCCESS) {
		cmn_err(CE_WARN, "%s@%d: Unable to allocate state",
		    DS_SNMP_NAME, minor);
		ds_snmp_rel_minor(minor);
		return (ENOMEM);
	}

	sp = ddi_get_soft_state(ds_snmp_statep, minor);
	if (devp != NULL)
		major = getemajor(*devp);
	else
		major = ddi_driver_major(ds_snmp_devi);

	sp->dev = makedevice(major, minor);
	if (devp != NULL)
		*devp = sp->dev;

	sp->instance = minor;
	sp->data = NULL;
	sp->data_len = 0;
	sp->req_id = 0;
	sp->last_req_id = 0;
	sp->state = DS_SNMP_READY;
	sp->sc_reset = B_FALSE;

	mutex_init(&sp->lock, NULL, MUTEX_DRIVER, NULL);
	cv_init(&sp->state_cv, NULL, CV_DRIVER, NULL);

	return (0);
}

static int
ds_snmp_destroy_state(dev_t dev)
{
	ds_snmp_state_t	*sp;
	minor_t	minor;

	minor = getminor(dev);

	if ((sp = ddi_get_soft_state(ds_snmp_statep, minor)) == NULL)
		return (ENXIO);

	ASSERT(sp->instance == minor);

	/*
	 * If the app has not exited cleanly, the data may not have been
	 * read/memory freed, hence take care of that here
	 */
	if (sp->data) {
		kmem_free(sp->data, sp->data_len);
	}
	cv_destroy(&sp->state_cv);
	mutex_destroy(&sp->lock);

	ddi_soft_state_free(ds_snmp_statep, minor);
	ds_snmp_rel_minor(minor);

	return (0);
}

/*ARGSUSED*/
static int
ds_snmp_open(dev_t *devp, int flag, int otyp, cred_t *credp)
{

	if (otyp != OTYP_CHR)
		return (EINVAL);

	if (ds_snmp_instance == -1)
		return (ENXIO);

	/*
	 * Avoid possible race condition - ds service may not be there yet
	 */
	mutex_enter(&ds_snmp_lock);
	while (ds_snmp_has_service == B_FALSE) {
		if (cv_wait_sig(&ds_snmp_service_cv, &ds_snmp_lock) == 0) {
			mutex_exit(&ds_snmp_lock);
			return (EINTR);
		}
	}
	mutex_exit(&ds_snmp_lock);

	return (ds_snmp_create_state(devp));
}


/*ARGSUSED*/
static int
ds_snmp_close(dev_t dev, int flag, int otyp, cred_t *credp)
{
	if (otyp != OTYP_CHR)
		return (EINVAL);

	if (ds_snmp_instance == -1)
		return (ENXIO);

	if (ds_snmp_handle == DS_INVALID_HDL)
		return (EIO);

	return (ds_snmp_destroy_state(dev));
}

/*ARGSUSED*/
static int
ds_snmp_read(dev_t dev, struct uio *uiop, cred_t *credp)
{
	ds_snmp_state_t *sp;
	minor_t	minor;
	size_t len;
	int retval;
	caddr_t tmpbufp = (caddr_t)NULL;

	/*
	 * Given that now we can have sc resets happening at any
	 * time, it is possible that it happened since the last time
	 * we issued a read, write or ioctl.  If so, we need to wait
	 * for the unreg-reg pair to complete before we can do
	 * anything.
	 */
	mutex_enter(&ds_snmp_lock);
	while (ds_snmp_has_service == B_FALSE) {
		DS_SNMP_DBG("ds_snmp_read: waiting for service\n");
		if (cv_wait_sig(&ds_snmp_service_cv, &ds_snmp_lock) == 0) {
			mutex_exit(&ds_snmp_lock);
			return (EINTR);
		}
	}
	mutex_exit(&ds_snmp_lock);

	if ((len = uiop->uio_resid) == 0)
		return (0);

	minor = getminor(dev);
	if ((sp = ddi_get_soft_state(ds_snmp_statep, minor)) == NULL)
		return (ENXIO);

	mutex_enter(&sp->lock);

	if (sp->sc_reset == B_TRUE) {
		mutex_exit(&sp->lock);
		return (ECANCELED);
	}

	/*
	 * Block or bail if there is no SNMP data
	 */
	if (sp->state != DS_SNMP_DATA_AVL && sp->state != DS_SNMP_DATA_ERR) {
		DS_SNMP_DBG("ds_snmp_read: no SNMP data\n");
		if (uiop->uio_fmode & (FNDELAY | FNONBLOCK)) {
			mutex_exit(&sp->lock);
			return (EAGAIN);
		}
		while (sp->state != DS_SNMP_DATA_AVL &&
		    sp->state != DS_SNMP_DATA_ERR) {
			if (cv_wait_sig(&sp->state_cv, &sp->lock) == 0) {
				mutex_exit(&sp->lock);
				return (EINTR);
			}
		}
	}

	/*
	 * If there has been an error, it could be because the agent
	 * returned failure and there is no data to read, or an ldc-reset
	 * has happened.  Figure out which and return appropriate
	 * error to the caller.
	 */
	if (sp->state == DS_SNMP_DATA_ERR) {
		if (sp->sc_reset == B_TRUE) {
			mutex_exit(&sp->lock);
			DS_SNMP_DBG("ds_snmp_read: sc got reset, "
			    "returning ECANCELED\n");
			return (ECANCELED);
		} else {
			sp->state = DS_SNMP_READY;
			cv_broadcast(&sp->state_cv);
			mutex_exit(&sp->lock);
			DS_SNMP_DBG("ds_snmp_read: data error, "
			    "returning EIO\n");
			return (EIO);
		}
	}

	if (len > sp->data_len)
		len = sp->data_len;

	tmpbufp = kmem_alloc(len, KM_SLEEP);

	bcopy(sp->data, (void *)tmpbufp, len);
	kmem_free(sp->data, sp->data_len);
	sp->data = (caddr_t)NULL;
	sp->data_len = 0;

	/*
	 * SNMP data has been consumed, wake up anyone waiting to send
	 */
	sp->state = DS_SNMP_READY;
	cv_broadcast(&sp->state_cv);

	mutex_exit(&sp->lock);

	retval = uiomove(tmpbufp, len, UIO_READ, uiop);
	kmem_free(tmpbufp, len);

	return (retval);
}

/*ARGSUSED*/
static int
ds_snmp_write(dev_t dev, struct uio *uiop, cred_t *credp)
{
	ds_snmp_state_t *sp;
	ds_snmp_msg_t hdr;
	minor_t minor;
	size_t len;
	caddr_t tmpbufp;

	/*
	 * Check if there was an sc reset; if yes, wait until we have the
	 * service back again.
	 */
	mutex_enter(&ds_snmp_lock);
	while (ds_snmp_has_service == B_FALSE) {
		DS_SNMP_DBG("ds_snmp_write: waiting for service\n");
		if (cv_wait_sig(&ds_snmp_service_cv, &ds_snmp_lock) == 0) {
			mutex_exit(&ds_snmp_lock);
			return (EINTR);
		}
	}
	mutex_exit(&ds_snmp_lock);

	minor = getminor(dev);
	if ((sp = ddi_get_soft_state(ds_snmp_statep, minor)) == NULL)
		return (ENXIO);

	len = uiop->uio_resid + sizeof (ds_snmp_msg_t);
	tmpbufp = kmem_alloc(len, KM_SLEEP);

	if (uiomove(tmpbufp + sizeof (ds_snmp_msg_t),
	    len - sizeof (ds_snmp_msg_t), UIO_WRITE, uiop) != 0) {
		kmem_free(tmpbufp, len);
		return (EIO);
	}

	mutex_enter(&sp->lock);

	if (sp->sc_reset == B_TRUE) {
		mutex_exit(&sp->lock);
		kmem_free(tmpbufp, len);
		DS_SNMP_DBG("ds_snmp_write: sc_reset is TRUE, "
		    "returning ECANCELD\n");
		return (ECANCELED);
	}

	/*
	 * wait if earlier transaction is not yet completed
	 */
	while (sp->state != DS_SNMP_READY) {
		if (cv_wait_sig(&sp->state_cv, &sp->lock) == 0) {
			mutex_exit(&sp->lock);
			kmem_free(tmpbufp, len);
			return (EINTR);
		}
		/*
		 * Normally, only a reader would ever wake us up. But if we
		 * did get signalled with an ERROR, it could only mean there
		 * was an sc reset and there's no point waiting; we need to
		 * fail this write().
		 */
		if (sp->state == DS_SNMP_DATA_ERR && sp->sc_reset == B_TRUE) {
			DS_SNMP_DBG("ds_snmp_write: woke up with an sc_reset, "
			    "returning ECANCELED\n");
			mutex_exit(&sp->lock);
			kmem_free(tmpbufp, len);
			return (ECANCELED);
		}
	}

	if (sp->req_id == (((uint64_t)1 << DS_SNMP_MINOR_SHIFT) - 1))
		sp->req_id = 0; /* Reset */

	hdr.seq_num = ((uint64_t)minor << DS_SNMP_MINOR_SHIFT) | sp->req_id;
	sp->last_req_id = hdr.seq_num;
	(sp->req_id)++;

	/*
	 * Set state to SNMP_REQUESTED, but don't wakeup anyone yet
	 */
	sp->state = DS_SNMP_REQUESTED;

	mutex_exit(&sp->lock);

	hdr.type = DS_SNMP_REQUEST;
	bcopy((void *)&hdr, (void *)tmpbufp, sizeof (hdr));

	/*
	 * If the service went away since the time we entered this
	 * routine and now, tough luck. Just ignore the current
	 * write() and return.
	 */
	mutex_enter(&ds_snmp_lock);
	if (ds_snmp_has_service == B_FALSE) {
		DS_SNMP_DBG("ds_snmp_write: service went away, aborting "
		    "write, returning ECANCELED\n");
		mutex_exit(&ds_snmp_lock);
		kmem_free(tmpbufp, len);
		return (ECANCELED);
	}
	DS_SNMP_DBG("ds_snmp_write: ds_cap_send(0x%lx, %lu) called.\n",
	    ds_snmp_handle, len);
	(void) ds_cap_send(ds_snmp_handle, tmpbufp, len);
	mutex_exit(&ds_snmp_lock);

	kmem_free(tmpbufp, len);

	return (0);
}

/*ARGSUSED*/
static int
ds_snmp_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
    int *rvalp)
{
	ds_snmp_state_t *sp;
	struct dssnmp_info info;
	minor_t	minor;

	/*
	 * Check if there was an sc reset; if yes, wait until we have the
	 * service back again.
	 */
	mutex_enter(&ds_snmp_lock);
	while (ds_snmp_has_service == B_FALSE) {
		DS_SNMP_DBG("ds_snmp_ioctl: waiting for service\n");
		if (cv_wait_sig(&ds_snmp_service_cv, &ds_snmp_lock) == 0) {
			mutex_exit(&ds_snmp_lock);
			return (EINTR);
		}
	}
	mutex_exit(&ds_snmp_lock);

	DS_SNMP_DBG("ds_snmp_ioctl: hdl=0x%lx\n", ds_snmp_handle);

	minor = getminor(dev);
	if ((sp = ddi_get_soft_state(ds_snmp_statep, minor)) == NULL)
		return (ENXIO);

	if (!(mode & FREAD))
		return (EACCES);

	switch (cmd) {
	case DSSNMP_GETINFO:
		mutex_enter(&sp->lock);

		if (sp->sc_reset == B_TRUE) {
			mutex_exit(&sp->lock);
			DS_SNMP_DBG("ds_snmp_ioctl: returning ECANCELED\n");
			return (ECANCELED);
		}

		while (sp->state != DS_SNMP_DATA_AVL &&
		    sp->state != DS_SNMP_DATA_ERR) {
			DS_SNMP_DBG("ds_snmp_ioctl: state=%d, sc_reset=%d, "
			    "waiting for data\n", sp->state, sp->sc_reset);
			if (cv_wait_sig(&sp->state_cv, &sp->lock) == 0) {
				sp->state = DS_SNMP_READY;
				mutex_exit(&sp->lock);
				return (EINTR);
			}
		}
		DS_SNMP_DBG("ds_snmp_ioctl: state=%d, sc_reset=%d, "
		    "out of wait!\n", sp->state, sp->sc_reset);

		/*
		 * If there has been an error, it could be because the
		 * agent returned failure and there is no data to read,
		 * or an ldc-reset has happened.  Figure out which and
		 * return appropriate error to the caller.
		 */
		if (sp->state == DS_SNMP_DATA_ERR) {
			if (sp->sc_reset == B_TRUE) {
				mutex_exit(&sp->lock);
				DS_SNMP_DBG("ds_snmp_ioctl: sc_reset=TRUE "
				    "returning ECANCELED\n");
				return (ECANCELED);
			} else {
				sp->state = DS_SNMP_READY;
				cv_broadcast(&sp->state_cv);
				mutex_exit(&sp->lock);
				DS_SNMP_DBG("ds_snmp_ioctl: sc_reset=FALSE "
				    "returning EIO\n");
				return (EIO);
			}
		}

		info.size = sp->data_len;
		info.token = sp->gencount;

		mutex_exit(&sp->lock);

		if (ddi_copyout(&info, (void *)arg, sizeof (info), mode) != 0)
			return (EFAULT);
		break;

	case DSSNMP_CLRLNKRESET:
		mutex_enter(&sp->lock);

		DS_SNMP_DBG("ds_snmp_ioctl: DSSNMP_CLRLNKRESET\n");
		DS_SNMP_DBG("ds_snmp_ioctl: sc_reset=%d\n", sp->sc_reset);

		if (sp->sc_reset == B_TRUE) {
			if (sp->data) {
				DS_SNMP_DBG("ds_snmp_ioctl: data=%p, len=%lu\n",
				    sp->data, sp->data_len);
				kmem_free(sp->data, sp->data_len);
			}
			sp->data = NULL;
			sp->data_len = 0;
			sp->state = DS_SNMP_READY;
			sp->req_id = 0;
			sp->last_req_id = 0;
			sp->sc_reset = B_FALSE;
		}
		mutex_exit(&sp->lock);
		break;

	default:
		return (ENOTTY);
	}

	return (0);
}

/*
 * DS Callbacks
 */
/*ARGSUSED*/
static void
ds_snmp_reg_handler(ds_cb_arg_t arg, ds_ver_t *ver, ds_svc_hdl_t hdl)
{
	DS_SNMP_DBG("ds_snmp_reg_handler: registering handle 0x%lx for version "
	    "0x%x:0x%x\n", (uint64_t)hdl, ver->major, ver->minor);

	mutex_enter(&ds_snmp_lock);

	ASSERT(ds_snmp_handle == DS_INVALID_HDL);

	ds_snmp_handle = hdl;
	ds_snmp_has_service = B_TRUE;

	cv_broadcast(&ds_snmp_service_cv);

	mutex_exit(&ds_snmp_lock);

}

/*ARGSUSED*/
static void
ds_snmp_unreg_handler(ds_cb_arg_t arg)
{
	minor_t minor;
	ds_snmp_state_t *sp;

	DS_SNMP_DBG("ds_snmp_unreg_handler: un-registering ds_snmp service\n");

	mutex_enter(&ds_snmp_lock);

	if (ds_snmp_num_opens) {
		DS_SNMP_DBG("ds_snmp_unreg_handler: %d opens, sc reset!\n",
		    ds_snmp_num_opens);
		for (minor = 1; minor <= DS_SNMP_MAX_OPENS; minor++) {
			if (ds_snmp_is_open(minor)) {
				DS_SNMP_DBG("ds_snmp_unreg_handler: minor %d "
				    "open\n", minor);
				sp = ddi_get_soft_state(ds_snmp_statep, minor);
				if (sp == NULL)
					continue;

				/*
				 * Set the sc_reset flag and break any waiters
				 * out of their existing reads/writes/ioctls.
				 */
				DS_SNMP_DBG("ds_snmp_unreg_hdlr: about to "
				    "signal waiters\n");
				mutex_enter(&sp->lock);
				sp->sc_reset = B_TRUE;
				sp->state = DS_SNMP_DATA_ERR;
				cv_broadcast(&sp->state_cv);
				mutex_exit(&sp->lock);
			}
		}
	}

	ds_snmp_handle = DS_INVALID_HDL;
	ds_snmp_has_service = B_FALSE;

	DS_SNMP_DBG("ds_snmp_unreg_handler: handle invalidated\n");

	mutex_exit(&ds_snmp_lock);
}

/*ARGSUSED*/
static void
ds_snmp_data_handler(ds_cb_arg_t arg, void *buf, size_t buflen)
{
	ds_snmp_state_t *sp;
	ds_snmp_msg_t   hdr;
	size_t  	snmp_size;
	minor_t 	minor;

	/*
	 * Make sure the header is at least valid
	 */
	if (buflen < sizeof (hdr)) {
		cmn_err(CE_WARN,
		"ds_snmp_data_handler: buflen <%lu> too small", buflen);
		return;
	}

	ASSERT(buf != NULL);
	bcopy(buf, (void *)&hdr, sizeof (hdr));

	DS_SNMP_DBG("ds_snmp_data_handler: msg buf len 0x%lx : type 0x%lx, "
	    "seqn 0x%lx\n", buflen, hdr.type, hdr.seq_num);

	minor = (int)(hdr.seq_num >> DS_SNMP_MINOR_SHIFT);
	if ((sp = ddi_get_soft_state(ds_snmp_statep, minor)) == NULL)
		return;

	mutex_enter(&sp->lock);

	/*
	 * If there is no pending SNMP request, then we've received
	 * bogus data or an SNMP trap or the reader was interrupted.
	 * Since we don't yet support SNMP traps, ignore it.
	 */
	if (sp->state != DS_SNMP_REQUESTED) {
		DS_SNMP_DBG("Received SNMP data without request");
		mutex_exit(&sp->lock);
		return;
	}

	/*
	 * Response to a request therefore old SNMP must've been consumed
	 */
	ASSERT(sp->data_len == 0);
	ASSERT(sp->data == NULL);

	/*
	 * Response seq_num should match our request seq_num
	 */
	if (hdr.seq_num != sp->last_req_id) {
		cmn_err(CE_WARN, "Received DS snmp data out of sequence with "
		    "request");
		mutex_exit(&sp->lock);
		return;
	}

	if (hdr.type == DS_SNMP_ERROR) {
		sp->state = DS_SNMP_DATA_ERR;
		DS_SNMP_DBG("ds_snmp_data_handler: hdr.type = DS_SNMP_ERROR\n");
	} else {
		snmp_size = buflen - sizeof (ds_snmp_msg_t);
		sp->data = kmem_alloc(snmp_size, KM_SLEEP);
		sp->data_len = snmp_size;
		sp->state = DS_SNMP_DATA_AVL;

		bcopy((caddr_t)buf + sizeof (ds_snmp_msg_t),
		    sp->data, sp->data_len);
	}

	sp->gencount++;

	/*
	 * Wake up any readers waiting for data
	 */
	cv_broadcast(&sp->state_cv);
	mutex_exit(&sp->lock);
}