root/usr/src/uts/sun4v/io/ds_snmp.c
/*
 * 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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
 */


/*
 * 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;
        size_t orig_size;

        /*
         * 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);

        orig_size = uiop->uio_resid;
        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);
                        uiop->uio_resid = orig_size;
                        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);
}