root/usr/src/uts/common/io/kstat.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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
 */


/*
 * kernel statistics driver
 */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/file.h>
#include <sys/cmn_err.h>
#include <sys/t_lock.h>
#include <sys/proc.h>
#include <sys/fcntl.h>
#include <sys/uio.h>
#include <sys/kmem.h>
#include <sys/cred.h>
#include <sys/mman.h>
#include <sys/errno.h>
#include <sys/ioccom.h>
#include <sys/cpuvar.h>
#include <sys/stat.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/modctl.h>
#include <sys/kobj.h>
#include <sys/kstat.h>
#include <sys/atomic.h>
#include <sys/policy.h>
#include <sys/zone.h>

static dev_info_t *kstat_devi;

static int
read_kstat_data(int *rvalp, void *user_ksp, int flag)
{
        kstat_t user_kstat, *ksp;
#ifdef _MULTI_DATAMODEL
        kstat32_t user_kstat32;
#endif
        void *kbuf = NULL;
        size_t kbufsize, ubufsize, copysize;
        int error = 0;
        uint_t model;

        switch (model = ddi_model_convert_from(flag & FMODELS)) {
#ifdef _MULTI_DATAMODEL
        case DDI_MODEL_ILP32:
                if (copyin(user_ksp, &user_kstat32, sizeof (kstat32_t)) != 0)
                        return (EFAULT);
                user_kstat.ks_kid = user_kstat32.ks_kid;
                user_kstat.ks_data = (void *)(uintptr_t)user_kstat32.ks_data;
                user_kstat.ks_data_size = (size_t)user_kstat32.ks_data_size;
                break;
#endif
        default:
        case DDI_MODEL_NONE:
                if (copyin(user_ksp, &user_kstat, sizeof (kstat_t)) != 0)
                        return (EFAULT);
        }

        ksp = kstat_hold_bykid(user_kstat.ks_kid, getzoneid());
        if (ksp == NULL) {
                /*
                 * There is no kstat with the specified KID
                 */
                return (ENXIO);
        }
        if (ksp->ks_flags & KSTAT_FLAG_INVALID) {
                /*
                 * The kstat exists, but is momentarily in some
                 * indeterminate state (e.g. the data section is not
                 * yet initialized).  Try again in a few milliseconds.
                 */
                kstat_rele(ksp);
                return (EAGAIN);
        }

        /*
         * If it's a fixed-size kstat, allocate the buffer now, so we
         * don't have to do it under the kstat's data lock.  (If it's a
         * var-size kstat or one with long strings, we don't know the size
         * until after the update routine is called, so we can't do this
         * optimization.)
         * The allocator relies on this behavior to prevent recursive
         * mutex_enter in its (fixed-size) kstat update routine.
         * It's a zalloc to prevent unintentional exposure of random
         * juicy morsels of (old) kernel data.
         */
        if (!(ksp->ks_flags & (KSTAT_FLAG_VAR_SIZE | KSTAT_FLAG_LONGSTRINGS))) {
                kbufsize = ksp->ks_data_size;
                kbuf = kmem_zalloc(kbufsize + 1, KM_NOSLEEP);
                if (kbuf == NULL) {
                        kstat_rele(ksp);
                        return (EAGAIN);
                }
        }
        KSTAT_ENTER(ksp);
        if ((error = KSTAT_UPDATE(ksp, KSTAT_READ)) != 0) {
                KSTAT_EXIT(ksp);
                kstat_rele(ksp);
                if (kbuf != NULL)
                        kmem_free(kbuf, kbufsize + 1);
                return (error);
        }

        kbufsize = ksp->ks_data_size;
        ubufsize = user_kstat.ks_data_size;

        if (ubufsize < kbufsize) {
                error = ENOMEM;
        } else {
                if (kbuf == NULL)
                        kbuf = kmem_zalloc(kbufsize + 1, KM_NOSLEEP);
                if (kbuf == NULL) {
                        error = EAGAIN;
                } else {
                        error = KSTAT_SNAPSHOT(ksp, kbuf, KSTAT_READ);
                }
        }

        /*
         * The following info must be returned to user level,
         * even if the the update or snapshot failed.  This allows
         * kstat readers to get a handle on variable-size kstats,
         * detect dormant kstats, etc.
         */
        user_kstat.ks_ndata     = ksp->ks_ndata;
        user_kstat.ks_data_size = kbufsize;
        user_kstat.ks_flags     = ksp->ks_flags;
        user_kstat.ks_snaptime  = ksp->ks_snaptime;

        *rvalp = kstat_chain_id;
        KSTAT_EXIT(ksp);
        kstat_rele(ksp);

        if (kbuf == NULL)
                goto out;

        /*
         * Copy the buffer containing the kstat back to userland.
         */
        copysize = kbufsize;

        switch (model) {
        int i;
#ifdef _MULTI_DATAMODEL
        kstat32_t *k32;
        kstat_t *k;

        case DDI_MODEL_ILP32:

                if (ksp->ks_type == KSTAT_TYPE_NAMED) {
                        kstat_named_t *kn = kbuf;
                        char *strbuf = (char *)((kstat_named_t *)kn +
                            ksp->ks_ndata);

                        for (i = 0; i < user_kstat.ks_ndata; kn++, i++)
                                switch (kn->data_type) {
                                /*
                                 * Named statistics have fields of type 'long'.
                                 * For a 32-bit application looking at a 64-bit
                                 * kernel, forcibly truncate these 64-bit
                                 * quantities to 32-bit values.
                                 */
                                case KSTAT_DATA_LONG:
                                        kn->value.i32 = (int32_t)kn->value.l;
                                        kn->data_type = KSTAT_DATA_INT32;
                                        break;
                                case KSTAT_DATA_ULONG:
                                        kn->value.ui32 = (uint32_t)kn->value.ul;
                                        kn->data_type = KSTAT_DATA_UINT32;
                                        break;
                                /*
                                 * Long strings must be massaged before being
                                 * copied out to userland.  Do that here.
                                 */
                                case KSTAT_DATA_STRING:
                                        if (KSTAT_NAMED_STR_PTR(kn) == NULL)
                                                break;
                                        /*
                                         * If the string lies outside of kbuf
                                         * copy it there and update the pointer.
                                         */
                                        if (KSTAT_NAMED_STR_PTR(kn) <
                                            (char *)kbuf ||
                                            KSTAT_NAMED_STR_PTR(kn) +
                                            KSTAT_NAMED_STR_BUFLEN(kn) >
                                            (char *)kbuf + kbufsize + 1) {
                                                bcopy(KSTAT_NAMED_STR_PTR(kn),
                                                    strbuf,
                                                    KSTAT_NAMED_STR_BUFLEN(kn));

                                                KSTAT_NAMED_STR_PTR(kn) =
                                                    strbuf;
                                                strbuf +=
                                                    KSTAT_NAMED_STR_BUFLEN(kn);
                                                ASSERT(strbuf <=
                                                    (char *)kbuf +
                                                    kbufsize + 1);
                                        }
                                        /*
                                         * The offsets within the buffers are
                                         * the same, so add the offset to the
                                         * beginning of the new buffer to fix
                                         * the pointer.
                                         */
                                        KSTAT_NAMED_STR_PTR(kn) =
                                            (char *)user_kstat.ks_data +
                                            (KSTAT_NAMED_STR_PTR(kn) -
                                            (char *)kbuf);
                                        /*
                                         * Make sure the string pointer lies
                                         * within the allocated buffer.
                                         */
                                        ASSERT(KSTAT_NAMED_STR_PTR(kn) +
                                            KSTAT_NAMED_STR_BUFLEN(kn) <=
                                            ((char *)user_kstat.ks_data +
                                            ubufsize));
                                        ASSERT(KSTAT_NAMED_STR_PTR(kn) >=
                                            (char *)((kstat_named_t *)
                                            user_kstat.ks_data +
                                            user_kstat.ks_ndata));
                                        /*
                                         * Cast 64-bit ptr to 32-bit.
                                         */
                                        kn->value.str.addr.ptr32 =
                                            (caddr32_t)(uintptr_t)
                                            KSTAT_NAMED_STR_PTR(kn);
                                        break;
                                default:
                                        break;
                                }
                }

                if (user_kstat.ks_kid != 0)
                        break;

                /*
                 * This is the special case of the kstat header
                 * list for the entire system.  Reshape the
                 * array in place, then copy it out.
                 */
                k32 = kbuf;
                k = kbuf;
                for (i = 0; i < user_kstat.ks_ndata; k32++, k++, i++) {
                        k32->ks_crtime          = k->ks_crtime;
                        k32->ks_next            = 0;
                        k32->ks_kid             = k->ks_kid;
                        (void) strcpy(k32->ks_module, k->ks_module);
                        k32->ks_resv            = k->ks_resv;
                        k32->ks_instance        = k->ks_instance;
                        (void) strcpy(k32->ks_name, k->ks_name);
                        k32->ks_type            = k->ks_type;
                        (void) strcpy(k32->ks_class, k->ks_class);
                        k32->ks_flags           = k->ks_flags;
                        k32->ks_data            = 0;
                        k32->ks_ndata           = k->ks_ndata;
                        if (k->ks_data_size > UINT32_MAX) {
                                error = EOVERFLOW;
                                break;
                        }
                        k32->ks_data_size = (size32_t)k->ks_data_size;
                        k32->ks_snaptime        = k->ks_snaptime;
                }

                /*
                 * XXX  In this case we copy less data than is
                 *      claimed in the header.
                 */
                copysize = user_kstat.ks_ndata * sizeof (kstat32_t);
                break;
#endif  /* _MULTI_DATAMODEL */
        default:
        case DDI_MODEL_NONE:
                if (ksp->ks_type == KSTAT_TYPE_NAMED) {
                        kstat_named_t *kn = kbuf;
                        char *strbuf = (char *)((kstat_named_t *)kn +
                            ksp->ks_ndata);

                        for (i = 0; i < user_kstat.ks_ndata; kn++, i++)
                                switch (kn->data_type) {
#ifdef _LP64
                                case KSTAT_DATA_LONG:
                                        kn->data_type =
                                            KSTAT_DATA_INT64;
                                        break;
                                case KSTAT_DATA_ULONG:
                                        kn->data_type =
                                            KSTAT_DATA_UINT64;
                                        break;
#endif  /* _LP64 */
                                case KSTAT_DATA_STRING:
                                        if (KSTAT_NAMED_STR_PTR(kn) == NULL)
                                                break;
                                        /*
                                         * If the string lies outside of kbuf
                                         * copy it there and update the pointer.
                                         */
                                        if (KSTAT_NAMED_STR_PTR(kn) <
                                            (char *)kbuf ||
                                            KSTAT_NAMED_STR_PTR(kn) +
                                            KSTAT_NAMED_STR_BUFLEN(kn) >
                                            (char *)kbuf + kbufsize + 1) {
                                                bcopy(KSTAT_NAMED_STR_PTR(kn),
                                                    strbuf,
                                                    KSTAT_NAMED_STR_BUFLEN(kn));

                                                KSTAT_NAMED_STR_PTR(kn) =
                                                    strbuf;
                                                strbuf +=
                                                    KSTAT_NAMED_STR_BUFLEN(kn);
                                                ASSERT(strbuf <=
                                                    (char *)kbuf +
                                                    kbufsize + 1);
                                        }

                                        KSTAT_NAMED_STR_PTR(kn) =
                                            (char *)user_kstat.ks_data +
                                            (KSTAT_NAMED_STR_PTR(kn) -
                                            (char *)kbuf);
                                        ASSERT(KSTAT_NAMED_STR_PTR(kn) +
                                            KSTAT_NAMED_STR_BUFLEN(kn) <=
                                            ((char *)user_kstat.ks_data +
                                            ubufsize));
                                        ASSERT(KSTAT_NAMED_STR_PTR(kn) >=
                                            (char *)((kstat_named_t *)
                                            user_kstat.ks_data +
                                            user_kstat.ks_ndata));
                                        break;
                                default:
                                        break;
                                }
                }
                break;
        }

        if (error == 0 &&
            copyout(kbuf, user_kstat.ks_data, copysize))
                error = EFAULT;
        kmem_free(kbuf, kbufsize + 1);

out:
        /*
         * We have modified the ks_ndata, ks_data_size, ks_flags, and
         * ks_snaptime fields of the user kstat; now copy it back to userland.
         */
        switch (model) {
#ifdef _MULTI_DATAMODEL
        case DDI_MODEL_ILP32:
                if (kbufsize > UINT32_MAX) {
                        error = EOVERFLOW;
                        break;
                }
                user_kstat32.ks_ndata           = user_kstat.ks_ndata;
                user_kstat32.ks_data_size       = (size32_t)kbufsize;
                user_kstat32.ks_flags           = user_kstat.ks_flags;
                user_kstat32.ks_snaptime        = user_kstat.ks_snaptime;
                if (copyout(&user_kstat32, user_ksp, sizeof (kstat32_t)) &&
                    error == 0)
                        error = EFAULT;
                break;
#endif
        default:
        case DDI_MODEL_NONE:
                if (copyout(&user_kstat, user_ksp, sizeof (kstat_t)) &&
                    error == 0)
                        error = EFAULT;
                break;
        }

        return (error);
}

static int
write_kstat_data(int *rvalp, void *user_ksp, int flag, cred_t *cred)
{
        kstat_t user_kstat, *ksp;
        void *buf = NULL;
        size_t bufsize;
        int error = 0;

        if (secpolicy_sys_config(cred, B_FALSE) != 0)
                return (EPERM);

        switch (ddi_model_convert_from(flag & FMODELS)) {
#ifdef _MULTI_DATAMODEL
                kstat32_t user_kstat32;

        case DDI_MODEL_ILP32:
                if (copyin(user_ksp, &user_kstat32, sizeof (kstat32_t)))
                        return (EFAULT);
                /*
                 * These are the only fields we actually look at.
                 */
                user_kstat.ks_kid = user_kstat32.ks_kid;
                user_kstat.ks_data = (void *)(uintptr_t)user_kstat32.ks_data;
                user_kstat.ks_data_size = (size_t)user_kstat32.ks_data_size;
                user_kstat.ks_ndata = user_kstat32.ks_ndata;
                break;
#endif
        default:
        case DDI_MODEL_NONE:
                if (copyin(user_ksp, &user_kstat, sizeof (kstat_t)))
                        return (EFAULT);
        }

        bufsize = user_kstat.ks_data_size;
        buf = kmem_alloc(bufsize + 1, KM_NOSLEEP);
        if (buf == NULL)
                return (EAGAIN);

        if (copyin(user_kstat.ks_data, buf, bufsize)) {
                kmem_free(buf, bufsize + 1);
                return (EFAULT);
        }

        ksp = kstat_hold_bykid(user_kstat.ks_kid, getzoneid());
        if (ksp == NULL) {
                kmem_free(buf, bufsize + 1);
                return (ENXIO);
        }
        if (ksp->ks_flags & KSTAT_FLAG_INVALID) {
                kstat_rele(ksp);
                kmem_free(buf, bufsize + 1);
                return (EAGAIN);
        }
        if (!(ksp->ks_flags & KSTAT_FLAG_WRITABLE)) {
                kstat_rele(ksp);
                kmem_free(buf, bufsize + 1);
                return (EACCES);
        }

        /*
         * With KSTAT_FLAG_VAR_SIZE, one must call the kstat's update callback
         * routine to ensure ks_data_size is up to date.
         * In this case it makes sense to do it anyhow, as it will be shortly
         * followed by a KSTAT_SNAPSHOT().
         */
        KSTAT_ENTER(ksp);
        error = KSTAT_UPDATE(ksp, KSTAT_READ);
        if (error || user_kstat.ks_data_size != ksp->ks_data_size ||
            user_kstat.ks_ndata != ksp->ks_ndata) {
                KSTAT_EXIT(ksp);
                kstat_rele(ksp);
                kmem_free(buf, bufsize + 1);
                return (error ? error : EINVAL);
        }

        /*
         * We have to ensure that we don't accidentally change the type of
         * existing kstat_named statistics when writing over them.
         * Since read_kstat_data() modifies some of the types on their way
         * out, we need to be sure to handle these types seperately.
         */
        if (ksp->ks_type == KSTAT_TYPE_NAMED) {
                void *kbuf;
                kstat_named_t *kold;
                kstat_named_t *knew = buf;
                int i;

#ifdef  _MULTI_DATAMODEL
                int model = ddi_model_convert_from(flag & FMODELS);
#endif

                /*
                 * Since ksp->ks_data may be NULL, we need to take a snapshot
                 * of the published data to look at the types.
                 */
                kbuf = kmem_alloc(bufsize + 1, KM_NOSLEEP);
                if (kbuf == NULL) {
                        KSTAT_EXIT(ksp);
                        kstat_rele(ksp);
                        kmem_free(buf, bufsize + 1);
                        return (EAGAIN);
                }
                error = KSTAT_SNAPSHOT(ksp, kbuf, KSTAT_READ);
                if (error) {
                        KSTAT_EXIT(ksp);
                        kstat_rele(ksp);
                        kmem_free(kbuf, bufsize + 1);
                        kmem_free(buf, bufsize + 1);
                        return (error);
                }
                kold = kbuf;

                /*
                 * read_kstat_data() changes the types of
                 * KSTAT_DATA_LONG / KSTAT_DATA_ULONG, so we need to
                 * make sure that these (modified) types are considered
                 * valid.
                 */
                for (i = 0; i < ksp->ks_ndata; i++, kold++, knew++) {
                        switch (kold->data_type) {
#ifdef  _MULTI_DATAMODEL
                        case KSTAT_DATA_LONG:
                                switch (model) {
                                case DDI_MODEL_ILP32:
                                        if (knew->data_type ==
                                            KSTAT_DATA_INT32) {
                                                knew->value.l =
                                                    (long)knew->value.i32;
                                                knew->data_type =
                                                    KSTAT_DATA_LONG;
                                        }
                                        break;
                                default:
                                case DDI_MODEL_NONE:
#ifdef _LP64
                                        if (knew->data_type ==
                                            KSTAT_DATA_INT64) {
                                                knew->value.l =
                                                    (long)knew->value.i64;
                                                knew->data_type =
                                                    KSTAT_DATA_LONG;
                                        }
#endif /* _LP64 */
                                        break;
                                }
                                break;
                        case KSTAT_DATA_ULONG:
                                switch (model) {
                                case DDI_MODEL_ILP32:
                                        if (knew->data_type ==
                                            KSTAT_DATA_UINT32) {
                                                knew->value.ul =
                                                    (ulong_t)knew->value.ui32;
                                                knew->data_type =
                                                    KSTAT_DATA_ULONG;
                                        }
                                        break;
                                default:
                                case DDI_MODEL_NONE:
#ifdef _LP64
                                        if (knew->data_type ==
                                            KSTAT_DATA_UINT64) {
                                                knew->value.ul =
                                                    (ulong_t)knew->value.ui64;
                                                knew->data_type =
                                                    KSTAT_DATA_ULONG;
                                        }
#endif /* _LP64 */
                                        break;
                                }
                                break;
#endif /* _MULTI_DATAMODEL */
                        case KSTAT_DATA_STRING:
                                if (knew->data_type != KSTAT_DATA_STRING) {
                                        KSTAT_EXIT(ksp);
                                        kstat_rele(ksp);
                                        kmem_free(kbuf, bufsize + 1);
                                        kmem_free(buf, bufsize + 1);
                                        return (EINVAL);
                                }

#ifdef _MULTI_DATAMODEL
                                if (model == DDI_MODEL_ILP32)
                                        KSTAT_NAMED_STR_PTR(knew) =
                                            (char *)(uintptr_t)
                                                knew->value.str.addr.ptr32;
#endif
                                /*
                                 * Nothing special for NULL
                                 */
                                if (KSTAT_NAMED_STR_PTR(knew) == NULL)
                                        break;

                                /*
                                 * Check to see that the pointers all point
                                 * to within the buffer and after the array
                                 * of kstat_named_t's.
                                 */
                                if (KSTAT_NAMED_STR_PTR(knew) <
                                    (char *)
                                    ((kstat_named_t *)user_kstat.ks_data +
                                    ksp->ks_ndata)) {
                                        KSTAT_EXIT(ksp);
                                        kstat_rele(ksp);
                                        kmem_free(kbuf, bufsize + 1);
                                        kmem_free(buf, bufsize + 1);
                                        return (EINVAL);
                                }
                                if (KSTAT_NAMED_STR_PTR(knew) +
                                    KSTAT_NAMED_STR_BUFLEN(knew) >
                                    ((char *)user_kstat.ks_data +
                                    ksp->ks_data_size)) {
                                        KSTAT_EXIT(ksp);
                                        kstat_rele(ksp);
                                        kmem_free(kbuf, bufsize + 1);
                                        kmem_free(buf, bufsize + 1);
                                        return (EINVAL);
                                }

                                /*
                                 * Update the pointers within the buffer
                                 */
                                KSTAT_NAMED_STR_PTR(knew) =
                                    (char *)buf +
                                    (KSTAT_NAMED_STR_PTR(knew) -
                                    (char *)user_kstat.ks_data);
                                break;
                        default:
                                break;
                        }
                }

                kold = kbuf;
                knew = buf;

                /*
                 * Now make sure the types are what we expected them to be.
                 */
                for (i = 0; i < ksp->ks_ndata; i++, kold++, knew++)
                        if (kold->data_type != knew->data_type) {
                                KSTAT_EXIT(ksp);
                                kstat_rele(ksp);
                                kmem_free(kbuf, bufsize + 1);
                                kmem_free(buf, bufsize + 1);
                                return (EINVAL);
                        }

                kmem_free(kbuf, bufsize + 1);
        }

        error = KSTAT_SNAPSHOT(ksp, buf, KSTAT_WRITE);
        if (!error)
                error = KSTAT_UPDATE(ksp, KSTAT_WRITE);
        *rvalp = kstat_chain_id;
        KSTAT_EXIT(ksp);
        kstat_rele(ksp);
        kmem_free(buf, bufsize + 1);
        return (error);
}

/*ARGSUSED*/
static int
kstat_ioctl(dev_t dev, int cmd, intptr_t data, int flag, cred_t *cr, int *rvalp)
{
        int rc = 0;

        switch (cmd) {

        case KSTAT_IOC_CHAIN_ID:
                *rvalp = kstat_chain_id;
                break;

        case KSTAT_IOC_READ:
                rc = read_kstat_data(rvalp, (void *)data, flag);
                break;

        case KSTAT_IOC_WRITE:
                rc = write_kstat_data(rvalp, (void *)data, flag, cr);
                break;

        default:
                /* invalid request */
                rc = EINVAL;
        }
        return (rc);
}

/* ARGSUSED */
static int
kstat_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
    void **result)
{
        switch (infocmd) {
        case DDI_INFO_DEVT2DEVINFO:
                *result = kstat_devi;
                return (DDI_SUCCESS);
        case DDI_INFO_DEVT2INSTANCE:
                *result = NULL;
                return (DDI_SUCCESS);
        }
        return (DDI_FAILURE);
}

static int
kstat_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
        if (cmd != DDI_ATTACH)
                return (DDI_FAILURE);

        if (ddi_create_minor_node(devi, "kstat", S_IFCHR,
            0, DDI_PSEUDO, 0) == DDI_FAILURE) {
                ddi_remove_minor_node(devi, NULL);
                return (DDI_FAILURE);
        }
        kstat_devi = devi;
        return (DDI_SUCCESS);
}

static int
kstat_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
        if (cmd != DDI_DETACH)
                return (DDI_FAILURE);

        ddi_remove_minor_node(devi, NULL);
        return (DDI_SUCCESS);
}

static struct cb_ops kstat_cb_ops = {
        nulldev,                /* open */
        nulldev,                /* close */
        nodev,                  /* strategy */
        nodev,                  /* print */
        nodev,                  /* dump */
        nodev,                  /* read */
        nodev,                  /* write */
        kstat_ioctl,            /* ioctl */
        nodev,                  /* devmap */
        nodev,                  /* mmap */
        nodev,                  /* segmap */
        nochpoll,               /* poll */
        ddi_prop_op,            /* prop_op */
        0,                      /* streamtab  */
        D_NEW | D_MP            /* Driver compatibility flag */
};

static struct dev_ops kstat_ops = {
        DEVO_REV,               /* devo_rev, */
        0,                      /* refcnt  */
        kstat_info,             /* get_dev_info */
        nulldev,                /* identify */
        nulldev,                /* probe */
        kstat_attach,           /* attach */
        kstat_detach,           /* detach */
        nodev,                  /* reset */
        &kstat_cb_ops,          /* driver operations */
        (struct bus_ops *)0,    /* no bus operations */
        NULL,                   /* power */
        ddi_quiesce_not_needed,         /* quiesce */
};

static struct modldrv modldrv = {
        &mod_driverops, "kernel statistics driver", &kstat_ops,
};

static struct modlinkage modlinkage = {
        MODREV_1, &modldrv, NULL
};

int
_init(void)
{
        return (mod_install(&modlinkage));
}

int
_fini(void)
{
        return (mod_remove(&modlinkage));
}

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