root/usr/src/uts/common/io/gen_drv.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.
 */



/*
 * generic character driver
 */
#include <sys/types.h>
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/uio.h>
#include <sys/buf.h>
#include <sys/modctl.h>
#include <sys/open.h>
#include <sys/kmem.h>
#include <sys/conf.h>
#include <sys/cmn_err.h>
#include <sys/stat.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>


#define NUMEVENTS 6
#define COMPONENTS 2
#define COMP_0_MAXPWR   3
#define COMP_1_MAXPWR   2
#define MINPWR          0
static int maxpwr[] = { COMP_0_MAXPWR, COMP_1_MAXPWR };

/*
 * The state for each generic device.
 * NOTE: We save the node_type in the state structure. The node_type string
 * (and not a copy) is stashed in a minor node by  ddi_create_minor_node(),
 * so ddi_remove_minor_node() must occur prior to state free.
 */
typedef struct dstate {
        uint_t          flag;
        dev_info_t      *dip;                   /* my devinfo handle */
        char            *node_type;     /* stable node_type copy */
        ddi_callback_id_t gen_cb_ids[NUMEVENTS];
        kmutex_t        lock;
        char            *nodename;
        int             level[COMPONENTS];      /* pm level */
        int             busy[COMPONENTS];       /* busy state */
} dstate_t;


static void *dstates;

static int gen_debug = 0;

#ifdef DEBUG
#define gen_debug gen_debug_on
static int gen_debug_on = 0;
#define GEN_DEBUG(args) if (gen_debug) cmn_err args
#else
#define GEN_DEBUG(args)
#endif

extern void prom_printf(const char *fmt, ...);

static int gen_open(dev_t *devp, int flag, int otyp, cred_t *cred);
static int gen_close(dev_t devp, int flag, int otyp, cred_t *cred);
static int gen_read(dev_t dev, struct uio *uiop, cred_t *credp);
static int gen_write(dev_t dev, struct uio *uiop, cred_t *credp);
static int gen_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
    cred_t *credp, int *rvalp);
static int gen_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int gen_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
static void gen_event_cb(dev_info_t *dip, ddi_eventcookie_t cookie,
    void *arg, void *impl_data);

static int gen_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
    void **result);
static int gen_create_minor_nodes(dev_info_t *, struct dstate *);
static int gen_power(dev_info_t *, int, int);

static struct cb_ops gen_cb_ops = {
        gen_open,                       /* open */
        gen_close,                      /* close */
        nodev,                          /* strategy */
        nodev,                          /* print */
        nodev,                          /* dump */
        gen_read,                       /* read */
        gen_write,                      /* write */
        gen_ioctl,                      /* ioctl */
        nodev,                          /* devmap */
        nodev,                          /* mmap */
        nodev,                          /* segmap */
        nochpoll,                       /* poll */
        ddi_prop_op,                    /* prop_op */
        NULL,                           /* streamtab */
        D_NEW | D_MP | D_HOTPLUG,       /* flag */
        CB_REV,                         /* cb_rev */
        nodev,                          /* aread */
        nodev                           /* awrite */
};


static struct dev_ops gen_ops = {
        DEVO_REV,               /* devo_rev */
        0,                      /* refcnt */
        gen_info,               /* getinfo */
        nulldev,                /* identify */
        nulldev,                /* probe */
        gen_attach,             /* attach */
        gen_detach,             /* detach */
        nodev,                  /* reset */
        &gen_cb_ops,            /* driver ops */
        (struct bus_ops *)0,    /* bus ops */
        gen_power,              /* power */
        ddi_quiesce_not_supported,      /* devo_quiesce */
};

/*
 * INST_TO_MINOR() gives the starting minor number for a given gen_drv driver
 * instance. A shift left by 6 bits allows for each instance to have upto
 * 64 (2^6) minor numbers. The maximum minor number allowed by the system
 * is L_MAXMIN32 (0x3ffff). This effectively limits the gen_drv instance
 * numbers from 0 to 0xfff for a total of 4096 instances.
 */
#define INST_TO_MINOR(i)        (i << 6)
#define MINOR_TO_INST(mn)       (mn >> 6)

static char *mnodetypes[] = {
        "ddi_nt",
        "ddi_nt:device_type",
        "ddi_nt:device_class:bus_class",
        "ddi_nt2",
        "ddi_nt2:device_type",
        "ddi_nt2:device_type:bus_class",
};
#define N_NTYPES        (sizeof (mnodetypes) / sizeof (char *))

static struct modldrv modldrv = {
        &mod_driverops,
        "generic test driver",
        &gen_ops
};

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


/*
 * flags
 */
#define OPEN_FLAG                       0x001
#define PWR_HAS_CHANGED_ON_RESUME_FLAG  0x002
#define FAIL_SUSPEND_FLAG               0x004
#define PUP_WITH_PWR_HAS_CHANGED_FLAG   0x008
#define POWER_FLAG                      0x010
#define LOWER_POWER_FLAG                0x020
#define NO_INVOL_FLAG                   0x040
#define PM_SUPPORTED_FLAG               0x080

/*
 * ioctl commands (non-devctl ioctl commands)
 */
#define GENDRV_IOCTL                            ('P' << 8)
#define GENDRV_IOFAULT_SIMULATE                 (GENDRV_IOCTL | 0)
#define GENDRV_NDI_EVENT_TEST                   (GENDRV_IOCTL | 1)

int
_init(void)
{
        int e;

        if ((e = ddi_soft_state_init(&dstates,
            sizeof (struct dstate), 0)) != 0) {
                return (e);
        }

        if ((e = mod_install(&modlinkage)) != 0)  {
                ddi_soft_state_fini(&dstates);
        }

        return (e);
}

int
_fini(void)
{
        int e;

        if ((e = mod_remove(&modlinkage)) != 0)  {
                return (e);
        }
        ddi_soft_state_fini(&dstates);
        return (e);
}

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

static int
gen_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
        int instance = ddi_get_instance(devi);
        struct dstate *dstatep;
        int rval;
        int n_devs;
        int n_minorcomps;
        int isclone;
        ddi_eventcookie_t dev_offline_cookie, dev_reset_cookie;
        ddi_eventcookie_t bus_reset_cookie, bus_quiesce_cookie;
        ddi_eventcookie_t bus_unquiesce_cookie, bus_test_post_cookie;
        int i_init = 0;
        int level_tmp;

        int i;
        char *pm_comp[] = {
                "NAME=leaf0",
                "0=D0",
                "1=D1",
                "2=D2",
                "3=D3",
                "NAME=leaf1",
                "0=off",
                "1=blank",
                "2=on"};
        char *pm_hw_state = {"needs-suspend-resume"};


        switch (cmd) {
        case DDI_ATTACH:

                if (ddi_soft_state_zalloc(dstates, instance) !=
                    DDI_SUCCESS) {
                        cmn_err(CE_CONT, "%s%d: can't allocate state\n",
                            ddi_get_name(devi), instance);

                        return (DDI_FAILURE);
                }

                dstatep = ddi_get_soft_state(dstates, instance);
                dstatep->dip = devi;
                mutex_init(&dstatep->lock, NULL, MUTEX_DRIVER, NULL);

                n_devs = ddi_prop_get_int(DDI_DEV_T_ANY, devi, 0,
                    "ndevs", 1);

                isclone = ddi_prop_get_int(DDI_DEV_T_ANY, devi, 0,
                    "isclone", 0);

                n_minorcomps = ddi_prop_get_int(DDI_DEV_T_ANY, devi, 0,
                    "ncomps", 1);

                GEN_DEBUG((CE_CONT,
                    "%s%d attaching: n_devs=%d n_minorcomps=%d isclone=%d",
                    ddi_get_name(devi), ddi_get_instance(devi),
                    n_devs, n_minorcomps, isclone));

                if (isclone) {
                        if (ddi_create_minor_node(devi, "gen", S_IFCHR,
                            INST_TO_MINOR(instance), mnodetypes[0],
                            isclone) != DDI_SUCCESS) {
                                ddi_remove_minor_node(devi, NULL);
                                ddi_soft_state_free(dstates, instance);
                                cmn_err(CE_WARN, "%s%d: can't create minor "
                                "node", ddi_get_name(devi), instance);

                                return (DDI_FAILURE);
                        }
                        rval = DDI_SUCCESS;
                } else {
                        rval = gen_create_minor_nodes(devi, dstatep);
                        if (rval != DDI_SUCCESS) {
                                ddi_prop_remove_all(devi);
                                ddi_remove_minor_node(devi, NULL);
                                ddi_soft_state_free(dstates, instance);
                                cmn_err(CE_WARN, "%s%d: can't create minor "
                                "nodes", ddi_get_name(devi), instance);

                                return (DDI_FAILURE);
                        }
                }

                if (ddi_get_eventcookie(devi, "pshot_dev_offline",
                    &dev_offline_cookie) == DDI_SUCCESS) {
                        (void) ddi_add_event_handler(devi, dev_offline_cookie,
                            gen_event_cb, NULL, &(dstatep->gen_cb_ids[0]));
                }

                if (ddi_get_eventcookie(devi, "pshot_dev_reset",
                    &dev_reset_cookie) == DDI_SUCCESS) {
                        (void) ddi_add_event_handler(devi, dev_reset_cookie,
                            gen_event_cb, NULL, &(dstatep->gen_cb_ids[1]));
                }

                if (ddi_get_eventcookie(devi, "pshot_bus_reset",
                    &bus_reset_cookie) == DDI_SUCCESS) {
                        (void) ddi_add_event_handler(devi, bus_reset_cookie,
                            gen_event_cb, NULL, &(dstatep->gen_cb_ids[2]));
                }

                if (ddi_get_eventcookie(devi, "pshot_bus_quiesce",
                    &bus_quiesce_cookie) == DDI_SUCCESS) {
                        (void) ddi_add_event_handler(devi, bus_quiesce_cookie,
                            gen_event_cb, NULL, &(dstatep->gen_cb_ids[3]));
                }

                if (ddi_get_eventcookie(devi, "pshot_bus_unquiesce",
                    &bus_unquiesce_cookie) == DDI_SUCCESS) {
                        (void) ddi_add_event_handler(devi,
                            bus_unquiesce_cookie, gen_event_cb,
                            NULL, &(dstatep->gen_cb_ids[4]));
                }

                if (ddi_get_eventcookie(devi, "pshot_bus_test_post",
                    &bus_test_post_cookie) == DDI_SUCCESS) {
                        (void) ddi_add_event_handler(devi,
                            bus_test_post_cookie, gen_event_cb,
                            NULL, &(dstatep->gen_cb_ids[5]));
                }

                /*
                 * initialize the devices' pm state
                 */
                mutex_enter(&dstatep->lock);
                dstatep->flag &= ~OPEN_FLAG;
                dstatep->flag &= ~PWR_HAS_CHANGED_ON_RESUME_FLAG;
                dstatep->flag &= ~FAIL_SUSPEND_FLAG;
                dstatep->flag &= ~PUP_WITH_PWR_HAS_CHANGED_FLAG;
                dstatep->flag |= LOWER_POWER_FLAG;
                dstatep->flag &= ~NO_INVOL_FLAG;
                dstatep->flag |= PM_SUPPORTED_FLAG;
                dstatep->busy[0] = 0;
                dstatep->busy[1] = 0;
                dstatep->level[0] = -1;
                dstatep->level[1] = -1;
                mutex_exit(&dstatep->lock);

                /*
                 * stash the nodename
                 */
                dstatep->nodename = ddi_node_name(devi);

                /*
                 * Check if the no-involuntary-power-cycles property
                 * was created. Set NO_INVOL_FLAG if so.
                 */
                if (ddi_prop_exists(DDI_DEV_T_ANY, dstatep->dip,
                    (DDI_PROP_DONTPASS | DDI_PROP_NOTPROM),
                    "no-involuntary-power-cycles") == 1) {
                        GEN_DEBUG((CE_CONT,
                            "%s%d: DDI_ATTACH:\n\tno-involuntary-power-cycles"
                            " property was created",
                            ddi_node_name(devi), ddi_get_instance(devi)));
                        mutex_enter(&dstatep->lock);
                        dstatep->flag |= NO_INVOL_FLAG;
                        mutex_exit(&dstatep->lock);
                }

                /*
                 * Check if the dependency-property property
                 * was created.
                 */
                if (ddi_prop_exists(DDI_DEV_T_ANY, dstatep->dip,
                    (DDI_PROP_DONTPASS | DDI_PROP_NOTPROM),
                    "dependency-property") == 1) {
                        GEN_DEBUG((CE_CONT,
                            "%s%d: DDI_ATTACH:\n\tdependency-property"
                            " property was created",
                            ddi_node_name(devi), ddi_get_instance(devi)));
                }

                /*
                 * create the pm-components property. two comps:
                 * 4 levels on comp0, 3 on comp 1.
                 * - skip for a "tape" device, clear PM_SUPPORTED_FLAG
                 */
                if (strcmp(ddi_node_name(devi), "tape") != 0) {
                        if (ddi_prop_update_string_array(DDI_DEV_T_NONE, devi,
                            "pm-components", pm_comp, 9) != DDI_PROP_SUCCESS) {
                                cmn_err(CE_WARN, "%s%d: %s\n",
                                    ddi_node_name(devi),
                                    ddi_get_instance(devi),
                                    "unable to create \"pm-components\" "
                                    " property.");

                                return (DDI_FAILURE);
                        }
                } else {
                        mutex_enter(&dstatep->lock);
                        dstatep->flag &= ~PM_SUPPORTED_FLAG;
                        mutex_exit(&dstatep->lock);
                }

                /*
                 * Check if the pm-components property was created
                 */
                if (dstatep->flag & PM_SUPPORTED_FLAG) {
                        if (ddi_prop_exists(DDI_DEV_T_ANY, dstatep->dip,
                            (DDI_PROP_DONTPASS | DDI_PROP_NOTPROM),
                            "pm-components") != 1) {
                                cmn_err(CE_WARN, "%s%d: DDI_ATTACH:\n\t%s",
                                    ddi_node_name(devi),
                                    ddi_get_instance(devi),
                                    "\"pm-components\" property does"
                                    " not exist");

                                return (DDI_FAILURE);

                        } else {
                                GEN_DEBUG((CE_CONT, "%s%d: DDI_ATTACH:"
                                    " created pm-components property",
                                    ddi_node_name(devi),
                                    ddi_get_instance(devi)));
                        }
                }

                /*
                 * create the pm-hardware-state property.
                 * needed to get DDI_SUSPEND and DDI_RESUME calls
                 */
                if (ddi_prop_update_string(DDI_DEV_T_NONE, devi,
                    "pm-hardware-state", pm_hw_state) != DDI_PROP_SUCCESS) {
                        cmn_err(CE_WARN, "%s%d: DDI_ATTACH:\n\t%s\n",
                            ddi_node_name(devi), ddi_get_instance(devi),
                            "unable to create \"pm-hardware-state\" "
                            " property.");

                        return (DDI_FAILURE);
                }

                /*
                 * set power levels to max via pm_raise_power(),
                 */
                mutex_enter(&dstatep->lock);
                i_init = (dstatep->flag & PM_SUPPORTED_FLAG) ? 0 : COMPONENTS;
                mutex_exit(&dstatep->lock);
                for (i = i_init; i < COMPONENTS; i++) {
                        GEN_DEBUG((CE_CONT,
                            "%s%d: DDI_ATTACH: pm_raise_power comp %d "
                            "to level %d", ddi_node_name(devi),
                            ddi_get_instance(devi), i, maxpwr[i]));
                        if (pm_raise_power(dstatep->dip, i, maxpwr[i]) !=
                            DDI_SUCCESS) {
                                cmn_err(CE_WARN,
                                    "%s%d: DDI_ATTACH: pm_raise_power failed\n",
                                    ddi_node_name(devi),
                                    ddi_get_instance(devi));
                                dstatep->level[i] = -1;

                                return (DDI_FAILURE);
                        }
                }

                if (rval == DDI_SUCCESS) {
                        ddi_report_dev(devi);
                }
                return (rval);


        case DDI_RESUME:
                GEN_DEBUG((CE_CONT, "%s%d: DDI_RESUME", ddi_node_name(devi),
                    ddi_get_instance(devi)));

                dstatep = ddi_get_soft_state(dstates, ddi_get_instance(devi));
                if (dstatep == NULL) {

                        return (DDI_FAILURE);
                }

                /*
                 * Call pm_power_has_changed() if flag
                 * PWR_HAS_CHANGED_ON_RESUME_FLAG is set,
                 * then clear the flag
                 */
                mutex_enter(&dstatep->lock);
                i_init = (dstatep->flag & PM_SUPPORTED_FLAG) ? 0 : COMPONENTS;
                mutex_exit(&dstatep->lock);
                if (dstatep->flag & PWR_HAS_CHANGED_ON_RESUME_FLAG) {
                        for (i = i_init; i < COMPONENTS; i++) {
                                GEN_DEBUG((CE_CONT,
                                    "%s%d: DDI_RESUME: pm_power_has_changed "
                                    "comp %d to level %d", ddi_node_name(devi),
                                    ddi_get_instance(devi), i, maxpwr[i]));
                                mutex_enter(&dstatep->lock);
                                level_tmp = dstatep->level[i];
                                dstatep->level[i] = maxpwr[i];
                                if (pm_power_has_changed(dstatep->dip, i,
                                    maxpwr[i]) != DDI_SUCCESS) {
                                        cmn_err(CE_WARN,
                                            "%s%d: DDI_RESUME:\n\t"
                                            " pm_power_has_changed"
                                            " failed: comp %d to level %d\n",
                                            ddi_node_name(devi),
                                            ddi_get_instance(devi),
                                            i, maxpwr[i]);
                                        dstatep->level[i] = level_tmp;
                                }
                                mutex_exit(&dstatep->lock);
                        }
                } else {
                        /*
                         * Call pm_raise_power() instead
                         */
                        for (i = i_init; i < COMPONENTS; i++) {
                                GEN_DEBUG((CE_CONT,
                                    "%s%d: DDI_RESUME: pm_raise_power"
                                    " comp %d to level %d",
                                    ddi_node_name(devi), ddi_get_instance(devi),
                                    i, maxpwr[i]));
                                if (pm_raise_power(dstatep->dip, i, maxpwr[i])
                                    != DDI_SUCCESS) {
                                        cmn_err(CE_WARN,
                                            "%s%d: DDI_RESUME:"
                                            "\n\tpm_raise_power"
                                            "failed: comp %d to level %d\n",
                                            ddi_node_name(devi),
                                            ddi_get_instance(devi),
                                            i, maxpwr[i]);
                                }
                        }
                }

                return (DDI_SUCCESS);

        default:
                GEN_DEBUG((CE_WARN, "attach: default"));
                return (DDI_FAILURE);
        }
}

static int
gen_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
        struct dstate *dstatep;
        int instance;
        int i;
        int rv;
        int rm_power;
        int level_tmp;

#ifdef DEBUG
        int n_devs;
        int n_minorcomps;
        int isclone;
#endif

        switch (cmd) {
        case DDI_DETACH:
                GEN_DEBUG((CE_CONT, "%s%d: DDI_DETACH", ddi_node_name(devi),
                    ddi_get_instance(devi)));

                instance = ddi_get_instance(devi);
                dstatep = ddi_get_soft_state(dstates, instance);
                if (dstatep == NULL) {

                        return (DDI_FAILURE);
}

#ifdef DEBUG
                n_devs = ddi_prop_get_int(DDI_DEV_T_ANY, devi, 0,
                    "ndevs", 1);

                isclone = ddi_prop_get_int(DDI_DEV_T_ANY, devi, 0,
                    "isclone", 0);

                n_minorcomps = ddi_prop_get_int(DDI_DEV_T_ANY, devi, 0,
                    "ncomps", 1);
#endif /* DEBUG */

                /*
                 * power off component 1.
                 */
                if (dstatep->flag & PM_SUPPORTED_FLAG) {
                        GEN_DEBUG((CE_CONT,
                            "%s%d: DDI_DETACH: pm_lower_power comp 1 level %d",
                            ddi_node_name(devi), ddi_get_instance(devi),
                            MINPWR));
                        if (pm_lower_power(dstatep->dip, 1, MINPWR)
                            != DDI_SUCCESS) {
                                cmn_err(CE_WARN, "%s%d: DDI_DETACH:\n\t"
                                    "pm_lower_power failed for comp 1 to"
                                    " level %d\n", ddi_node_name(devi),
                                    ddi_get_instance(devi), MINPWR);

                                return (DDI_FAILURE);
                        }

                        /*
                         * check power level. Issue pm_power_has_changed
                         * if not at MINPWR.
                         */
                        mutex_enter(&dstatep->lock);
                        level_tmp = dstatep->level[1];
                        dstatep->level[1] = MINPWR;
                        if (dstatep->level[1] != MINPWR) {
                                GEN_DEBUG((CE_NOTE, "%s%d: DDI_DETACH:"
                                    " power off via pm_power_has_changed"
                                    " instead", ddi_node_name(devi),
                                    ddi_get_instance(devi)));
                                if (pm_power_has_changed(dstatep->dip,
                                    1, MINPWR) != DDI_SUCCESS) {
                                        GEN_DEBUG((CE_NOTE, "%s%d: DDI_DETACH:"
                                            " pm_power_has_changed failed for"
                                            " comp 1 to level %d",
                                            ddi_node_name(devi),
                                            ddi_get_instance(devi),
                                            MINPWR));
                                        dstatep->level[1] = level_tmp;
                                        mutex_exit(&dstatep->lock);

                                        return (DDI_FAILURE);
                                }
                        }
                        mutex_exit(&dstatep->lock);
                }

                /*
                 * If the LOWER_POWER_FLAG flag is not set,
                 * don't call pm_lowr_power() for comp 0.
                 * This should be used only for the XXXXX@XX,no_invol
                 * devices that export the
                 * no-involuntary-power-cycles property
                 */
                if (!(dstatep->flag & LOWER_POWER_FLAG) &&
                    dstatep->flag & PM_SUPPORTED_FLAG) {
                        cmn_err(CE_NOTE, "%s%d: DDI_DETACH:\n\t"
                            " NOT CALLING PM_LOWER_POWER():"
                            " LOWER_POWER_FLAG NOT SET\n",
                            ddi_node_name(devi), ddi_get_instance(devi));
                } else if (dstatep->flag & PM_SUPPORTED_FLAG) {
                        GEN_DEBUG((CE_CONT,
                            "%s%d: DDI_DETACH: pm_lower_power comp 0 level %d",
                            ddi_node_name(devi), ddi_get_instance(devi),
                            MINPWR));
                        if (pm_lower_power(dstatep->dip, 0, MINPWR)
                            != DDI_SUCCESS) {
                                cmn_err(CE_WARN, "%s%d: DDI_DETACH:\n\t"
                                    "pm_lower_power failed for comp 0 to"
                                    " level %d\n", ddi_node_name(devi),
                                    ddi_get_instance(devi), MINPWR);

                                return (DDI_FAILURE);
                        }

                        /*
                         * check power level. Issue pm_power_has_changed
                         * if not at MINPWR.
                         */
                        mutex_enter(&dstatep->lock);
                        level_tmp = dstatep->level[0];
                        dstatep->level[0] = MINPWR;
                        if (dstatep->level[0] != MINPWR) {
                                GEN_DEBUG((CE_NOTE, "%s%d: DDI_DETACH:"
                                    " power off via pm_power_has_changed"
                                    " instead", ddi_node_name(devi),
                                    ddi_get_instance(devi)));
                                if (pm_power_has_changed(dstatep->dip,
                                    0, MINPWR) != DDI_SUCCESS) {
                                        GEN_DEBUG((CE_NOTE, "%s%d: DDI_DETACH:"
                                            " pm_power_has_changed failed for"
                                            " comp 0 to level %d",
                                            ddi_node_name(devi),
                                            ddi_get_instance(devi),
                                            MINPWR));
                                        dstatep->level[0] = level_tmp;
                                        mutex_exit(&dstatep->lock);

                                        return (DDI_FAILURE);
                                }
                        }
                        mutex_exit(&dstatep->lock);
                }

                GEN_DEBUG((CE_CONT,
                    "%s%d detaching: n_devs=%d n_minorcomps=%d isclone=%d",
                    ddi_node_name(devi), ddi_get_instance(devi),
                    n_devs, n_minorcomps, isclone));

                for (i = 0; i < NUMEVENTS; i++) {
                        if (dstatep->gen_cb_ids[i]) {
                (void) ddi_remove_event_handler(dstatep->gen_cb_ids[i]);
                                dstatep->gen_cb_ids[i] = NULL;
                        }
                }

                ddi_prop_remove_all(devi);
                ddi_remove_minor_node(devi, NULL);
                if (dstatep->node_type)
                        kmem_free(dstatep->node_type,
                            strlen(dstatep->node_type) + 1);
                ddi_soft_state_free(dstates, instance);
                return (DDI_SUCCESS);

        case DDI_SUSPEND:
                GEN_DEBUG((CE_CONT, "%s%d: DDI_SUSPEND",
                    ddi_node_name(devi), ddi_get_instance(devi)));

                instance = ddi_get_instance(devi);
                dstatep = ddi_get_soft_state(dstates, instance);
                if (dstatep == NULL) {

                        return (DDI_FAILURE);
                }

                /*
                 * fail the suspend if FAIL_SUSPEND_FLAG is set.
                 * clear the FAIL_SUSPEND_FLAG flag
                 */
                mutex_enter(&dstatep->lock);
                if (dstatep->flag & FAIL_SUSPEND_FLAG) {
                        GEN_DEBUG((CE_CONT, "%s%d: DDI_SUSPEND:"
                            " FAIL_SUSPEND_FLAG is set,"
                            " fail suspend",
                            ddi_node_name(devi), ddi_get_instance(devi)));
                        dstatep->flag &= ~FAIL_SUSPEND_FLAG;
                        rv = DDI_FAILURE;
                } else {
                        rv = DDI_SUCCESS;
                }
                mutex_exit(&dstatep->lock);

                /*
                 * Issue ddi_removing_power() to determine if the suspend
                 * was initiated by either CPR or DR. If CPR, the system
                 * will be powered OFF; if this driver has set the
                 * NO_INVOL_FLAG, then refuse to suspend. If DR, power
                 * will not be removed, thus allow the suspend.
                 */
                if (dstatep->flag & NO_INVOL_FLAG &&
                    dstatep->flag & PM_SUPPORTED_FLAG) {
                        GEN_DEBUG((CE_CONT, "%s%d: DDI_SUSPEND:"
                            " check via ddi_removing_power()",
                            ddi_node_name(devi), ddi_get_instance(devi)));

                        rm_power = ddi_removing_power(dstatep->dip);

                        if (rm_power < 0) {
                                cmn_err(CE_WARN, "%s%d: DDI_SUSPEND:"
                                    " ddi_removing_power() failed\n",
                                    ddi_node_name(devi),
                                    ddi_get_instance(devi));
                        } else if (rm_power == 1) {
                                /*
                                 * CPR: power will be removed
                                 */
                                GEN_DEBUG((CE_CONT, "%s%d: DDI_SUSPEND:\n\t"
                                    " CPR: POWER WILL BE REMOVED, THEREFORE"
                                    " REFUSE TO SUSPEND", ddi_node_name(devi),
                                    ddi_get_instance(devi)));
                                rv = DDI_FAILURE;
                        } else if (rm_power == 0) {
                                /*
                                 * DR: power will not be removed
                                 */
                                GEN_DEBUG((CE_CONT, "%s%d: DDI_SUSPEND:\n\t"
                                    " DR: POWER WILL NOT BE REMOVED, THEREFORE"
                                    " ALLOW THE SUSPEND", ddi_node_name(devi),
                                    ddi_get_instance(devi)));
                                rv = DDI_SUCCESS;
                        }
                }

                /*
                 * power OFF via pm_power_has_changed()
                 */
                mutex_enter(&dstatep->lock);
                if (dstatep->flag & PM_SUPPORTED_FLAG &&
                    !(dstatep->flag & NO_INVOL_FLAG)) {
                        level_tmp = dstatep->level[0];
                        dstatep->level[0] = MINPWR;
                        GEN_DEBUG((CE_CONT,
                            "%s%d: DDI_SUSPEND: pm_power_has_changed comp 0"
                            " level %d", ddi_node_name(devi),
                            ddi_get_instance(devi), MINPWR));
                        if (pm_power_has_changed(dstatep->dip, 0, MINPWR)
                            != DDI_SUCCESS) {
                                cmn_err(CE_WARN, "%s%d: DDI_SUSPEND:\n\t"
                                    "pm_power_has_changed failed for comp 0 to"
                                    " level %d\n", ddi_node_name(devi),
                                    ddi_get_instance(devi), MINPWR);
                                dstatep->level[0] = level_tmp;
                                mutex_exit(&dstatep->lock);

                                return (DDI_FAILURE);
                        }
                }
                mutex_exit(&dstatep->lock);

                return (rv);

        default:

                return (DDI_FAILURE);
        }
}

/* ARGSUSED */
static int
gen_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
        dev_t   dev;
        int     instance;

        if (infocmd != DDI_INFO_DEVT2INSTANCE)
                return (DDI_FAILURE);

        dev = (dev_t)arg;
        instance = MINOR_TO_INST(getminor(dev));
        *result = (void *)(uintptr_t)instance;
        return (DDI_SUCCESS);
}


/*ARGSUSED*/
static int
gen_open(dev_t *devp, int flag, int otyp, cred_t *cred)
{
        minor_t minor;
        struct dstate *dstatep;

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

        minor = getminor(*devp);
        if ((dstatep = ddi_get_soft_state(dstates,
            MINOR_TO_INST(minor))) == NULL)
                return (ENXIO);

        mutex_enter(&dstatep->lock);
        dstatep->flag |= OPEN_FLAG;
        mutex_exit(&dstatep->lock);

        GEN_DEBUG((CE_CONT,
            "%s%d open",
            dstatep->nodename, MINOR_TO_INST(minor)));

        return (0);
}

/*ARGSUSED*/
static int
gen_close(dev_t dev, int flag, int otyp, cred_t *cred)
{
        struct dstate *dstatep;
        minor_t minor = getminor(dev);

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

        dstatep = ddi_get_soft_state(dstates, MINOR_TO_INST(minor));

        if (dstatep == NULL)
                return (ENXIO);

        mutex_enter(&dstatep->lock);
        dstatep->flag &= ~OPEN_FLAG;
        mutex_exit(&dstatep->lock);

        GEN_DEBUG((CE_CONT,
            "%s%d close",
            dstatep->nodename, MINOR_TO_INST(minor)));

        return (0);
}

/*ARGSUSED*/
static int
gen_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp)
{
        struct dstate *dstatep;
        ddi_eventcookie_t cookie;
        int instance;
        int rval = 0;
        char *nodename;
        int i;
        struct devctl_iocdata *dcp;
        uint_t state;
        int ret;
        int level_tmp;

        instance = MINOR_TO_INST(getminor(dev));
        dstatep = ddi_get_soft_state(dstates, instance);
        nodename = dstatep->nodename;

        if (dstatep == NULL)
                return (ENXIO);

        /*
         * read devctl ioctl data
         */
        if (ndi_dc_allochdl((void *)arg, &dcp) != NDI_SUCCESS)
                return (EFAULT);

        switch (cmd) {
        case GENDRV_IOFAULT_SIMULATE:
                if (ddi_get_eventcookie(dstatep->dip, DDI_DEVI_FAULT_EVENT,
                    &(cookie)) != NDI_SUCCESS)
                        return (DDI_FAILURE);

                return (ndi_post_event(dstatep->dip, dstatep->dip, cookie,
                    NULL));

        case GENDRV_NDI_EVENT_TEST:
                if (ddi_get_eventcookie(dstatep->dip, "pshot_dev_offline",
                    &cookie) == NDI_SUCCESS) {
                        (void) ndi_post_event(dstatep->dip, dstatep->dip,
                            cookie, NULL);
                }

                if (ddi_get_eventcookie(dstatep->dip, "pshot_dev_reset",
                    &cookie) == NDI_SUCCESS) {
                        (void) ndi_post_event(dstatep->dip, dstatep->dip,
                            cookie, NULL);
                }

                if (ddi_get_eventcookie(dstatep->dip, "pshot_bus_reset",
                    &cookie) == NDI_SUCCESS) {
                        (void) ndi_post_event(dstatep->dip, dstatep->dip,
                            cookie, NULL);
                }

                if (ddi_get_eventcookie(dstatep->dip, "pshot_bus_quiesce",
                    &cookie) == NDI_SUCCESS) {
                        (void) ndi_post_event(dstatep->dip, dstatep->dip,
                            cookie, NULL);
                }

                if (ddi_get_eventcookie(dstatep->dip, "pshot_bus_unquiesce",
                    &cookie) == NDI_SUCCESS) {
                        (void) ndi_post_event(dstatep->dip, dstatep->dip,
                            cookie, NULL);
                }

                if (ddi_get_eventcookie(dstatep->dip, "pshot_bus_test_post",
                    &cookie) == NDI_SUCCESS) {
                        (void) ndi_post_event(dstatep->dip, dstatep->dip,
                            cookie, NULL);
                }

                break;

        case DEVCTL_PM_PWR_HAS_CHANGED_ON_RESUME:
                /*
                 * Issue pm_power_has_changed() call on DDI_RESUME
                 */
                mutex_enter(&dstatep->lock);
                dstatep->flag |= PWR_HAS_CHANGED_ON_RESUME_FLAG;
                mutex_exit(&dstatep->lock);
                GEN_DEBUG((CE_CONT, "%s%d:"
                    " DEVCTL_PM_PWR_HAS_CHANGED_ON_RESUME", nodename,
                    instance));

                break;

        case DEVCTL_PM_FAIL_SUSPEND:
                /*
                 * Fail the suspend attempt in DDI_SUSPEND
                 */
                mutex_enter(&dstatep->lock);
                dstatep->flag |= FAIL_SUSPEND_FLAG;
                mutex_exit(&dstatep->lock);
                GEN_DEBUG((CE_CONT, "%s%d: DEVCTL_PM_FAIL_SUSPEND",
                    nodename, instance));

                break;

        case DEVCTL_PM_PUP_WITH_PWR_HAS_CHANGED:
                /*
                 * Use pm_power_has_changed() to power up comp 0 when
                 * enforcing the comp 0 vs comp-not 0 dependency:
                 * Power up comp 0 first, if request for comp-not-0
                 * comes in.
                 * Else, default to pm_raise_power().
                 */
                mutex_enter(&dstatep->lock);
                dstatep->flag |= PUP_WITH_PWR_HAS_CHANGED_FLAG;
                mutex_exit(&dstatep->lock);
                GEN_DEBUG((CE_CONT, "%s%d: DEVCTL_PM_PUP_WITH_PWR_HAS_CHANGED",
                    nodename, instance));

                break;

        case DEVCTL_PM_BUSY_COMP:
                /*
                 * mark component 0 busy via a pm_busy_component() call.
                 * update the busy[] array.
                 */
                mutex_enter(&dstatep->lock);
                ++dstatep->busy[0];
                GEN_DEBUG((CE_CONT, "%s%d: DEVCTL_PM_BUSY_COMP: comp 0:"
                    " busy=%d", nodename, instance, dstatep->busy[0]));
                mutex_exit(&dstatep->lock);
                ret = pm_busy_component(dstatep->dip, 0);
                ASSERT(ret == DDI_SUCCESS);

                break;

        case DEVCTL_PM_BUSY_COMP_TEST:
                /*
                 * test busy state on component 0
                 */
                mutex_enter(&dstatep->lock);
                state = dstatep->busy[0];
                if (copyout(&state, dcp->cpyout_buf,
                    sizeof (uint_t)) != 0) {
                        cmn_err(CE_WARN, "%s%d:"
                            " DEVCTL_PM_BUSY_COMP_TEST: copyout failed\n",
                            nodename, instance);
                        rval = EINVAL;
                }
                GEN_DEBUG((CE_CONT, "%s%d: DEVCTL_PM_BUSY_COMP_TEST:"
                    " comp 0 busy %d",
                    nodename, instance, state));
                mutex_exit(&dstatep->lock);

                break;

        case DEVCTL_PM_IDLE_COMP:
                /*
                 * mark component 0 idle via a pm_idle_component() call.
                 * NOP if dstatep->busy[0] == 0.
                 */
                mutex_enter(&dstatep->lock);
                if (dstatep->busy[0] > 0) {
                        --dstatep->busy[0];
                        GEN_DEBUG((CE_CONT, "%s%d: DEVCTL_PM_IDLE_COMP:"
                            " comp 0: busy=%d", nodename, instance,
                            dstatep->busy[0]));
                        mutex_exit(&dstatep->lock);
                        ret = pm_idle_component(dstatep->dip, 0);
                        ASSERT(ret == DDI_SUCCESS);
                } else {
                        mutex_exit(&dstatep->lock);
                }

                break;

        case DEVCTL_PM_PROM_PRINTF:
                (void) prom_printf("%s%d: PROM_PRINTF FROM GEN_DRV\n",
                    nodename, instance);

                break;

        case DEVCTL_PM_RAISE_PWR:
                /*
                 * power up both components to MAXPWR via
                 * pm_raise_power() calls. this ioctl() cmd
                 * assumes that the current level is 0
                 */
                for (i = 0; i < COMPONENTS; i++) {
                        GEN_DEBUG((CE_CONT, "%s%d: DEVCTL_PM_RAISE_PWR:"
                            " comp %d old 0 new %d",
                            nodename, instance, i, maxpwr[i]));
                        if (pm_raise_power(dstatep->dip, 0, maxpwr[i])
                            != DDI_SUCCESS) {
                                rval = EINVAL;
                        }
                }

                break;

        case DEVCTL_PM_CHANGE_PWR_LOW:
                /*
                 * power off both components via pm_power_has_changed() calls
                 */
                for (i = (COMPONENTS - 1); i >= 0; --i) {
                        GEN_DEBUG((CE_CONT, "%s%d: DEVCTL_PM_CHANGE_PWR_LOW:"
                            " comp %d new 0",
                            nodename, instance, i));
                        mutex_enter(&dstatep->lock);
                        level_tmp = dstatep->level[i];
                        dstatep->level[i] = 0;
                        if (pm_power_has_changed(dstatep->dip, i, 0)
                            != DDI_SUCCESS) {
                                dstatep->level[i] = level_tmp;
                                rval = EINVAL;
                        }
                        mutex_exit(&dstatep->lock);
                }

                break;

        case DEVCTL_PM_CHANGE_PWR_HIGH:
                /*
                 * power up both components to MAXPWR via
                 * pm_power_has_changed() calls
                 */
                for (i = 0; i < COMPONENTS; i++) {
                        GEN_DEBUG((CE_CONT, "%s%d: DEVCTL_PM_CHANGE_PWR_HIGH:"
                            " comp %d new %d",
                            nodename, instance, i, maxpwr[i]));
                        mutex_enter(&dstatep->lock);
                        level_tmp = dstatep->level[i];
                        dstatep->level[i] = maxpwr[i];
                        if (pm_power_has_changed(dstatep->dip, i, maxpwr[i])
                            != DDI_SUCCESS) {
                                dstatep->level[i] = level_tmp;
                                rval = EINVAL;
                        }
                        mutex_exit(&dstatep->lock);
                }

                break;

        case DEVCTL_PM_POWER:
                /*
                 * test if the gen_drv_power() routine has been called,
                 * then clear
                 */
                mutex_enter(&dstatep->lock);
                state = (dstatep->flag & POWER_FLAG) ? 1 : 0;
                if (copyout(&state, dcp->cpyout_buf,
                    sizeof (uint_t)) != 0) {
                        cmn_err(CE_WARN, "%s%d: DEVCTL_PM_POWER:"
                            " copyout failed\n", nodename, instance);
                        rval = EINVAL;
                }
                GEN_DEBUG((CE_CONT, "%s%d: %s POWER_FLAG: %d",
                    nodename, instance, "DEVCTL_PM_POWER", state));
                dstatep->flag &= ~POWER_FLAG;
                mutex_exit(&dstatep->lock);
                break;

        case DEVCTL_PM_NO_LOWER_POWER:
                /*
                 * issue to not invoke pm_lower_power() on detach
                 */
                mutex_enter(&dstatep->lock);
                dstatep->flag &= ~LOWER_POWER_FLAG;
                mutex_exit(&dstatep->lock);
                GEN_DEBUG((CE_CONT, "%s%d: DEVCTL_PM_NO_LOWER_POWER",
                    nodename, instance));
                break;

        default:
                return (ENOTTY);
        }

        return (rval);
}

/*ARGSUSED*/
static int
gen_read(dev_t dev, struct uio *uiop, cred_t *credp)
{
        return (0);
}

/*ARGSUSED*/
static int
gen_write(dev_t dev, struct uio *uiop, cred_t *credp)
{
        return (0);
}

/*ARGSUSED0*/
static int
gen_power(dev_info_t *dip, int cmpt, int level)
{
        struct dstate *dstatep;
        int instance = ddi_get_instance(dip);
        char *nodename = ddi_node_name(dip);
        int level_tmp;

        GEN_DEBUG((CE_CONT, "%s%d: power: cmpt %d to level %d",
            nodename, instance, cmpt, level));

        dstatep = ddi_get_soft_state(dstates, instance);
        if (dstatep == NULL) {

                return (DDI_FAILURE);
        }

        /*
         * Keep track of the power levels for both components
         * in the dstatep->comp[] array.
         * Set comp 0 to full level if non-zero comps
         * are being set to a higher, non-zero level.
         */
        if (cmpt == 0) {
                mutex_enter(&dstatep->lock);
                dstatep->level[cmpt] = level;
                mutex_exit(&dstatep->lock);
        } else if (level > dstatep->level[cmpt] && level != 0 &&
            dstatep->level[0] != COMP_0_MAXPWR) {
                /*
                 * If component 0 is not at COMP_0_MAXPWR, and component 1
                 * is being powered ON, invoke pm_raise_power() or
                 * pm_power_has_changed() based on the
                 * PUP_WITH_PWR_HAS_CHANGED_FLAG flag.
                 * PUP_WITH_PWR_HAS_CHANGED_FLAG = FALSE by default, invoking
                 * pm_raise_power().
                 */
                if (!(dstatep->flag & PUP_WITH_PWR_HAS_CHANGED_FLAG)) {
                        /*
                         * first set comp 0 to level COMP_0_MAXPWR
                         */
                        GEN_DEBUG((CE_CONT, "%s%d: power:  "
                            "pm_raise_power: comp 0 to level %d",
                            nodename, instance, COMP_0_MAXPWR));
                        if (pm_raise_power(dip, 0, COMP_0_MAXPWR) !=
                            DDI_SUCCESS) {
                                cmn_err(CE_WARN,
                                    "%s%d: power: pm_raise_power() "
                                    "failed: comp 0 to level %d\n",
                                    nodename, instance, COMP_0_MAXPWR);

                                return (DDI_FAILURE);

                        } else {
                                mutex_enter(&dstatep->lock);
                                dstatep->level[0] = COMP_0_MAXPWR;
                                /*
                                 * now set the level on the non-zero comp
                                 */
                                dstatep->level[cmpt] = level;
                                mutex_exit(&dstatep->lock);
                                GEN_DEBUG((CE_CONT, "%s%d: power: "
                                    "comp %d to level %d",
                                    nodename, instance, cmpt, level));
                        }
                } else {
                        GEN_DEBUG((CE_CONT, "%s%d: power: "
                            "pm_power_has_changed: comp 0 to level %d",
                            nodename, instance, COMP_0_MAXPWR));
                        mutex_enter(&dstatep->lock);
                        level_tmp = dstatep->level[0];
                        dstatep->level[0] = COMP_0_MAXPWR;
                        if (pm_power_has_changed(dip, 0, COMP_0_MAXPWR) !=
                            DDI_SUCCESS) {
                                cmn_err(CE_WARN,
                                    "%s%d: power: pm_power_has_changed() "
                                    "failed: comp 0 to level %d\n",
                                    nodename, instance, COMP_0_MAXPWR);
                                dstatep->level[0] = level_tmp;
                        } else {
                                /*
                                 * now set the level on the non-zero comp
                                 */
                                GEN_DEBUG((CE_CONT, "%s%d: power:"
                                    " pm_power_has_changed: comp %d"
                                    " to level %d", nodename, instance,
                                    cmpt, level));
                                dstatep->level[cmpt] = level;
                        }
                        mutex_exit(&dstatep->lock);
                }
        } else {
                mutex_enter(&dstatep->lock);
                dstatep->level[cmpt] = level;
                mutex_exit(&dstatep->lock);
        }

        return (DDI_SUCCESS);
}


/*
 * Create properties of various data types for testing devfs events.
 */
static int
gen_create_properties(dev_info_t *devi)
{
        int int_val = 3023;
        int int_array[] = { 3, 10, 304, 230, 4};
        int64_t int64_val = 20;
        int64_t int64_array[] = { 12, 24, 36, 48};
        char *string_val = "Dev_node_prop";
        char *string_array[] = {"Dev_node_prop:0",
            "Dev_node_prop:1", "Dev_node_prop:2", "Dev_node_prop:3"};
        uchar_t byte_array[] = { (uchar_t)0xaa, (uchar_t)0x55,
            (uchar_t)0x12, (uchar_t)0xcd };
        char bytes[] = { (char)0x00, (char)0xef, (char)0xff };

        if (ddi_prop_update_int(DDI_DEV_T_NONE, devi, "int", int_val)
            != DDI_PROP_SUCCESS)
                return (DDI_FAILURE);

        if (ddi_prop_update_int_array(DDI_DEV_T_NONE, devi, "int-array",
            int_array, sizeof (int_array) / sizeof (int)) != DDI_PROP_SUCCESS)
                return (DDI_FAILURE);

        if (ddi_prop_update_int64(DDI_DEV_T_NONE, devi, "int64", int64_val)
            != DDI_PROP_SUCCESS)
                return (DDI_FAILURE);

        if (ddi_prop_update_int64_array(DDI_DEV_T_NONE, devi, "int64-array",
            int64_array, sizeof (int64_array) / sizeof (int64_t))
            != DDI_PROP_SUCCESS)
                return (DDI_FAILURE);

        if (ddi_prop_update_string(DDI_DEV_T_NONE, devi, "string", string_val)
            != DDI_PROP_SUCCESS)
                return (DDI_FAILURE);

        if (ddi_prop_update_string_array(DDI_DEV_T_NONE, devi, "string-array",
            string_array, sizeof (string_array) / sizeof (char *))
            != DDI_PROP_SUCCESS)
                return (DDI_FAILURE);

        if (ddi_prop_create(DDI_DEV_T_NONE, devi, DDI_PROP_CANSLEEP,
            "boolean", NULL, 0) != DDI_PROP_SUCCESS)
                return (DDI_FAILURE);

        if (ddi_prop_update_byte_array(DDI_DEV_T_NONE, devi, "byte-array",
            byte_array, sizeof (byte_array)) != DDI_PROP_SUCCESS)
                return (DDI_FAILURE);

        /* untyped property */
        if (ddi_prop_create(DDI_DEV_T_NONE, devi, DDI_PROP_CANSLEEP, "untyped",
            (caddr_t)bytes, sizeof (bytes)) != DDI_PROP_SUCCESS)
                return (DDI_FAILURE);

        return (DDI_SUCCESS);
}

static struct driver_minor_data {
        char    *name;
        minor_t minor;
        int     type;
} disk_minor_data[] = {
        {"a", 0, S_IFBLK},
        {"b", 1, S_IFBLK},
        {"c", 2, S_IFBLK},
        {"d", 3, S_IFBLK},
        {"e", 4, S_IFBLK},
        {"f", 5, S_IFBLK},
        {"g", 6, S_IFBLK},
        {"h", 7, S_IFBLK},
        {"a,raw", 0, S_IFCHR},
        {"b,raw", 1, S_IFCHR},
        {"c,raw", 2, S_IFCHR},
        {"d,raw", 3, S_IFCHR},
        {"e,raw", 4, S_IFCHR},
        {"f,raw", 5, S_IFCHR},
        {"g,raw", 6, S_IFCHR},
        {"h,raw", 7, S_IFCHR},
        {0}
};


static struct driver_serial_minor_data {
        char    *name;
        minor_t minor;
        int     type;
        char    *node_type;
}  serial_minor_data[] = {
        {"0", 0, S_IFCHR, "ddi_serial"},
        {"1", 1, S_IFCHR, "ddi_serial"},
        {"0,cu", 2, S_IFCHR, "ddi_serial:dialout"},
        {"1,cu", 3, S_IFCHR, "ddi_serial:dialout"},
        {0}
};


static int
gen_create_display(dev_info_t *devi)
{

        int instance = ddi_get_instance(devi);
        char minor_name[15];

        (void) sprintf(minor_name, "cgtwenty%d", instance);

        return (ddi_create_minor_node(devi, minor_name, S_IFCHR,
            INST_TO_MINOR(instance), DDI_NT_DISPLAY, 0));
}

static int
gen_create_mn_disk_chan(dev_info_t *devi)
{
        struct driver_minor_data *dmdp;
        int instance = ddi_get_instance(devi);

        if (gen_create_properties(devi) != DDI_SUCCESS)
                return (DDI_FAILURE);

        for (dmdp = disk_minor_data; dmdp->name != NULL; dmdp++) {
                if (ddi_create_minor_node(devi, dmdp->name, dmdp->type,
                    (INST_TO_MINOR(instance)) | dmdp->minor,
                    DDI_NT_BLOCK_CHAN, 0) != DDI_SUCCESS) {

                        return (DDI_FAILURE);
                }
        }
        return (DDI_SUCCESS);
}

static uint_t
atod(char *s)
{
        uint_t val = 0;
        uint_t digit;

        while (*s) {
                if (*s >= '0' && *s <= '9')
                        digit = *s++ - '0';
                else
                        break;
                val = (val * 10) + digit;
        }
        return (val);
}


static int
gen_create_mn_disk_wwn(dev_info_t *devi)
{
        struct driver_minor_data *dmdp;
        int instance = ddi_get_instance(devi);
        char *address = ddi_get_name_addr(devi);
        int target, lun;

        if (address[0] >= '0' && address[0] <= '9' &&
            strchr(address, ',')) {
                target = atod(address);
                address = strchr(address, ',');
                lun = atod(++address);
        } else { /* this hack is for rm_stale_link() testing */
                target = 10;
                lun = 5;
        }

        if (ddi_prop_create(DDI_DEV_T_NONE, devi, DDI_PROP_CANSLEEP,
            "target", (caddr_t)&target, sizeof (int))
            != DDI_PROP_SUCCESS) {
                return (DDI_FAILURE);
        }
        if (ddi_prop_create(DDI_DEV_T_NONE, devi, DDI_PROP_CANSLEEP,
            "lun", (caddr_t)&lun, sizeof (int))
            != DDI_PROP_SUCCESS) {
                return (DDI_FAILURE);
        }

        for (dmdp = disk_minor_data; dmdp->name != NULL; dmdp++) {
                if (ddi_create_minor_node(devi, dmdp->name, dmdp->type,
                    (INST_TO_MINOR(instance)) | dmdp->minor,
                    DDI_NT_BLOCK_WWN, 0) != DDI_SUCCESS) {

                        return (DDI_FAILURE);
                }
        }
        return (DDI_SUCCESS);
}

static int
gen_create_mn_disk_cdrom(dev_info_t *devi)
{
        struct driver_minor_data *dmdp;
        int instance = ddi_get_instance(devi);

        for (dmdp = disk_minor_data; dmdp->name != NULL; dmdp++) {
                if (ddi_create_minor_node(devi, dmdp->name, dmdp->type,
                    (INST_TO_MINOR(instance)) | dmdp->minor,
                    DDI_NT_CD_CHAN, 0) != DDI_SUCCESS) {

                        return (DDI_FAILURE);
                }
        }
        return (DDI_SUCCESS);
}

static int
gen_create_mn_disk_fd(dev_info_t *devi)
{
        struct driver_minor_data *dmdp;
        int instance = ddi_get_instance(devi);

        for (dmdp = disk_minor_data; dmdp->name != NULL; dmdp++) {
                if (ddi_create_minor_node(devi, dmdp->name, dmdp->type,
                    (INST_TO_MINOR(instance)) | dmdp->minor,
                    DDI_NT_BLOCK_CHAN, 0) != DDI_SUCCESS) {

                        return (DDI_FAILURE);
                }
        }
        return (DDI_SUCCESS);
}

static int
gen_create_serial(dev_info_t *devi)
{
        struct driver_serial_minor_data *dmdp;
        int instance = ddi_get_instance(devi);

        for (dmdp = serial_minor_data; dmdp->name != NULL; dmdp++) {
                if (ddi_create_minor_node(devi, dmdp->name, dmdp->type,
                    (INST_TO_MINOR(instance)) | dmdp->minor,
                    dmdp->node_type, 0) != DDI_SUCCESS) {

                        return (DDI_FAILURE);
                }
        }
        return (DDI_SUCCESS);
}

static int
gen_create_net(dev_info_t *devi)
{
        int instance = ddi_get_instance(devi);
        char minorname[32];

        if (gen_create_properties(devi) != DDI_SUCCESS)
                return (DDI_FAILURE);

        (void) snprintf(minorname, sizeof (minorname), "gen_drv%d", instance);
        return (ddi_create_minor_node(devi, minorname, S_IFCHR,
            INST_TO_MINOR(instance), DDI_NT_NET, 0));
}

static int
gen_create_minor_nodes(dev_info_t *devi, struct dstate *dstatep)
{
        int rval = DDI_SUCCESS;
        char *node_name;

        node_name = ddi_node_name(devi);

        if (strcmp(node_name, "disk_chan") == 0) {
                rval = gen_create_mn_disk_chan(devi);
        } else if (strcmp(node_name, "disk_wwn") == 0) {
                rval = gen_create_mn_disk_wwn(devi);
        } else if (strcmp(node_name, "disk_cdrom") == 0) {
                rval = gen_create_mn_disk_cdrom(devi);
        } else if (strcmp(node_name, "disk_fd") == 0) {
                rval = gen_create_mn_disk_fd(devi);
        } else if (strcmp(node_name, "cgtwenty") == 0) {
                rval = gen_create_display(devi);
        } else if (strcmp(node_name, "genzs") == 0) {
                rval = gen_create_serial(devi);
        } else if (strcmp(node_name, "net") == 0) {
                rval = gen_create_net(devi);
        } else {
                int instance = ddi_get_instance(devi);
                char *node_type;

                /*
                 * Solaris may directly hang the node_type off the minor node
                 * (without making a copy).  Since we free the node_type
                 * property below we need to make a private copy to pass
                 * to ddi_create_minor_node to avoid devinfo snapshot panics.
                 * We store a pointer to our copy in dstate and free it in
                 * gen_detach after the minor nodes have been deleted by
                 * ddi_remove_minor_node.
                 */
                if (ddi_prop_lookup_string(DDI_DEV_T_ANY, devi,
                    DDI_PROP_DONTPASS, "node-type", &node_type) != 0) {
                        cmn_err(CE_WARN, "couldn't get node-type\n");
                        return (DDI_FAILURE);
                }
                if (node_type) {
                        dstatep->node_type = kmem_alloc(
                            strlen(node_type) + 1, KM_SLEEP);
                        (void) strcpy(dstatep->node_type, node_type);
                }
                ddi_prop_free(node_type);

                /* the minor name is the same as the node name */
                if (ddi_create_minor_node(devi, node_name, S_IFCHR,
                    (INST_TO_MINOR(instance)), dstatep->node_type, 0) !=
                    DDI_SUCCESS) {
                        if (dstatep->node_type) {
                                kmem_free(dstatep->node_type,
                                    strlen(dstatep->node_type) + 1);
                                dstatep->node_type = NULL;
                        }
                        return (DDI_FAILURE);
                }
                return (DDI_SUCCESS);
        }

        if (rval != DDI_SUCCESS) {
                ddi_prop_remove_all(devi);
                ddi_remove_minor_node(devi, NULL);
        }

        return (rval);
}

/*ARGSUSED*/
static void
gen_event_cb(dev_info_t *dip, ddi_eventcookie_t cookie, void *arg,
    void *impl_data)
{
        if (gen_debug)
                cmn_err(CE_NOTE, "gen_event_cb invoked");

}