Bug fixes to flow control code; put correct dlpi header on data
[ppp.git] / svr4 / ppp.c
index 4b6665819e730173904228dbc81db320d4a88806..497a414c2a16c620893b6b4c5d823ea0dd38fd1d 100644 (file)
@@ -1,3 +1,36 @@
+/*
+ * ppp.c - STREAMS multiplexing pseudo-device driver for PPP.
+ *
+ * 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 HAVE 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.
+ *
+ * $Id: ppp.c,v 1.5 1995/06/23 01:38:49 paulus Exp $
+ */
+
+/*
+ * This file is used under Solaris 2.
+ */
+
 #include <sys/types.h>
 #include <sys/param.h>
 #include <sys/stat.h>
 #include <sys/stropts.h>
 #include <sys/errno.h>
 #include <sys/cmn_err.h>
-#include <sys/modctl.h>
 #include <sys/conf.h>
-#include <sys/ddi.h>
-#include <sys/sunddi.h>
 #include <sys/dlpi.h>
 #include <sys/ioccom.h>
+#include <sys/kmem.h>
+#include <sys/ddi.h>
+#ifdef sun
+#include <sys/modctl.h>
+#include <sys/kstat.h>
+#include <sys/sunddi.h>
+#endif
 #include <net/ppp_defs.h>
 #include <net/pppio.h>
 
 #endif
 
 #ifndef PPP_MAXMTU
-#define PPP_MAXMTU     65536
+#define PPP_MAXMTU     65535
 #endif
 
 /*
  * Private information; one per upper stream.
  */
-struct upperstr {
+typedef struct upperstr {
     minor_t mn;                        /* minor device number */
+    struct upperstr *nextmn;   /* next minor device */
     queue_t *q;                        /* read q associated with this upper stream */
     int flags;                 /* flag bits, see below */
     int state;                 /* current DLPI state */
     int sap;                   /* service access point */
+    int req_sap;               /* which SAP the DLPI client requested */
     struct upperstr *ppa;      /* control stream for our ppa */
     struct upperstr *next;     /* next stream for this ppa */
+    uint ioc_id;               /* last ioctl ID for this stream */
     /*
      * There is exactly one control stream for each PPA.
      * The following fields are only used for control streams.
@@ -49,34 +89,49 @@ struct upperstr {
     int ppa_id;
     queue_t *lowerq;           /* write queue attached below this PPA */
     struct upperstr *nextppa;  /* next control stream */
-};
+    int mru;
+    int mtu;
+    struct pppstat stats;      /* statistics */
+#ifdef sun
+    kstat_t *kstats;           /* stats for netstat */
+#endif
+} upperstr_t;
 
 /* Values for flags */
 #define US_PRIV                1       /* stream was opened by superuser */
 #define US_CONTROL     2       /* stream is a control stream */
-#define US_BLOCKED     4       /* flow ctrl has blocked lower stream */
+#define US_BLOCKED     4       /* flow ctrl has blocked lower write stream */
+#define US_LASTMOD     8       /* no PPP modules below us */
 
-static void *upper_states;
-static struct upperstr *ppas;
+static upperstr_t *minor_devs = NULL;
+static upperstr_t *ppas = NULL;
 
+#ifdef sun
 static int ppp_identify __P((dev_info_t *));
 static int ppp_attach __P((dev_info_t *, ddi_attach_cmd_t));
 static int ppp_detach __P((dev_info_t *, ddi_detach_cmd_t));
 static int ppp_devinfo __P((dev_info_t *, ddi_info_cmd_t, void *, void **));
+#endif
 static int pppopen __P((queue_t *, dev_t *, int, int, cred_t *));
 static int pppclose __P((queue_t *, int, cred_t *));
 static int pppuwput __P((queue_t *, mblk_t *));
 static int pppursrv __P((queue_t *));
 static int pppuwsrv __P((queue_t *));
 static int ppplrput __P((queue_t *, mblk_t *));
+static int ppplwput __P((queue_t *, mblk_t *));
 static int ppplrsrv __P((queue_t *));
 static int ppplwsrv __P((queue_t *));
-static void dlpi_request __P((queue_t *, mblk_t *, struct upperstr *));
+static void dlpi_request __P((queue_t *, mblk_t *, upperstr_t *));
 static void dlpi_error __P((queue_t *, int, int, int));
 static void dlpi_ok __P((queue_t *, int));
-static int send_data __P((mblk_t *, struct upperstr *));
+static int send_data __P((mblk_t *, upperstr_t *));
 static void new_ppa __P((queue_t *, mblk_t *));
-static struct upperstr *find_dest __P((struct upperstr *, int));
+static void attach_ppa __P((queue_t *, mblk_t *));
+static void detach_ppa __P((queue_t *, mblk_t *));
+static void debug_dump __P((queue_t *, mblk_t *));
+static upperstr_t *find_dest __P((upperstr_t *, int));
+static int putctl2 __P((queue_t *, int, int, int));
+static int putctl4 __P((queue_t *, int, int, int));
 
 static struct module_info ppp_info = {
     0xb1a6, "ppp", 0, 512, 512, 128
@@ -95,7 +150,7 @@ static struct qinit ppplrint = {
 };
 
 static struct qinit ppplwint = {
-    NULL, ppplwsrv, NULL, NULL, NULL, &ppp_info, NULL
+    ppplwput, ppplwsrv, NULL, NULL, NULL, &ppp_info, NULL
 };
 
 static struct streamtab pppinfo = {
@@ -103,6 +158,7 @@ static struct streamtab pppinfo = {
     &ppplrint, &ppplwint
 };
 
+#ifdef sun
 static dev_info_t *ppp_dip;
 
 static struct cb_ops cb_ppp_ops = {
@@ -146,28 +202,13 @@ static struct modlinkage modlinkage = {
 int
 _init(void)
 {
-    int error;
-
-    error = ddi_soft_state_init(&upper_states, sizeof(struct upperstr), 4);
-    if (!error) {
-       error = mod_install(&modlinkage);
-       if (!error)
-           return 0;
-       ddi_soft_state_fini(&upper_states);
-    }
-    return error;
+    return mod_install(&modlinkage);
 }
 
 int
 _fini(void)
 {
-    int error;
-
-    error = mod_remove(&modlinkage);
-    if (error)
-       return error;
-    ddi_soft_state_fini(&upper_states);
-    return 0;
+    return mod_remove(&modlinkage);
 }
 
 int
@@ -235,6 +276,14 @@ ppp_devinfo(dip, cmd, arg, result)
     }
     return error;
 }
+#endif /* sun */
+
+#ifndef sun
+# define qprocson(q)
+# define qprocsoff(q)
+# define canputnext(q) canput((q)->q_next)
+# define qwriter(q, mp, func, scope)   (func)((q), (mp))
+#endif
 
 static int
 pppopen(q, devp, oflag, sflag, credp)
@@ -243,37 +292,50 @@ pppopen(q, devp, oflag, sflag, credp)
     int oflag, sflag;
     cred_t *credp;
 {
-    struct upperstr *up;
+    upperstr_t *up;
+    upperstr_t **prevp;
     minor_t mn;
 
     if (q->q_ptr)
        return 0;               /* device is already open */
 
     if (sflag == CLONEOPEN) {
-       for (mn = 0; ddi_get_soft_state(upper_states, mn) != NULL; ++mn)
-           ;
+       mn = 0;
+       for (prevp = &minor_devs; (up = *prevp) != 0; prevp = &up->nextmn) {
+           if (up->mn != mn)
+               break;
+           ++mn;
+       }
     } else {
        mn = getminor(*devp);
+       for (prevp = &minor_devs; (up = *prevp) != 0; prevp = &up->nextmn) {
+           if (up->mn >= mn)
+               break;
+       }
+       if (up->mn == mn) {
+           /* this can't happen */
+           q->q_ptr = WR(q)->q_ptr = up;
+           return 0;
+       }
     }
-    cmn_err(CE_CONT, "pppopen: minor %d\n", mn);
 
     /*
      * Construct a new minor node.
      */
-    if (ddi_soft_state_zalloc(upper_states, mn) != DDI_SUCCESS)
+    up = (upperstr_t *) kmem_zalloc(sizeof(upperstr_t), KM_SLEEP);
+    if (up == 0) {
+       cmn_err(CE_CONT, "pppopen: out of kernel memory\n");
        return ENXIO;
-    up = ddi_get_soft_state(upper_states, mn);
+    }
+    up->nextmn = *prevp;
+    *prevp = up;
+    up->mn = mn;
     *devp = makedevice(getmajor(*devp), mn);
     up->q = q;
-    up->mn = mn;
-    up->flags = 0;
     if (drv_priv(credp) == 0)
        up->flags |= US_PRIV;
     up->state = DL_UNATTACHED;
     up->sap = -1;
-    up->ppa = 0;
-    up->next = 0;
-    up->lowerq = 0;
     q->q_ptr = up;
     WR(q)->q_ptr = up;
     noenable(WR(q));
@@ -288,14 +350,15 @@ pppclose(q, flag, credp)
     int flag;
     cred_t *credp;
 {
-    struct upperstr *up, **upp;
-    struct upperstr *as, *asnext;
-    struct lowerstr *ls;
+    upperstr_t *up, **upp;
+    upperstr_t *as, *asnext;
+    upperstr_t **prevp;
 
     qprocsoff(q);
 
-    up = (struct upperstr *) q->q_ptr;
-    cmn_err(CE_CONT, "pppclose: minor %d\n", up->mn);
+    up = (upperstr_t *) q->q_ptr;
+    if (up == 0)
+       return 0;
     if (up->flags & US_CONTROL) {
        /*
         * This stream represents a PPA:
@@ -332,9 +395,21 @@ pppclose(q, flag, credp)
        }
     }
 
+#ifdef sun
+    if (up->kstats)
+       kstat_delete(up->kstats);
+#endif
+
     q->q_ptr = NULL;
     WR(q)->q_ptr = NULL;
-    ddi_soft_state_free(upper_states, up->mn);
+
+    for (prevp = &minor_devs; *prevp != 0; prevp = &(*prevp)->nextmn) {
+       if (*prevp == up) {
+           *prevp = up->nextmn;
+           break;
+       }
+    }
+    kmem_free(up, sizeof(upperstr_t));
 
     return 0;
 }
@@ -350,14 +425,14 @@ pppuwput(q, mp)
     queue_t *q;
     mblk_t *mp;
 {
-    struct upperstr *us, *usnext;
+    upperstr_t *us, *usnext, *ppa;
     struct iocblk *iop;
     struct linkblk *lb;
     queue_t *lq;
-    int error;
+    int error, n;
     mblk_t *mq;
 
-    us = (struct upperstr *) q->q_ptr;
+    us = (upperstr_t *) q->q_ptr;
     switch (mp->b_datap->db_type) {
     case M_PCPROTO:
     case M_PROTO:
@@ -365,56 +440,70 @@ pppuwput(q, mp)
        break;
 
     case M_DATA:
+       if ((us->flags & US_CONTROL) == 0
+           || msgdsize(mp) > us->mtu + PPP_HDRLEN) {
+#if DEBUG
+           cmn_err(CE_CONT, "pppuwput: junk data len=%d\n", msgdsize(mp));
+#endif
+           freemsg(mp);
+           break;
+       }
        if (!send_data(mp, us))
            putq(q, mp);
        break;
 
     case M_IOCTL:
        iop = (struct iocblk *) mp->b_rptr;
-       cmn_err(CE_CONT, "ppp%d ioctl %x count=%d\n", us->mn, iop->ioc_cmd,
-               iop->ioc_count);
+       error = EINVAL;
        switch (iop->ioc_cmd) {
        case I_LINK:
            if ((us->flags & US_CONTROL) == 0 || us->lowerq != 0)
-               goto iocnak;
+               break;
            lb = (struct linkblk *) mp->b_cont->b_rptr;
            us->lowerq = lq = lb->l_qbot;
            lq->q_ptr = us;
-           WR(lq)->q_ptr = us;
+           RD(lq)->q_ptr = us;
+           noenable(RD(lq));
            iop->ioc_count = 0;
-           mp->b_datap->db_type = M_IOCACK;
-           qreply(q, mp);
+           error = 0;
+           us->flags &= ~US_LASTMOD;
            /* Unblock upper streams which now feed this lower stream. */
            qenable(lq);
+           /* Send useful information down to the modules which
+              are now linked below us. */
+           putctl2(lq, M_CTL, PPPCTL_UNIT, us->ppa_id);
+           putctl4(lq, M_CTL, PPPCTL_MRU, us->mru);
+           putctl4(lq, M_CTL, PPPCTL_MTU, us->mtu);
            break;
 
        case I_UNLINK:
            lb = (struct linkblk *) mp->b_cont->b_rptr;
+#if DEBUG
            if (us->lowerq != lb->l_qbot)
                cmn_err(CE_CONT, "ppp unlink: lowerq=%x qbot=%x\n",
                        us->lowerq, lb->l_qbot);
+#endif
            us->lowerq = 0;
            iop->ioc_count = 0;
-           mp->b_datap->db_type = M_IOCACK;
-           qreply(q, mp);
+           error = 0;
            /* Unblock streams which now feed back up the control stream. */
            qenable(us->q);
            break;
 
        case PPPIO_NEWPPA:
            if (us->flags & US_CONTROL)
-               goto iocnak;
+               break;
            if ((us->flags & US_PRIV) == 0) {
-               iop->ioc_error = EPERM;
-               goto iocnak;
+               error = EPERM;
+               break;
            }
            /* Arrange to return an int */
            if ((mq = mp->b_cont) == 0
                || mq->b_datap->db_lim - mq->b_rptr < sizeof(int)) {
                mq = allocb(sizeof(int), BPRI_HI);
                if (mq == 0) {
-                   iop->ioc_error = ENOSR;
-                   goto iocnak;
+                   error = ENOSR;
+                   break;
                }
                if (mp->b_cont != 0)
                    freemsg(mp->b_cont);
@@ -424,28 +513,112 @@ pppuwput(q, mp)
            iop->ioc_count = sizeof(int);
            mq->b_wptr = mq->b_rptr + sizeof(int);
            qwriter(q, mp, new_ppa, PERIM_OUTER);
+           error = -1;
+           break;
+
+       case PPPIO_ATTACH:
+           /* like dlpi_attach, for programs which can't write to
+              the stream (like pppstats) */
+           if (iop->ioc_count != sizeof(int) || us->ppa != 0)
+               break;
+           n = *(int *)mp->b_cont->b_rptr;
+           for (ppa = ppas; ppa != 0; ppa = ppa->nextppa)
+               if (ppa->ppa_id == n)
+                   break;
+           if (ppa == 0)
+               break;
+           us->ppa = ppa;
+           iop->ioc_count = 0;
+           qwriter(q, mp, attach_ppa, PERIM_OUTER);
+           error = -1;
+           break;
+
+       case PPPIO_MRU:
+           if (iop->ioc_count != sizeof(int) || (us->flags & US_CONTROL) == 0)
+               break;
+           n = *(int *)mp->b_cont->b_rptr;
+           if (n <= 0 || n > PPP_MAXMTU)
+               break;
+           if (n < PPP_MRU)
+               n = PPP_MRU;
+           us->mru = n;
+           if (us->lowerq)
+               putctl4(us->lowerq, M_CTL, PPPCTL_MRU, n);
+           error = 0;
+           iop->ioc_count = 0;
+           break;
+
+       case PPPIO_MTU:
+           if (iop->ioc_count != sizeof(int) || (us->flags & US_CONTROL) == 0)
+               break;
+           n = *(int *)mp->b_cont->b_rptr;
+           if (n <= 0 || n > PPP_MAXMTU)
+               break;
+           if (n < PPP_MRU)
+               n = PPP_MRU;
+           us->mtu = n;
+           if (us->lowerq)
+               putctl4(us->lowerq, M_CTL, PPPCTL_MTU, n);
+           error = 0;
+           iop->ioc_count = 0;
+           break;
+
+       case PPPIO_LASTMOD:
+           us->flags |= US_LASTMOD;
+           error = 0;
+           break;
+
+       case PPPIO_DEBUG:
+           if (iop->ioc_count != sizeof(int))
+               break;
+           n = *(int *)mp->b_cont->b_rptr;
+           if (n == PPPDBG_DUMP + PPPDBG_DRIVER) {
+               qwriter(q, NULL, debug_dump, PERIM_OUTER);
+               iop->ioc_count = 0;
+               error = 0;
+           } else {
+               if (us->ppa == 0 || us->ppa->lowerq == 0)
+                   break;
+               putnext(us->ppa->lowerq, mp);
+               error = -1;
+           }
            break;
 
        default:
            if (us->ppa == 0 || us->ppa->lowerq == 0)
-               goto iocnak;
+               break;
+           us->ioc_id = iop->ioc_id;
+           error = -1;
            switch (iop->ioc_cmd) {
            case PPPIO_GETSTAT:
            case PPPIO_GETCSTAT:
+               if (us->flags & US_LASTMOD) {
+                   error = EINVAL;
+                   break;
+               }
+               putnext(us->ppa->lowerq, mp);
                break;
            default:
-               if ((us->flags & US_PRIV) == 0) {
-                   iop->ioc_error = EPERM;
-                   goto iocnak;
+               if (us->flags & US_PRIV)
+                   putnext(us->ppa->lowerq, mp);
+               else {
+#if DEBUG
+                   cmn_err(CE_CONT, "ppp ioctl %x rejected\n", iop->ioc_cmd);
+#endif
+                   error = EPERM;
                }
+               break;
            }
-           putnext(us->ppa->lowerq, mp);
            break;
+       }
 
-       iocnak:
+       if (error > 0) {
+           iop->ioc_error = error;
            mp->b_datap->db_type = M_IOCNAK;
            qreply(q, mp);
-           break;
+       } else if (error == 0) {
+           mp->b_datap->db_type = M_IOCACK;
+           qreply(q, mp);
        }
        break;
 
@@ -470,19 +643,16 @@ static void
 dlpi_request(q, mp, us)
     queue_t *q;
     mblk_t *mp;
-    struct upperstr *us;
+    upperstr_t *us;
 {
     union DL_primitives *d = (union DL_primitives *) mp->b_rptr;
     int size = mp->b_wptr - mp->b_rptr;
     mblk_t *reply, *np;
-    struct upperstr *t, *ppa;
-    int sap;
+    upperstr_t *ppa, *os;
+    int sap, *ip, len;
     dl_info_ack_t *info;
     dl_bind_ack_t *ackp;
-    dl_phys_addr_ack_t *adrp;
 
-    cmn_err(CE_CONT, "ppp%d dlpi prim %x, state=%x\n", us->mn,
-           d->dl_primitive, us->state);
     switch (d->dl_primitive) {
     case DL_INFO_REQ:
        if (size < sizeof(dl_info_req_t))
@@ -497,12 +667,18 @@ dlpi_request(q, mp, us)
        info->dl_max_sdu = PPP_MAXMTU;
        info->dl_min_sdu = 1;
        info->dl_addr_length = sizeof(ulong);
+#ifdef DL_OTHER
        info->dl_mac_type = DL_OTHER;
+#else
+       info->dl_mac_type = DL_HDLC;    /* a lie */
+#endif
        info->dl_current_state = us->state;
-       info->dl_sap_length = sizeof(ulong);
        info->dl_service_mode = DL_CLDLS;
        info->dl_provider_style = DL_STYLE2;
+#if DL_CURRENT_VERSION >= 2
+       info->dl_sap_length = sizeof(ulong);
        info->dl_version = DL_CURRENT_VERSION;
+#endif
        qreply(q, reply);
        break;
 
@@ -521,12 +697,7 @@ dlpi_request(q, mp, us)
            break;
        }
        us->ppa = ppa;
-       us->state = DL_UNBOUND;
-       for (t = ppa; t->next != 0; t = t->next)
-           ;
-       t->next = us;
-       us->next = 0;
-       dlpi_ok(q, DL_ATTACH_REQ);
+       qwriter(q, mp, attach_ppa, PERIM_OUTER);
        break;
 
     case DL_DETACH_REQ:
@@ -536,21 +707,13 @@ dlpi_request(q, mp, us)
            dlpi_error(q, DL_DETACH_REQ, DL_OUTSTATE, 0);
            break;
        }
-       for (t = us->ppa; t->next != 0; t = t->next)
-           if (t->next == us) {
-               t->next = us->next;
-               break;
-           }
-       us->next = 0;
-       us->ppa = 0;
-       us->state = DL_UNATTACHED;
-       dlpi_ok(q, DL_DETACH_REQ);
+       qwriter(q, mp, detach_ppa, PERIM_OUTER);
        break;
 
     case DL_BIND_REQ:
        if (size < sizeof(dl_bind_req_t))
            goto badprim;
-       if (us->state != DL_UNBOUND) {
+       if (us->state != DL_UNBOUND || us->ppa == 0) {
            dlpi_error(q, DL_BIND_REQ, DL_OUTSTATE, 0);
            break;
        }
@@ -558,18 +721,33 @@ dlpi_request(q, mp, us)
            dlpi_error(q, DL_BIND_REQ, DL_UNSUPPORTED, 0);
            break;
        }
-       /* saps must be valid PPP network protocol numbers */
+
+       /* saps must be valid PPP network protocol numbers,
+          except that we accept ETHERTYPE_IP in place of PPP_IP. */
        sap = d->bind_req.dl_sap;
+       us->req_sap = sap;
+#if DEBUG
        cmn_err(CE_CONT, "ppp bind %x\n", sap);
+#endif
        if (sap == ETHERTYPE_IP)
            sap = PPP_IP;
-       if (sap < 0x21 || sap > 0x3fff
-           || (sap & 1) == 0 || (sap & 0x100) != 0) {
+       if (sap < 0x21 || sap > 0x3fff || (sap & 0x101) != 1) {
            dlpi_error(q, DL_BIND_REQ, DL_BADADDR, 0);
            break;
        }
+
+       /* check that no other stream is bound to this sap already. */
+       for (os = us->ppa; os != 0; os = os->next)
+           if (os->sap == sap)
+               break;
+       if (os != 0) {
+           dlpi_error(q, DL_BIND_REQ, DL_NOADDR, 0);
+           break;
+       }
+
        us->sap = sap;
        us->state = DL_IDLE;
+
        if ((reply = allocb(sizeof(dl_bind_ack_t) + sizeof(ulong),
                            BPRI_HI)) == 0)
            break;              /* should do bufcall */
@@ -604,6 +782,17 @@ dlpi_request(q, mp, us)
            dlpi_error(q, DL_UNITDATA_REQ, DL_OUTSTATE, 0);
            break;
        }
+       if (us->ppa == 0) {
+           cmn_err(CE_CONT, "ppp: in state dl_idle but ppa == 0?\n");
+           break;
+       }
+       len = mp->b_cont == 0? 0: msgdsize(mp->b_cont);
+       if (len > us->ppa->mtu) {
+#if DEBUG
+           cmn_err(CE_CONT, "dlpi data too large (%d > %d)\n", len, us->mtu);
+#endif
+           break;
+       }
        /* this assumes PPP_HDRLEN <= sizeof(dl_unitdata_req_t) */
        if (mp->b_datap->db_ref > 1) {
            np = allocb(PPP_HDRLEN, BPRI_HI);
@@ -613,8 +802,10 @@ dlpi_request(q, mp, us)
            mp->b_cont = 0;
            freeb(mp);
            mp = np;
-       }
-       /* XXX should use dl_dest_addr_offset/length here */
+       } else
+           mp->b_datap->db_type = M_DATA;
+       /* XXX should use dl_dest_addr_offset/length here,
+          but we would have to translate ETHERTYPE_IP -> PPP_IP */
        mp->b_wptr = mp->b_rptr + PPP_HDRLEN;
        mp->b_rptr[0] = PPP_ALLSTATIONS;
        mp->b_rptr[1] = PPP_UI;
@@ -624,22 +815,22 @@ dlpi_request(q, mp, us)
            putq(q, mp);
        return;
 
+#if DL_CURRENT_VERSION >= 2
     case DL_SUBS_BIND_REQ:
     case DL_SUBS_UNBIND_REQ:
     case DL_ENABMULTI_REQ:
     case DL_DISABMULTI_REQ:
     case DL_PROMISCON_REQ:
     case DL_PROMISCOFF_REQ:
-    case DL_PHYS_ADDR_REQ:
     case DL_SET_PHYS_ADDR_REQ:
     case DL_XID_REQ:
     case DL_TEST_REQ:
-    case DL_CONNECT_REQ:
-    case DL_TOKEN_REQ:
     case DL_REPLY_UPDATE_REQ:
-    case DL_GET_STATISTICS_REQ:
     case DL_REPLY_REQ:
     case DL_DATA_ACK_REQ:
+#endif
+    case DL_CONNECT_REQ:
+    case DL_TOKEN_REQ:
        dlpi_error(q, d->dl_primitive, DL_NOTSUPPORTED, 0);
        break;
 
@@ -654,11 +845,15 @@ dlpi_request(q, mp, us)
        dlpi_error(q, d->dl_primitive, DL_BADQOSTYPE, 0);
        break;
 
+#if DL_CURRENT_VERSION >= 2
     case DL_TEST_RES:
     case DL_XID_RES:
        break;
+#endif
 
     default:
+       cmn_err(CE_CONT, "ppp: unknown dlpi prim 0x%x\n", d->dl_primitive);
+       /* fall through */
     badprim:
        dlpi_error(q, d->dl_primitive, DL_BADPRIM, 0);
        break;
@@ -709,10 +904,10 @@ dlpi_ok(q, prim)
 static int
 send_data(mp, us)
     mblk_t *mp;
-    struct upperstr *us;
+    upperstr_t *us;
 {
     queue_t *q;
-    struct upperstr *ppa;
+    upperstr_t *ppa;
 
     if (us->flags & US_BLOCKED)
        return 0;
@@ -723,35 +918,45 @@ send_data(mp, us)
     }
     if ((q = ppa->lowerq) == 0) {
        /* try to send it up the control stream */
-       q = ppa->q;
-    }
-    if (canputnext(q)) {
-       putnext(q, mp);
-       return 1;
+       if (canputnext(ppa->q)) {
+           putnext(ppa->q, mp);
+           return 1;
+       }
+    } else {
+       if (canputnext(ppa->lowerq)) {
+           /*
+            * The lower write queue's put procedure just updates counters
+            * and does a putnext.  We call it in order to enter the lower
+            * queues' perimeter so that the counter updates are serialized.
+            */
+           put(ppa->lowerq, mp);
+           return 1;
+       }
     }
     us->flags |= US_BLOCKED;
     return 0;
 }
 
+/*
+ * Allocate a new PPA id and link this stream into the list of PPAs.
+ * This procedure is called with an exclusive lock on all queues in
+ * this driver.
+ */
 static void
 new_ppa(q, mp)
     queue_t *q;
     mblk_t *mp;
 {
-    struct upperstr *us, **usp;
+    upperstr_t *us, **usp;
     int ppa_id;
 
-    /*
-     * Allocate a new PPA id and link this stream into
-     * the list of PPAs.
-     */
     usp = &ppas;
     ppa_id = 0;
     while ((us = *usp) != 0 && ppa_id == us->ppa_id) {
        ++ppa_id;
        usp = &us->nextppa;
     }
-    us = (struct upperstr *) q->q_ptr;
+    us = (upperstr_t *) q->q_ptr;
     us->ppa_id = ppa_id;
     us->ppa = us;
     us->next = 0;
@@ -759,21 +964,88 @@ new_ppa(q, mp)
     *usp = us;
     us->flags |= US_CONTROL;
 
+    us->mtu = PPP_MRU;
+    us->mru = PPP_MRU;
+
+#ifdef sun
+    if (us->kstats == 0) {
+       char unit[32];
+
+       sprintf(unit, "ppp%d", us->ppa->ppa_id);
+       us->kstats = kstat_create("ppp", us->ppa->ppa_id, unit,
+                                 "net", KSTAT_TYPE_NAMED, 4, 0);
+       if (us->kstats != 0) {
+           kstat_named_t *kn = KSTAT_NAMED_PTR(us->kstats);
+
+           strcpy(kn[0].name, "ipackets");
+           kn[0].data_type = KSTAT_DATA_ULONG;
+           strcpy(kn[1].name, "ierrors");
+           kn[1].data_type = KSTAT_DATA_ULONG;
+           strcpy(kn[2].name, "opackets");
+           kn[2].data_type = KSTAT_DATA_ULONG;
+           strcpy(kn[3].name, "oerrors");
+           kn[3].data_type = KSTAT_DATA_ULONG;
+           kstat_install(us->kstats);
+       }
+    }
+#endif
+
     *(int *)mp->b_cont->b_rptr = ppa_id;
     mp->b_datap->db_type = M_IOCACK;
     qreply(q, mp);
 }
 
+static void
+attach_ppa(q, mp)
+    queue_t *q;
+    mblk_t *mp;
+{
+    upperstr_t *us, *t;
+
+    us = (upperstr_t *) q->q_ptr;
+    us->state = DL_UNBOUND;
+    for (t = us->ppa; t->next != 0; t = t->next)
+       ;
+    t->next = us;
+    us->next = 0;
+    if (mp->b_datap->db_type == M_IOCTL) {
+       mp->b_datap->db_type = M_IOCACK;
+       qreply(q, mp);
+    } else {
+       dlpi_ok(q, DL_ATTACH_REQ);
+    }
+}
+
+static void
+detach_ppa(q, mp)
+    queue_t *q;
+    mblk_t *mp;
+{
+    upperstr_t *us, *t;
+
+    us = (upperstr_t *) q->q_ptr;
+    for (t = us->ppa; t->next != 0; t = t->next)
+       if (t->next == us) {
+           t->next = us->next;
+           break;
+       }
+    us->next = 0;
+    us->ppa = 0;
+    us->state = DL_UNATTACHED;
+    dlpi_ok(q, DL_DETACH_REQ);
+}
+
 static int
 pppuwsrv(q)
     queue_t *q;
 {
-    struct upperstr *us;
+    upperstr_t *us;
     struct lowerstr *ls;
     queue_t *lwq;
     mblk_t *mp;
 
-    us = (struct upperstr *) q->q_ptr;
+    us = (upperstr_t *) q->q_ptr;
+    us->flags &= ~US_BLOCKED;
     while ((mp = getq(q)) != 0) {
        if (!send_data(mp, us)) {
            putbq(q, mp);
@@ -783,18 +1055,38 @@ pppuwsrv(q)
     return 0;
 }
 
+static int
+ppplwput(q, mp)
+    queue_t *q;
+    mblk_t *mp;
+{
+    upperstr_t *ppa;
+
+    ppa = q->q_ptr;
+    if (ppa != 0) {            /* why wouldn't it? */
+       ppa->stats.ppp_opackets++;
+       ppa->stats.ppp_obytes += msgdsize(mp);
+#ifdef sun
+       if (ppa->kstats != 0)
+           KSTAT_NAMED_PTR(ppa->kstats)[2].value.ul++;
+#endif
+    }
+    putnext(q, mp);
+    return 0;
+}
+
 static int
 ppplwsrv(q)
     queue_t *q;
 {
-    struct upperstr *us;
+    upperstr_t *us;
 
     /*
      * Flow control has back-enabled this stream:
      * enable the write service procedures of all upper
      * streams feeding this lower stream.
      */
-    for (us = (struct upperstr *) q->q_ptr; us != NULL; us = us->next)
+    for (us = (upperstr_t *) q->q_ptr; us != NULL; us = us->next)
        if (us->flags & US_BLOCKED)
            qenable(WR(us->q));
     return 0;
@@ -804,16 +1096,12 @@ static int
 pppursrv(q)
     queue_t *q;
 {
-    struct upperstr *us, *as;
+    upperstr_t *us, *as;
     mblk_t *mp, *hdr;
     dl_unitdata_ind_t *ud;
     int proto;
 
-    /*
-     * If this is a control stream and we don't have a lower queue attached,
-     * run the write service routines of other streams attached to this PPA.
-     */
-    us = (struct upperstr *) q->q_ptr;
+    us = (upperstr_t *) q->q_ptr;
     if (us->flags & US_CONTROL) {
        /*
         * A control stream.
@@ -832,6 +1120,8 @@ pppursrv(q)
        /*
         * A network protocol stream.  Put a DLPI header on each
         * packet and send it on.
+        * (Actually, it seems that the IP module will happily
+        * accept M_DATA messages without the DL_UNITDATA_IND header.)
         */
        while ((mp = getq(q)) != 0) {
            if (!canputnext(q)) {
@@ -847,6 +1137,7 @@ pppursrv(q)
                freemsg(mp);
                continue;
            }
+           hdr->b_datap->db_type = M_PROTO;
            ud = (dl_unitdata_ind_t *) hdr->b_wptr;
            hdr->b_wptr += sizeof(dl_unitdata_ind_t) + 2 * sizeof(ulong);
            hdr->b_cont = mp;
@@ -855,26 +1146,41 @@ pppursrv(q)
            ud->dl_dest_addr_offset = sizeof(dl_unitdata_ind_t);
            ud->dl_src_addr_length = sizeof(ulong);
            ud->dl_src_addr_offset = ud->dl_dest_addr_offset + sizeof(ulong);
+#if DL_CURRENT_VERSION >= 2
            ud->dl_group_address = 0;
-           ((ulong *)(ud + 1))[0] = proto;     /* dest SAP */
-           ((ulong *)(ud + 1))[1] = proto;     /* src SAP */
-           putnext(q, mp);
+#endif
+           /* Send the DLPI client the data with the SAP they requested,
+              (e.g. ETHERTYPE_IP) rather than the PPP protocol number
+              (e.g. PPP_IP) */
+           ((ulong *)(ud + 1))[0] = us->req_sap;       /* dest SAP */
+           ((ulong *)(ud + 1))[1] = us->req_sap;       /* src SAP */
+           putnext(q, hdr);
        }
     }
+
+    /*
+     * If this stream is attached to a PPA with a lower queue pair,
+     * enable the read queue's service routine if it has data queued.
+     * XXX there is a possibility that packets could get out of order
+     * if ppplrput now runs before ppplrsrv.
+     */
+    if (us->ppa != 0 && us->ppa->lowerq != 0)
+       qenable(RD(us->ppa->lowerq));
+
     return 0;
 }
 
-static struct upperstr *
+static upperstr_t *
 find_dest(ppa, proto)
-    struct upperstr *ppa;
+    upperstr_t *ppa;
     int proto;
 {
-    struct upperstr *us;
+    upperstr_t *us;
 
     for (us = ppa->next; us != 0; us = us->next)
        if (proto == us->sap)
-           return us;
-    return 0;
+           break;
+    return us;
 }
 
 static int
@@ -882,11 +1188,20 @@ ppplrput(q, mp)
     queue_t *q;
     mblk_t *mp;
 {
-    struct upperstr *ppa, *us;
+    upperstr_t *ppa, *us;
     queue_t *uq;
-    int proto;
+    int proto, len;
+    mblk_t *np;
+    struct iocblk *iop;
 
-    ppa = (struct upperstr *) q->q_ptr;
+    ppa = (upperstr_t *) q->q_ptr;
+    if (ppa == 0) {
+#if DEBUG
+       cmn_err(CE_CONT, "ppplrput: q = %x, ppa = 0??\n", q);
+#endif
+       freemsg(mp);
+       return 0;
+    }
     switch (mp->b_datap->db_type) {
     case M_FLUSH:
        if (*mp->b_rptr & FLUSHW) {
@@ -896,30 +1211,88 @@ ppplrput(q, mp)
            freemsg(mp);
        break;
 
-    default:
-       if (mp->b_datap->db_type == M_DATA
-           && (proto = PPP_PROTOCOL(mp->b_rptr)) < 0x8000
-           && (us = find_dest(ppa, proto)) != 0) {
-           /*
-            * A data packet for some network protocol.
-            * Queue it on the upper stream for that protocol.
-            */
-           if (canput(us->q))
-               putq(us->q, mp);
-           else
-               putq(q, mp);
+    case M_CTL:
+       switch (*mp->b_rptr) {
+       case PPPCTL_IERROR:
+#ifdef sun
+           if (ppa->kstats != 0) {
+               KSTAT_NAMED_PTR(ppa->kstats)[1].value.ul++;
+           }
+#endif
+           ppa->stats.ppp_ierrors++;
            break;
-       } else {
-           /*
-            * A control frame, a frame for an unknown protocol,
-            * or some other message type.
-            * Send it up to pppd via the control stream.
-            */
-           if (mp->b_datap->db_type >= QPCTL || canputnext(ppa->q))
-               putnext(ppa->q, mp);
-           else
-               putq(q, mp);
+       case PPPCTL_OERROR:
+#ifdef sun
+           if (ppa->kstats != 0) {
+               KSTAT_NAMED_PTR(ppa->kstats)[3].value.ul++;
+           }
+#endif
+           ppa->stats.ppp_oerrors++;
+           break;
+       }
+       freemsg(mp);
+       break;
+
+    case M_IOCACK:
+    case M_IOCNAK:
+       /*
+        * Attempt to match up the response with the stream
+        * that the request came from.
+        */
+       iop = (struct iocblk *) mp->b_rptr;
+       for (us = ppa; us != 0; us = us->next)
+           if (us->ioc_id == iop->ioc_id)
+               break;
+       if (us == 0)
+           freemsg(mp);
+       else
+           putnext(us->q, mp);
+       break;
+
+    default:
+       if (mp->b_datap->db_type == M_DATA) {
+           len = msgdsize(mp);
+           if (mp->b_wptr - mp->b_rptr < PPP_HDRLEN) {
+               np = msgpullup(mp, PPP_HDRLEN);
+               freemsg(mp);
+               if (np == 0) {
+#if DEBUG
+                   cmn_err(CE_CONT, "ppp_lrput: msgpullup failed (len=%d)\n",
+                           len);
+#endif
+                   break;
+               }
+               mp = np;
+           }
+           ppa->stats.ppp_ipackets++;
+           ppa->stats.ppp_ibytes += len;
+#ifdef sun
+           if (ppa->kstats != 0) {
+               KSTAT_NAMED_PTR(ppa->kstats)[0].value.ul++;
+           }
+#endif
+           proto = PPP_PROTOCOL(mp->b_rptr);
+           if (proto < 0x8000 && (us = find_dest(ppa, proto)) != 0) {
+               /*
+                * A data packet for some network protocol.
+                * Queue it on the upper stream for that protocol.
+                */
+               if (canput(us->q))
+                   putq(us->q, mp);
+               else
+                   putq(q, mp);
+               break;
+           }
        }
+       /*
+        * A control frame, a frame for an unknown protocol,
+        * or some other message type.
+        * Send it up to pppd via the control stream.
+        */
+       if (queclass(mp) == QPCTL || canputnext(ppa->q))
+           putnext(ppa->q, mp);
+       else
+           putq(q, mp);
        break;
     }
 
@@ -931,13 +1304,13 @@ ppplrsrv(q)
     queue_t *q;
 {
     mblk_t *mp;
-    struct upperstr *ppa, *us;
+    upperstr_t *ppa, *us;
     int proto;
 
     /*
      * Packets only get queued here for flow control reasons.
      */
-    ppa = (struct upperstr *) q->q_ptr;
+    ppa = (upperstr_t *) q->q_ptr;
     while ((mp = getq(q)) != 0) {
        if (mp->b_datap->db_type == M_DATA
            && (proto = PPP_PROTOCOL(mp->b_rptr)) < 0x8000
@@ -959,3 +1332,69 @@ ppplrsrv(q)
     }
     return 0;
 }
+
+static int
+putctl2(q, type, code, val)
+    queue_t *q;
+    int type, code, val;
+{
+    mblk_t *mp;
+
+    mp = allocb(2, BPRI_HI);
+    if (mp == 0)
+       return 0;
+    mp->b_datap->db_type = type;
+    mp->b_wptr[0] = code;
+    mp->b_wptr[1] = val;
+    mp->b_wptr += 2;
+    putnext(q, mp);
+    return 1;
+}
+
+static int
+putctl4(q, type, code, val)
+    queue_t *q;
+    int type, code, val;
+{
+    mblk_t *mp;
+
+    mp = allocb(4, BPRI_HI);
+    if (mp == 0)
+       return 0;
+    mp->b_datap->db_type = type;
+    mp->b_wptr[0] = code;
+    ((short *)mp->b_wptr)[1] = val;
+    mp->b_wptr += 4;
+    putnext(q, mp);
+    return 1;
+}
+
+static void
+debug_dump(q, mp)
+    queue_t *q;                        /* not used */
+    mblk_t *mp;                        /* not used either */
+{
+    upperstr_t *us;
+    queue_t *uq, *lq;
+
+    cmn_err(CE_CONT, "ppp upper streams:\n");
+    for (us = minor_devs; us != 0; us = us->nextmn) {
+       uq = us->q;
+       cmn_err(CE_CONT, " %d: q=%x rlev=%d wlev=%d flags=0x%b",
+               us->mn, uq, (uq? qsize(uq): 0), (uq? qsize(WR(uq)): 0),
+               us->flags, "\020\1priv\2control\3blocked\4last");
+       cmn_err(CE_CONT, " state=%x sap=%x req_sap=%x", us->state, us->sap,
+               us->req_sap);
+       if (us->ppa == 0)
+           cmn_err(CE_CONT, " ppa=?\n");
+       else
+           cmn_err(CE_CONT, " ppa=%d\n", us->ppa->ppa_id);
+       if (us->flags & US_CONTROL) {
+           lq = us->lowerq;
+           cmn_err(CE_CONT, "    control for %d lq=%x rlev=%d wlev=%d",
+                   us->ppa_id, lq, (lq? qsize(RD(lq)): 0),
+                   (lq? qsize(lq): 0));
+           cmn_err(CE_CONT, " mru=%d mtu=%d\n", us->mru, us->mtu);
+       }
+    }
+}