Bug fixes to flow control code; put correct dlpi header on data
[ppp.git] / svr4 / ppp.c
index ef748e42d5403ba7066b0526319f3992aa5b1469..497a414c2a16c620893b6b4c5d823ea0dd38fd1d 100644 (file)
@@ -24,7 +24,7 @@
  * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
  * OR MODIFICATIONS.
  *
- * $Id: ppp.c,v 1.2 1995/05/19 02:17:42 paulus Exp $
+ * $Id: ppp.c,v 1.5 1995/06/23 01:38:49 paulus Exp $
  */
 
 /*
 #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>
 
@@ -67,8 +71,9 @@
 /*
  * 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 */
@@ -76,6 +81,7 @@ struct upperstr {
     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.
@@ -85,34 +91,45 @@ struct upperstr {
     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));
 
@@ -133,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 = {
@@ -141,6 +158,7 @@ static struct streamtab pppinfo = {
     &ppplrint, &ppplwint
 };
 
+#ifdef sun
 static dev_info_t *ppp_dip;
 
 static struct cb_ops cb_ppp_ops = {
@@ -184,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
@@ -273,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)
@@ -281,36 +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;
+       }
     }
 
     /*
      * 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));
@@ -325,13 +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;
+    up = (upperstr_t *) q->q_ptr;
+    if (up == 0)
+       return 0;
     if (up->flags & US_CONTROL) {
        /*
         * This stream represents a PPA:
@@ -368,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;
 }
@@ -386,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, 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:
@@ -424,8 +463,10 @@ pppuwput(q, mp)
            us->lowerq = lq = lb->l_qbot;
            lq->q_ptr = us;
            RD(lq)->q_ptr = us;
+           noenable(RD(lq));
            iop->ioc_count = 0;
            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
@@ -475,6 +516,23 @@ pppuwput(q, mp)
            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;
@@ -505,20 +563,48 @@ pppuwput(q, mp)
            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)
                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)
                    putnext(us->ppa->lowerq, mp);
                else {
+#if DEBUG
                    cmn_err(CE_CONT, "ppp ioctl %x rejected\n", iop->ioc_cmd);
+#endif
                    error = EPERM;
                }
                break;
@@ -557,17 +643,15 @@ 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, *ip;
+    upperstr_t *ppa, *os;
+    int sap, *ip, len;
     dl_info_ack_t *info;
     dl_bind_ack_t *ackp;
-    dl_phys_addr_ack_t *adrp;
-    dl_get_statistics_ack_t *statsp;
 
     switch (d->dl_primitive) {
     case DL_INFO_REQ:
@@ -583,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;
 
@@ -607,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:
@@ -622,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;
        }
@@ -644,7 +721,9 @@ 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
@@ -652,13 +731,23 @@ dlpi_request(q, mp, us)
 #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 */
@@ -693,11 +782,14 @@ dlpi_request(q, mp, us)
            dlpi_error(q, DL_UNITDATA_REQ, DL_OUTSTATE, 0);
            break;
        }
-       if (mp->b_cont != 0 && us->ppa != 0
-           && msgdsize(mp->b_cont) > us->ppa->mtu) {
+       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",
-                   msgdsize(mp->b_cont), us->mtu);
+           cmn_err(CE_CONT, "dlpi data too large (%d > %d)\n", len, us->mtu);
 #endif
            break;
        }
@@ -723,44 +815,22 @@ dlpi_request(q, mp, us)
            putq(q, mp);
        return;
 
-#if 0
-    case DL_GET_STATISTICS_REQ:
-       if (size < sizeof(dl_get_statistics_req_t))
-           goto badprim;
-       if ((reply = allocb(sizeof(dl_get_statistics_ack_t) + 5 * sizeof(int),
-                           BPRI_HI)) == 0)
-           break;              /* XXX should do bufcall */
-       statsp = (dl_get_statistics_ack_t *) reply->b_wptr;
-       reply->b_wptr += sizeof(dl_get_statistics_ack_t) + 5 * sizeof(int);
-       reply->b_datap->db_type = M_PCPROTO;
-       statsp->dl_primitive = DL_GET_STATISTICS_ACK;
-       statsp->dl_stat_length = 5 * sizeof(int);
-       statsp->dl_stat_offset = sizeof(dl_get_statistics_ack_t);
-       ip = (int *) (statsp + 1);
-       ip[0] = 1;
-       ip[1] = 2;
-       ip[2] = 3;
-       ip[3] = 4;
-       ip[4] = 5;
-       qreply(q, reply);
-       break;
-#endif
-
+#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_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;
 
@@ -775,9 +845,11 @@ 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);
@@ -832,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;
@@ -846,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;
@@ -885,29 +967,111 @@ new_ppa(q, mp)
     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);
            break;
        }
     }
-    if (mp == 0)
-       us->flags &= ~US_BLOCKED;
+    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;
 }
 
@@ -915,14 +1079,14 @@ 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;
@@ -932,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.
@@ -960,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)) {
@@ -975,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;
@@ -983,29 +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;
+#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, mp);
+           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
@@ -1013,11 +1188,13 @@ 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);
@@ -1035,19 +1212,65 @@ ppplrput(q, mp)
        break;
 
     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;
+       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) {
-           if (mp->b_wptr - mp->b_rptr < PPP_HDRLEN
-               && !pullupmsg(mp, PPP_HDRLEN)) {
+           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: pullupmsg failed\n");
+                   cmn_err(CE_CONT, "ppp_lrput: msgpullup failed (len=%d)\n",
+                           len);
 #endif
-               freemsg(mp);
-               break;
+                   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) {
                /*
@@ -1066,7 +1289,7 @@ ppplrput(q, mp)
         * or some other message type.
         * Send it up to pppd via the control stream.
         */
-       if (mp->b_datap->db_type >= QPCTL || canputnext(ppa->q))
+       if (queclass(mp) == QPCTL || canputnext(ppa->q))
            putnext(ppa->q, mp);
        else
            putq(q, mp);
@@ -1081,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
@@ -1145,3 +1368,33 @@ putctl4(q, type, code, val)
     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);
+       }
+    }
+}