root/usr/src/uts/common/io/ppp/sppp/sppp_dlpi.c
/*
 * sppp_dlpi.c - Solaris STREAMS PPP multiplexing pseudo-driver DLPI handlers
 *
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation is hereby granted, provided that the above copyright
 * notice appears in all copies.
 *
 * SUN MAKES NO REPRESENTATION OR WARRANTIES ABOUT THE SUITABILITY OF
 * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, OR NON-INFRINGEMENT.  SUN SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES
 *
 * Copyright (c) 1994 The Australian National University.
 * All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation is hereby granted, provided that the above copyright
 * notice appears in all copies.  This software is provided without any
 * warranty, express or implied. The Australian National University
 * makes no representations about the suitability of this software for
 * any purpose.
 *
 * IN NO EVENT SHALL THE AUSTRALIAN NATIONAL UNIVERSITY BE LIABLE TO ANY
 * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
 * THE AUSTRALIAN NATIONAL UNIVERSITY HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * THE AUSTRALIAN NATIONAL UNIVERSITY SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE AUSTRALIAN NATIONAL UNIVERSITY HAS NO
 * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
 * OR MODIFICATIONS.
 *
 * This driver is derived from the original SVR4 STREAMS PPP driver
 * originally written by Paul Mackerras <paul.mackerras@cs.anu.edu.au>.
 *
 * Adi Masputra <adi.masputra@sun.com> rewrote and restructured the code
 * for improved performance and scalability.
 */

#define RCSID   "$Id: sppp_dlpi.c,v 1.0 2000/05/08 01:10:12 masputra Exp $"

#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <sys/cmn_err.h>
#include <sys/conf.h>
#include <sys/dlpi.h>
#include <sys/ddi.h>
#include <sys/kstat.h>
#include <sys/strsubr.h>
#include <sys/strsun.h>
#include <sys/ethernet.h>
#include <net/ppp_defs.h>
#include <netinet/in.h>
#include <net/pppio.h>
#include "s_common.h"
#include "sppp.h"

static int      sppp_dlattachreq(queue_t *, mblk_t *, spppstr_t *);
static int      sppp_dldetachreq(queue_t *, mblk_t *, spppstr_t *);
static int      sppp_dlbindreq(queue_t *, mblk_t *, spppstr_t *);
static int      sppp_dlunbindreq(queue_t *, mblk_t *, spppstr_t *);
static int      sppp_dlinforeq(queue_t *, mblk_t *, spppstr_t *);
static int      sppp_dlunitdatareq(queue_t *, mblk_t *, spppstr_t *);
static int      sppp_dlpromisconreq(queue_t *, mblk_t *, spppstr_t *);
static int      sppp_dlpromiscoffreq(queue_t *, mblk_t *, spppstr_t *);
static int      sppp_dlphyreq(queue_t *, mblk_t *, spppstr_t *);
static void     sppp_dl_attach_upper(queue_t *, mblk_t *);
static void     sppp_dl_detach_upper(queue_t *, mblk_t *);
static void     sppp_dl_bind(queue_t *, mblk_t *);
static void     sppp_dl_unbind(queue_t *, mblk_t *);
static void     sppp_dl_promiscon(queue_t *, mblk_t *);
static void     sppp_dl_promiscoff(queue_t *, mblk_t *);
static mblk_t   *sppp_dladdether(spppstr_t *, mblk_t *, t_scalar_t);

static struct sppp_dlpi_pinfo_t dl_pinfo[DL_MAXPRIM + 1];

#if 0
#define DBGERROR(x)     cmn_err x
#else
#define DBGERROR(x)     ((void)0)
#endif

/* #define      DBG_DLPI        1 */

#ifdef DBG_DLPI
struct sppp_dlpi_entry {
        uint32_t sde_val;
        const char *sde_name;
};

static const struct sppp_dlpi_entry sppp_dlpi_list[] = {
        { DL_INFO_REQ, "DL_INFO_REQ" },
        { DL_INFO_ACK, "DL_INFO_ACK" },
        { DL_ATTACH_REQ, "DL_ATTACH_REQ" },
        { DL_DETACH_REQ, "DL_DETACH_REQ" },
        { DL_BIND_REQ, "DL_BIND_REQ" },
        { DL_BIND_ACK, "DL_BIND_ACK" },
        { DL_UNBIND_REQ, "DL_UNBIND_REQ" },
        { DL_OK_ACK, "DL_OK_ACK" },
        { DL_ERROR_ACK, "DL_ERROR_ACK" },
        { DL_SUBS_BIND_REQ, "DL_SUBS_BIND_REQ" },
        { DL_SUBS_BIND_ACK, "DL_SUBS_BIND_ACK" },
        { DL_SUBS_UNBIND_REQ, "DL_SUBS_UNBIND_REQ" },
        { DL_ENABMULTI_REQ, "DL_ENABMULTI_REQ" },
        { DL_DISABMULTI_REQ, "DL_DISABMULTI_REQ" },
        { DL_PROMISCON_REQ, "DL_PROMISCON_REQ" },
        { DL_PROMISCOFF_REQ, "DL_PROMISCOFF_REQ" },
        { DL_UNITDATA_REQ, "DL_UNITDATA_REQ" },
        { DL_UNITDATA_IND, "DL_UNITDATA_IND" },
        { DL_UDERROR_IND, "DL_UDERROR_IND" },
        { DL_UDQOS_REQ, "DL_UDQOS_REQ" },
        { DL_CONNECT_REQ, "DL_CONNECT_REQ" },
        { DL_CONNECT_IND, "DL_CONNECT_IND" },
        { DL_CONNECT_RES, "DL_CONNECT_RES" },
        { DL_CONNECT_CON, "DL_CONNECT_CON" },
        { DL_TOKEN_REQ, "DL_TOKEN_REQ" },
        { DL_TOKEN_ACK, "DL_TOKEN_ACK" },
        { DL_DISCONNECT_REQ, "DL_DISCONNECT_REQ" },
        { DL_DISCONNECT_IND, "DL_DISCONNECT_IND" },
        { DL_RESET_REQ, "DL_RESET_REQ" },
        { DL_RESET_IND, "DL_RESET_IND" },
        { DL_RESET_RES, "DL_RESET_RES" },
        { DL_RESET_CON, "DL_RESET_CON" },
        { DL_DATA_ACK_REQ, "DL_DATA_ACK_REQ" },
        { DL_DATA_ACK_IND, "DL_DATA_ACK_IND" },
        { DL_DATA_ACK_STATUS_IND, "DL_DATA_ACK_STATUS_IND" },
        { DL_REPLY_REQ, "DL_REPLY_REQ" },
        { DL_REPLY_IND, "DL_REPLY_IND" },
        { DL_REPLY_STATUS_IND, "DL_REPLY_STATUS_IND" },
        { DL_REPLY_UPDATE_REQ, "DL_REPLY_UPDATE_REQ" },
        { DL_REPLY_UPDATE_STATUS_IND, "DL_REPLY_UPDATE_STATUS_IND" },
        { DL_XID_REQ, "DL_XID_REQ" },
        { DL_XID_IND, "DL_XID_IND" },
        { DL_XID_RES, "DL_XID_RES" },
        { DL_XID_CON, "DL_XID_CON" },
        { DL_TEST_REQ, "DL_TEST_REQ" },
        { DL_TEST_IND, "DL_TEST_IND" },
        { DL_TEST_RES, "DL_TEST_RES" },
        { DL_TEST_CON, "DL_TEST_CON" },
        { DL_PHYS_ADDR_REQ, "DL_PHYS_ADDR_REQ" },
        { DL_PHYS_ADDR_ACK, "DL_PHYS_ADDR_ACK" },
        { DL_SET_PHYS_ADDR_REQ, "DL_SET_PHYS_ADDR_REQ" },
        { DL_GET_STATISTICS_REQ, "DL_GET_STATISTICS_REQ" },
        { DL_GET_STATISTICS_ACK, "DL_GET_STATISTICS_ACK" },
        { 0, NULL }
};

static const struct sppp_dlpi_entry sppp_state_list[] = {
        { DL_UNBOUND, "DL_UNBOUND" },
        { DL_BIND_PENDING, "DL_BIND_PENDING" },
        { DL_UNBIND_PENDING, "DL_UNBIND_PENDING" },
        { DL_IDLE, "DL_IDLE" },
        { DL_UNATTACHED, "DL_UNATTACHED" },
        { DL_ATTACH_PENDING, "DL_ATTACH_PENDING" },
        { DL_DETACH_PENDING, "DL_DETACH_PENDING" },
        { DL_UDQOS_PENDING, "DL_UDQOS_PENDING" },
        { DL_OUTCON_PENDING, "DL_OUTCON_PENDING" },
        { DL_INCON_PENDING, "DL_INCON_PENDING" },
        { DL_CONN_RES_PENDING, "DL_CONN_RES_PENDING" },
        { DL_DATAXFER, "DL_DATAXFER" },
        { DL_USER_RESET_PENDING, "DL_USER_RESET_PENDING" },
        { DL_PROV_RESET_PENDING, "DL_PROV_RESET_PENDING" },
        { DL_RESET_RES_PENDING, "DL_RESET_RES_PENDING" },
        { DL_DISCON8_PENDING, "DL_DISCON8_PENDING" },
        { DL_DISCON9_PENDING, "DL_DISCON9_PENDING" },
        { DL_DISCON11_PENDING, "DL_DISCON11_PENDING" },
        { DL_DISCON12_PENDING, "DL_DISCON12_PENDING" },
        { DL_DISCON13_PENDING, "DL_DISCON13_PENDING" },
        { DL_SUBS_BIND_PND, "DL_SUBS_BIND_PND" },
        { DL_SUBS_UNBIND_PND, "DL_SUBS_UNBIND_PND" },
        { 0, NULL }
};

static const char *
prim2name(uint32_t prim)
{
        const struct sppp_dlpi_entry *sde;

        for (sde = sppp_dlpi_list; sde->sde_name != NULL; sde++)
                if (sde->sde_val == prim)
                        break;
        return (sde->sde_name);
}

static const char *
state2name(uint32_t state)
{
        const struct sppp_dlpi_entry *sde;

        for (sde = sppp_state_list; sde->sde_name != NULL; sde++)
                if (sde->sde_val == state)
                        break;
        return (sde->sde_name);
}

#define DBGDLPI(x)      cmn_err x
#else
#define DBGDLPI(x)      ((void)0)
#endif /* DBG_DLPI */

/*
 * DL_INFO_ACK template for point-to-point interface.
 */
static dl_info_ack_t    sppp_infoack = {
        DL_INFO_ACK,                    /* dl_primitive */
        PPP_MAXMTU,                     /* dl_max_sdu */
        0,                              /* dl_min_sdu */
        SPPP_ADDRL,                     /* dl_addr_length */
        /*
         * snoop et. al. don't know about DL_OTHER so this entry
         * was changed to DL_ETHER so ethernet tracing/snooping
         * facilities will work with PPP interfaces.
         */
        DL_ETHER,                       /* dl_mac_type */
        0,                              /* dl_reserved */
        0,                              /* dl_current_state */
        SPPP_SAPL,                      /* dl_sap_length */
        DL_CLDLS,                       /* dl_service_mode */
        0,                              /* dl_qos_length */
        0,                              /* dl_qos_offset */
        0,                              /* dl_range_length */
        0,                              /* dl_range_offset */
        DL_STYLE2,                      /* dl_provider_style */
        sizeof (dl_info_ack_t),         /* dl_addr_offset */
        DL_VERSION_2,                   /* dl_version */
        0,                              /* dl_brdcst_addr_length */
        0,                              /* dl_brdcst_addr_offset */
        0                               /* dl_growth */
};

/*
 * sppp_dlpi_pinfoinit()
 *
 * Description:
 *    Initialize dl_pinfo[], called from sppp_attach.
 */
void
sppp_dlpi_pinfoinit(void)
{
        bzero(dl_pinfo, sizeof (dl_pinfo));     /* Just to be safe */

        dl_pinfo[DL_ATTACH_REQ].pi_minlen = sizeof (dl_attach_req_t);
        dl_pinfo[DL_ATTACH_REQ].pi_state = DL_UNATTACHED;
        dl_pinfo[DL_ATTACH_REQ].pi_funcp = sppp_dlattachreq;

        dl_pinfo[DL_DETACH_REQ].pi_minlen = sizeof (dl_detach_req_t);
        dl_pinfo[DL_DETACH_REQ].pi_state = DL_UNBOUND;
        dl_pinfo[DL_DETACH_REQ].pi_funcp = sppp_dldetachreq;

        dl_pinfo[DL_BIND_REQ].pi_minlen = sizeof (dl_bind_req_t);
        dl_pinfo[DL_BIND_REQ].pi_state = DL_UNBOUND;
        dl_pinfo[DL_BIND_REQ].pi_funcp = sppp_dlbindreq;

        dl_pinfo[DL_UNBIND_REQ].pi_minlen = sizeof (dl_unbind_req_t);
        dl_pinfo[DL_UNBIND_REQ].pi_state = DL_IDLE;
        dl_pinfo[DL_UNBIND_REQ].pi_funcp = sppp_dlunbindreq;

        dl_pinfo[DL_INFO_REQ].pi_minlen = sizeof (dl_info_req_t);
        dl_pinfo[DL_INFO_REQ].pi_state = -1;    /* special handling */
        dl_pinfo[DL_INFO_REQ].pi_funcp = sppp_dlinforeq;

        dl_pinfo[DL_UNITDATA_REQ].pi_minlen = sizeof (dl_unitdata_req_t);
        dl_pinfo[DL_UNITDATA_REQ].pi_state = DL_IDLE;
        dl_pinfo[DL_UNITDATA_REQ].pi_funcp = sppp_dlunitdatareq;

        dl_pinfo[DL_PROMISCON_REQ].pi_minlen = sizeof (dl_promiscon_req_t);
        dl_pinfo[DL_PROMISCON_REQ].pi_state = -1; /* special handling */
        dl_pinfo[DL_PROMISCON_REQ].pi_funcp = sppp_dlpromisconreq;

        dl_pinfo[DL_PROMISCOFF_REQ].pi_minlen = sizeof (dl_promiscoff_req_t);
        dl_pinfo[DL_PROMISCOFF_REQ].pi_state = -1; /* special handling */
        dl_pinfo[DL_PROMISCOFF_REQ].pi_funcp = sppp_dlpromiscoffreq;

        dl_pinfo[DL_PHYS_ADDR_REQ].pi_minlen = sizeof (dl_phys_addr_req_t);
        dl_pinfo[DL_PHYS_ADDR_REQ].pi_state = -1; /* special handling */
        dl_pinfo[DL_PHYS_ADDR_REQ].pi_funcp = sppp_dlphyreq;
}

/*
 * sppp_mproto()
 *
 * MT-Perimeters:
 *    shared inner, shared outer.
 *
 * Description:
 *    Handle M_PCPROTO/M_PROTO messages, called by sppp_uwput.
 */
int
sppp_mproto(queue_t *q, mblk_t *mp, spppstr_t *sps)
{
        union DL_primitives *dlp;
        struct sppp_dlpi_pinfo_t *dpi;
        t_uscalar_t     prim;
        int             len;
        int             error = 0;

        ASSERT(!IS_SPS_CONTROL(sps));
        if ((len = MBLKL(mp)) < sizeof (t_uscalar_t)) {
                DBGERROR((CE_CONT, "bad mproto: block length %d\n", len));
                merror(q, mp, EPROTO);
                return (0);
        }
        dlp = (union DL_primitives *)mp->b_rptr;
        prim = dlp->dl_primitive;
        if (prim > DL_MAXPRIM) {
                DBGERROR((CE_CONT, "bad mproto: primitive %d > %d\n", prim,
                    DL_MAXPRIM));
                error = DL_BADPRIM;
        } else {
                dpi = &dl_pinfo[prim];
                if (dpi->pi_funcp == NULL) {
                        DBGERROR((CE_CONT,
                            "bad mproto: primitive %d not supported\n", prim));
                        error = DL_NOTSUPPORTED;
                } else if (len < dpi->pi_minlen) {
                        DBGERROR((CE_CONT,
                            "bad mproto: primitive len %d < %d\n", len,
                            dpi->pi_minlen));
                        error = DL_BADPRIM;
                } else if (dpi->pi_state != -1 &&
                    sps->sps_dlstate != dpi->pi_state) {
                        DBGERROR((CE_CONT,
                            "bad state %d != %d for primitive %d\n",
                            sps->sps_dlstate, dpi->pi_state, prim));
                        error = DL_OUTSTATE;
                }
        }
        if (error != 0) {
                dlerrorack(q, mp, dlp->dl_primitive, error, 0);
                return (0);
        }
#ifdef DBG_DLPI
        {
                const char *cp = prim2name(prim);
                if (cp != NULL)
                        cmn_err(CE_CONT, "/%d: Dispatching %s\n",
                            sps->sps_mn_id, cp);
                else
                        cmn_err(CE_CONT,
                            "/%d: Dispatching unknown primitive %d\n",
                            sps->sps_mn_id, prim);
        }
#endif
        return ((*dpi->pi_funcp)(q, mp, sps));
}

/*
 * sppp_dlattachreq()
 *
 * MT-Perimeters:
 *    shared inner, shared outer.
 *
 * Description:
 *    Perform DL_ATTACH_REQ request, called by sppp_mproto.
 */
static int
sppp_dlattachreq(queue_t *q, mblk_t *mp, spppstr_t *sps)
{
        int     error = 0;
        union DL_primitives *dlp;

        ASSERT(q != NULL && q->q_ptr != NULL);
        ASSERT(mp != NULL && mp->b_rptr != NULL);
        dlp = (union DL_primitives *)mp->b_rptr;
        ASSERT(sps != NULL);
        ASSERT(sps->sps_dlstate == DL_UNATTACHED);

        if (IS_SPS_PIOATTACH(sps)) {
                DBGERROR((CE_CONT, "DLPI attach: already attached\n"));
                error = EINVAL;
        }
        if (error != 0) {
                dlerrorack(q, mp, dlp->dl_primitive, DL_OUTSTATE, error);
        } else {
                qwriter(q, mp, sppp_dl_attach_upper, PERIM_OUTER);
        }
        return (0);
}

/*
 * sppp_dl_attach_upper()
 *
 * MT-Perimeters:
 *    exclusive inner, exclusive outer.
 *
 * Description:
 *    Called by qwriter (INNER) from sppp_dlattachreq as the result of
 *    receiving a DL_ATTACH_REQ message.
 */
static void
sppp_dl_attach_upper(queue_t *q, mblk_t *mp)
{
        sppa_t          *ppa;
        spppstr_t       *sps = q->q_ptr;
        union DL_primitives *dlp;
        int             err = ENOMEM;
        cred_t          *cr;
        zoneid_t        zoneid;

        ASSERT(!IS_SPS_PIOATTACH(sps));
        dlp = (union DL_primitives *)mp->b_rptr;

        /* If there's something here, it's detached. */
        if (sps->sps_ppa != NULL) {
                sppp_remove_ppa(sps);
        }

        if ((cr = msg_getcred(mp, NULL)) == NULL)
                zoneid = sps->sps_zoneid;
        else
                zoneid = crgetzoneid(cr);

        ppa = sppp_find_ppa(dlp->attach_req.dl_ppa);
        if (ppa == NULL) {
                ppa = sppp_create_ppa(dlp->attach_req.dl_ppa, zoneid);
        } else if (ppa->ppa_zoneid != zoneid) {
                ppa = NULL;
                err = EPERM;
        }

        /*
         * If we can't find or create it, then it's either because we're out of
         * memory or because the requested PPA is owned by a different zone.
         */
        if (ppa == NULL) {
                DBGERROR((CE_CONT, "DLPI attach: cannot create ppa %u\n",
                    dlp->attach_req.dl_ppa));
                dlerrorack(q, mp, dlp->dl_primitive, DL_SYSERR, err);
                return;
        }
        /*
         * Preallocate the hangup message so that we're always able to
         * send this upstream in the event of a catastrophic failure.
         */
        if ((sps->sps_hangup = allocb(1, BPRI_MED)) == NULL) {
                DBGERROR((CE_CONT, "DLPI attach: cannot allocate hangup\n"));
                dlerrorack(q, mp, dlp->dl_primitive, DL_SYSERR, ENOSR);
                return;
        }
        sps->sps_dlstate = DL_UNBOUND;
        sps->sps_ppa = ppa;
        /*
         * Add this stream to the head of the list of sibling streams
         * which belong to the specified ppa.
         */
        rw_enter(&ppa->ppa_sib_lock, RW_WRITER);
        ppa->ppa_refcnt++;
        sps->sps_nextsib = ppa->ppa_streams;
        ppa->ppa_streams = sps;
        /*
         * And if this stream was marked as promiscuous (SPS_PROMISC), then we
         * need to update the promiscuous streams count. This should only
         * happen when DL_PROMISCON_REQ was issued prior to attachment.
         */
        if (IS_SPS_PROMISC(sps)) {
                ppa->ppa_promicnt++;
        }
        rw_exit(&ppa->ppa_sib_lock);
        DBGDLPI((CE_CONT, "/%d: attached to ppa %d\n", sps->sps_mn_id,
            ppa->ppa_ppa_id));
        dlokack(q, mp, DL_ATTACH_REQ);
}

/*
 * sppp_dldetachreq()
 *
 * MT-Perimeters:
 *    shared inner, shared outer.
 *
 * Description:
 *    Perform DL_DETACH_REQ request, called by sppp_mproto.
 */
/* ARGSUSED */
static int
sppp_dldetachreq(queue_t *q, mblk_t *mp, spppstr_t *sps)
{
        ASSERT(q != NULL && q->q_ptr != NULL);
        ASSERT(mp != NULL && mp->b_rptr != NULL);
        ASSERT(sps != NULL);
        ASSERT(sps->sps_dlstate == DL_UNBOUND);
        ASSERT(!IS_SPS_PIOATTACH(sps));

        qwriter(q, mp, sppp_dl_detach_upper, PERIM_INNER);
        return (0);
}

/*
 * sppp_dl_detach_upper()
 *
 * MT-Perimeters:
 *    exclusive inner, shared outer.
 *
 * Description:
 *    Called by qwriter (INNER) from sppp_dldetachreq as the result of
 *    receiving a DL_DETACH_REQ message.
 */
/* ARGSUSED */
static void
sppp_dl_detach_upper(queue_t *q, mblk_t *mp)
{
        spppstr_t       *sps;

        ASSERT(q != NULL && q->q_ptr != NULL);
        ASSERT(mp != NULL && mp->b_rptr != NULL);
        sps = (spppstr_t *)q->q_ptr;
        /*
         * We don't actually detach from the PPA until closed or
         * reattached.
         */
        sps->sps_flags &= ~SPS_PROMISC; /* clear flag anyway */
        sps->sps_dlstate = DL_UNATTACHED;
        dlokack(q, mp, DL_DETACH_REQ);
}

/*
 * sppp_dlbindreq()
 *
 * MT-Perimeters:
 *    shared inner, shared outer.
 *
 * Description:
 *    Perform DL_BIND_REQ request, called by sppp_mproto.
 */
static int
sppp_dlbindreq(queue_t *q, mblk_t *mp, spppstr_t *sps)
{
        sppa_t                  *ppa;
        union DL_primitives     *dlp;
        spppreqsap_t            req_sap;
        int                     error = 0;

        ASSERT(q != NULL && q->q_ptr != NULL);
        ASSERT(mp != NULL && mp->b_rptr != NULL);
        dlp = (union DL_primitives *)mp->b_rptr;
        req_sap = dlp->bind_req.dl_sap;
        ASSERT(sps != NULL);
        ASSERT(!IS_SPS_PIOATTACH(sps));
        ASSERT(sps->sps_dlstate == DL_UNBOUND);

        ppa = sps->sps_ppa;
        if (ppa == NULL) {
                DBGERROR((CE_CONT, "DLPI bind: no attached ppa\n"));
                error = DL_OUTSTATE;
        } else if ((req_sap != ETHERTYPE_IP) && (req_sap != ETHERTYPE_IPV6) &&
            (req_sap != ETHERTYPE_ALLSAP)) {
                DBGERROR((CE_CONT, "DLPI bind: unknown SAP %x\n", req_sap));
                error = DL_BADADDR;
        }
        if (error != 0) {
                dlerrorack(q, mp, dlp->dl_primitive, error, 0);
        } else {
                qwriter(q, mp, sppp_dl_bind, PERIM_INNER);
        }
        return (0);
}

/*
 * sppp_dl_bind()
 *
 * MT-Perimeters:
 *    exclusive inner, shared outer.
 *
 * Description:
 *    Called by qwriter (INNER) from sppp_dlbindreq as the result of
 *    receiving a DL_BIND_REQ message.
 */
static void
sppp_dl_bind(queue_t *q, mblk_t *mp)
{
        spppstr_t               *sps;
        sppa_t                  *ppa;
        union DL_primitives     *dlp;
        t_scalar_t              sap;
        spppreqsap_t            req_sap;
        mblk_t                  *lsmp;

        ASSERT(q != NULL && q->q_ptr != NULL);
        sps = (spppstr_t *)q->q_ptr;
        ASSERT(mp != NULL && mp->b_rptr != NULL);
        dlp = (union DL_primitives *)mp->b_rptr;
        ppa = sps->sps_ppa;
        ASSERT(ppa != NULL);
        req_sap = dlp->bind_req.dl_sap;
        ASSERT((req_sap == ETHERTYPE_IP) || (req_sap == ETHERTYPE_IPV6) ||
            (req_sap == ETHERTYPE_ALLSAP));

        if (req_sap == ETHERTYPE_IP) {
                sap = PPP_IP;
        } else if (req_sap == ETHERTYPE_IPV6) {
                sap = PPP_IPV6;
        } else if (req_sap == ETHERTYPE_ALLSAP) {
                sap = PPP_ALLSAP;
        }
        /*
         * If there's another stream with the same sap has already been bound
         * to the same ppa, then return with DL_NOADDR. However, we do make an
         * exception for snoop (req_sap=0x00, sap=0xff) since multiple
         * instances of snoop may execute an a given device.
         */
        lsmp = NULL;
        if (sap != PPP_ALLSAP) {
                if ((sap == PPP_IP) && (ppa->ppa_ip_cache == NULL)) {
                        ppa->ppa_ip_cache = sps;
                        if (ppa->ppa_ctl != NULL) {
                                lsmp = create_lsmsg(PPP_LINKSTAT_IPV4_BOUND);
                        }
                } else if ((sap == PPP_IPV6) && (ppa->ppa_ip6_cache == NULL)) {
                        ppa->ppa_ip6_cache = sps;
                        if (ppa->ppa_ctl != NULL) {
                                lsmp = create_lsmsg(PPP_LINKSTAT_IPV6_BOUND);
                        }
                } else {
                        DBGERROR((CE_CONT, "DLPI bind: bad SAP %x\n", sap));
                        dlerrorack(q, mp, dlp->dl_primitive, DL_NOADDR,
                            EEXIST);
                        return;
                }
                sps->sps_flags |= SPS_CACHED;
        }
        /*
         * Tell the daemon that a DLPI bind has happened on this stream,
         * and we'll only do this for PPP_IP or PPP_IPV6 sap (not snoop).
         */
        if (lsmp != NULL && ppa->ppa_ctl != NULL) {
#ifdef DBG_DLPI
                cmn_err(CE_CONT, "sending up %s\n",
                    ((sap == PPP_IP) ? "PPP_LINKSTAT_IPV4_BOUND" :
                    "PPP_LINKSTAT_IPV6_BOUND"));
#endif
                putnext(ppa->ppa_ctl->sps_rq, lsmp);
        }
        DBGDLPI((CE_CONT, "/%d: bound to sap %X (req %X)\n", sps->sps_mn_id,
            sap, req_sap));
        sps->sps_req_sap = req_sap;
        sps->sps_sap = sap;
        sps->sps_dlstate = DL_IDLE;
        dlbindack(q, mp, req_sap, &sap, sizeof (int32_t), 0, 0);
}

/*
 * sppp_dlunbindreq()
 *
 * MT-Perimeters:
 *    shared inner, shared outer.
 *
 * Description:
 *    Perform DL_UNBIND_REQ request, called by sppp_mproto.
 */
/* ARGSUSED */
static int
sppp_dlunbindreq(queue_t *q, mblk_t *mp, spppstr_t *sps)
{
        ASSERT(q != NULL && q->q_ptr != NULL);
        ASSERT(mp != NULL && mp->b_rptr != NULL);
        ASSERT(sps != NULL);
        ASSERT(!IS_SPS_PIOATTACH(sps));
        ASSERT(sps->sps_dlstate == DL_IDLE);

        qwriter(q, mp, sppp_dl_unbind, PERIM_INNER);
        return (0);
}

/*
 * sppp_dl_unbind()
 *
 * MT-Perimeters:
 *    exclusive inner, shared outer.
 *
 * Description:
 *    Called by qwriter (INNER) from sppp_dlunbindreq as the result of
 *    receiving a DL_UNBIND_REQ message.
 */
static void
sppp_dl_unbind(queue_t *q, mblk_t *mp)
{
        spppstr_t       *sps;
        sppa_t          *ppa;
        t_scalar_t      sap;
        mblk_t          *msg;
        boolean_t       saydown;

        ASSERT(q != NULL && q->q_ptr != NULL);
        sps = (spppstr_t *)q->q_ptr;
        ppa = sps->sps_ppa;
        ASSERT(mp != NULL && mp->b_rptr != NULL);
        sap = sps->sps_sap;
        ASSERT((sap == PPP_IP) || (sap == PPP_IPV6) || (sap == PPP_ALLSAP));

        /* Flush messages on unbind, per DLPI specification. */
        flushq(WR(q), FLUSHALL);
        flushq(RD(q), FLUSHALL);

        if ((ppa != NULL) && IS_SPS_CACHED(sps)) {
                sps->sps_flags &= ~SPS_CACHED;
                msg = NULL;
                saydown = (ppa->ppa_ctl != NULL &&
                    (sps->sps_npmode == NPMODE_PASS ||
                    sps->sps_npmode == NPMODE_QUEUE));
                if (sap == PPP_IP) {
                        ppa->ppa_ip_cache = NULL;
                        if (saydown)
                                msg = create_lsmsg(PPP_LINKSTAT_IPV4_UNBOUND);
                } else if (sap == PPP_IPV6) {
                        ppa->ppa_ip6_cache = NULL;
                        if (saydown)
                                msg = create_lsmsg(PPP_LINKSTAT_IPV6_UNBOUND);
                }
                if (msg != NULL) {
#ifdef DBG_DLPI
                        cmn_err(CE_CONT, "sending up %s\n",
                            ((sap == PPP_IP) ? "PPP_LINKSTAT_IPV4_UNBOUND" :
                            "PPP_LINKSTAT_IPV6_UNBOUND"));
#endif
                        putnext(ppa->ppa_ctl->sps_rq, msg);
                }
        }
        DBGDLPI((CE_CONT, "/%d: unbound from sap %X (req %X)\n", sps->sps_mn_id,
            sps->sps_sap, sps->sps_req_sap));
        sps->sps_req_sap = 0;
        sps->sps_sap = -1;
        sps->sps_dlstate = DL_UNBOUND;

        dlokack(q, mp, DL_UNBIND_REQ);
}

/*
 * sppp_dlinforeq()
 *
 * MT-Perimeters:
 *    shared inner, shared outer.
 *
 * Description:
 *    Perform DL_INFO_REQ request, called by sppp_mproto.
 */
static int
sppp_dlinforeq(queue_t *q, mblk_t *mp, spppstr_t *sps)
{
        dl_info_ack_t   *dlip;
        uint32_t        size;
        uint32_t        addr_size;
        sppa_t          *ppa;

        ASSERT(q != NULL && q->q_ptr != NULL);
        ASSERT(mp != NULL && mp->b_rptr != NULL);
        ASSERT(sps != NULL);
        ppa = sps->sps_ppa;

        /* Exchange current msg for a DL_INFO_ACK. */
        addr_size = SPPP_ADDRL;
        size = sizeof (dl_info_ack_t) + addr_size;
        if ((mp = mexchange(q, mp, size, M_PCPROTO, DL_INFO_ACK)) == NULL) {
                DBGERROR((CE_CONT, "DLPI info: mexchange failed\n"));
                /* mexchange already sent up an merror ENOSR */
                return (0);
        }
        /* Fill in DL_INFO_ACK fields and reply */
        dlip = (dl_info_ack_t *)mp->b_rptr;
        *dlip = sppp_infoack;
        dlip->dl_current_state = sps->sps_dlstate;
        dlip->dl_max_sdu = ppa != NULL ? ppa->ppa_mtu : PPP_MAXMTU;
#ifdef DBG_DLPI
        {
                const char *cp = state2name(dlip->dl_current_state);
                if (cp != NULL)
                        cmn_err(CE_CONT, "info returns state %s, max sdu %d\n",
                            cp, dlip->dl_max_sdu);
                else
                        cmn_err(CE_CONT, "info returns state %d, max sdu %d\n",
                            dlip->dl_current_state, dlip->dl_max_sdu);
        }
#endif
        qreply(q, mp);
        return (0);
}

/*
 * sppp_dlunitdatareq()
 *
 * MT-Perimeters:
 *    shared inner, shared outer.
 *
 * Description:
 *    Handle DL_UNITDATA_REQ request, called by sppp_mproto. This procedure
 *    gets called for M_PROTO (DLPI) style of transmission. The fact that we
 *    have acknowledged IP's fastpath probing (DL_IOC_HDR_INFO) does not
 *    guarantee that IP will always transmit via M_DATA, and it merely implies
 *    that such situation _may_ happen. In other words, IP may decide to use
 *    M_PROTO (DLPI) for data transmission should it decide to do so.
 *    Therefore, we should never place any restrictions or checks against
 *    streams marked with SPS_FASTPATH, since it is legal for this procedure
 *    to be entered with or without the bit set.
 */
static int
sppp_dlunitdatareq(queue_t *q, mblk_t *mp, spppstr_t *sps)
{
        sppa_t          *ppa;
        mblk_t          *hdrmp;
        mblk_t          *pktmp;
        dl_unitdata_req_t *dludp;
        int             dladdroff;
        int             dladdrlen;
        int             msize;
        int             error = 0;
        boolean_t       is_promisc;

        ASSERT(q != NULL && q->q_ptr != NULL);
        ASSERT(mp != NULL && mp->b_rptr != NULL);
        ASSERT((MTYPE(mp) == M_PCPROTO) || (MTYPE(mp) == M_PROTO));
        dludp = (dl_unitdata_req_t *)mp->b_rptr;
        dladdroff = dludp->dl_dest_addr_offset;
        dladdrlen = dludp->dl_dest_addr_length;
        ASSERT(sps != NULL);
        ASSERT(!IS_SPS_PIOATTACH(sps));
        ASSERT(sps->sps_dlstate == DL_IDLE);
        ASSERT(q->q_ptr == sps);
        /*
         * If this stream is not attached to any ppas, then discard data
         * coming down through this stream.
         */
        ppa = sps->sps_ppa;
        if (ppa == NULL) {
                DBGERROR((CE_CONT, "DLPI unitdata: no attached ppa\n"));
                error = ENOLINK;
        } else if (mp->b_cont == NULL) {
                DBGERROR((CE_CONT, "DLPI unitdata: missing data\n"));
                error = EPROTO;
        }
        if (error != 0) {
                dluderrorind(q, mp, mp->b_rptr + dladdroff, dladdrlen,
                    DL_BADDATA, error);
                return (0);
        }
        ASSERT(mp->b_cont->b_rptr != NULL);
        /*
         * Check if outgoing packet size is larger than allowed. We use
         * msgdsize to count all of M_DATA blocks in the message.
         */
        msize = msgdsize(mp);
        if (msize > ppa->ppa_mtu) {
                /* Log, and send it anyway */
                mutex_enter(&ppa->ppa_sta_lock);
                ppa->ppa_otoolongs++;
                mutex_exit(&ppa->ppa_sta_lock);
        }
        if (IS_SPS_KDEBUG(sps)) {
                SPDEBUG(PPP_DRV_NAME
                    "/%d: DL_UNITDATA_REQ (%d bytes) sps=0x%p flags=0x%b "
                    "ppa=0x%p flags=0x%b\n", sps->sps_mn_id, msize,
                    (void *)sps, sps->sps_flags, SPS_FLAGS_STR,
                    (void *)ppa, ppa->ppa_flags, PPA_FLAGS_STR);
        }
        /* Allocate a message (M_DATA) to contain PPP header bytes. */
        if ((hdrmp = allocb(PPP_HDRLEN, BPRI_MED)) == NULL) {
                mutex_enter(&ppa->ppa_sta_lock);
                ppa->ppa_allocbfail++;
                mutex_exit(&ppa->ppa_sta_lock);
                DBGERROR((CE_CONT,
                    "DLPI unitdata: can't allocate header buffer\n"));
                dluderrorind(q, mp, mp->b_rptr + dladdroff, dladdrlen,
                    DL_SYSERR, ENOSR);
                return (0);
        }
        /*
         * Should there be any promiscuous stream(s), send the data up
         * for each promiscuous stream that we recognize.
         */
        rw_enter(&ppa->ppa_sib_lock, RW_READER);
        is_promisc = ppa->ppa_promicnt;
        if (is_promisc) {
                ASSERT(ppa->ppa_streams != NULL);
                sppp_dlprsendup(ppa->ppa_streams, mp->b_cont, sps->sps_sap,
                    B_FALSE);
        }
        rw_exit(&ppa->ppa_sib_lock);
        /* Discard DLPI header and keep only IP payload (mp->b_cont). */
        pktmp = mp->b_cont;
        mp->b_cont = NULL;
        freemsg(mp);
        mp = hdrmp;

        *(uchar_t *)mp->b_wptr++ = PPP_ALLSTATIONS;
        *(uchar_t *)mp->b_wptr++ = PPP_UI;
        *(uchar_t *)mp->b_wptr++ = ((uint16_t)sps->sps_sap >> 8) & 0xff;
        *(uchar_t *)mp->b_wptr++ = ((uint16_t)sps->sps_sap) & 0xff;
        ASSERT(MBLKL(mp) == PPP_HDRLEN);

        linkb(mp, pktmp);
        /*
         * Only time-stamp the packet with hrtime if the upper stream
         * is configured to do so.
         */
        if (IS_PPA_TIMESTAMP(ppa)) {
                ppa->ppa_lasttx = gethrtime();
        }
        /*
         * Just put this back on the queue and allow the write service
         * routine to handle it.  We're nested too deeply here to
         * rewind the stack sufficiently to prevent overflow.  This is
         * the slow path anyway.
         */
        if (putq(q, mp) == 0) {
                mutex_enter(&ppa->ppa_sta_lock);
                ppa->ppa_oqdropped++;
                mutex_exit(&ppa->ppa_sta_lock);
                freemsg(mp);
        } else {
                qenable(q);
        }
        return (0);
}

/*
 * sppp_dlpromisconreq()
 *
 * MT-Perimeters:
 *    shared inner, shared outer.
 *
 * Description:
 *    Perform DL_PROMISCON_REQ request, called by sppp_mproto.
 */
static int
sppp_dlpromisconreq(queue_t *q, mblk_t *mp, spppstr_t *sps)
{
        t_uscalar_t     level;

        ASSERT(q != NULL && q->q_ptr != NULL);
        ASSERT(mp != NULL && mp->b_rptr != NULL);
        level = ((dl_promiscon_req_t *)mp->b_rptr)->dl_level;
        ASSERT(sps != NULL);

        /* snoop issues DL_PROMISCON_REQ more than once. */
        if (IS_SPS_PROMISC(sps)) {
                dlokack(q, mp, DL_PROMISCON_REQ);
        } else if ((level != DL_PROMISC_PHYS) && (level != DL_PROMISC_SAP) &&
            (level != DL_PROMISC_MULTI)) {
                DBGERROR((CE_CONT, "DLPI promiscon: bad level %d\n", level));
                dlerrorack(q, mp, DL_PROMISCON_REQ, DL_NOTSUPPORTED, 0);
        } else {
                qwriter(q, mp, sppp_dl_promiscon, PERIM_INNER);
        }
        return (0);
}

/*
 * sppp_dl_promiscon()
 *
 * MT-Perimeters:
 *    exclusive inner, shared outer.
 *
 * Description:
 *    Called by qwriter (INNER) from sppp_dlpromisconreq as the result of
 *    receiving a DL_PROMISCON_REQ message.
 */
static void
sppp_dl_promiscon(queue_t *q, mblk_t *mp)
{
        spppstr_t       *sps;
        sppa_t          *ppa;

        ASSERT(q != NULL && q->q_ptr != NULL);
        sps = (spppstr_t *)q->q_ptr;
        ASSERT(!IS_SPS_PROMISC(sps));
        ASSERT(mp != NULL && mp->b_rptr != NULL);
        ppa = sps->sps_ppa;

        sps->sps_flags |= SPS_PROMISC;
        /*
         * We can't be sure that the sps_ppa field is valid, since the DLPI
         * spec says that DL_PROMISCON_REQ can be issued at any state, i.e.,
         * the request can be issued even before DL_ATTACH_REQ or PPPIO_ATTACH
         * be issued to associate this stream with a ppa.
         */
        if (ppa != NULL) {
                rw_enter(&ppa->ppa_sib_lock, RW_WRITER);
                ppa->ppa_promicnt++;
                rw_exit(&ppa->ppa_sib_lock);
        }
        DBGDLPI((CE_CONT, "/%d: promiscuous mode on\n", sps->sps_mn_id));
        dlokack(q, mp, DL_PROMISCON_REQ);
}

/*
 * sppp_dlpromiscoffreq()
 *
 * MT-Perimeters:
 *    shared inner, shared outer.
 *
 * Description:
 *    Perform DL_PROMISCOFF_REQ request, called by sppp_mproto.
 */
static int
sppp_dlpromiscoffreq(queue_t *q, mblk_t *mp, spppstr_t *sps)
{
        t_uscalar_t     level;

        ASSERT(q != NULL && q->q_ptr != NULL);
        ASSERT(mp != NULL && mp->b_rptr != NULL);
        level = ((dl_promiscoff_req_t *)mp->b_rptr)->dl_level;
        ASSERT(sps != NULL);

        if (!IS_SPS_PROMISC(sps)) {
                DBGERROR((CE_CONT, "DLPI promiscoff: not promiscuous\n"));
                dlerrorack(q, mp, DL_PROMISCOFF_REQ, DL_NOTENAB, 0);
        } else if ((level != DL_PROMISC_PHYS) && (level != DL_PROMISC_SAP) &&
            (level != DL_PROMISC_MULTI)) {
                dlerrorack(q, mp, DL_PROMISCOFF_REQ, DL_NOTSUPPORTED, 0);
                DBGERROR((CE_CONT, "DLPI promiscoff: bad level %d\n", level));
        } else {
                qwriter(q, mp, sppp_dl_promiscoff, PERIM_INNER);
        }
        return (0);

}

/*
 * sppp_dl_promiscoff()
 *
 * MT-Perimeters:
 *    exclusive inner, shared outer.
 *
 * Description:
 *    Called by qwriter (INNER) from sppp_dlpromiscoffreq as the result of
 *    receiving a DL_PROMISCOFF_REQ message.
 */
static void
sppp_dl_promiscoff(queue_t *q, mblk_t *mp)
{
        spppstr_t       *sps;
        sppa_t          *ppa;

        ASSERT(q != NULL && q->q_ptr != NULL);
        sps = (spppstr_t *)q->q_ptr;
        ASSERT(IS_SPS_PROMISC(sps));
        ASSERT(mp != NULL && mp->b_rptr != NULL);
        ppa = sps->sps_ppa;

        sps->sps_flags &= ~SPS_PROMISC;
        /*
         * We can't be guaranteed that the sps_ppa field is still valid, since
         * the control stream might have been closed earlier, in which case
         * the close procedure would have NULL'd out the sps_ppa.
         */
        if (ppa != NULL) {
                rw_enter(&ppa->ppa_sib_lock, RW_WRITER);
                ASSERT(ppa->ppa_promicnt > 0);
                ppa->ppa_promicnt--;
                rw_exit(&ppa->ppa_sib_lock);
        }
        DBGDLPI((CE_CONT, "/%d: promiscuous mode off\n", sps->sps_mn_id));
        dlokack(q, mp, DL_PROMISCOFF_REQ);
}

/*
 * sppp_dlphyreq()
 *
 * MT-Perimeters:
 *    shared inner, shared outer.
 *
 * Description:
 *    Perform DL_PHYS_ADDR_REQ request, called by sppp_mproto. This doesn't
 *    return anything useful, but it keeps ifconfig happy.
 */
/* ARGSUSED */
static int
sppp_dlphyreq(queue_t *q, mblk_t *mp, spppstr_t *us)
{
        static struct ether_addr addr = { 0 };

        dlphysaddrack(q, mp, (char *)&addr, ETHERADDRL);
        return (0);
}

/*
 * sppp_dladdether()
 *
 * Description:
 *    Prepend an empty Ethernet header to msg for snoop, et al. Free
 *    the original mblk if alloc fails. Only called for the purpose of sending
 *    packets up the promiscous stream.
 */
/* ARGSUSED */
static mblk_t *
sppp_dladdether(spppstr_t *sps, mblk_t *mp, t_scalar_t proto)
{
        mblk_t          *eh;
        t_scalar_t      type;

        if ((eh = allocb(sizeof (struct ether_header), BPRI_MED)) == NULL) {
                freemsg(mp);
                return (NULL);
        }
        if (proto == PPP_IP) {
                type = ETHERTYPE_IP;
        } else if (proto == PPP_IPV6) {
                type = ETHERTYPE_IPV6;
        } else {
                /*
                 * For all other protocols, end this up as an ETHERTYPE_PPP
                 * type of packet. Since we've skipped the PPP headers in the
                 * caller, make sure that we restore it. We know for sure that
                 * the PPP header still exists in the message (only skipped),
                 * since the sender of this message is pppd and it must have
                 * included the PPP header in front.
                 */
                type = ETHERTYPE_PPP;
                mp->b_rptr -= PPP_HDRLEN;
                ASSERT(mp->b_rptr >= mp->b_datap->db_base);
        }
        eh->b_wptr += sizeof (struct ether_header);
        bzero((caddr_t)eh->b_rptr, sizeof (struct ether_header));
        ((struct ether_header *)eh->b_rptr)->ether_type = htons((int16_t)type);

        linkb(eh, mp);
        return (eh);
}

/*
 * sppp_dladdud()
 *
 * Description:
 *    Prepend DL_UNITDATA_IND mblk to msg, free original alloc fails.
 */
/* ARGSUSED */
mblk_t *
sppp_dladdud(spppstr_t *sps, mblk_t *mp, t_scalar_t proto, boolean_t promisc)
{
        dl_unitdata_ind_t *dlu;
        mblk_t          *dh;
        size_t          size;
        t_scalar_t      type;

        size = sizeof (dl_unitdata_ind_t) + (2 * SPPP_ADDRL);
        if ((dh = allocb(size, BPRI_MED)) == NULL) {
                freemsg(mp);
                return (NULL);
        }

        dh->b_datap->db_type = M_PROTO;
        dh->b_wptr = dh->b_datap->db_lim;
        dh->b_rptr = dh->b_wptr - size;

        dlu = (dl_unitdata_ind_t *)dh->b_rptr;
        dlu->dl_primitive = DL_UNITDATA_IND;
        dlu->dl_dest_addr_length = SPPP_ADDRL;
        dlu->dl_dest_addr_offset = sizeof (dl_unitdata_ind_t);
        dlu->dl_src_addr_length = SPPP_ADDRL;
        dlu->dl_src_addr_offset = sizeof (dl_unitdata_ind_t) + SPPP_ADDRL;
        dlu->dl_group_address = 0;

        if (promisc) {
                if (proto == PPP_IP) {
                        type = ETHERTYPE_IP;
                } else if (proto == PPP_IPV6) {
                        type = ETHERTYPE_IPV6;
                } else {
                        /*
                         * For all other protocols, send this up as an
                         * ETHERTYPE_PPP type of packet. Since we've skipped
                         * the PPP headers in the caller, make sure that we
                         * restore it. We know for sure that the PPP header
                         * still exists in the message (only skipped), since
                         * the sender of this message is pppd and it must
                         * have included the PPP header in front.
                         */
                        type = ETHERTYPE_PPP;
                        mp->b_rptr -= PPP_HDRLEN;
                        ASSERT(mp->b_rptr >= mp->b_datap->db_base);
                }
        } else {
                type = sps->sps_req_sap;
        }
        /*
         * Send the DLPI client the data with the SAP they requested,
         * (e.g. ETHERTYPE_IP) rather than the PPP protocol (e.g. PPP_IP).
         */
        ((spppreqsap_t *)(dlu + 1))[0] = type;
        ((spppreqsap_t *)(dlu + 1))[1] = type;

        linkb(dh, mp);
        return (dh);
}

/*
 * sppp_dlprsendup()
 *
 * Description:
 *    For any valid promiscuous streams (marked with SPS_PROMISC and its
 *    sps_dlstate is DL_IDLE), send data upstream. The caller is expected
 *    to hold ppa_sib_lock when calling this procedure.
 */
void
sppp_dlprsendup(spppstr_t *sps, mblk_t *mp, t_scalar_t proto, boolean_t header)
{
        sppa_t  *ppa;
        mblk_t  *dmp;

        ASSERT(sps != NULL);
        ASSERT(mp != NULL && mp->b_rptr != NULL);
        ppa = sps->sps_ppa;
        ASSERT(ppa != NULL);

        /* NOTE: caller must hold ppa_sib_lock in RW_READER mode */
        ASSERT(RW_READ_HELD(&ppa->ppa_sib_lock));

        for (; sps != NULL; sps = sps->sps_nextsib) {
                /*
                 * We specifically test to ensure that the DLPI state for the
                 * promiscous stream is IDLE (DL_IDLE), since such state tells
                 * us that the promiscous stream has been bound to PPP_ALLSAP.
                 */
                if (IS_SPS_PROMISC(sps) && (sps->sps_dlstate == DL_IDLE) &&
                    canputnext(sps->sps_rq)) {
                        if ((dmp = dupmsg(mp)) == NULL) {
                                mutex_enter(&ppa->ppa_sta_lock);
                                ppa->ppa_allocbfail++;
                                mutex_exit(&ppa->ppa_sta_lock);
                                continue;
                        }
                        if (header) {
                                dmp->b_rptr += PPP_HDRLEN;
                        }
                        if (IS_SPS_RAWDATA(sps)) {
                                /* function frees original message if fails */
                                dmp = sppp_dladdether(sps, dmp, proto);
                        } else {
                                /* function frees original message if fails */
                                dmp = sppp_dladdud(sps, dmp, proto, B_TRUE);
                        }
                        if (dmp != NULL) {
                                putnext(sps->sps_rq, dmp);
                        } else {
                                mutex_enter(&ppa->ppa_sta_lock);
                                ppa->ppa_allocbfail++;
                                mutex_exit(&ppa->ppa_sta_lock);
                        }
                }
        }
}