root/usr/src/uts/common/io/ppp/sppp/sppp.c
/*
 * sppp.c - Solaris STREAMS PPP multiplexing pseudo-driver
 *
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright (c) 2016 by Delphix. All rights reserved.
 * Copyright 2019, Joyent, Inc.
 *
 * 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.c,v 1.0 2000/05/08 01:10:12 masputra Exp $"

#include <sys/types.h>
#include <sys/debug.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/sysmacros.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <sys/cmn_err.h>
#include <sys/kmem.h>
#include <sys/conf.h>
#include <sys/dlpi.h>
#include <sys/ddi.h>
#include <sys/kstat.h>
#include <sys/strsun.h>
#include <sys/ethernet.h>
#include <sys/policy.h>
#include <sys/zone.h>
#include <net/ppp_defs.h>
#include <net/pppio.h>
#include "sppp.h"
#include "s_common.h"

/*
 * This is used to tag official Solaris sources.  Please do not define
 * "INTERNAL_BUILD" when building this software outside of Sun Microsystems.
 */
#ifdef INTERNAL_BUILD
/* MODINFO is limited to 32 characters. */
const char sppp_module_description[] = "PPP 4.0 mux";
#else /* INTERNAL_BUILD */
const char sppp_module_description[] = "ANU PPP mux";

/* LINTED */
static const char buildtime[] = "Built " __DATE__ " at " __TIME__
#ifdef DEBUG
" DEBUG"
#endif
"\n";
#endif /* INTERNAL_BUILD */

static void     sppp_inner_ioctl(queue_t *, mblk_t *);
static void     sppp_outer_ioctl(queue_t *, mblk_t *);
static queue_t  *sppp_send(queue_t *, mblk_t **, spppstr_t *);
static queue_t  *sppp_recv(queue_t *, mblk_t **, spppstr_t *);
static void     sppp_recv_nondata(queue_t *, mblk_t *, spppstr_t *);
static queue_t  *sppp_outpkt(queue_t *, mblk_t **, int, spppstr_t *);
static spppstr_t *sppp_inpkt(queue_t *, mblk_t *, spppstr_t *);
static int      sppp_kstat_update(kstat_t *, int);
static void     sppp_release_pkts(sppa_t *, uint16_t);

/*
 * sps_list contains the list of active per-stream instance state structures
 * ordered on the minor device number (see sppp.h for details). All streams
 * opened to this driver are threaded together in this list.
 */
static spppstr_t *sps_list = NULL;
/*
 * ppa_list contains the list of active per-attachment instance state
 * structures ordered on the ppa id number (see sppp.h for details). All of
 * the ppa structures created once per PPPIO_NEWPPA ioctl are threaded together
 * in this list. There is exactly one ppa structure for a given PPP interface,
 * and multiple sps streams (upper streams) may share a ppa by performing
 * an attachment explicitly (PPPIO_ATTACH) or implicitly (DL_ATTACH_REQ).
 */
static sppa_t *ppa_list = NULL;

static const char *kstats_names[] = { SPPP_KSTATS_NAMES };
static const char *kstats64_names[] = { SPPP_KSTATS64_NAMES };

/*
 * map proto (which is an IANA defined ppp network protocol) to
 * a bit position indicated by NP_* in ppa_npflag
 */
static uint32_t
sppp_ppp2np(uint16_t proto)
{
        switch (proto) {
        case PPP_IP:
                return (NP_IP);
        case PPP_IPV6:
                return (NP_IPV6);
        default:
                return (0);
        }
}

/*
 * sppp_open()
 *
 * MT-Perimeters:
 *    exclusive inner, exclusive outer.
 *
 * Description:
 *    Common open procedure for module.
 */
/* ARGSUSED */
int
sppp_open(queue_t *q, dev_t *devp, int oflag, int sflag, cred_t *credp)
{
        spppstr_t       *sps;
        spppstr_t       **nextmn;
        minor_t         mn;

        ASSERT(q != NULL && devp != NULL);
        ASSERT(sflag != MODOPEN);

        if (q->q_ptr != NULL) {
                return (0);             /* already open */
        }
        if (sflag != CLONEOPEN) {
                return (OPENFAIL);
        }
        /*
         * The sps list is sorted using the minor number as the key. The
         * following code walks the list to find the lowest valued minor
         * number available to be used.
         */
        mn = 0;
        for (nextmn = &sps_list; (sps = *nextmn) != NULL;
            nextmn = &sps->sps_nextmn) {
                if (sps->sps_mn_id != mn) {
                        break;
                }
                ++mn;
        }
        sps = (spppstr_t *)kmem_zalloc(sizeof (spppstr_t), KM_SLEEP);
        ASSERT(sps != NULL);            /* KM_SLEEP must never return NULL */
        sps->sps_nextmn = *nextmn;      /* insert stream in global list */
        *nextmn = sps;
        sps->sps_mn_id = mn;            /* save minor id for this stream */
        sps->sps_rq = q;                /* save read queue pointer */
        sps->sps_sap = -1;              /* no sap bound to stream */
        sps->sps_dlstate = DL_UNATTACHED; /* dlpi state is unattached */
        sps->sps_npmode = NPMODE_DROP;  /* drop all packets initially */
        sps->sps_zoneid = crgetzoneid(credp);
        q->q_ptr = WR(q)->q_ptr = (caddr_t)sps;
        /*
         * We explicitly disable the automatic queue scheduling for the
         * write-side to obtain complete control over queuing during transmit.
         * Packets will be queued at the upper write queue and the service
         * routine will not be called until it gets scheduled by having the
         * lower write service routine call the qenable(WR(uq)) for all streams
         * attached to the same ppa instance.
         */
        noenable(WR(q));
        *devp = makedevice(getmajor(*devp), mn);
        qprocson(q);
        return (0);
}

/*
 * Free storage used by a PPA.  This is not called until the last PPA
 * user closes their connection or reattaches to a different PPA.
 */
static void
sppp_free_ppa(sppa_t *ppa)
{
        sppa_t **nextppa;

        ASSERT(ppa->ppa_refcnt == 1);
        if (ppa->ppa_kstats != NULL) {
                kstat_delete(ppa->ppa_kstats);
                ppa->ppa_kstats = NULL;
        }
        mutex_destroy(&ppa->ppa_sta_lock);
        mutex_destroy(&ppa->ppa_npmutex);
        rw_destroy(&ppa->ppa_sib_lock);
        nextppa = &ppa_list;
        while (*nextppa != NULL) {
                if (*nextppa == ppa) {
                        *nextppa = ppa->ppa_nextppa;
                        break;
                }
                nextppa = &(*nextppa)->ppa_nextppa;
        }
        kmem_free(ppa, sizeof (*ppa));
}

/*
 * Create a new PPA.  Caller must be exclusive on outer perimeter.
 */
sppa_t *
sppp_create_ppa(uint32_t ppa_id, zoneid_t zoneid)
{
        sppa_t *ppa;
        sppa_t *curppa;
        sppa_t **availppa;
        char unit[32];          /* Unit name */
        const char **cpp;
        kstat_t *ksp;
        kstat_named_t *knt;

        /*
         * NOTE: unit *must* be named for the driver
         * name plus the ppa number so that netstat
         * can find the statistics.
         */
        (void) sprintf(unit, "%s" "%d", PPP_DRV_NAME, ppa_id);
        /*
         * Make sure we can allocate a buffer to
         * contain the ppa to be sent upstream, as
         * well as the actual ppa structure and its
         * associated kstat structure.
         */
        ppa = (sppa_t *)kmem_zalloc(sizeof (sppa_t),
            KM_NOSLEEP);
        ksp = kstat_create(PPP_DRV_NAME, ppa_id, unit, "net", KSTAT_TYPE_NAMED,
            sizeof (sppp_kstats_t) / sizeof (kstat_named_t), 0);

        if (ppa == NULL || ksp == NULL) {
                if (ppa != NULL) {
                        kmem_free(ppa, sizeof (sppa_t));
                }
                if (ksp != NULL) {
                        kstat_delete(ksp);
                }
                return (NULL);
        }
        ppa->ppa_kstats = ksp;          /* chain kstat structure */
        ppa->ppa_ppa_id = ppa_id;       /* record ppa id */
        ppa->ppa_zoneid = zoneid;       /* zone that owns this PPA */
        ppa->ppa_mtu = PPP_MAXMTU;      /* 65535-(PPP_HDRLEN+PPP_FCSLEN) */
        ppa->ppa_mru = PPP_MAXMRU;      /* 65000 */

        mutex_init(&ppa->ppa_sta_lock, NULL, MUTEX_DRIVER, NULL);
        mutex_init(&ppa->ppa_npmutex, NULL, MUTEX_DRIVER, NULL);
        rw_init(&ppa->ppa_sib_lock, NULL, RW_DRIVER, NULL);

        /*
         * Prepare and install kstat counters.  Note that for netstat
         * -i to work, there needs to be "ipackets", "opackets",
         * "ierrors", and "oerrors" kstat named variables.
         */
        knt = (kstat_named_t *)ksp->ks_data;
        for (cpp = kstats_names; cpp < kstats_names + Dim(kstats_names);
            cpp++) {
                kstat_named_init(knt, *cpp, KSTAT_DATA_UINT32);
                knt++;
        }
        for (cpp = kstats64_names; cpp < kstats64_names + Dim(kstats64_names);
            cpp++) {
                kstat_named_init(knt, *cpp, KSTAT_DATA_UINT64);
                knt++;
        }
        ksp->ks_update = sppp_kstat_update;
        ksp->ks_private = (void *)ppa;
        kstat_install(ksp);

        /* link to the next ppa and insert into global list */
        availppa = &ppa_list;
        while ((curppa = *availppa) != NULL) {
                if (ppa_id < curppa->ppa_ppa_id)
                        break;
                availppa = &curppa->ppa_nextppa;
        }
        ppa->ppa_nextppa = *availppa;
        *availppa = ppa;
        return (ppa);
}

/*
 * sppp_close()
 *
 * MT-Perimeters:
 *    exclusive inner, exclusive outer.
 *
 * Description:
 *    Common close procedure for module.
 */
/* ARGSUSED */
int
sppp_close(queue_t *q, int flags __unused, cred_t *credp __unused)
{
        spppstr_t       *sps;
        spppstr_t       **nextmn;
        spppstr_t       *sib;
        sppa_t          *ppa;
        mblk_t          *mp;

        ASSERT(q != NULL && q->q_ptr != NULL);
        sps = (spppstr_t *)q->q_ptr;
        qprocsoff(q);

        ppa = sps->sps_ppa;
        if (ppa == NULL) {
                ASSERT(!IS_SPS_CONTROL(sps));
                goto close_unattached;
        }
        if (IS_SPS_CONTROL(sps)) {
                uint32_t        cnt = 0;

                ASSERT(ppa != NULL);
                ASSERT(ppa->ppa_ctl == sps);
                ppa->ppa_ctl = NULL;
                /*
                 * STREAMS framework always issues I_UNLINK prior to close,
                 * since we only allow I_LINK under the control stream.
                 * A given ppa structure has at most one lower stream pointed
                 * by the ppa_lower_wq field, because we only allow a single
                 * linkage (I_LINK) to be done on the control stream.
                 */
                ASSERT(ppa->ppa_lower_wq == NULL);
                /*
                 * Walk through all of sibling streams attached to this ppa,
                 * and remove all references to this ppa. We have exclusive
                 * access for the entire driver here, so there's no need
                 * to hold ppa_sib_lock.
                 */
                cnt++;
                sib = ppa->ppa_streams;
                while (sib != NULL) {
                        ASSERT(ppa == sib->sps_ppa);
                        sib->sps_npmode = NPMODE_DROP;
                        sib->sps_flags &= ~(SPS_PIOATTACH | SPS_CACHED);
                        /*
                         * There should be a preallocated hangup
                         * message here.  Fetch it and send it up to
                         * the stream head.  This will cause IP to
                         * mark the interface as "down."
                         */
                        if ((mp = sib->sps_hangup) != NULL) {
                                sib->sps_hangup = NULL;
                                /*
                                 * M_HANGUP works with IP, but snoop
                                 * is lame and requires M_ERROR.  Send
                                 * up a clean error code instead.
                                 *
                                 * XXX if snoop is fixed, fix this, too.
                                 */
                                MTYPE(mp) = M_ERROR;
                                *mp->b_wptr++ = ENXIO;
                                putnext(sib->sps_rq, mp);
                        }
                        qenable(WR(sib->sps_rq));
                        cnt++;
                        sib = sib->sps_nextsib;
                }
                ASSERT(ppa->ppa_refcnt == cnt);
        } else {
                ASSERT(ppa->ppa_streams != NULL);
                ASSERT(ppa->ppa_ctl != sps);
                mp = NULL;
                if (sps->sps_sap == PPP_IP) {
                        ppa->ppa_ip_cache = NULL;
                        mp = create_lsmsg(PPP_LINKSTAT_IPV4_UNBOUND);
                } else if (sps->sps_sap == PPP_IPV6) {
                        ppa->ppa_ip6_cache = NULL;
                        mp = create_lsmsg(PPP_LINKSTAT_IPV6_UNBOUND);
                }
                /* Tell the daemon the bad news. */
                if (mp != NULL && ppa->ppa_ctl != NULL &&
                    (sps->sps_npmode == NPMODE_PASS ||
                    sps->sps_npmode == NPMODE_QUEUE)) {
                        putnext(ppa->ppa_ctl->sps_rq, mp);
                } else {
                        freemsg(mp);
                }
                /*
                 * Walk through all of sibling streams attached to the
                 * same ppa, and remove this stream from the sibling
                 * streams list. We have exclusive access for the
                 * entire driver here, so there's no need to hold
                 * ppa_sib_lock.
                 */
                sib = ppa->ppa_streams;
                if (sib == sps) {
                        ppa->ppa_streams = sps->sps_nextsib;
                } else {
                        while (sib->sps_nextsib != NULL) {
                                if (sib->sps_nextsib == sps) {
                                        sib->sps_nextsib = sps->sps_nextsib;
                                        break;
                                }
                                sib = sib->sps_nextsib;
                        }
                }
                sps->sps_nextsib = NULL;
                freemsg(sps->sps_hangup);
                sps->sps_hangup = NULL;
                /*
                 * Check if this is a promiscous stream. If the SPS_PROMISC bit
                 * is still set, it means that the stream is closed without
                 * ever having issued DL_DETACH_REQ or DL_PROMISCOFF_REQ.
                 * In this case, we simply decrement the promiscous counter,
                 * and it's safe to do it without holding ppa_sib_lock since
                 * we're exclusive (inner and outer) at this point.
                 */
                if (IS_SPS_PROMISC(sps)) {
                        ASSERT(ppa->ppa_promicnt > 0);
                        ppa->ppa_promicnt--;
                }
        }
        /* If we're the only one left, then delete now. */
        if (ppa->ppa_refcnt <= 1)
                sppp_free_ppa(ppa);
        else
                ppa->ppa_refcnt--;
close_unattached:
        q->q_ptr = WR(q)->q_ptr = NULL;
        for (nextmn = &sps_list; *nextmn != NULL;
            nextmn = &(*nextmn)->sps_nextmn) {
                if (*nextmn == sps) {
                        *nextmn = sps->sps_nextmn;
                        break;
                }
        }
        kmem_free(sps, sizeof (spppstr_t));
        return (0);
}

static void
sppp_ioctl(struct queue *q, mblk_t *mp)
{
        spppstr_t       *sps;
        spppstr_t       *nextsib;
        sppa_t          *ppa;
        struct iocblk   *iop;
        mblk_t          *nmp;
        enum NPmode     npmode;
        struct ppp_idle *pip;
        struct ppp_stats64 *psp;
        struct ppp_comp_stats *pcsp;
        hrtime_t        hrtime;
        int             sap;
        int             count = 0;
        int             error = EINVAL;

        sps = (spppstr_t *)q->q_ptr;
        ppa = sps->sps_ppa;

        iop = (struct iocblk *)mp->b_rptr;
        switch (iop->ioc_cmd) {
        case PPPIO_NPMODE:
                if (!IS_SPS_CONTROL(sps)) {
                        break;          /* return EINVAL */
                } else if (iop->ioc_count != 2 * sizeof (uint32_t) ||
                    (mp->b_cont == NULL)) {
                        error = EPROTO;
                        break;
                }
                ASSERT(ppa != NULL);
                ASSERT(mp->b_cont->b_rptr != NULL);
                ASSERT(sps->sps_npmode == NPMODE_PASS);
                sap = ((uint32_t *)mp->b_cont->b_rptr)[0];
                npmode = (enum NPmode)((uint32_t *)mp->b_cont->b_rptr)[1];
                /*
                 * Walk the sibling streams which belong to the same
                 * ppa, and try to find a stream with matching sap
                 * number.
                 */
                rw_enter(&ppa->ppa_sib_lock, RW_WRITER);
                for (nextsib = ppa->ppa_streams; nextsib != NULL;
                    nextsib = nextsib->sps_nextsib) {
                        if (nextsib->sps_sap == sap) {
                                break;  /* found it */
                        }
                }
                if (nextsib == NULL) {
                        rw_exit(&ppa->ppa_sib_lock);
                        break;          /* return EINVAL */
                } else {
                        nextsib->sps_npmode = npmode;
                        if ((nextsib->sps_npmode != NPMODE_QUEUE) &&
                            (WR(nextsib->sps_rq)->q_first != NULL)) {
                                qenable(WR(nextsib->sps_rq));
                        }
                }
                rw_exit(&ppa->ppa_sib_lock);
                error = 0;      /* return success */
                break;
        case PPPIO_GIDLE:
                if (ppa == NULL) {
                        ASSERT(!IS_SPS_CONTROL(sps));
                        error = ENOLINK;
                        break;
                } else if (!IS_PPA_TIMESTAMP(ppa)) {
                        break;          /* return EINVAL */
                }
                if ((nmp = allocb(sizeof (struct ppp_idle),
                    BPRI_MED)) == NULL) {
                        mutex_enter(&ppa->ppa_sta_lock);
                        ppa->ppa_allocbfail++;
                        mutex_exit(&ppa->ppa_sta_lock);
                        error = ENOSR;
                        break;
                }
                if (mp->b_cont != NULL) {
                        freemsg(mp->b_cont);
                }
                mp->b_cont = nmp;
                pip = (struct ppp_idle *)nmp->b_wptr;
                nmp->b_wptr += sizeof (struct ppp_idle);
                /*
                 * Get current timestamp and subtract the tx and rx
                 * timestamps to get the actual idle time to be
                 * returned.
                 */
                hrtime = gethrtime();
                pip->xmit_idle = (hrtime - ppa->ppa_lasttx) / 1000000000ul;
                pip->recv_idle = (hrtime - ppa->ppa_lastrx) / 1000000000ul;
                count = msgsize(nmp);
                error = 0;
                break;          /* return success (error is 0) */
        case PPPIO_GTYPE:
                nmp = allocb(sizeof (uint32_t), BPRI_MED);
                if (nmp == NULL) {
                        error = ENOSR;
                        break;
                }
                if (mp->b_cont != NULL) {
                        freemsg(mp->b_cont);
                }
                mp->b_cont = nmp;
                /*
                 * Let the requestor know that we are the PPP
                 * multiplexer (PPPTYP_MUX).
                 */
                *(uint32_t *)nmp->b_wptr = PPPTYP_MUX;
                nmp->b_wptr += sizeof (uint32_t);
                count = msgsize(nmp);
                error = 0;              /* return success */
                break;
        case PPPIO_GETSTAT64:
                if (ppa == NULL) {
                        break;          /* return EINVAL */
                } else if ((ppa->ppa_lower_wq != NULL) &&
                    !IS_PPA_LASTMOD(ppa)) {
                        mutex_enter(&ppa->ppa_sta_lock);
                        /*
                         * We match sps_ioc_id on the M_IOC{ACK,NAK},
                         * so if the response hasn't come back yet,
                         * new ioctls must be queued instead.
                         */
                        if (IS_SPS_IOCQ(sps)) {
                                mutex_exit(&ppa->ppa_sta_lock);
                                if (!putq(q, mp)) {
                                        error = EAGAIN;
                                        break;
                                }
                                return;
                        } else {
                                ppa->ppa_ioctlsfwd++;
                                /*
                                 * Record the ioctl CMD & ID - this will be
                                 * used to check the ACK or NAK responses
                                 * coming from below.
                                 */
                                sps->sps_ioc_id = iop->ioc_id;
                                sps->sps_flags |= SPS_IOCQ;
                                mutex_exit(&ppa->ppa_sta_lock);
                        }
                        putnext(ppa->ppa_lower_wq, mp);
                        return; /* don't ack or nak the request */
                }
                nmp = allocb(sizeof (*psp), BPRI_MED);
                if (nmp == NULL) {
                        mutex_enter(&ppa->ppa_sta_lock);
                        ppa->ppa_allocbfail++;
                        mutex_exit(&ppa->ppa_sta_lock);
                        error = ENOSR;
                        break;
                }
                if (mp->b_cont != NULL) {
                        freemsg(mp->b_cont);
                }
                mp->b_cont = nmp;
                psp = (struct ppp_stats64 *)nmp->b_wptr;
                /*
                 * Copy the contents of ppp_stats64 structure for this
                 * ppa and return them to the caller.
                 */
                mutex_enter(&ppa->ppa_sta_lock);
                bcopy(&ppa->ppa_stats, psp, sizeof (*psp));
                mutex_exit(&ppa->ppa_sta_lock);
                nmp->b_wptr += sizeof (*psp);
                count = sizeof (*psp);
                error = 0;              /* return success */
                break;
        case PPPIO_GETCSTAT:
                if (ppa == NULL) {
                        break;          /* return EINVAL */
                } else if ((ppa->ppa_lower_wq != NULL) &&
                    !IS_PPA_LASTMOD(ppa)) {
                        mutex_enter(&ppa->ppa_sta_lock);
                        /*
                         * See comments in PPPIO_GETSTAT64 case
                         * in sppp_ioctl().
                         */
                        if (IS_SPS_IOCQ(sps)) {
                                mutex_exit(&ppa->ppa_sta_lock);
                                if (!putq(q, mp)) {
                                        error = EAGAIN;
                                        break;
                                }
                                return;
                        } else {
                                ppa->ppa_ioctlsfwd++;
                                /*
                                 * Record the ioctl CMD & ID - this will be
                                 * used to check the ACK or NAK responses
                                 * coming from below.
                                 */
                                sps->sps_ioc_id = iop->ioc_id;
                                sps->sps_flags |= SPS_IOCQ;
                                mutex_exit(&ppa->ppa_sta_lock);
                        }
                        putnext(ppa->ppa_lower_wq, mp);
                        return; /* don't ack or nak the request */
                }
                nmp = allocb(sizeof (struct ppp_comp_stats), BPRI_MED);
                if (nmp == NULL) {
                        mutex_enter(&ppa->ppa_sta_lock);
                        ppa->ppa_allocbfail++;
                        mutex_exit(&ppa->ppa_sta_lock);
                        error = ENOSR;
                        break;
                }
                if (mp->b_cont != NULL) {
                        freemsg(mp->b_cont);
                }
                mp->b_cont = nmp;
                pcsp = (struct ppp_comp_stats *)nmp->b_wptr;
                nmp->b_wptr += sizeof (struct ppp_comp_stats);
                bzero((caddr_t)pcsp, sizeof (struct ppp_comp_stats));
                count = msgsize(nmp);
                error = 0;              /* return success */
                break;
        }

        if (error == 0) {
                /* Success; tell the user. */
                miocack(q, mp, count, 0);
        } else {
                /* Failure; send error back upstream. */
                miocnak(q, mp, 0, error);
        }
}

/*
 * sppp_uwput()
 *
 * MT-Perimeters:
 *    shared inner, shared outer.
 *
 * Description:
 *    Upper write-side put procedure. Messages from above arrive here.
 */
int
sppp_uwput(queue_t *q, mblk_t *mp)
{
        queue_t         *nextq;
        spppstr_t       *sps;
        sppa_t          *ppa;
        struct iocblk   *iop;
        int             error;

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

        switch (MTYPE(mp)) {
        case M_PCPROTO:
        case M_PROTO:
                if (IS_SPS_CONTROL(sps)) {
                        ASSERT(ppa != NULL);
                        /*
                         * Intentionally change this to a high priority
                         * message so it doesn't get queued up. M_PROTO is
                         * specifically used for signalling between pppd and its
                         * kernel-level component(s), such as ppptun, so we
                         * make sure that it doesn't get queued up behind
                         * data messages.
                         */
                        MTYPE(mp) = M_PCPROTO;
                        if ((ppa->ppa_lower_wq != NULL) &&
                            canputnext(ppa->ppa_lower_wq)) {
                                mutex_enter(&ppa->ppa_sta_lock);
                                ppa->ppa_mctlsfwd++;
                                mutex_exit(&ppa->ppa_sta_lock);
                                putnext(ppa->ppa_lower_wq, mp);
                        } else {
                                mutex_enter(&ppa->ppa_sta_lock);
                                ppa->ppa_mctlsfwderr++;
                                mutex_exit(&ppa->ppa_sta_lock);
                                freemsg(mp);
                        }
                } else {
                        (void) sppp_mproto(q, mp, sps);
                        return (0);
                }
                break;
        case M_DATA:
                if ((nextq = sppp_send(q, &mp, sps)) != NULL)
                        putnext(nextq, mp);
                break;
        case M_IOCTL:
                error = EINVAL;
                iop = (struct iocblk *)mp->b_rptr;
                switch (iop->ioc_cmd) {
                case DLIOCRAW:
                case DL_IOC_HDR_INFO:
                case PPPIO_ATTACH:
                case PPPIO_DEBUG:
                case PPPIO_DETACH:
                case PPPIO_LASTMOD:
                case PPPIO_MRU:
                case PPPIO_MTU:
                case PPPIO_USETIMESTAMP:
                case PPPIO_BLOCKNP:
                case PPPIO_UNBLOCKNP:
                        qwriter(q, mp, sppp_inner_ioctl, PERIM_INNER);
                        return (0);
                case I_LINK:
                case I_UNLINK:
                case PPPIO_NEWPPA:
                        qwriter(q, mp, sppp_outer_ioctl, PERIM_OUTER);
                        return (0);
                case PPPIO_NPMODE:
                case PPPIO_GIDLE:
                case PPPIO_GTYPE:
                case PPPIO_GETSTAT64:
                case PPPIO_GETCSTAT:
                        /*
                         * These require additional auto variables to
                         * handle, so (for optimization reasons)
                         * they're moved off to a separate function.
                         */
                        sppp_ioctl(q, mp);
                        return (0);
                case PPPIO_GETSTAT:
                        break;                  /* 32 bit interface gone */
                default:
                        if (iop->ioc_cr == NULL ||
                            secpolicy_ppp_config(iop->ioc_cr) != 0) {
                                error = EPERM;
                                break;
                        } else if ((ppa == NULL) ||
                            (ppa->ppa_lower_wq == NULL)) {
                                break;          /* return EINVAL */
                        }
                        mutex_enter(&ppa->ppa_sta_lock);
                        /*
                         * See comments in PPPIO_GETSTAT64 case
                         * in sppp_ioctl().
                         */
                        if (IS_SPS_IOCQ(sps)) {
                                mutex_exit(&ppa->ppa_sta_lock);
                                if (!putq(q, mp)) {
                                        error = EAGAIN;
                                        break;
                                }
                                return (0);
                        } else {
                                ppa->ppa_ioctlsfwd++;
                                /*
                                 * Record the ioctl CMD & ID -
                                 * this will be used to check the
                                 * ACK or NAK responses coming from below.
                                 */
                                sps->sps_ioc_id = iop->ioc_id;
                                sps->sps_flags |= SPS_IOCQ;
                                mutex_exit(&ppa->ppa_sta_lock);
                        }
                        putnext(ppa->ppa_lower_wq, mp);
                        return (0);     /* don't ack or nak the request */
                }
                /* Failure; send error back upstream. */
                miocnak(q, mp, 0, error);
                break;
        case M_FLUSH:
                if (*mp->b_rptr & FLUSHW) {
                        flushq(q, FLUSHDATA);
                }
                if (*mp->b_rptr & FLUSHR) {
                        *mp->b_rptr &= ~FLUSHW;
                        qreply(q, mp);
                } else {
                        freemsg(mp);
                }
                break;
        default:
                freemsg(mp);
                break;
        }
        return (0);
}

/*
 * sppp_uwsrv()
 *
 * MT-Perimeters:
 *    exclusive inner, shared outer.
 *
 * Description:
 *    Upper write-side service procedure. Note that this procedure does
 *    not get called when a message is placed on our write-side queue, since
 *    automatic queue scheduling has been turned off by noenable() when
 *    the queue was opened. We do this on purpose, as we explicitly control
 *    the write-side queue. Therefore, this procedure gets called when
 *    the lower write service procedure qenable() the upper write stream queue.
 */
int
sppp_uwsrv(queue_t *q)
{
        spppstr_t       *sps;
        sppa_t          *ppa;
        mblk_t          *mp;
        queue_t         *nextq;
        struct iocblk   *iop;

        ASSERT(q != NULL && q->q_ptr != NULL);
        sps = (spppstr_t *)q->q_ptr;

        while ((mp = getq(q)) != NULL) {
                if (MTYPE(mp) == M_IOCTL) {
                        ppa = sps->sps_ppa;
                        if ((ppa == NULL) || (ppa->ppa_lower_wq == NULL)) {
                                miocnak(q, mp, 0, EINVAL);
                                continue;
                        }

                        iop = (struct iocblk *)mp->b_rptr;
                        mutex_enter(&ppa->ppa_sta_lock);
                        /*
                         * See comments in PPPIO_GETSTAT64 case
                         * in sppp_ioctl().
                         */
                        if (IS_SPS_IOCQ(sps)) {
                                mutex_exit(&ppa->ppa_sta_lock);
                                if (putbq(q, mp) == 0)
                                        miocnak(q, mp, 0, EAGAIN);
                                break;
                        } else {
                                ppa->ppa_ioctlsfwd++;
                                sps->sps_ioc_id = iop->ioc_id;
                                sps->sps_flags |= SPS_IOCQ;
                                mutex_exit(&ppa->ppa_sta_lock);
                                putnext(ppa->ppa_lower_wq, mp);
                        }
                } else if ((nextq =
                    sppp_outpkt(q, &mp, msgdsize(mp), sps)) == NULL) {
                        if (mp != NULL) {
                                if (putbq(q, mp) == 0)
                                        freemsg(mp);
                                break;
                        }
                } else {
                        putnext(nextq, mp);
                }
        }
        return (0);
}

void
sppp_remove_ppa(spppstr_t *sps)
{
        spppstr_t *nextsib;
        sppa_t *ppa = sps->sps_ppa;

        rw_enter(&ppa->ppa_sib_lock, RW_WRITER);
        if (ppa->ppa_refcnt <= 1) {
                rw_exit(&ppa->ppa_sib_lock);
                sppp_free_ppa(ppa);
        } else {
                nextsib = ppa->ppa_streams;
                if (nextsib == sps) {
                        ppa->ppa_streams = sps->sps_nextsib;
                } else {
                        while (nextsib->sps_nextsib != NULL) {
                                if (nextsib->sps_nextsib == sps) {
                                        nextsib->sps_nextsib =
                                            sps->sps_nextsib;
                                        break;
                                }
                                nextsib = nextsib->sps_nextsib;
                        }
                }
                ppa->ppa_refcnt--;
                /*
                 * 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_DETACH_REQ is issued prior to marking the
                 * stream as non-promiscuous, through
                 * DL_PROMISCOFF_REQ request.
                 */
                if (IS_SPS_PROMISC(sps)) {
                        ASSERT(ppa->ppa_promicnt > 0);
                        ppa->ppa_promicnt--;
                }
                rw_exit(&ppa->ppa_sib_lock);
        }
        sps->sps_nextsib = NULL;
        sps->sps_ppa = NULL;
        freemsg(sps->sps_hangup);
        sps->sps_hangup = NULL;
}

sppa_t *
sppp_find_ppa(uint32_t ppa_id)
{
        sppa_t *ppa;

        for (ppa = ppa_list; ppa != NULL; ppa = ppa->ppa_nextppa) {
                if (ppa->ppa_ppa_id == ppa_id) {
                        break;  /* found the ppa */
                }
        }
        return (ppa);
}

/*
 * sppp_inner_ioctl()
 *
 * MT-Perimeters:
 *    exclusive inner, shared outer
 *
 * Description:
 *    Called by sppp_uwput as a result of receiving ioctls which require
 *    an exclusive access at the inner perimeter.
 */
static void
sppp_inner_ioctl(queue_t *q, mblk_t *mp)
{
        spppstr_t       *sps;
        sppa_t          *ppa;
        struct iocblk   *iop;
        mblk_t          *nmp;
        int             error = EINVAL;
        int             count = 0;
        int             dbgcmd;
        int             mru, mtu;
        uint32_t        ppa_id;
        hrtime_t        hrtime;
        uint16_t        proto;

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

        sps = (spppstr_t *)q->q_ptr;
        ppa = sps->sps_ppa;
        iop = (struct iocblk *)mp->b_rptr;
        switch (iop->ioc_cmd) {
        case DLIOCRAW:
                if (IS_SPS_CONTROL(sps)) {
                        break;          /* return EINVAL */
                }
                sps->sps_flags |= SPS_RAWDATA;
                error = 0;              /* return success */
                break;
        case DL_IOC_HDR_INFO:
                if (IS_SPS_CONTROL(sps)) {
                        break;          /* return EINVAL */
                } else if ((mp->b_cont == NULL) ||
                    *((t_uscalar_t *)mp->b_cont->b_rptr) != DL_UNITDATA_REQ ||
                    (MBLKL(mp->b_cont) < (sizeof (dl_unitdata_req_t) +
                    SPPP_ADDRL))) {
                        error = EPROTO;
                        break;
                } else if (ppa == NULL) {
                        error = ENOLINK;
                        break;
                }
                if ((nmp = allocb(PPP_HDRLEN, BPRI_MED)) == NULL) {
                        mutex_enter(&ppa->ppa_sta_lock);
                        ppa->ppa_allocbfail++;
                        mutex_exit(&ppa->ppa_sta_lock);
                        error = ENOMEM;
                        break;
                }
                *(uchar_t *)nmp->b_wptr++ = PPP_ALLSTATIONS;
                *(uchar_t *)nmp->b_wptr++ = PPP_UI;
                *(uchar_t *)nmp->b_wptr++ = sps->sps_sap >> 8;
                *(uchar_t *)nmp->b_wptr++ = sps->sps_sap & 0xff;
                ASSERT(MBLKL(nmp) == PPP_HDRLEN);

                linkb(mp, nmp);
                sps->sps_flags |= SPS_FASTPATH;
                error = 0;              /* return success */
                count = msgsize(nmp);
                break;
        case PPPIO_ATTACH:
                if (IS_SPS_CONTROL(sps) || IS_SPS_PIOATTACH(sps) ||
                    (sps->sps_dlstate != DL_UNATTACHED) ||
                    (iop->ioc_count != sizeof (uint32_t))) {
                        break;          /* return EINVAL */
                } else if (mp->b_cont == NULL) {
                        error = EPROTO;
                        break;
                }
                ASSERT(mp->b_cont->b_rptr != NULL);
                /* If there's something here, it's detached. */
                if (ppa != NULL) {
                        sppp_remove_ppa(sps);
                }
                ppa_id = *(uint32_t *)mp->b_cont->b_rptr;
                ppa = sppp_find_ppa(ppa_id);
                /*
                 * If we can't find it, then it's either because the requestor
                 * has supplied a wrong ppa_id to be attached to, or because
                 * the control stream for the specified ppa_id has been closed
                 * before we get here.
                 */
                if (ppa == NULL) {
                        error = ENOENT;
                        break;
                }
                if (iop->ioc_cr == NULL ||
                    ppa->ppa_zoneid != crgetzoneid(iop->ioc_cr)) {
                        error = EPERM;
                        break;
                }
                /*
                 * 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) {
                        error = ENOSR;
                        break;
                }
                /*
                 * There are two ways to attach a stream to a ppa: one is
                 * through DLPI (DL_ATTACH_REQ) and the other is through
                 * PPPIO_ATTACH. This is why we need to distinguish whether or
                 * not a stream was allocated via PPPIO_ATTACH, so that we can
                 * properly detach it when we receive PPPIO_DETACH ioctl
                 * request.
                 */
                sps->sps_flags |= SPS_PIOATTACH;
                sps->sps_ppa = ppa;
                /*
                 * Add this stream to the head of the list of sibling streams
                 * which belong to the same ppa as specified.
                 */
                rw_enter(&ppa->ppa_sib_lock, RW_WRITER);
                ppa->ppa_refcnt++;
                sps->sps_nextsib = ppa->ppa_streams;
                ppa->ppa_streams = sps;
                rw_exit(&ppa->ppa_sib_lock);
                error = 0;              /* return success */
                break;
        case PPPIO_BLOCKNP:
        case PPPIO_UNBLOCKNP:
                if (iop->ioc_cr == NULL ||
                    secpolicy_ppp_config(iop->ioc_cr) != 0) {
                        error = EPERM;
                        break;
                }
                error = miocpullup(mp, sizeof (uint16_t));
                if (error != 0)
                        break;
                ASSERT(mp->b_cont->b_rptr != NULL);
                proto = *(uint16_t *)mp->b_cont->b_rptr;
                if (iop->ioc_cmd == PPPIO_BLOCKNP) {
                        uint32_t npflagpos = sppp_ppp2np(proto);
                        /*
                         * Mark proto as blocked in ppa_npflag until the
                         * corresponding queues for proto have been plumbed.
                         */
                        if (npflagpos != 0) {
                                mutex_enter(&ppa->ppa_npmutex);
                                ppa->ppa_npflag |= (1 << npflagpos);
                                mutex_exit(&ppa->ppa_npmutex);
                        } else {
                                error = EINVAL;
                        }
                } else {
                        /*
                         * reset ppa_npflag and release proto
                         * packets that were being held in control queue.
                         */
                        sppp_release_pkts(ppa, proto);
                }
                break;
        case PPPIO_DEBUG:
                if (iop->ioc_cr == NULL ||
                    secpolicy_ppp_config(iop->ioc_cr) != 0) {
                        error = EPERM;
                        break;
                } else if (iop->ioc_count != sizeof (uint32_t)) {
                        break;          /* return EINVAL */
                } else if (mp->b_cont == NULL) {
                        error = EPROTO;
                        break;
                }
                ASSERT(mp->b_cont->b_rptr != NULL);
                dbgcmd = *(uint32_t *)mp->b_cont->b_rptr;
                /*
                 * We accept PPPDBG_LOG + PPPDBG_DRIVER value as an indication
                 * that SPS_KDEBUG needs to be enabled for this upper stream.
                 */
                if (dbgcmd == PPPDBG_LOG + PPPDBG_DRIVER) {
                        sps->sps_flags |= SPS_KDEBUG;
                        error = 0;      /* return success */
                        break;
                }
                /*
                 * Otherwise, for any other values, we send them down only if
                 * there is an attachment and if the attachment has something
                 * linked underneath it.
                 */
                if ((ppa == NULL) || (ppa->ppa_lower_wq == NULL)) {
                        error = ENOLINK;
                        break;
                }
                mutex_enter(&ppa->ppa_sta_lock);
                /*
                 * See comments in PPPIO_GETSTAT64 case
                 * in sppp_ioctl().
                 */
                if (IS_SPS_IOCQ(sps)) {
                        mutex_exit(&ppa->ppa_sta_lock);
                        if (!putq(q, mp)) {
                                error = EAGAIN;
                                break;
                        }
                        return;
                } else {
                        ppa->ppa_ioctlsfwd++;
                        /*
                         * Record the ioctl CMD & ID -
                         * this will be used to check the
                         * ACK or NAK responses coming from below.
                         */
                        sps->sps_ioc_id = iop->ioc_id;
                        sps->sps_flags |= SPS_IOCQ;
                        mutex_exit(&ppa->ppa_sta_lock);
                }
                putnext(ppa->ppa_lower_wq, mp);
                return;                 /* don't ack or nak the request */
        case PPPIO_DETACH:
                if (!IS_SPS_PIOATTACH(sps)) {
                        break;          /* return EINVAL */
                }
                /*
                 * The SPS_PIOATTACH flag set on the stream tells us that
                 * the ppa field is still valid. In the event that the control
                 * stream be closed prior to this stream's detachment, the
                 * SPS_PIOATTACH flag would have been cleared from this stream
                 * during close; in that case we won't get here.
                 */
                ASSERT(ppa != NULL);
                ASSERT(ppa->ppa_ctl != sps);
                ASSERT(sps->sps_dlstate == DL_UNATTACHED);

                /*
                 * We don't actually detach anything until the stream is
                 * closed or reattached.
                 */

                sps->sps_flags &= ~SPS_PIOATTACH;
                error = 0;              /* return success */
                break;
        case PPPIO_LASTMOD:
                if (!IS_SPS_CONTROL(sps)) {
                        break;          /* return EINVAL */
                }
                ASSERT(ppa != NULL);
                ppa->ppa_flags |= PPA_LASTMOD;
                error = 0;              /* return success */
                break;
        case PPPIO_MRU:
                if (!IS_SPS_CONTROL(sps) ||
                    (iop->ioc_count != sizeof (uint32_t))) {
                        break;          /* return EINVAL */
                } else if (mp->b_cont == NULL) {
                        error = EPROTO;
                        break;
                }
                ASSERT(ppa != NULL);
                ASSERT(mp->b_cont->b_rptr != NULL);
                mru = *(uint32_t *)mp->b_cont->b_rptr;
                if ((mru <= 0) || (mru > PPP_MAXMRU)) {
                        error = EPROTO;
                        break;
                }
                if (mru < PPP_MRU) {
                        mru = PPP_MRU;
                }
                ppa->ppa_mru = (uint16_t)mru;
                /*
                 * If there's something beneath this driver for the ppa, then
                 * inform it (or them) of the MRU size. Only do this is we
                 * are not the last PPP module on the stream.
                 */
                if (!IS_PPA_LASTMOD(ppa) && (ppa->ppa_lower_wq != NULL)) {
                        (void) putctl4(ppa->ppa_lower_wq, M_CTL, PPPCTL_MRU,
                            mru);
                }
                error = 0;              /* return success */
                break;
        case PPPIO_MTU:
                if (!IS_SPS_CONTROL(sps) ||
                    (iop->ioc_count != sizeof (uint32_t))) {
                        break;          /* return EINVAL */
                } else if (mp->b_cont == NULL) {
                        error = EPROTO;
                        break;
                }
                ASSERT(ppa != NULL);
                ASSERT(mp->b_cont->b_rptr != NULL);
                mtu = *(uint32_t *)mp->b_cont->b_rptr;
                if ((mtu <= 0) || (mtu > PPP_MAXMTU)) {
                        error = EPROTO;
                        break;
                }
                ppa->ppa_mtu = (uint16_t)mtu;
                /*
                 * If there's something beneath this driver for the ppa, then
                 * inform it (or them) of the MTU size. Only do this if we
                 * are not the last PPP module on the stream.
                 */
                if (!IS_PPA_LASTMOD(ppa) && (ppa->ppa_lower_wq != NULL)) {
                        (void) putctl4(ppa->ppa_lower_wq, M_CTL, PPPCTL_MTU,
                            mtu);
                }
                error = 0;              /* return success */
                break;
        case PPPIO_USETIMESTAMP:
                if (!IS_SPS_CONTROL(sps)) {
                        break;          /* return EINVAL */
                }
                if (!IS_PPA_TIMESTAMP(ppa)) {
                        hrtime = gethrtime();
                        ppa->ppa_lasttx = ppa->ppa_lastrx = hrtime;
                        ppa->ppa_flags |= PPA_TIMESTAMP;
                }
                error = 0;
                break;
        }

        if (error == 0) {
                /* Success; tell the user */
                miocack(q, mp, count, 0);
        } else {
                /* Failure; send error back upstream */
                miocnak(q, mp, 0, error);
        }
}

/*
 * sppp_outer_ioctl()
 *
 * MT-Perimeters:
 *    exclusive inner, exclusive outer
 *
 * Description:
 *    Called by sppp_uwput as a result of receiving ioctls which require
 *    an exclusive access at the outer perimeter.
 */
static void
sppp_outer_ioctl(queue_t *q, mblk_t *mp)
{
        spppstr_t       *sps = q->q_ptr;
        spppstr_t       *nextsib;
        queue_t         *lwq;
        sppa_t          *ppa;
        struct iocblk   *iop;
        int             error = EINVAL;
        int             count = 0;
        uint32_t        ppa_id;
        mblk_t          *nmp;
        zoneid_t        zoneid;

        sps = (spppstr_t *)q->q_ptr;
        ppa = sps->sps_ppa;
        iop = (struct iocblk *)mp->b_rptr;
        switch (iop->ioc_cmd) {
        case I_LINK:
                if (!IS_SPS_CONTROL(sps)) {
                        break;          /* return EINVAL */
                } else if (ppa->ppa_lower_wq != NULL) {
                        error = EEXIST;
                        break;
                }
                ASSERT(ppa->ppa_ctl != NULL);
                ASSERT(sps->sps_npmode == NPMODE_PASS);
                ASSERT(mp->b_cont != NULL && mp->b_cont->b_rptr != NULL);

                lwq = ((struct linkblk *)mp->b_cont->b_rptr)->l_qbot;
                ASSERT(lwq != NULL);

                ppa->ppa_lower_wq = lwq;
                lwq->q_ptr = RD(lwq)->q_ptr = (caddr_t)ppa;
                /*
                 * Unblock upper network streams which now feed this lower
                 * stream. We don't need to hold ppa_sib_lock here, since we
                 * are writer at the outer perimeter.
                 */
                if (WR(sps->sps_rq)->q_first != NULL)
                        qenable(WR(sps->sps_rq));
                for (nextsib = ppa->ppa_streams; nextsib != NULL;
                    nextsib = nextsib->sps_nextsib) {
                        nextsib->sps_npmode = NPMODE_PASS;
                        if (WR(nextsib->sps_rq)->q_first != NULL) {
                                qenable(WR(nextsib->sps_rq));
                        }
                }

                /*
                 * Also unblock (run once) our lower read-side queue.  This is
                 * where packets received while doing the I_LINK may be
                 * languishing; see sppp_lrsrv.
                 */
                qenable(RD(lwq));

                /*
                 * Send useful information down to the modules which are now
                 * linked below this driver (for this particular ppa). Only
                 * do this if we are not the last PPP module on the stream.
                 */
                if (!IS_PPA_LASTMOD(ppa)) {
                        (void) putctl8(lwq, M_CTL, PPPCTL_UNIT,
                            ppa->ppa_ppa_id);
                        (void) putctl4(lwq, M_CTL, PPPCTL_MRU, ppa->ppa_mru);
                        (void) putctl4(lwq, M_CTL, PPPCTL_MTU, ppa->ppa_mtu);
                }

                if (IS_SPS_KDEBUG(sps)) {
                        SPDEBUG(PPP_DRV_NAME
                            "/%d: I_LINK lwq=0x%p sps=0x%p flags=0x%b ppa=0x%p "
                            "flags=0x%b\n", sps->sps_mn_id,
                            (void *)ppa->ppa_lower_wq, (void *)sps,
                            sps->sps_flags, SPS_FLAGS_STR,
                            (void *)ppa, ppa->ppa_flags,
                            PPA_FLAGS_STR);
                }
                error = 0;              /* return success */
                break;
        case I_UNLINK:
                ASSERT(IS_SPS_CONTROL(sps));
                ASSERT(ppa != NULL);
                lwq = ppa->ppa_lower_wq;
                ASSERT(mp->b_cont != NULL && mp->b_cont->b_rptr != NULL);
                ASSERT(lwq == ((struct linkblk *)mp->b_cont->b_rptr)->l_qbot);

                if (IS_SPS_KDEBUG(sps)) {
                        SPDEBUG(PPP_DRV_NAME
                            "/%d: I_UNLINK lwq=0x%p sps=0x%p flags=0x%b "
                            "ppa=0x%p flags=0x%b\n", sps->sps_mn_id,
                            (void *)lwq, (void *)sps, sps->sps_flags,
                            SPS_FLAGS_STR, (void *)ppa, ppa->ppa_flags,
                            PPA_FLAGS_STR);
                }
                /*
                 * While accessing the outer perimeter exclusively, we
                 * disassociate our ppa's lower_wq from the lower stream linked
                 * beneath us, and we also disassociate our control stream from
                 * the q_ptr of the lower stream.
                 */
                lwq->q_ptr = RD(lwq)->q_ptr = NULL;
                ppa->ppa_lower_wq = NULL;
                /*
                 * Unblock streams which now feed back up the control stream,
                 * and acknowledge the request. We don't need to hold
                 * ppa_sib_lock here, since we are writer at the outer
                 * perimeter.
                 */
                if (WR(sps->sps_rq)->q_first != NULL)
                        qenable(WR(sps->sps_rq));
                for (nextsib = ppa->ppa_streams; nextsib != NULL;
                    nextsib = nextsib->sps_nextsib) {
                        if (WR(nextsib->sps_rq)->q_first != NULL) {
                                qenable(WR(nextsib->sps_rq));
                        }
                }
                error = 0;              /* return success */
                break;
        case PPPIO_NEWPPA:
                /*
                 * Do sanity check to ensure that we don't accept PPPIO_NEWPPA
                 * on a stream which DLPI is used (since certain DLPI messages
                 * will cause state transition reflected in sps_dlstate,
                 * changing it from its default DL_UNATTACHED value). In other
                 * words, we won't allow a network/snoop stream to become
                 * a control stream.
                 */
                if (iop->ioc_cr == NULL ||
                    secpolicy_ppp_config(iop->ioc_cr) != 0) {
                        error = EPERM;
                        break;
                } else if (IS_SPS_CONTROL(sps) || IS_SPS_PIOATTACH(sps) ||
                    (ppa != NULL) || (sps->sps_dlstate != DL_UNATTACHED)) {
                        break;          /* return EINVAL */
                }
                /* Get requested unit number (if any) */
                if (iop->ioc_count == sizeof (uint32_t) && mp->b_cont != NULL)
                        ppa_id = *(uint32_t *)mp->b_cont->b_rptr;
                else
                        ppa_id = 0;
                /* Get mblk to use for response message */
                nmp = allocb(sizeof (uint32_t), BPRI_MED);
                if (nmp == NULL) {
                        error = ENOSR;
                        break;
                }
                if (mp->b_cont != NULL) {
                        freemsg(mp->b_cont);
                }
                mp->b_cont = nmp;               /* chain our response mblk */
                /*
                 * Walk the global ppa list and determine the lowest
                 * available ppa_id number to be used.
                 */
                if (ppa_id == (uint32_t)-1)
                        ppa_id = 0;
                zoneid = crgetzoneid(iop->ioc_cr);
                for (ppa = ppa_list; ppa != NULL; ppa = ppa->ppa_nextppa) {
                        if (ppa_id == (uint32_t)-2) {
                                if (ppa->ppa_ctl == NULL &&
                                    ppa->ppa_zoneid == zoneid)
                                        break;
                        } else {
                                if (ppa_id < ppa->ppa_ppa_id)
                                        break;
                                if (ppa_id == ppa->ppa_ppa_id)
                                        ++ppa_id;
                        }
                }
                if (ppa_id == (uint32_t)-2) {
                        if (ppa == NULL) {
                                error = ENXIO;
                                break;
                        }
                        /* Clear timestamp and lastmod flags */
                        ppa->ppa_flags = 0;
                } else {
                        ppa = sppp_create_ppa(ppa_id, zoneid);
                        if (ppa == NULL) {
                                error = ENOMEM;
                                break;
                        }
                }

                sps->sps_ppa = ppa;             /* chain the ppa structure */
                sps->sps_npmode = NPMODE_PASS;  /* network packets may travel */
                sps->sps_flags |= SPS_CONTROL;  /* this is the control stream */

                ppa->ppa_refcnt++;              /* new PPA reference */
                ppa->ppa_ctl = sps;             /* back ptr to upper stream */
                /*
                 * Return the newly created ppa_id to the requestor and
                 * acnowledge the request.
                 */
                *(uint32_t *)nmp->b_wptr = ppa->ppa_ppa_id;
                nmp->b_wptr += sizeof (uint32_t);

                if (IS_SPS_KDEBUG(sps)) {
                        SPDEBUG(PPP_DRV_NAME
                            "/%d: PPPIO_NEWPPA ppa_id=%d sps=0x%p flags=0x%b "
                            "ppa=0x%p flags=0x%b\n", sps->sps_mn_id, ppa_id,
                            (void *)sps, sps->sps_flags, SPS_FLAGS_STR,
                            (void *)ppa, ppa->ppa_flags,
                            PPA_FLAGS_STR);
                }
                count = msgsize(nmp);
                error = 0;
                break;
        }

        if (error == 0) {
                /* Success; tell the user. */
                miocack(q, mp, count, 0);
        } else {
                /* Failure; send error back upstream. */
                miocnak(q, mp, 0, error);
        }
}

/*
 * sppp_send()
 *
 * MT-Perimeters:
 *    shared inner, shared outer.
 *
 * Description:
 *    Called by sppp_uwput to handle M_DATA message type.  Returns
 *    queue_t for putnext, or NULL to mean that the packet was
 *    handled internally.
 */
static queue_t *
sppp_send(queue_t *q, mblk_t **mpp, spppstr_t *sps)
{
        mblk_t  *mp;
        sppa_t  *ppa;
        int     is_promisc;
        int     msize;
        int     error = 0;
        queue_t *nextq;

        ASSERT(mpp != NULL);
        mp = *mpp;
        ASSERT(q != NULL && q->q_ptr != NULL);
        ASSERT(mp != NULL && mp->b_rptr != NULL);
        ASSERT(sps != NULL);
        ASSERT(q->q_ptr == sps);
        /*
         * We only let M_DATA through if the sender is either the control
         * stream (for PPP control packets) or one of the network streams
         * (for IP packets) in IP fastpath mode. If this stream is not attached
         * to any ppas, then discard data coming down through this stream.
         */
        ppa = sps->sps_ppa;
        if (ppa == NULL) {
                ASSERT(!IS_SPS_CONTROL(sps));
                error = ENOLINK;
        } else if (!IS_SPS_CONTROL(sps) && !IS_SPS_FASTPATH(sps)) {
                error = EPROTO;
        }
        if (error != 0) {
                merror(q, mp, error);
                return (NULL);
        }
        msize = msgdsize(mp);
        if (msize > (ppa->ppa_mtu + PPP_HDRLEN)) {
                /* Log, and send it anyway */
                mutex_enter(&ppa->ppa_sta_lock);
                ppa->ppa_otoolongs++;
                mutex_exit(&ppa->ppa_sta_lock);
        } else if (msize < PPP_HDRLEN) {
                /*
                 * Log, and send it anyway. We log it because we get things
                 * in M_DATA form here, which tells us that the sender is
                 * either IP in fastpath transmission mode, or pppd. In both
                 * cases, they are currently expected to send the 4-bytes
                 * PPP header in front of any possible payloads.
                 */
                mutex_enter(&ppa->ppa_sta_lock);
                ppa->ppa_orunts++;
                mutex_exit(&ppa->ppa_sta_lock);
        }

        if (IS_SPS_KDEBUG(sps)) {
                SPDEBUG(PPP_DRV_NAME
                    "/%d: M_DATA send (%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);
        }
        /*
         * Should there be any promiscuous stream(s), send the data up
         * for each promiscuous stream that we recognize. Make sure that
         * for fastpath, we skip the PPP header in the M_DATA mblk. We skip
         * the control stream as we obviously never allow the control stream
         * to become promiscous and bind to PPP_ALLSAP.
         */
        rw_enter(&ppa->ppa_sib_lock, RW_READER);
        is_promisc = sps->sps_ppa->ppa_promicnt;
        if (is_promisc) {
                ASSERT(ppa->ppa_streams != NULL);
                sppp_dlprsendup(ppa->ppa_streams, mp, sps->sps_sap, B_TRUE);
        }
        rw_exit(&ppa->ppa_sib_lock);
        /*
         * Only time-stamp the packet with hrtime if the upper stream
         * is configured to do so.  PPP control (negotiation) messages
         * are never considered link activity; only data is activity.
         */
        if (!IS_SPS_CONTROL(sps) && IS_PPA_TIMESTAMP(ppa)) {
                ppa->ppa_lasttx = gethrtime();
        }
        /*
         * If there's already a message in the write-side service queue,
         * then queue this message there as well, otherwise, try to send
         * it down to the module immediately below us.
         */
        if (q->q_first != NULL ||
            (nextq = sppp_outpkt(q, mpp, msize, sps)) == NULL) {
                mp = *mpp;
                if (mp != NULL && putq(q, mp) == 0) {
                        mutex_enter(&ppa->ppa_sta_lock);
                        ppa->ppa_oqdropped++;
                        mutex_exit(&ppa->ppa_sta_lock);
                        freemsg(mp);
                }
                return (NULL);
        }
        return (nextq);
}

/*
 * sppp_outpkt()
 *
 * MT-Perimeters:
 *    shared inner, shared outer (if called from sppp_wput, sppp_dlunitdatareq).
 *    exclusive inner, shared outer (if called from sppp_wsrv).
 *
 * Description:
 *    Called from 1) sppp_uwput when processing a M_DATA fastpath message,
 *    or 2) sppp_uwsrv when processing the upper write-side service queue.
 *    For both cases, it prepares to send the data to the module below
 *    this driver if there is a lower stream linked underneath. If none, then
 *    the data will be sent upstream via the control channel to pppd.
 *
 * Returns:
 *      Non-NULL queue_t if message should be sent now, otherwise
 *      if *mpp == NULL, then message was freed, otherwise put *mpp
 *      (back) on the queue.  (Does not do putq/putbq, since it's
 *      called both from srv and put procedures.)
 */
static queue_t *
sppp_outpkt(queue_t *q, mblk_t **mpp, int msize, spppstr_t *sps)
{
        mblk_t          *mp;
        sppa_t          *ppa;
        enum NPmode     npmode;
        mblk_t          *mpnew;

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

        ppa = sps->sps_ppa;
        npmode = sps->sps_npmode;

        if (npmode == NPMODE_QUEUE) {
                ASSERT(!IS_SPS_CONTROL(sps));
                return (NULL);  /* queue it for later */
        } else if (ppa == NULL || ppa->ppa_ctl == NULL ||
            npmode == NPMODE_DROP || npmode == NPMODE_ERROR) {
                /*
                 * This can not be the control stream, as it must always have
                 * a valid ppa, and its npmode must always be NPMODE_PASS.
                 */
                ASSERT(!IS_SPS_CONTROL(sps));
                if (npmode == NPMODE_DROP) {
                        freemsg(mp);
                } else {
                        /*
                         * If we no longer have the control stream, or if the
                         * mode is set to NPMODE_ERROR, then we need to tell IP
                         * that the interface need to be marked as down. In
                         * other words, we tell IP to be quiescent.
                         */
                        merror(q, mp, EPROTO);
                }
                *mpp = NULL;
                return (NULL);  /* don't queue it */
        }
        /*
         * Do we have a driver stream linked underneath ? If not, we need to
         * notify pppd that the link needs to be brought up and configure
         * this upper stream to drop subsequent outgoing packets. This is
         * for demand-dialing, in which case pppd has done the IP plumbing
         * but hasn't linked the driver stream underneath us. Therefore, when
         * a packet is sent down the IP interface, a notification message
         * will be sent up the control stream to pppd in order for it to
         * establish the physical link. The driver stream is then expected
         * to be linked underneath after physical link establishment is done.
         */
        if (ppa->ppa_lower_wq == NULL) {
                ASSERT(ppa->ppa_ctl != NULL);
                ASSERT(ppa->ppa_ctl->sps_rq != NULL);

                *mpp = NULL;
                mpnew = create_lsmsg(PPP_LINKSTAT_NEEDUP);
                if (mpnew == NULL) {
                        freemsg(mp);
                        mutex_enter(&ppa->ppa_sta_lock);
                        ppa->ppa_allocbfail++;
                        mutex_exit(&ppa->ppa_sta_lock);
                        return (NULL);  /* don't queue it */
                }
                /* Include the data in the message for logging. */
                mpnew->b_cont = mp;
                mutex_enter(&ppa->ppa_sta_lock);
                ppa->ppa_lsneedup++;
                mutex_exit(&ppa->ppa_sta_lock);
                /*
                 * We need to set the mode to NPMODE_DROP, but should only
                 * do so when this stream is not the control stream.
                 */
                if (!IS_SPS_CONTROL(sps)) {
                        sps->sps_npmode = NPMODE_DROP;
                }
                putnext(ppa->ppa_ctl->sps_rq, mpnew);
                return (NULL);  /* don't queue it */
        }
        /*
         * If so, then try to send it down. The lower queue is only ever
         * detached while holding an exclusive lock on the whole driver,
         * so we can be confident that the lower queue is still there.
         */
        if (bcanputnext(ppa->ppa_lower_wq, mp->b_band)) {
                mutex_enter(&ppa->ppa_sta_lock);
                ppa->ppa_stats.p.ppp_opackets++;
                if (IS_SPS_CONTROL(sps)) {
                        ppa->ppa_opkt_ctl++;
                }
                ppa->ppa_stats.p.ppp_obytes += msize;
                mutex_exit(&ppa->ppa_sta_lock);
                return (ppa->ppa_lower_wq);     /* don't queue it */
        }
        return (NULL);  /* queue it for later */
}

/*
 * sppp_lwsrv()
 *
 * MT-Perimeters:
 *    exclusive inner, shared outer.
 *
 * Description:
 *    Lower write-side service procedure. No messages are ever placed on
 *    the write queue here, this just back-enables all upper write side
 *    service procedures.
 */
int
sppp_lwsrv(queue_t *q)
{
        sppa_t          *ppa;
        spppstr_t       *nextsib;

        ASSERT(q != NULL && q->q_ptr != NULL);
        ppa = (sppa_t *)q->q_ptr;
        ASSERT(ppa != NULL);

        rw_enter(&ppa->ppa_sib_lock, RW_READER);
        if ((nextsib = ppa->ppa_ctl) != NULL &&
            WR(nextsib->sps_rq)->q_first != NULL)
                qenable(WR(nextsib->sps_rq));
        for (nextsib = ppa->ppa_streams; nextsib != NULL;
            nextsib = nextsib->sps_nextsib) {
                if (WR(nextsib->sps_rq)->q_first != NULL) {
                        qenable(WR(nextsib->sps_rq));
                }
        }
        rw_exit(&ppa->ppa_sib_lock);
        return (0);
}

/*
 * sppp_lrput()
 *
 * MT-Perimeters:
 *    shared inner, shared outer.
 *
 * Description:
 *    Lower read-side put procedure. Messages from below get here.
 *    Data messages are handled separately to limit stack usage
 *    going into IP.
 *
 *    Note that during I_UNLINK processing, it's possible for a downstream
 *    message to enable upstream data (due to pass_wput() removing the
 *    SQ_BLOCKED flag), and thus we must protect against a NULL sppa pointer.
 *    In this case, the only thing above us is passthru, and we might as well
 *    discard.
 */
int
sppp_lrput(queue_t *q, mblk_t *mp)
{
        sppa_t          *ppa;
        spppstr_t       *sps;

        if ((ppa = q->q_ptr) == NULL) {
                freemsg(mp);
                return (0);
        }

        sps = ppa->ppa_ctl;

        if (MTYPE(mp) != M_DATA) {
                sppp_recv_nondata(q, mp, sps);
        } else if (sps == NULL) {
                freemsg(mp);
        } else if ((q = sppp_recv(q, &mp, sps)) != NULL) {
                putnext(q, mp);
        }
        return (0);
}

/*
 * sppp_lrsrv()
 *
 * MT-Perimeters:
 *    exclusive inner, shared outer.
 *
 * Description:
 *    Lower read-side service procedure.  This is run once after the I_LINK
 *    occurs in order to clean up any packets that came in while we were
 *    transferring in the lower stream.  Otherwise, it's not used.
 */
int
sppp_lrsrv(queue_t *q)
{
        mblk_t *mp;

        while ((mp = getq(q)) != NULL)
                (void) sppp_lrput(q, mp);
        return (0);
}

/*
 * sppp_recv_nondata()
 *
 * MT-Perimeters:
 *    shared inner, shared outer.
 *
 * Description:
 *    All received non-data messages come through here.
 */
static void
sppp_recv_nondata(queue_t *q, mblk_t *mp, spppstr_t *ctlsps)
{
        sppa_t          *ppa;
        spppstr_t       *destsps;
        struct iocblk   *iop;

        ppa = (sppa_t *)q->q_ptr;
        ctlsps = ppa->ppa_ctl;

        switch (MTYPE(mp)) {
        case M_CTL:
                mutex_enter(&ppa->ppa_sta_lock);
                if (*mp->b_rptr == PPPCTL_IERROR) {
                        ppa->ppa_stats.p.ppp_ierrors++;
                        ppa->ppa_ierr_low++;
                        ppa->ppa_mctlsknown++;
                } else if (*mp->b_rptr == PPPCTL_OERROR) {
                        ppa->ppa_stats.p.ppp_oerrors++;
                        ppa->ppa_oerr_low++;
                        ppa->ppa_mctlsknown++;
                } else {
                        ppa->ppa_mctlsunknown++;
                }
                mutex_exit(&ppa->ppa_sta_lock);
                freemsg(mp);
                break;
        case M_IOCTL:
                miocnak(q, mp, 0, EINVAL);
                break;
        case M_IOCACK:
        case M_IOCNAK:
                iop = (struct iocblk *)mp->b_rptr;
                ASSERT(iop != NULL);
                /*
                 * Attempt to match up the response with the stream that the
                 * request came from. If ioc_id doesn't match the one that we
                 * recorded, then discard this message.
                 */
                rw_enter(&ppa->ppa_sib_lock, RW_READER);
                if ((destsps = ctlsps) == NULL ||
                    destsps->sps_ioc_id != iop->ioc_id) {
                        destsps = ppa->ppa_streams;
                        while (destsps != NULL) {
                                if (destsps->sps_ioc_id == iop->ioc_id) {
                                        break;  /* found the upper stream */
                                }
                                destsps = destsps->sps_nextsib;
                        }
                }
                rw_exit(&ppa->ppa_sib_lock);
                if (destsps == NULL) {
                        mutex_enter(&ppa->ppa_sta_lock);
                        ppa->ppa_ioctlsfwderr++;
                        mutex_exit(&ppa->ppa_sta_lock);
                        freemsg(mp);
                        break;
                }
                mutex_enter(&ppa->ppa_sta_lock);
                ppa->ppa_ioctlsfwdok++;

                /*
                 * Clear SPS_IOCQ and enable the lower write side queue,
                 * this would allow the upper stream service routine
                 * to start processing the queue for pending messages.
                 * sppp_lwsrv -> sppp_uwsrv.
                 */
                destsps->sps_flags &= ~SPS_IOCQ;
                mutex_exit(&ppa->ppa_sta_lock);
                qenable(WR(destsps->sps_rq));

                putnext(destsps->sps_rq, mp);
                break;
        case M_HANGUP:
                /*
                 * Free the original mblk_t. We don't really want to send
                 * a M_HANGUP message upstream, so we need to translate this
                 * message into something else.
                 */
                freemsg(mp);
                if (ctlsps == NULL)
                        break;
                mp = create_lsmsg(PPP_LINKSTAT_HANGUP);
                if (mp == NULL) {
                        mutex_enter(&ppa->ppa_sta_lock);
                        ppa->ppa_allocbfail++;
                        mutex_exit(&ppa->ppa_sta_lock);
                        break;
                }
                mutex_enter(&ppa->ppa_sta_lock);
                ppa->ppa_lsdown++;
                mutex_exit(&ppa->ppa_sta_lock);
                putnext(ctlsps->sps_rq, mp);
                break;
        case M_FLUSH:
                if (*mp->b_rptr & FLUSHR) {
                        flushq(q, FLUSHDATA);
                }
                if (*mp->b_rptr & FLUSHW) {
                        *mp->b_rptr &= ~FLUSHR;
                        qreply(q, mp);
                } else {
                        freemsg(mp);
                }
                break;
        default:
                if (ctlsps != NULL &&
                    (queclass(mp) == QPCTL) || canputnext(ctlsps->sps_rq)) {
                        putnext(ctlsps->sps_rq, mp);
                } else {
                        mutex_enter(&ppa->ppa_sta_lock);
                        ppa->ppa_iqdropped++;
                        mutex_exit(&ppa->ppa_sta_lock);
                        freemsg(mp);
                }
                break;
        }
}

/*
 * sppp_recv()
 *
 * MT-Perimeters:
 *    shared inner, shared outer.
 *
 * Description:
 *    Receive function called by sppp_lrput.  Finds appropriate
 *    receive stream and does accounting.
 */
static queue_t *
sppp_recv(queue_t *q, mblk_t **mpp, spppstr_t *ctlsps)
{
        mblk_t          *mp;
        int             len;
        sppa_t          *ppa;
        spppstr_t       *destsps;
        mblk_t          *zmp;
        uint32_t        npflagpos;

        ASSERT(mpp != NULL);
        mp = *mpp;
        ASSERT(q != NULL && q->q_ptr != NULL);
        ASSERT(mp != NULL && mp->b_rptr != NULL);
        ASSERT(ctlsps != NULL);
        ASSERT(IS_SPS_CONTROL(ctlsps));
        ppa = ctlsps->sps_ppa;
        ASSERT(ppa != NULL && ppa->ppa_ctl != NULL);

        len = msgdsize(mp);
        mutex_enter(&ppa->ppa_sta_lock);
        ppa->ppa_stats.p.ppp_ibytes += len;
        mutex_exit(&ppa->ppa_sta_lock);
        /*
         * If the entire data size of the mblk is less than the length of the
         * PPP header, then free it. We can't do much with such message anyway,
         * since we can't really determine what the PPP protocol type is.
         */
        if (len < PPP_HDRLEN) {
                /* Log, and free it */
                mutex_enter(&ppa->ppa_sta_lock);
                ppa->ppa_irunts++;
                mutex_exit(&ppa->ppa_sta_lock);
                freemsg(mp);
                return (NULL);
        } else if (len > (ppa->ppa_mru + PPP_HDRLEN)) {
                /* Log, and accept it anyway */
                mutex_enter(&ppa->ppa_sta_lock);
                ppa->ppa_itoolongs++;
                mutex_exit(&ppa->ppa_sta_lock);
        }
        /*
         * We need at least be able to read the PPP protocol from the header,
         * so if the first message block is too small, then we concatenate the
         * rest of the following blocks into one message.
         */
        if (MBLKL(mp) < PPP_HDRLEN) {
                zmp = msgpullup(mp, PPP_HDRLEN);
                freemsg(mp);
                mp = zmp;
                if (mp == NULL) {
                        mutex_enter(&ppa->ppa_sta_lock);
                        ppa->ppa_allocbfail++;
                        mutex_exit(&ppa->ppa_sta_lock);
                        return (NULL);
                }
                *mpp = mp;
        }
        /*
         * Hold this packet in the control-queue until
         * the matching network-layer upper stream for the PPP protocol (sap)
         * has not been plumbed and configured
         */
        npflagpos = sppp_ppp2np(PPP_PROTOCOL(mp->b_rptr));
        mutex_enter(&ppa->ppa_npmutex);
        if (npflagpos != 0 && (ppa->ppa_npflag & (1 << npflagpos))) {
                /*
                 * proto is currently blocked; Hold up to 4 packets
                 * in the kernel.
                 */
                if (ppa->ppa_holdpkts[npflagpos] > 3 ||
                    putq(ctlsps->sps_rq, mp) == 0)
                        freemsg(mp);
                else
                        ppa->ppa_holdpkts[npflagpos]++;
                mutex_exit(&ppa->ppa_npmutex);
                return (NULL);
        }
        mutex_exit(&ppa->ppa_npmutex);
        /*
         * Try to find a matching network-layer upper stream for the specified
         * PPP protocol (sap), and if none is found, send this frame up the
         * control stream.
         */
        destsps = sppp_inpkt(q, mp, ctlsps);
        if (destsps == NULL) {
                mutex_enter(&ppa->ppa_sta_lock);
                ppa->ppa_ipkt_ctl++;
                mutex_exit(&ppa->ppa_sta_lock);
                if (canputnext(ctlsps->sps_rq)) {
                        if (IS_SPS_KDEBUG(ctlsps)) {
                                SPDEBUG(PPP_DRV_NAME
                                    "/%d: M_DATA recv (%d bytes) sps=0x%p "
                                    "flags=0x%b ppa=0x%p flags=0x%b\n",
                                    ctlsps->sps_mn_id, len, (void *)ctlsps,
                                    ctlsps->sps_flags, SPS_FLAGS_STR,
                                    (void *)ppa, ppa->ppa_flags,
                                    PPA_FLAGS_STR);
                        }
                        return (ctlsps->sps_rq);
                } else {
                        mutex_enter(&ppa->ppa_sta_lock);
                        ppa->ppa_iqdropped++;
                        mutex_exit(&ppa->ppa_sta_lock);
                        freemsg(mp);
                        return (NULL);
                }
        }
        if (canputnext(destsps->sps_rq)) {
                if (IS_SPS_KDEBUG(destsps)) {
                        SPDEBUG(PPP_DRV_NAME
                            "/%d: M_DATA recv (%d bytes) sps=0x%p flags=0x%b "
                            "ppa=0x%p flags=0x%b\n", destsps->sps_mn_id, len,
                            (void *)destsps, destsps->sps_flags,
                            SPS_FLAGS_STR, (void *)ppa, ppa->ppa_flags,
                            PPA_FLAGS_STR);
                }
                /*
                 * If fastpath is enabled on the network-layer stream, then
                 * make sure we skip over the PPP header, otherwise, we wrap
                 * the message in a DLPI message.
                 */
                if (IS_SPS_FASTPATH(destsps)) {
                        mp->b_rptr += PPP_HDRLEN;
                        return (destsps->sps_rq);
                } else {
                        spppstr_t *uqs = (spppstr_t *)destsps->sps_rq->q_ptr;
                        ASSERT(uqs != NULL);
                        mp->b_rptr += PPP_HDRLEN;
                        mp = sppp_dladdud(uqs, mp, uqs->sps_sap, B_FALSE);
                        if (mp != NULL) {
                                *mpp = mp;
                                return (destsps->sps_rq);
                        } else {
                                mutex_enter(&ppa->ppa_sta_lock);
                                ppa->ppa_allocbfail++;
                                mutex_exit(&ppa->ppa_sta_lock);
                                /* mp already freed by sppp_dladdud */
                                return (NULL);
                        }
                }
        } else {
                mutex_enter(&ppa->ppa_sta_lock);
                ppa->ppa_iqdropped++;
                mutex_exit(&ppa->ppa_sta_lock);
                freemsg(mp);
                return (NULL);
        }
}

/*
 * sppp_inpkt()
 *
 * MT-Perimeters:
 *    shared inner, shared outer.
 *
 * Description:
 *    Find the destination upper stream for the received packet, called
 *    from sppp_recv.
 *
 * Returns:
 *    ptr to destination upper network stream, or NULL for control stream.
 */
/* ARGSUSED */
static spppstr_t *
sppp_inpkt(queue_t *q, mblk_t *mp, spppstr_t *ctlsps)
{
        spppstr_t       *destsps = NULL;
        sppa_t          *ppa;
        uint16_t        proto;
        int             is_promisc;

        ASSERT(q != NULL && q->q_ptr != NULL);
        ASSERT(mp != NULL && mp->b_rptr != NULL);
        ASSERT(IS_SPS_CONTROL(ctlsps));
        ppa = ctlsps->sps_ppa;
        ASSERT(ppa != NULL);
        /*
         * From RFC 1661 (Section 2):
         *
         * The Protocol field is one or two octets, and its value identifies
         * the datagram encapsulated in the Information field of the packet.
         * The field is transmitted and received most significant octet first.
         *
         * The structure of this field is consistent with the ISO 3309
         * extension mechanism for address fields.  All Protocols MUST be odd;
         * the least significant bit of the least significant octet MUST equal
         * "1".  Also, all Protocols MUST be assigned such that the least
         * significant bit of the most significant octet equals "0". Frames
         * received which don't comply with these rules MUST be treated as
         * having an unrecognized Protocol.
         *
         * Protocol field values in the "0***" to "3***" range identify the
         * network-layer protocol of specific packets, and values in the
         * "8***" to "b***" range identify packets belonging to the associated
         * Network Control Protocols (NCPs), if any.
         *
         * Protocol field values in the "4***" to "7***" range are used for
         * protocols with low volume traffic which have no associated NCP.
         * Protocol field values in the "c***" to "f***" range identify packets
         * as link-layer Control Protocols (such as LCP).
         */
        proto = PPP_PROTOCOL(mp->b_rptr);
        mutex_enter(&ppa->ppa_sta_lock);
        ppa->ppa_stats.p.ppp_ipackets++;
        mutex_exit(&ppa->ppa_sta_lock);
        /*
         * We check if this is not a network-layer protocol, and if so,
         * then send this packet up the control stream.
         */
        if (proto > 0x7fff) {
                goto inpkt_done;        /* send it up the control stream */
        }
        /*
         * Try to grab the destination upper stream from the network-layer
         * stream cache for this ppa for PPP_IP (0x0021) or PPP_IPV6 (0x0057)
         * protocol types. Otherwise, if the type is not known to the cache,
         * or if its sap can't be matched with any of the upper streams, then
         * send this packet up the control stream so that it can be rejected.
         */
        if (proto == PPP_IP) {
                destsps = ppa->ppa_ip_cache;
        } else if (proto == PPP_IPV6) {
                destsps = ppa->ppa_ip6_cache;
        }
        /*
         * Toss this one away up the control stream if there's no matching sap;
         * this way the protocol can be rejected (destsps is NULL).
         */

inpkt_done:
        /*
         * Only time-stamp the packet with hrtime if the upper stream
         * is configured to do so.  PPP control (negotiation) messages
         * are never considered link activity; only data is activity.
         */
        if (destsps != NULL && IS_PPA_TIMESTAMP(ppa)) {
                ppa->ppa_lastrx = gethrtime();
        }
        /*
         * Should there be any promiscuous stream(s), send the data up for
         * each promiscuous stream that we recognize. We skip the control
         * stream as we obviously never allow the control stream to become
         * promiscous and bind to PPP_ALLSAP.
         */
        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, proto, B_TRUE);
        }
        rw_exit(&ppa->ppa_sib_lock);
        return (destsps);
}

/*
 * sppp_kstat_update()
 *
 * Description:
 *    Update per-ppa kstat interface statistics.
 */
static int
sppp_kstat_update(kstat_t *ksp, int rw)
{
        register sppa_t         *ppa;
        register sppp_kstats_t  *pppkp;
        register struct pppstat64 *sp;

        if (rw == KSTAT_WRITE) {
                return (EACCES);
        }

        ppa = (sppa_t *)ksp->ks_private;
        ASSERT(ppa != NULL);

        pppkp = (sppp_kstats_t *)ksp->ks_data;
        sp = &ppa->ppa_stats.p;

        mutex_enter(&ppa->ppa_sta_lock);
        pppkp->allocbfail.value.ui32    = ppa->ppa_allocbfail;
        pppkp->mctlsfwd.value.ui32      = ppa->ppa_mctlsfwd;
        pppkp->mctlsfwderr.value.ui32   = ppa->ppa_mctlsfwderr;
        pppkp->rbytes.value.ui32        = sp->ppp_ibytes;
        pppkp->rbytes64.value.ui64      = sp->ppp_ibytes;
        pppkp->ierrors.value.ui32       = sp->ppp_ierrors;
        pppkp->ierrors_lower.value.ui32 = ppa->ppa_ierr_low;
        pppkp->ioctlsfwd.value.ui32     = ppa->ppa_ioctlsfwd;
        pppkp->ioctlsfwdok.value.ui32   = ppa->ppa_ioctlsfwdok;
        pppkp->ioctlsfwderr.value.ui32  = ppa->ppa_ioctlsfwderr;
        pppkp->ipackets.value.ui32      = sp->ppp_ipackets;
        pppkp->ipackets64.value.ui64    = sp->ppp_ipackets;
        pppkp->ipackets_ctl.value.ui32  = ppa->ppa_ipkt_ctl;
        pppkp->iqdropped.value.ui32     = ppa->ppa_iqdropped;
        pppkp->irunts.value.ui32        = ppa->ppa_irunts;
        pppkp->itoolongs.value.ui32     = ppa->ppa_itoolongs;
        pppkp->lsneedup.value.ui32      = ppa->ppa_lsneedup;
        pppkp->lsdown.value.ui32        = ppa->ppa_lsdown;
        pppkp->mctlsknown.value.ui32    = ppa->ppa_mctlsknown;
        pppkp->mctlsunknown.value.ui32  = ppa->ppa_mctlsunknown;
        pppkp->obytes.value.ui32        = sp->ppp_obytes;
        pppkp->obytes64.value.ui64      = sp->ppp_obytes;
        pppkp->oerrors.value.ui32       = sp->ppp_oerrors;
        pppkp->oerrors_lower.value.ui32 = ppa->ppa_oerr_low;
        pppkp->opackets.value.ui32      = sp->ppp_opackets;
        pppkp->opackets64.value.ui64    = sp->ppp_opackets;
        pppkp->opackets_ctl.value.ui32  = ppa->ppa_opkt_ctl;
        pppkp->oqdropped.value.ui32     = ppa->ppa_oqdropped;
        pppkp->otoolongs.value.ui32     = ppa->ppa_otoolongs;
        pppkp->orunts.value.ui32        = ppa->ppa_orunts;
        mutex_exit(&ppa->ppa_sta_lock);

        return (0);
}

/*
 * Turn off proto in ppa_npflag to indicate that
 * the corresponding network protocol has been plumbed.
 * Release proto packets that were being held in the control
 * queue in anticipation of this event.
 */
static void
sppp_release_pkts(sppa_t *ppa, uint16_t proto)
{
        uint32_t npflagpos = sppp_ppp2np(proto);
        int count;
        mblk_t *mp;
        uint16_t mp_proto;
        queue_t *q;
        spppstr_t *destsps;

        ASSERT(ppa != NULL);

        if (npflagpos == 0 || (ppa->ppa_npflag & (1 << npflagpos)) == 0)
                return;

        mutex_enter(&ppa->ppa_npmutex);
        ppa->ppa_npflag &= ~(1 << npflagpos);
        count = ppa->ppa_holdpkts[npflagpos];
        ppa->ppa_holdpkts[npflagpos] = 0;
        mutex_exit(&ppa->ppa_npmutex);

        q = ppa->ppa_ctl->sps_rq;

        while (count > 0) {
                mp = getq(q);
                ASSERT(mp != NULL);

                mp_proto = PPP_PROTOCOL(mp->b_rptr);
                if (mp_proto !=  proto) {
                        (void) putq(q, mp);
                        continue;
                }
                count--;
                destsps = NULL;
                if (mp_proto == PPP_IP) {
                        destsps = ppa->ppa_ip_cache;
                } else if (mp_proto == PPP_IPV6) {
                        destsps = ppa->ppa_ip6_cache;
                }
                ASSERT(destsps != NULL);

                if (IS_SPS_FASTPATH(destsps)) {
                        mp->b_rptr += PPP_HDRLEN;
                } else {
                        spppstr_t *uqs = (spppstr_t *)destsps->sps_rq->q_ptr;
                        ASSERT(uqs != NULL);
                        mp->b_rptr += PPP_HDRLEN;
                        mp = sppp_dladdud(uqs, mp, uqs->sps_sap, B_FALSE);
                        if (mp == NULL) {
                                mutex_enter(&ppa->ppa_sta_lock);
                                ppa->ppa_allocbfail++;
                                mutex_exit(&ppa->ppa_sta_lock);
                                /* mp already freed by sppp_dladdud */
                                continue;
                        }
                }

                if (canputnext(destsps->sps_rq)) {
                        putnext(destsps->sps_rq, mp);
                } else {
                        mutex_enter(&ppa->ppa_sta_lock);
                        ppa->ppa_iqdropped++;
                        mutex_exit(&ppa->ppa_sta_lock);
                        freemsg(mp);
                        continue;
                }
        }
}