root/usr/src/uts/common/inet/dlpistub/dlpistub.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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * DLPI stub driver; currently supports VNI and IPMP stub devices.
 */

#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/dlpi.h>
#include <sys/stat.h>
#include <sys/strsun.h>
#include <sys/stropts.h>
#include <sys/types.h>
#include <sys/id_space.h>
#include <sys/sysmacros.h>
#include <sys/kmem.h>
#include <sys/modctl.h>
#include <sys/mkdev.h>
#include <sys/sdt.h>

#include "dlpistub_impl.h"

static id_space_t *ds_minors;
static dev_info_t *ds_dip;

/*
 * DL_INFO_ACK template.
 */
static dl_info_ack_t ds_infoack = {
        DL_INFO_ACK,    /* dl_primitive */
        0,              /* dl_max_sdu */
        0,              /* dl_min_sdu */
        0,              /* dl_addr_length */
        0,              /* dl_mac_type */
        0,              /* dl_reserved */
        0,              /* dl_current_state */
        0,              /* dl_sap_length */
        DL_CLDLS,       /* dl_service_mode */
        0,              /* dl_qos_length */
        0,              /* dl_qos_offset */
        0,              /* dl_qos_range_length */
        0,              /* dl_qos_range_offset */
        DL_STYLE2,      /* dl_provider_style */
        0,              /* dl_addr_offset */
        DL_VERSION_2,   /* dl_version */
        0,              /* dl_brdcst_addr_length */
        0,              /* dl_brdcst_addr_offset */
        0               /* dl_growth */
};

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

        if (ddi_create_minor_node(dip, "vni", S_IFCHR, DS_MINOR_VNI,
            DDI_PSEUDO, 0) == DDI_FAILURE ||
            ddi_create_minor_node(dip, "ipmpstub", S_IFCHR, DS_MINOR_IPMP,
            DDI_PSEUDO, 0) == DDI_FAILURE) {
                ddi_remove_minor_node(dip, NULL);
                cmn_err(CE_NOTE, "ds_attach: cannot create minor nodes");
                return (DDI_FAILURE);
        }

        ds_dip = dip;
        ds_minors = id_space_create("ds_minors", DS_MINOR_START, MAXMIN32);
        return (DDI_SUCCESS);
}

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

        id_space_destroy(ds_minors);
        ds_minors = NULL;
        ASSERT(dip == ds_dip);
        ddi_remove_minor_node(dip, NULL);
        ds_dip = NULL;
        return (DDI_SUCCESS);
}

/* ARGSUSED */
static int
ds_devinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
        int error = DDI_FAILURE;

        switch (infocmd) {
        case DDI_INFO_DEVT2INSTANCE:
                *result = (void *)0;
                error = DDI_SUCCESS;
                break;
        case DDI_INFO_DEVT2DEVINFO:
                if (ds_dip != NULL) {
                        *result = ds_dip;
                        error = DDI_SUCCESS;
                }
                break;
        }
        return (error);
}

/* ARGSUSED */
static int
ds_open(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *credp)
{
        int type;
        dlpistub_t *dsp;

        if (sflag == CLONEOPEN || sflag == MODOPEN)
                return (EINVAL);

        if (q->q_ptr != NULL)
                return (0);

        switch (getminor(*devp)) {
        case DS_MINOR_VNI:
                type = SUNW_DL_VNI;
                break;
        case DS_MINOR_IPMP:
                type = SUNW_DL_IPMP;
                break;
        default:
                return (ENXIO);
        }

        dsp = kmem_zalloc(sizeof (dlpistub_t), KM_SLEEP);
        dsp->ds_type = type;
        dsp->ds_minor = (minor_t)id_alloc(ds_minors);
        dsp->ds_state = DL_UNATTACHED;
        *devp = makedevice(getmajor(*devp), dsp->ds_minor);
        q->q_ptr = WR(q)->q_ptr = dsp;
        qprocson(q);

        return (0);
}

/* ARGSUSED */
static int
ds_close(queue_t *q, int flag, cred_t *credp)
{
        dlpistub_t      *dsp = q->q_ptr;

        qprocsoff(q);
        q->q_ptr = WR(q)->q_ptr = NULL;

        id_free(ds_minors, dsp->ds_minor);
        kmem_free(dsp, sizeof (dlpistub_t));

        return (0);
}

static int
ds_badprim(queue_t *q, mblk_t *mp, t_scalar_t prim)
{
        dlerrorack(q, mp, prim, DL_BADPRIM, 0);
        return (0);
}

static int
ds_outstate(queue_t *q, mblk_t *mp, t_scalar_t prim)
{
        dlerrorack(q, mp, prim, DL_OUTSTATE, 0);
        return (0);
}

static int
ds_wput(queue_t *q, mblk_t *mp)
{
        union DL_primitives     *dlp;
        dl_info_ack_t           *dlip;
        dlpistub_t              *dsp = q->q_ptr;
        t_scalar_t              prim;

        switch (DB_TYPE(mp)) {
        case M_PROTO:
        case M_PCPROTO:
                if (MBLKL(mp) < sizeof (t_scalar_t)) {
                        dlerrorack(q, mp, DL_PRIM_INVAL, DL_UNSUPPORTED, 0);
                        return (0);
                }

                dlp = (void *)mp->b_rptr;
                prim = dlp->dl_primitive;
                switch (prim) {
                case DL_ATTACH_REQ:
                        if (MBLKL(mp) < DL_ATTACH_REQ_SIZE)
                                return (ds_badprim(q, mp, prim));

                        if (dsp->ds_state != DL_UNATTACHED)
                                return (ds_outstate(q, mp, prim));

                        dsp->ds_state = DL_UNBOUND;
                        dlokack(q, mp, DL_ATTACH_REQ);
                        break;

                case DL_BIND_REQ:
                        if (MBLKL(mp) < DL_BIND_REQ_SIZE)
                                return (ds_badprim(q, mp, prim));

                        if (dsp->ds_state != DL_UNBOUND)
                                return (ds_outstate(q, mp, prim));

                        dsp->ds_state = DL_IDLE;
                        dlbindack(q, mp, dlp->bind_req.dl_sap, NULL, 0, 0, 0);
                        break;

                case DL_INFO_REQ:
                        if (MBLKL(mp) < DL_INFO_REQ_SIZE)
                                return (ds_badprim(q, mp, prim));

                        mp = mexchange(q, mp, sizeof (dl_info_ack_t),
                            M_PCPROTO, DL_INFO_ACK);
                        if (mp != NULL) {
                                dlip = (void *)mp->b_rptr;
                                *dlip = ds_infoack;
                                dlip->dl_mac_type = dsp->ds_type;
                                dlip->dl_current_state = dsp->ds_state;
                                qreply(q, mp);
                        }
                        break;

                case DL_PHYS_ADDR_REQ:
                        if (MBLKL(mp) < DL_PHYS_ADDR_REQ_SIZE)
                                return (ds_badprim(q, mp, prim));

                        dlphysaddrack(q, mp, NULL, 0);
                        break;

                case DL_UNBIND_REQ:
                        if (MBLKL(mp) < DL_UNBIND_REQ_SIZE)
                                return (ds_badprim(q, mp, prim));

                        if (dsp->ds_state != DL_IDLE)
                                return (ds_outstate(q, mp, prim));

                        dsp->ds_state = DL_UNBOUND;
                        dlokack(q, mp, DL_UNBIND_REQ);
                        break;

                case DL_DETACH_REQ:
                        if (MBLKL(mp) < DL_DETACH_REQ_SIZE)
                                return (ds_badprim(q, mp, prim));

                        if (dsp->ds_state != DL_UNBOUND)
                                return (ds_outstate(q, mp, prim));

                        dsp->ds_state = DL_UNATTACHED;
                        dlokack(q, mp, DL_DETACH_REQ);
                        break;

                case DL_UNITDATA_REQ:
                        DTRACE_PROBE2(dlpistub__data, dlpistub_t *, dsp,
                            mblk_t *, mp);
                        freemsg(mp);
                        break;

                default:
                        dlerrorack(q, mp, prim, DL_UNSUPPORTED, 0);
                }
                break;

        case M_IOCTL:
                miocnak(q, mp, 0, EINVAL);
                break;

        case M_FLUSH:
                *mp->b_rptr &= ~FLUSHW;
                if (*mp->b_rptr & FLUSHR)
                        qreply(q, mp);
                else
                        freemsg(mp);
                break;
        default:
                freemsg(mp);
                break;
        }

        return (0);
}

static struct module_info ds_minfo = {
        DS_IDNUM,       /* mi_idnum */
        "dlpistub",     /* mi_idname */
        0,              /* mi_minpsz */
        INFPSZ,         /* mi_maxpsz */
        0,              /* mi_hiwat */
        0,              /* mi_lowat */
};

static struct qinit ds_rinit = {
        NULL,           /* qi_putp */
        NULL,           /* qi_srvp */
        ds_open,        /* qi_qopen */
        ds_close,       /* qi_qclose */
        NULL,           /* qi_qadmin */
        &ds_minfo,      /* qi_minfo */
};

static struct qinit ds_winit = {
        ds_wput,        /* qi_putp */
        NULL,           /* qi_srvp */
        NULL,           /* qi_qopen */
        NULL,           /* qi_qclose */
        NULL,           /* qi_qadmin */
        &ds_minfo,      /* qi_minfo */
};

static struct streamtab ds_info = {
        &ds_rinit,      /* st_rdinit */
        &ds_winit       /* st_wrinit */
};

DDI_DEFINE_STREAM_OPS(ds_ops, nulldev, nulldev, ds_attach, ds_detach,
    nodev, ds_devinfo, D_MP|D_MTPERMOD, &ds_info, ddi_quiesce_not_supported);

static struct modldrv modldrv = {
        &mod_driverops,
        "DLPI stub driver",
        &ds_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));
}