/*
 * DLPI Compliant, Cloneable, Modloadble Network Device Driver for
 * Dialup PPP interfaces
 *
 * Copyright (C) 1993 Purdue University, All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by Purdue University.  The name of the University may not be used
 * to endorse or promote products derived from this software without
 * specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include <sys/systm.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/errno.h>
#include <sys/cred.h>
#include <sys/stat.h>
#include <sys/kmem.h>
#include <sys/ddi.h>
#include <sys/modctl.h>
#ifdef	KSTAT
#include <sys/kstat.h>
#endif
#include <sys/sunddi.h>
#include <sys/dlpi.h>
#include <sys/byteorder.h>

#include <netinet/in.h>		/* For TCP activity tracking */
#include <netinet/in_systm.h>
#include <netinet/ip.h>	
#include <netinet/tcp.h>	

#include "../h/ppp.h"
#include "../h/dpio.h"
#include "../h/dpvar.h"

#define	IP_ETHERCENTRIC
#ifdef	IP_ETHERCENTRIC
/*
 * The Solaris 2.1 IP module likes to request SAPs using ethernet types.
 * This conversion shouldn't be necessary, but it appears to be the case...
 * If anyone knows how to eliminate this kludge, please let me know. --ks
 */
#include <sys/ethernet.h>
#endif


#ifdef	DEBUGS
#define	static
int dp_debug = 0;		/* All sorts of debug stuff */
int dp_debug_dump_msg = 0;	/* Print out messages */
int dp_debug_lists = 0;		/* Debug linked lists */
#ifdef	SOLARIS1
#define	DLOG(s)		   if (dp_debug) log(LOG_INFO, (s))
#define	DLOG1(s,a)	   if (dp_debug) log(LOG_INFO, (s),(a))
#define	DLOG2(s,a,b)	   if (dp_debug) log(LOG_INFO, (s),(a),(b))
#define	DLOG3(s,a,b,c)	   if (dp_debug) log(LOG_INFO, (s),(a),(b),(c))
#define	DLOG4(s,a,b,c,d)   if (dp_debug) log(LOG_INFO, (s),(a),(b),(c),(d))
#define	DLOG5(s,a,b,c,d,e) if (dp_debug) log(LOG_INFO, (s),(a),(b),(c),(d),(e))
#else
#define	DLOG(s)		   if (dp_debug) cmn_err(CE_CONT, (s))
#define	DLOG1(s,a)	   if (dp_debug) cmn_err(CE_CONT, (s),(a))
#define	DLOG2(s,a,b)	   if (dp_debug) cmn_err(CE_CONT, (s),(a),(b))
#define	DLOG3(s,a,b,c)	   if (dp_debug) cmn_err(CE_CONT, (s),(a),(b),(c))
#define	DLOG4(s,a,b,c,d)   if (dp_debug) cmn_err(CE_CONT, (s),(a),(b),(c),(d))
#define	DLOG5(s,a,b,c,d,e) if (dp_debug) cmn_err(CE_CONT, (s),(a),(b),(c),(d),(e))
#endif
static char *dps_str[] = {
    "ACTIVE",
    "ACTIVE_C",
    "ACTIVE_U",
    "WAITING",
    "FAILCALL",
    "DISCON",
    "DOWN",
};

static char *dpcs_str[] = {
    "FAILURE",
    "SUCCESS",
    "IN_PROGRESS",
    "NO_MODEM"
};

#else
#define	DLOG(s)
#define	DLOG1(s, a)
#define	DLOG2(s, a, b)
#define	DLOG3(s, a, b, c)
#define	DLOG4(s, a, b, c, d)
#define	DLOG5(s, a, b, c, d, e)
#endif

#ifdef	__STDC__
static int dpopen(queue_t *, dev_t *, int, int, cred_t *);
static int dpclose(queue_t *, int, cred_t *);
static int dpuwput(queue_t *, mblk_t *);
static int dplwsrv(queue_t *);
static int dplrput(queue_t *, mblk_t *);
static int dpuwsrv(queue_t *);
static int dpidentify(dev_info_t *);
static int dpattach(dev_info_t *, ddi_attach_cmd_t);
static int dpdetach(dev_info_t *, ddi_detach_cmd_t);
static int dpgetinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);

static void dp_ioc(queue_t *, mblk_t *);
static void dp_dlpi(queue_t *, mblk_t *);
static mblk_t *dp_reallocb(mblk_t *, int, uint);
static void dp_dl_ok_ack(queue_t *, mblk_t *);
static void dp_dl_error_ack(queue_t *, mblk_t *, ulong, ulong);
static int dp_ud_addr_ok(dl_unitdata_req_t *);
static void dp_dl_uderror_ind(queue_t *, mblk_t *, ulong, ulong);
static void dp_ph2dlsap(uchar_t *, phdr_t *);
static void dp_dlsap2ph(phdr_t *, ulong);
static mblk_t *dp_ud2ppp(mblk_t *, dpstr_t *);
static mblk_t *dp_ppp2ud(mblk_t *, dpstr_t *, int);
static int dp_match_sap(phdr_t *, phdr_t *);
static int dp_ppp_input(dp_t *, mblk_t *);
static void dp_sap_enable(dp_t *, ulong, int);
static void dp_send_sig(dp_t *, ulong, int);
static void dp_send_sigmsg(dp_t *, ulong, ulong);
static void dp_mp_input(dp_t *, ulong, mblk_t *);
static void dp_ioc_ack(dp_t *, mblk_t *);
static void dp_rctl(dp_t *, int);
static void dp_settimeos(dp_t *, dp_timeo_t *);
static void dp_gettimeos(dp_t *, dp_timeo_t *);
static void dp_state(dp_t *, int);
static void dp_timer(dp_t *);
static void dp_timeout(dp_t *);
static void dp_untimeout(dp_t *);
static void dp_uwq_enable(dp_t *, int);
static void dp_flush(dp_t *);
static void dp_active_init(dp_t *);
static void dp_activity(dp_t *, mblk_t *, int);
static void dp_event(dp_t *, int);
static void dp_callstat(dp_t *, int);
#ifdef	KSTAT
static void dpstatinit(dp_t *);
#endif
#ifdef	DEBUGS
static void dp_dump_msg(mblk_t *);
#endif
#else
static int dpopen(/* queue_t *, dev_t *, int, int, cred_t * */);
static int dpclose(/* queue_t *, int, cred_t * */);
static int dpuwput(/* queue_t *, mblk_t * */);
static int dplwsrv(/* queue_t * */);
static int dplrput(/* queue_t *, mblk_t * */);
static int dpuwsrv(/* queue_t * */);
static int dpidentify(/* dev_info_t * */);
static int dpattach(/* dev_info_t *, ddi_attach_cmd_t */);
static int dpdetach(/* dev_info_t *, ddi_detach_cmd_t */);
static int dpgetinfo(/* dev_info_t *, ddi_info_cmd_t, void *, void ** */);

static void dp_ioc(/* queue_t *, mblk_t * */);
static void dp_dlpi(/* queue_t *, mblk_t * */);
static mblk_t *dp_reallocb(/* mblk_t *, int, uint */);
static void dp_dl_ok_ack(/* queue_t *, mblk_t * */);
static void dp_dl_error_ack(/* queue_t *, mblk_t *, ulong, ulong */);
static int dp_ud_addr_ok(/* dl_unitdata_req_t * */);
static void dp_dl_uderror_ind(/* queue_t *, mblk_t *, ulong, ulong */);
static void dp_ph2dlsap(/* uchar_t *, phdr_t * */);
static void dp_dlsap2ph(/* phdr_t *, ulong */);
static mblk_t *dp_ud2ppp(/* mblk_t *, dpstr_t * */);
static mblk_t *dp_ppp2ud(/* mblk_t *, dpstr_t *, int */);
static int dp_match_sap(/* phdr_t *, phdr_t * */);
static int dp_ppp_input(/* dp_t *, mblk_t * */);
static void dp_sap_enable(/* dp_t *, ulong, int */);
static void dp_send_sig(/* dp_t *, ulong, int */);
static void dp_send_sigmsg(/* dp_t *, ulong, ulong */);
static void dp_mp_input(/* dp_t *, ulong, mblk_t * */);
static void dp_ioc_ack(/* dp_t *, mblk_t * */);
static void dp_rctl(/* dp_t *, int */);
static void dp_settimeos(/* dp_t *, dp_timeo_t * */);
static void dp_gettimeos(/* dp_t *, dp_timeo_t * */);
static void dp_state(/* dp_t *, int */);
static void dp_timer(/* dp_t * */);
static void dp_timeout(/* dp_t * */);
static void dp_untimeout(/* dp_t * */);
static void dp_uwq_enable(/* dp_t *, int */);
static void dp_flush(/* dp_t * */);
static void dp_active_init(/* dp_t * */);
static void dp_activity(/* dp_t *, mblk_t *, int */);
static void dp_event(/* dp_t *, int */);
static void dp_callstat(/* dp_t *, int */);
#endif

#define	DP_DLSAP_CMP(a, b)	(bcmp((caddr_t)&(a), (caddr_t)&(b), sizeof(phdr_t)))

/*
 * Streams device definitions..
 */
static struct module_info dpm_info = {
    0x3abc, "dp", 0, INFPSZ, 65536, 1
};

static struct qinit dpurinit = {
    NULL, NULL, dpopen, dpclose, NULL, &dpm_info, NULL
};

static struct qinit dpuwinit = {
    dpuwput, dpuwsrv, NULL, NULL, NULL, &dpm_info, NULL
};

static struct qinit dplrinit = {
    dplrput, NULL, NULL, NULL, NULL, &dpm_info, NULL
};

static struct qinit dplwinit = {
    NULL, dplwsrv, NULL, NULL, NULL, &dpm_info, NULL
};

struct streamtab dpinfo = {
    &dpurinit, &dpuwinit, &dplrinit, &dplwinit
};

/*
 * Loadable module information.
 */
static struct cb_ops cb_dp_ops = {
    nulldev, nulldev,
    nodev, nodev, nodev, nodev, nodev, nodev, nodev, nodev, nodev,
    nochpoll, ddi_prop_op,
    &dpinfo,
    D_MP | D_NEW
};

struct dev_ops dp_ops = {
    DEVO_REV, 0,
    dpgetinfo, dpidentify, nulldev, dpattach, dpdetach, nodev,
    &cb_dp_ops,
    (struct bus_ops *)NULL
};


extern struct mod_ops mod_driverops;

static struct modldrv dpldrv = {
    &mod_driverops,
    "Dialup PPP DLPI Driver v3.1.2",
    &dp_ops
};

static struct modlinkage dplinkage = {
    MODREV_1,
    (void *)&dpldrv,
    NULL
};

/*
 * Module Config Entry Points.
 */

_init()
{
    return (mod_install(&dplinkage));
}

_fini()
{
    register int ret;

    if ((ret = mod_remove(&dplinkage)) == DDI_SUCCESS)
	return ret;
    else
	return EBUSY;
}

_info(modinfop)
struct modinfo *modinfop;
{
    return mod_info(&dplinkage, modinfop);
}

#ifdef  KSTAT

static int
dpstat_kstat_update(ksp, rw)
kstat_t *ksp;
int rw;
{
    dp_t     *dp;
    dpstat_t *dps;

    if (rw == KSTAT_WRITE)
	return EACCES;
    dp = (dp_t *) ksp->ks_private;
    dps = (dpstat_t *) ksp->ks_data;
    dps->dps_ipackets.value.ul   = dp->dp_ipackets;
    dps->dps_ierrors.value.ul    = dp->dp_ierrors;
    dps->dps_opackets.value.ul   = dp->dp_opackets;
    dps->dps_oerrors.value.ul    = dp->dp_oerrors;
    dps->dps_collisions.value.ul = dp->dp_callfails;
    return 0;
}
 
static void
dpstatinit(dp)
dp_t *dp;
{
    dpstat_t     *dps;
    struct kstat *ks;

    ks = kstat_create("dp", dp->dp_ppa, NULL, "net", KSTAT_TYPE_NAMED,
		       sizeof (struct dpstat) / sizeof (kstat_named_t), 0);
    if (ks == NULL) {
	DLOG("dpstatinit: kstat_create failed\n");
	return;
    }        
    dp->dp_ks = ks;
    dps = (struct dpstat *) (ks->ks_data);
    kstat_named_init(&dps->dps_ipackets,   "ipackets",   KSTAT_DATA_ULONG);
    kstat_named_init(&dps->dps_ierrors,    "ierrors",    KSTAT_DATA_ULONG);
    kstat_named_init(&dps->dps_opackets,   "opackets",   KSTAT_DATA_ULONG);
    kstat_named_init(&dps->dps_oerrors,    "oerrors",    KSTAT_DATA_ULONG);
    kstat_named_init(&dps->dps_collisions, "collisions", KSTAT_DATA_ULONG);
    ks->ks_update = dpstat_kstat_update;
    ks->ks_private = (void *)dp;
    kstat_install(ks);
}

static void
dpstatfini(dp)
dp_t *dp;
{
    if (dp->dp_ks)
	kstat_delete(dp->dp_ks);
}

#endif	KSTAT

static dpstr_t *dsup;
static kmutex_t dsup_lock;

static dp_t *dpup;
static kmutex_t dpup_lock;

static long dp_ticks;

/*
 * Default timeouts in seconds.
 * This is an attempt to come up with some reasonable defaults.  These
 * ought to be set for each ppa by the user level dpd deamon.
 */
static dp_timeo_t dp_def_timeouts = {
    2 * 60,		/* ACTIVE */
    DPT_NO_TIMEOUT,	/* ACTIVE (after last TCP close) */
    DPT_NO_TIMEOUT,	/* ACTIVE (after last TCP close with non-TCP traffic) */
    1 * 60,		/* WAITING */
    1 * 60		/* FAILEDCALL */
};

/*
 * Auto-configuration Entry Points.
 */

static int
dpidentify(dip)
dev_info_t *dip;
{
    DLOG1("dpidentify: %s\n", (strcmp(ddi_get_name(dip), "dp") == 0)
			    ? "DDI_IDENTIFIED" : "DDI_NOT_IDENTIFIED");
    return (strcmp(ddi_get_name(dip), "dp") == 0)
	   ? DDI_IDENTIFIED : DDI_NOT_IDENTIFIED;
}

/*ARGUSED*/
static int
dpattach(dip, cmd)
dev_info_t *dip;
ddi_attach_cmd_t cmd;
{
    DLOG("dpattach\n");
    mutex_init(&dsup_lock, "dp upper stream list lock",
	       MUTEX_DRIVER, (void *)0);
    mutex_init(&dpup_lock, "dp driver ppa list lock",
	       MUTEX_DRIVER, (void *)0);

    if (ddi_create_minor_node(dip, "dp", S_IFCHR, ddi_get_instance(dip),
			      DDI_PSEUDO, CLONE_DEV) == DDI_FAILURE) {
	ddi_remove_minor_node(dip, NULL);
	DLOG("dpattach - failure\n");
	return DDI_FAILURE;
    }

    ddi_report_dev(dip);
    dp_ticks = drv_usectohz(DPT_HZ * 1000000);
    DLOG("dpattach - success\n");
    return DDI_SUCCESS;
}

/*ARGUSED*/
static int
dpdetach(dip, cmd)
dev_info_t *dip;
ddi_detach_cmd_t cmd;
{
    mutex_enter(&dsup_lock);
    mutex_enter(&dpup_lock);
    if (dsup || dpup) {
	mutex_exit(&dpup_lock);
	mutex_exit(&dsup_lock);
	DLOG("dpdetach: busy\n");
	return DDI_FAILURE;
    }
    DLOG("dpdetach\n");
    mutex_exit(&dpup_lock);
    mutex_exit(&dsup_lock);
    mutex_destroy(&dpup_lock);
    mutex_destroy(&dsup_lock);

    ddi_remove_minor_node(dip, NULL);
    return DDI_SUCCESS;
}

/*ARGUSED*/
static int
dpgetinfo(dip, infocmd, arg, result)
dev_info_t *dip;
ddi_info_cmd_t infocmd;
void *arg;
void **result;
{
    register dev_t dev = (dev_t)arg;
    register int instance, ret;

    instance = getminor(dev);

    DLOG1("dpgetinfo: minor %d\n", instance);

    switch (infocmd) {
     case DDI_INFO_DEVT2DEVINFO:
	*result = dip;
	ret = DDI_SUCCESS;
	break;
     case DDI_INFO_DEVT2INSTANCE:
	*result = (void *)instance;
	ret = DDI_SUCCESS;
	break;
     default:
	ret = DDI_FAILURE;
	break;
    }
    return ret;
}

#ifdef	DEBUGS
static void
dp_list_debug(s)
char *s;
{
    register dp_t *dp;
    register dpstr_t *ds;
    register dpsaplist_t *dsl;
    if (!dp_debug_lists)
	return;
    DLOG1("%s\n", s);
    DLOG(" ds list:\n");
    for (ds = dsup ; ds ; ds = ds->ds_next)
	DLOG3("  ds %x minor %d dlsap %x\n", ds, ds->ds_minor,
	      *(ulong *)&ds->ds_dlsap);
    DLOG(" dp list:\n");
    for (dp = dpup ; dp ; dp = dp->dp_next) {
	DLOG3("  dp %x ppa %d lwq %x\n", dp, dp->dp_ppa, dp->dp_lwq);
	for (ds = dp->dp_ds ; ds ; ds = ds->ds_ppanext)
	    DLOG3("    ds  %x minor %d dlsap %x\n", ds, ds->ds_minor,
		  *(ulong *)&ds->ds_dlsap);
	for (dsl = dp->dp_dsl ; dsl ; dsl = dsl->dsl_next)
	    DLOG3("    dsl %x ds %x dlsap %x\n", dsl, dsl->dsl_ds,
		  *(ulong *)&dsl->dsl_dlsap);

    }
}
#endif

/*ARGUSED*/
static int
dpopen(rq, devp, flag, sflag, credp)
queue_t *rq;
dev_t *devp;
int flag,
    sflag;
cred_t *credp;
{
    register dpstr_t *ds;
    register dpstr_t **prevds;
    int minordev;
    int ret = 0;

    DLOG("dpopen\n");

    mutex_enter(&dsup_lock);

    /*
     * Figure out the minor device number
     */
    if (sflag == CLONEOPEN) {
	prevds = &dsup;
	minordev = 0;
	for ( ; ds = *prevds ; prevds = &ds->ds_next ) {
	    if (minordev < ds->ds_minor)
		break;
	    minordev++;
	}
	*devp = makedevice(getmajor(*devp), minordev);
	DLOG1("dpopen: CLONEOPEN minor %d\n", minordev);
    }
    else {
	minordev = getminor(*devp);
	DLOG1("dpopen: not CLONEOPEN minor %d\n", minordev);
    }

    /*
     * On first open, do some allocation..
     */
    if (rq->q_ptr) {
	DLOG("dpopen: ds already initialized\n");
	goto opendone;
    }

    DLOG("dpopen: initializing ds\n");

    if ((ds = (dpstr_t *)kmem_zalloc(sizeof(dpstr_t), KM_SLEEP)) == NULL) {
	ret = ENOMEM;
	DLOG("dpopen: no mem?\n");
	goto opendone;
    }

    rq->q_ptr = WR(rq)->q_ptr = (char *)ds;

    ds->ds_urq = rq;
    ds->ds_uwq = WR(rq);
    ds->ds_minor = minordev;
    ds->ds_state = DL_UNATTACHED;
    ds->ds_flags = DSF_ENABLED;
    mutex_init(&ds->ds_lock, "dp upper stream lock", MUTEX_DRIVER, (void *)0);

    ds->ds_next = *prevds;
    *prevds = ds;

    qprocson(rq);

  opendone:
    mutex_exit(&dsup_lock);

    return ret;
}

/*ARGUSED*/
static int
dpclose(rq, flag, credp)
queue_t *rq;
int	flag;
cred_t	*credp;
{
    register dpstr_t *ds;
    register dpstr_t **prevds;
    dpsaplist_t *dsl, **prevdsl;
    dp_t *dp;
    
    qprocsoff(rq);

    DLOG("dpclose\n");

    mutex_enter(&dsup_lock);

    /*
     * Remove from list of open streams.
     */
    for (prevds = &dsup ; ds = *prevds ; prevds = &ds->ds_next)
	if (ds == (dpstr_t *)rq->q_ptr)
	    break;
    *prevds = ds->ds_next;

    /*
     * If attached to a PPA, remove from its list of attached streams.
     */
    if (dp = ds->ds_dp) {
	rw_enter(&dp->dp_dslock, RW_WRITER);
	rw_enter(&dp->dp_dsllock, RW_WRITER);
	for (prevds = &dp->dp_ds ; ds = *prevds ; prevds = &ds->ds_ppanext)
	    if (ds == (dpstr_t *)rq->q_ptr)
		break;
	*prevds = ds->ds_ppanext;

	/*
	 * Remove any leftover bound dlsaps.
	 */
	for (prevdsl = &dp->dp_dsl ; dsl = *prevdsl ; )
	    if (dsl->dsl_ds == ds) {
		*prevdsl = dsl->dsl_next;
		kmem_free((char *)dsl, sizeof(*dsl));
	    }
	    else
		prevdsl = &dsl->dsl_next;
	rw_exit(&dp->dp_dsllock);
	rw_exit(&dp->dp_dslock);
    }
    mutex_destroy(&ds->ds_lock);

    kmem_free((char *)ds, sizeof(dpstr_t));

    rq->q_ptr = WR(rq)->q_ptr = NULL;

    mutex_exit(&dsup_lock);

    return 0;
}

/*
 * In general, we process everything without queueing.
 * However, DL_UNITDATA_REQ packets will be queued in the dp_dlpi routine.
 */

static int
dpuwput(uwq, mp)
queue_t *uwq;
mblk_t *mp;
{
    dpstr_t *ds;
    DLOG1("dpuwput: type 0x%x\n", mp->b_datap->db_type);

    switch (mp->b_datap->db_type) {
     case M_PROTO:
     case M_PCPROTO:
	dp_dlpi(uwq, mp);
	break;
     case M_IOCTL:
	dp_ioc(uwq, mp);
	break;
     case M_FLUSH:
	DLOG1("dpuwput: M_FLUSH 0x%x\n", *mp->b_rptr);
	if (*mp->b_rptr & FLUSHW)
	    flushq(uwq, FLUSHDATA);
	if (*mp->b_rptr & FLUSHR) {
	    *mp->b_rptr &= ~FLUSHW;
	    qreply(uwq, mp);
	}
	else
	    freemsg(mp);
	break;
     case M_DATA:
#ifdef	DLIOCRAW
	ds = (dpstr_t *)uwq->q_ptr;
	if (ds->ds_flags & DSF_RAW) {
	    putq(uwq, mp);
	    break;
	}
	/* DROP THROUGH */
#endif
	    
     default:
	DLOG1("dpuwput: unknown msg type 0x%x\n", mp->b_datap->db_type);
	freemsg(mp);
	break;
    }
}

static int
dplrput(lrq, mp)
queue_t *lrq;
mblk_t *mp;
{
    dp_t *dp = lrq->q_ptr;
    if (!dp) {
	/*
	 * This might happen if there were mblks on the queue when
	 * the device is closed.  Apparently, the close of the stream
	 * head will do a I_UNLINK and then run the service routines
	 * for any packets left on queues.  They no longer have
	 * anywhere to go.
	 */
	DLOG1("dplrput: type 0x%x no dp\n", mp->b_datap->db_type);
	freemsg(mp);
	return;
    }
    DLOG2("dplrput: type 0x%x ppa %d\n", mp->b_datap->db_type,
	  ((dp_t *)(lrq->q_ptr))->dp_ppa);
    switch (mp->b_datap->db_type) {
     case M_DATA:
	dp->dp_ipackets++;
	dp->dp_ibytes += msgdsize(mp);
	dp_activity(dp, mp, DC_RECV_ACTIVE);
	(void)dp_ppp_input(dp, mp);
	return;

     case M_IOCACK:
     case M_IOCNAK:
	dp_ioc_ack(dp, mp);
	return;

     case M_HANGUP:
	mutex_enter(&dp->dp_timerlock);
	dp_state(dp, DPS_DISCON);
	dp->dp_lwq = (queue_t *)0;
	mutex_exit(&dp->dp_timerlock);
	dp_mp_input(dp, DP_PROTO_SAP(PPP_LCP), mp);
	return;

     case M_CTL:
	dp_rctl(dp, *mp->b_rptr);
	break;

     default:
	DLOG1("dplrput: unknown msg type 0x%x\n", mp->b_datap->db_type);
    }
    freemsg(mp);
}

static int
dplwsrv(lwq)
queue_t *lwq;
{
    DLOG("dplwsrv\n");
    dp_uwq_enable((dp_t *)lwq->q_ptr, 0);
}

static void
dp_uwq_enable(dp, flag)
dp_t *dp;
int flag;
{
    dpstr_t *ds;

    DLOG1("dp_uwq_enable: %d\n", flag);

    /*
     * Search through all Streams attached to this PPA and make
     * sure that if they have queued messages, then they are enabled.
     * This is our way of passing flow control up to the upper half.
     */
    rw_enter(&dp->dp_dslock, RW_READER);
    for ( ds = dp->dp_ds ; ds ; ds = ds->ds_ppanext ) {
	if (flag && !(ds->ds_flags & DSF_ENABLED))
	    ds->ds_flags |= DSF_ENABLED;
	if (ds->ds_flags & DSF_ENABLED) {
	    DLOG1("dp_uwq_enable: %x enabled\n", *(ulong *)&ds->ds_dlsap);
	    qenable(ds->ds_uwq);
	}
	else {
	    DLOG1("dp_uwq_enable: %x not enabled\n",
		  *(ulong *)&ds->ds_dlsap);
	}
    }
    rw_exit(&dp->dp_dslock);
}

static void
dp_flush(dp)
dp_t *dp;
{
    dpstr_t *ds;

    DLOG("dp_flush\n");

    /*
     * Flush all Streams attached to this PPA.
     */
    rw_enter(&dp->dp_dslock, RW_READER);
    for ( ds = dp->dp_ds ; ds ; ds = ds->ds_ppanext )
	flushq(ds->ds_uwq, FLUSHALL);
    if (dp->dp_lwq)
	flushq(dp->dp_lwq, FLUSHALL);
    rw_exit(&dp->dp_dslock);
}

/*
 * Handle IOCTLs.  These primitives are used to implement features outside
 * the scope of the DLPI (Data Link Provider Interface) including:
 *
 * Generic ioctls
 *  DLIOCRAW			Sun addition to DLPI
 *  I_LINK | I_PLINK		Stream to add to lower queue.
 *  I_UNLINK | I_PUNLINK	Remove stream from lower queue.
 *
 * Dialup PPP specific ioctls
 *  DPIOCIFATTACH		Attach an interface (at boot time)
 *  DPIOCIFDETACH		Detach an interface (before unloading driver)
 *  DPIOCSTIMEOS		Set Activity Timeouts
 *  DPIOCGTIMEOS		Get Activity Timeouts
 *  DPIOCCALLSTAT		Report Call Status
 *  DPIOCGIFSTATS		Retrieve Statistics
 *  DPIOCSDPDEBUG		Set Debug Flag
 *  DPIOCGDPDEBUG		Get Debug Flag
 */

static void
dp_ioc(uwq, mp)
queue_t *uwq;
mblk_t *mp;
{
    struct iocblk *ioc = (struct iocblk *)mp->b_rptr;
    dpstr_t *ds = (dpstr_t *)uwq->q_ptr;
    dp_t  *dp,
	 **prevdp;
    ulong ppa;
    int err = 0;
    struct linkblk *lb;

    /*
     * Serialize DLPI and ioctl requests on this stream
     */

    DLOG2("dp_ioc(0x%x, 0x%x)\n", ioc->ioc_cmd,
	  mp->b_cont ? msgdsize(mp->b_cont) : 0);

    mutex_enter(&ds->ds_lock);

    if (ioc->ioc_count == TRANSPARENT) {
	/*
	 * For now, don't bother with TRANSPARENT ioctl's
	 */
	DLOG1("transparent ioctl: %x\n", ioc->ioc_cmd);
	if (mp->b_cont)
	    DLOG2("transparent ioctl data?: 0x%x %x\n",
		  mp->b_cont->b_datap->db_type, msgdsize(mp->b_cont));
	err = EINVAL;
	goto dpioc_ret;
    }

    switch (ioc->ioc_cmd) {
     case DPIOCIFATTACH:
     case DPIOCIFDETACH:
	if (ds->ds_dp) {
	    err = EINVAL;
	    break;
	}
	if (ioc->ioc_count != sizeof(ulong)) {
	    err = EINVAL;
	    break;
	}
	ppa = *(ulong *)mp->b_cont->b_rptr;
	mutex_enter(&dpup_lock);
	for (prevdp = &dpup ; dp = *prevdp ; prevdp = &dp->dp_next)
	    if (dp->dp_ppa == ppa)
		break;

	switch (ioc->ioc_cmd) {
	 case DPIOCIFATTACH:
	    DLOG1("dp_ioc: DPIOCIFATTACH %d\n", ppa);
	    if (dp) {
		err = EEXIST;
		break;
	    }
	    if ((dp = (dp_t *)kmem_zalloc(sizeof(dp_t), KM_NOSLEEP)) == NULL) {
		err = ENOMEM;
		break;
	    }
	    dp->dp_ppa = ppa;
	    dp->dp_mtu = PPP_MTU;
	    dp->dp_state = DPS_DISCON;
	    rw_init(&dp->dp_dslock, "dp upper stream list lock", RW_DRIVER,
		    (void *)0);
	    rw_init(&dp->dp_dsllock, "dp bound DLSAP list lock", RW_DRIVER,
		    (void *)0);
	    mutex_init(&dp->dp_timerlock, "dp timer lock",
		       MUTEX_DRIVER, (void *)0);
	    mutex_init(&dp->dp_tcptablock, "dp tcp connection table lock",
		       MUTEX_DRIVER, (void *)0);
	    dp_settimeos(dp, &dp_def_timeouts);

	    DLOG4("dp_timeout[0-3] %d %d %d %d\n",
		  dp->dp_timeout[0], dp->dp_timeout[1],
		  dp->dp_timeout[2], dp->dp_timeout[3]);
	    DLOG3("dp_timeout[4-6] %d %d %d\n",
		  dp->dp_timeout[4], dp->dp_timeout[5], dp->dp_timeout[6]);

	    dp->dp_next = dpup;
	    dpup = dp;
#ifdef	KSTAT
	    dpstatinit(dp);
#endif
	    break;

	 case DPIOCIFDETACH:
	    DLOG1("dp_ioc: DPIOCIFDETACH %d\n", ppa);
	    if (!dp) {
		err = ENXIO;
		break;
	    }
	    rw_enter(&dp->dp_dslock, RW_READER);
	    if (dp->dp_ds) {
		err = EBUSY;
		rw_exit(&dp->dp_dslock);
		break;
	    }
	    dp_untimeout(dp);
#ifdef	KSTAT
	    dpstatfini(dp);
#endif
	    mutex_destroy(&dp->dp_tcptablock);
	    mutex_destroy(&dp->dp_timerlock);
	    rw_destroy(&dp->dp_dsllock);
	    rw_exit(&dp->dp_dslock);
	    rw_destroy(&dp->dp_dslock);
	    *prevdp = dp->dp_next;
	    kmem_free((char *)dp, sizeof(dp_t));
	    break;
	}
	mutex_exit(&dpup_lock);
	break;
#ifdef	DLIOCRAW
     case DLIOCRAW:
	if (ioc->ioc_count != 0) {
	    err = EINVAL;
	    break;
	}
	ds->ds_flags |= DSF_RAW;
	break;
#endif
     case I_LINK:
     case I_UNLINK:
	if (ioc->ioc_count != sizeof(struct linkblk)) {
	    err = EINVAL;
	    break;
	}
	if (!(dp = ds->ds_dp)) {
	    err = ENXIO;
	    break;
	}
	lb = (struct linkblk *)mp->b_cont->b_rptr;
	switch (ioc->ioc_cmd) {
	 case I_LINK:
	    DLOG1("dp_ioc: I_LINK %d\n", lb->l_index);
	    if (dp->dp_lwq) {
		err = EEXIST;
		break;
	    }
	    dp->dp_lwq = lb->l_qbot;
	    dp->dp_lwq->q_ptr = RD(dp->dp_lwq)->q_ptr = (char *)dp;
	    qenable(ds->ds_uwq);
	    break;
	 case I_UNLINK:
	    DLOG1("dp_ioc: I_UNLINK %d\n", lb->l_index);
	    if (!dp->dp_lwq) {
		err = ENXIO;
		break;
	    }
	    mutex_enter(&dp->dp_timerlock);
	    dp->dp_lwq->q_ptr = RD(dp->dp_lwq)->q_ptr = (char *)0;
	    dp->dp_lwq = (queue_t *)0;
	    dp_state(dp, DPS_DISCON);
	    mutex_exit(&dp->dp_timerlock);
	    break;
	}
	break;

     case DPIOCSTIMEOS:
     case DPIOCGTIMEOS:
	if (ioc->ioc_count != sizeof(dp_timeo_t)) {
	    err = EINVAL;
	    break;
	}
	if (!(dp = ds->ds_dp)) {
	    err = ENXIO;
	    break;
	}
	switch (ioc->ioc_cmd) {
	 case DPIOCSTIMEOS:
	    dp_settimeos(dp, (dp_timeo_t *)mp->b_cont->b_rptr);
	    break;
	 case DPIOCGTIMEOS:
	    dp_gettimeos(dp, (dp_timeo_t *)mp->b_cont->b_rptr);
	    goto ioc_data_ret;
	}
	break;

     case DPIOCCALLSTAT:
	if (ioc->ioc_count != sizeof(char)) {
	    err = EINVAL;
	    break;
	}
	if (!(dp = ds->ds_dp)) {
	    err = ENXIO;
	    break;
	}
	dp_callstat(dp, (int)*(char *)mp->b_cont->b_rptr);
	break;

     case DPIOCSAPENABLE:
     case DPIOCSAPDISABLE:
	if (ioc->ioc_count != sizeof(ulong)) {
	    err = EINVAL;
	    break;
	}
	if (!(dp = ds->ds_dp)) {
	    err = ENXIO;
	    break;
	}
	dp_sap_enable(dp, *(u_long *)mp->b_cont->b_rptr,
		      ioc->ioc_cmd == DPIOCSAPENABLE);
	break;

     case DPIOCGIFSTATS:
	/*
	 * This is supplied for the time being.  It seems like there ought
	 * to be some standard way to do this...
	 */
	if (!(dp = ds->ds_dp)) {
	    err = ENXIO;
	    break;
	}
	if (ioc->ioc_count != sizeof(dp_stats_t)) {
	    err = EINVAL;
	    break;
	}
	{
	    dp_stats_t *dps = (dp_stats_t *)mp->b_cont->b_rptr;
	    dps->dps_ibytes   = dp->dp_ibytes;
	    dps->dps_ipackets = dp->dp_ipackets;
	    dps->dps_ierrors  = dp->dp_ierrors;
	    dps->dps_obytes   = dp->dp_obytes;
	    dps->dps_opackets = dp->dp_opackets;
	    dps->dps_oerrors  = dp->dp_oerrors;
	}
	goto ioc_data_ret;

     case DPIOCSDPDEBUG:
     case DPIOCGDPDEBUG:
	if (ioc->ioc_count != sizeof(int)) {
	    err = EINVAL;
	    break;
	}
	if (!(dp = ds->ds_dp)) {
	    err = ENXIO;
	    break;
	}
	switch (ioc->ioc_cmd) {
	 case DPIOCSDPDEBUG:
	    {
		int debug_bits = *(int *)mp->b_cont->b_rptr;
		dp_debug	  = debug_bits & 0x1;
		dp_debug_dump_msg = debug_bits & 0x2;
		dp_debug_lists 	  = debug_bits & 0x4;
	    }
	    break;

	 case DPIOCGDPDEBUG:
	    *(int *)mp->b_cont->b_rptr = (dp_debug	    ? 0x1 : 0x0)
				       | (dp_debug_dump_msg ? 0x2 : 0x0)
				       | (dp_debug_lists    ? 0x4 : 0x0);
	    goto ioc_data_ret;
	}
	break;

     /*
      * Ioctl's to pass down to vjc or pppasync modules
      */
     case DPIOCSVJCOMP:
     case DPIOCSVJCDEBUG:
     case DPIOCGVJCDEBUG:

     case DPIOCSCOMPAC:
     case DPIOCSCOMPPROT:
     case DPIOCSMRU:
     case DPIOCGMRU:
     case DPIOCSASYNCMAP:
     case DPIOCGASYNCMAP:
     case DPIOCSASDEBUG:
     case DPIOCGASDEBUG:
	if (!(dp = ds->ds_dp) || !dp->dp_lwq) {
	    err = ENXIO;
	    break;
	}
	DLOG1("dp_ioc: passing down ioctl id %x\n", ioc->ioc_id);
	ds->ds_ioc_id = ioc->ioc_id;
	putnext(dp->dp_lwq, mp);
	goto dpioc_done;

     default:
	err = EINVAL;
	break;
    }

dpioc_ret:
    if (mp->b_cont) {
	freemsg(mp->b_cont);
	mp->b_cont = NULL;
    }
    ioc->ioc_count = 0;
ioc_data_ret:
    if (err) {
	ioc->ioc_error = err;
	mp->b_datap->db_type = M_IOCNAK;
    }
    else
	mp->b_datap->db_type = M_IOCACK;
    qreply(uwq, mp);

dpioc_done:
    mutex_exit(&ds->ds_lock);
}

/*
 * This table defines all the DLPI primitives.  If we can handle the
 * given primitive in a request, the entry contains its size.  Otherwise,
 * the entry contains the negation of an appropriate error code.
 */
#define	DL_TODO			-DL_NOTSUPPORTED
#define	DL_PROVIDER_TO_USER	DL_BADPRIM

typedef struct dp_dl_prim {
    short pr_size;
} dp_dl_prim_t;

static dp_dl_prim_t dp_dl_prim_tab[] = {
    { DL_INFO_REQ_SIZE },	/* DL_INFO_REQ */
    { DL_BIND_REQ_SIZE },	/* DL_BIND_REQ */
    { DL_UNBIND_REQ_SIZE },	/* DL_UNBIND_REQ */
    { -DL_PROVIDER_TO_USER },	/* DL_INFO_ACK */
    { -DL_PROVIDER_TO_USER },	/* DL_BIND_ACK */
    { -DL_PROVIDER_TO_USER },	/* DL_ERROR_ACK */
    { -DL_PROVIDER_TO_USER },	/* DL_OK_ACK */
    { DL_UNITDATA_REQ_SIZE },	/* DL_UNITDATA_REQ */
    { -DL_PROVIDER_TO_USER },	/* DL_UNITDATA_IND */
    { -DL_PROVIDER_TO_USER },	/* DL_UDERROR_IND */
    { -DL_NOTSUPPORTED },	/* DL_UDQOS_REQ */
    { DL_ATTACH_REQ_SIZE },	/* DL_ATTACH_REQ */
    { DL_DETACH_REQ_SIZE },	/* DL_DETACH_REQ */
    { -DL_NOTSUPPORTED },	/* DL_CONNECT_REQ */
    { -DL_PROVIDER_TO_USER },	/* DL_CONNECT_IND */
    { -DL_NOTSUPPORTED },	/* DL_CONNECT_RES */
    { -DL_PROVIDER_TO_USER },	/* DL_CONNECT_CON */
    { -DL_NOTSUPPORTED },	/* DL_TOKEN_REQ */
    { -DL_PROVIDER_TO_USER },	/* DL_TOKEN_ACK */
    { -DL_NOTSUPPORTED },	/* DL_DISCONNECT_REQ */
    { -DL_PROVIDER_TO_USER },	/* DL_DISCONNECT_IND */
    { DL_SUBS_UNBIND_REQ_SIZE },/* DL_SUBS_UNBIND_REQ */
    { -DL_BADPRIM },		/* unused */
    { -DL_NOTSUPPORTED },	/* DL_RESET_REQ */
    { -DL_PROVIDER_TO_USER },	/* DL_RESET_IND */
    { -DL_NOTSUPPORTED },	/* DL_RESET_RES */
    { -DL_PROVIDER_TO_USER },	/* DL_RESET_CON */
    { DL_SUBS_BIND_REQ_SIZE },	/* DL_SUBS_BIND_REQ */
    { -DL_PROVIDER_TO_USER },	/* DL_SUBS_BIND_ACK */
    { -DL_UNSUPPORTED },	/* DL_ENABMULTI_REQ */
    { -DL_UNSUPPORTED },	/* DL_DISABMULTI_REQ */
    { DL_TODO },		/* DL_PROMISCON_REQ */
    { DL_TODO },		/* DL_PROMISCOFF_REQ */
    { -DL_NOTSUPPORTED },	/* DL_DATA_ACK_REQ */
    { -DL_PROVIDER_TO_USER },	/* DL_DATA_ACK_IND */
    { -DL_PROVIDER_TO_USER },	/* DL_DATA_ACK_STATUS_IND */
    { -DL_NOTSUPPORTED },	/* DL_REPLY_REQ */
    { -DL_PROVIDER_TO_USER },	/* DL_REPLY_IND */
    { -DL_PROVIDER_TO_USER },	/* DL_REPLY_STATUS_IND */
    { -DL_NOTSUPPORTED },	/* DL_REPLY_UPDATE_REQ */
    { -DL_PROVIDER_TO_USER },	/* DL_REPLY_UPDATE_STATUS_IND */
    { -DL_NOTSUPPORTED },	/* DL_XID_REQ */
    { -DL_PROVIDER_TO_USER },	/* DL_XID_IND */
    { -DL_NOTSUPPORTED },	/* DL_XID_RES */
    { -DL_PROVIDER_TO_USER },	/* DL_XID_CON */
    { -DL_NOTSUPPORTED },	/* DL_TEST_REQ */
    { -DL_PROVIDER_TO_USER },	/* DL_TEST_IND */
    { -DL_NOTSUPPORTED },	/* DL_TEST_RES */
    { -DL_PROVIDER_TO_USER },	/* DL_TEST_CON */
    { -DL_NOTSUPPORTED },	/* DL_PHYS_ADDR_REQ */
    { -DL_PROVIDER_TO_USER },	/* DL_PHYS_ADDR_ACK */
    { -DL_NOTSUPPORTED },	/* DL_SET_PHYS_ADDR_REQ */
    { DL_TODO },		/* DL_GET_STATISTICS_REQ */
    { -DL_PROVIDER_TO_USER }	/* DL_GET_STATISTICS_ACK */
};

#define	MAX_DL_REQ	(sizeof(dp_dl_prim_tab)/sizeof(dp_dl_prim_tab[0]))

dl_info_ack_t dp_dlpi_info = {
    DL_INFO_ACK,		/* dl_primitive */
    PPP_MTU,			/* dl_max_sdu */
    0,				/* dl_min_sdu */
    0,				/* dl_addr_length */
    DL_OTHER,			/* dl_mac_type */
    0,				/* dl_reserved */
    0,				/* dl_current_state */
    -(long)DP_SAP_SIZE,		/* dl_sap_length */
    DL_CLDLS,			/* dl_service_mode */
    0,				/* dl_qos_length */
    0,				/* dl_qos_offset */
    0,				/* dl_qos_range_length */
    0,				/* dl_qos_range_offset */
    DL_STYLE2,			/* dl_provider_style */
    0,			 	/* dl_addr_offset */
    DL_VERSION_2,		/* dl_version */
    0,				/* dl_brdcst_addr_length */
    0,				/* dl_brdcst_addr_offset */
    0,				/* dl_growth */
};


/*
 * Interpret a DLPI request from the user.
 */
static void
dp_dlpi(uwq, mp)
queue_t *uwq;
mblk_t *mp;
{
    dpstr_t *ds = (dpstr_t *)uwq->q_ptr;
    union DL_primitives *dlp = (union DL_primitives *)mp->b_rptr;
    dp_t *dp;
    int size;

    DLOG3("dp_dlpi: type 0x%x prim 0x%x state 0x%x\n",
	  mp->b_datap->db_type, *(ulong *)mp->b_rptr, ds->ds_state);
    /*
     * Serialize DLPI and ioctl requests on this stream
     */
    mutex_enter(&ds->ds_lock);

    if (mp->b_wptr - mp->b_rptr < sizeof(ulong)) {
	DLOG1("dp_dlpi: msg too short %d\n", mp->b_wptr - mp->b_rptr);
	dp_dl_error_ack(uwq, mp, DL_BADPRIM, 0);
	goto dp_dlpi_done;
    }
    if (dlp->dl_primitive >= MAX_DL_REQ) {
	DLOG1("dp_dlpi: prim 0x%x prim too large\n", dlp->dl_primitive);
	dp_dl_error_ack(uwq, mp, DL_BADPRIM, 0);
	goto dp_dlpi_done;
    }

    size = dp_dl_prim_tab[dlp->dl_primitive].pr_size;

    if (size < 0) {
	DLOG2("dp_dlpi: prim 0x%x %s\n", dlp->dl_primitive,
	      (size == -DL_BADPRIM) ? "DL_BADPRIM" : "DL_NOTSUPPORTED");
	dp_dl_error_ack(uwq, mp, -size, 0);
	goto dp_dlpi_done;
    }
    else if (mp->b_wptr - mp->b_rptr < size) {
	DLOG2("dp_dlpi: msg too small (expected 0x%x, got 0x%x)\n",
	      size, mp->b_wptr - mp->b_rptr);
#ifdef	DEBUGS
	if (dp_debug)
	     dp_dump_msg(mp);
#endif
	dp_dl_error_ack(uwq, mp, DL_BADPRIM, 0);
	goto dp_dlpi_done;
    }

    switch (dlp->dl_primitive) {

     case DL_UNITDATA_REQ:
	putq(uwq, mp);
	break;

     case DL_INFO_REQ:
	{
	    dl_info_ack_t *dli;
	    mblk_t *amp;

	    DLOG("dp_dlpi: DL_INFO_REQ\n");
	    amp = dp_reallocb(mp, DL_INFO_ACK_SIZE+DP_DLSAP_SIZE, BPRI_MED);
	    if (!amp)
		break;
	    dli = (dl_info_ack_t *)amp->b_wptr;
	    dli->dl_primitive = DL_INFO_ACK;
	    bcopy((caddr_t)&dp_dlpi_info, (caddr_t)dli, DL_INFO_ACK_SIZE);
	    amp->b_wptr += DL_INFO_ACK_SIZE;
	    dli->dl_current_state = ds->ds_state;
	    if (ds) {
		dli->dl_addr_length = DP_DLSAP_SIZE;
		dli->dl_addr_offset = DL_INFO_ACK_SIZE;
		dp_ph2dlsap(amp->b_wptr, &ds->ds_dlsap);
		amp->b_wptr += DP_DLSAP_SIZE;
	    }
	    if (dp = ds->ds_dp)
		dli->dl_max_sdu = dp->dp_mtu;

	    qreply(uwq, amp);
	}
	break;
     case DL_ATTACH_REQ:
        {
	    dl_attach_req_t *dla = &dlp->attach_req;

	    DLOG1("dp_dlpi: DL_ATTACH_REQ ppa %d\n", dla->dl_ppa);
	    if (ds->ds_state != DL_UNATTACHED) {
		dp_dl_error_ack(uwq, mp, DL_OUTSTATE, 0);
		break;
	    }
	    mutex_enter(&dpup_lock);
	    for (dp = dpup ; dp ; dp = dp->dp_next)
		if (dp->dp_ppa == dla->dl_ppa)
		    break;
	    if (dp) {
		rw_enter(&dp->dp_dslock, RW_WRITER);
		ds->ds_ppanext = dp->dp_ds;
		dp->dp_ds = ds;

		ds->ds_dp = dp;
		ds->ds_state = DL_UNBOUND;
		rw_exit(&dp->dp_dslock);
	    }

	    mutex_exit(&dpup_lock);

	    if (dp)
		dp_dl_ok_ack(uwq, mp);
	    else
		dp_dl_error_ack(uwq, mp, DL_BADPPA, 0);
	}
	break;
     case DL_DETACH_REQ:
	{
	    dpstr_t *dsp, **prevds;

	    DLOG("dp_dlpi: DL_DETACH_REQ\n");
	    if (ds->ds_state != DL_UNBOUND) {
		dp_dl_error_ack(uwq, mp, DL_OUTSTATE, 0);
		break;
	    }
	    dp = ds->ds_dp;

	    rw_enter(&dp->dp_dslock, RW_WRITER);

	    ds->ds_state = DL_UNATTACHED;

	    for (prevds = &dp->dp_ds ;
		 dsp = *prevds ;
		 prevds = &dsp->ds_ppanext)
		if (dsp == ds)
		    break;
	    *prevds = ds->ds_ppanext;

	    ds->ds_ppanext = (dpstr_t *)0;
	    ds->ds_dp = (dp_t *)0;
	    rw_exit(&dp->dp_dslock);

	    dp_dl_ok_ack(uwq, mp);
	}
	break;
     case DL_BIND_REQ:
	{
	    mblk_t *amp;
	    dl_bind_req_t *dlb = &dlp->bind_req;
	    dl_bind_ack_t *dlba;
	    dpsaplist_t *dsl;
	    ulong err = 0, uerr = 0;

	    DLOG3("dp_dlpi: DL_BIND_REQ sap %x mode %x xid %x\n",
		  dlb->dl_sap, dlb->dl_service_mode, dlb->dl_xidtest_flg);
	    
	    if (ds->ds_state != DL_UNBOUND)
		err = DL_OUTSTATE;
	    else if (dlb->dl_service_mode != DL_CLDLS)
		err = DL_UNSUPPORTED;
	    else if (dlb->dl_xidtest_flg)
		err = DL_NOAUTO;

	    dsl = (dpsaplist_t *)kmem_zalloc(sizeof(dpsaplist_t), KM_SLEEP);
	    if (!dsl) {
		err = DL_SYSERR;
		uerr = ENOMEM;
	    }

	    if (err) {
		dp_dl_error_ack(uwq, mp, err, uerr);
		break;
	    }
	    /*
	     * Set dlsap and link it into our data structures.
	     */
	    dp_dlsap2ph(&dsl->dsl_dlsap, dlb->dl_sap);
	    ds->ds_dlsap = dsl->dsl_dlsap;
	    dsl->dsl_ds = ds;
	    dp = ds->ds_dp;
	    rw_enter(&dp->dp_dsllock, RW_WRITER);
	    dsl->dsl_next = dp->dp_dsl;
	    dp->dp_dsl = dsl;
	    rw_exit(&dp->dp_dsllock);

	    /*
	     * Reply with a DL_BIND_ACK message.
	     */
	    amp = dp_reallocb(mp, DL_BIND_ACK_SIZE+DP_DLSAP_SIZE, BPRI_MED);
	    if (!amp)
		break;
	    dlba = (dl_bind_ack_t *)amp->b_wptr;
	    dlba->dl_primitive = DL_BIND_ACK;
	    dlba->dl_addr_length = DP_DLSAP_SIZE;
	    dlba->dl_addr_offset = DL_BIND_ACK_SIZE;
	    dlba->dl_max_conind = 0;
	    dlba->dl_xidtest_flg = 0;
	    amp->b_wptr += DL_BIND_ACK_SIZE;
	    dp_ph2dlsap(amp->b_wptr, &dsl->dsl_dlsap);
	    amp->b_wptr += DP_DLSAP_SIZE;

	    ds->ds_state = DL_IDLE;

	    qreply(uwq, amp);
	}
	break;

     case DL_UNBIND_REQ:
	{
	    dpsaplist_t *dsl, **prevdsl;

	    DLOG("dp_dlpi: DL_UNBIND_REQ\n");

	    if (ds->ds_state != DL_IDLE) {
		dp_dl_error_ack(uwq, mp, DL_OUTSTATE, 0);
		break;
	    }

	    /*
	     * Remove from list of dlsaps for this PPA
	     */
	    dp = ds->ds_dp;

	    rw_enter(&dp->dp_dsllock, RW_WRITER);
	    for (prevdsl = &dp->dp_dsl ;
		 dsl = *prevdsl ;
		 prevdsl = &dsl->dsl_next )
		if (dsl->dsl_ds == ds &&
		    DP_DLSAP_CMP(dsl->dsl_dlsap, ds->ds_dlsap) == 0) {
		    *prevdsl = dsl->dsl_next;
		    kmem_free((char *)dsl, sizeof(*dsl));
		    break;
		}

	    /*
	     * Clean up stream state.
	     */
	    ds->ds_state = DL_UNBOUND;
	    rw_exit(&dp->dp_dsllock);

	    ds->ds_dlsap.ph_address = ds->ds_dlsap.ph_control = 0;
	    ds->ds_dlsap.ph_protocol = 0;


	    dp_dl_ok_ack(uwq, mp);
	}
	break;
     case DL_SUBS_BIND_REQ:
	{
	    mblk_t *amp;
	    dl_subs_bind_req_t *dls = &dlp->subs_bind_req;
	    dl_subs_bind_ack_t *dlsa;
	    dpsaplist_t *dsl;
	    ulong err = 0, uerr = 0;

	    DLOG3("dp_dlpi: DL_SUBS_BIND_REQ sap %x sap_len %x bind_class %x\n",
		  *(ulong *)(((caddr_t)dls) + dls->dl_subs_sap_offset),
		  dls->dl_subs_sap_length, dls->dl_subs_bind_class);
	    
	    if (ds->ds_state != DL_IDLE)
		err = DL_OUTSTATE;
	    else if (dls->dl_subs_bind_class != DL_PEER_BIND)
		err = DL_UNSUPPORTED;
	    else if (dls->dl_subs_sap_length != sizeof(ulong))
		err = DL_BADADDR;

	    dsl = (dpsaplist_t *)kmem_zalloc(sizeof(dpsaplist_t), KM_SLEEP);
	    if (!dsl) {
		err = DL_SYSERR;
		uerr = ENOMEM;
	    }

	    if (err) {
		dp_dl_error_ack(uwq, mp, err, uerr);
		break;
	    }
	    /*
	     * Set dlsap and link it into our data structures.
	     */
	    dp_dlsap2ph(&dsl->dsl_dlsap,
			*(ulong *)(((caddr_t)dls) + dls->dl_subs_sap_offset));
	    dsl->dsl_ds = ds;
	    dp = ds->ds_dp;
	    rw_enter(&dp->dp_dsllock, RW_WRITER);
	    dsl->dsl_next = dp->dp_dsl;
	    dp->dp_dsl = dsl;
	    rw_exit(&dp->dp_dsllock);

	    /*
	     * Reply with a DL_SUBS_BIND_ACK message.
	     */
	    amp = dp_reallocb(mp, DL_SUBS_BIND_ACK_SIZE+DP_DLSAP_SIZE,
			      BPRI_MED);
	    if (!amp)
		break;
	    dlsa = (dl_subs_bind_ack_t *)amp->b_wptr;
	    dlsa->dl_primitive = DL_SUBS_BIND_ACK;
	    dlsa->dl_subs_sap_length = DP_DLSAP_SIZE;
	    dlsa->dl_subs_sap_offset = DL_SUBS_BIND_ACK_SIZE;
	    amp->b_wptr += DL_SUBS_BIND_ACK_SIZE;
	    dp_ph2dlsap(amp->b_wptr, &dsl->dsl_dlsap);
	    amp->b_wptr += DP_DLSAP_SIZE;

	    qreply(uwq, amp);
	}
	break;

     case DL_SUBS_UNBIND_REQ:
	{
	    dpsaplist_t *dsl, **prevdsl;
	    dl_subs_unbind_req_t *dls = &dlp->subs_unbind_req;

	    DLOG2("dp_dlpi: DL_SUBS_UNBIND_REQ sap %x sap_len %x\n",
		  *(ulong *)(((caddr_t)dls) + dls->dl_subs_sap_offset),
		  dls->dl_subs_sap_length);

	    if (ds->ds_state != DL_IDLE) {
		dp_dl_error_ack(uwq, mp, DL_OUTSTATE, 0);
		break;
	    }

	    /*
	     * Remove from list of dlsaps for this PPA
	     */
	    dp = ds->ds_dp;

	    rw_enter(&dp->dp_dsllock, RW_WRITER);
	    for (prevdsl = &dp->dp_dsl ;
		 dsl = *prevdsl ;
		 prevdsl = &dsl->dsl_next )
		if (dsl->dsl_ds == ds &&
		    DP_DLSAP_CMP(dsl->dsl_dlsap, ds->ds_dlsap) == 0) {
		    *prevdsl = dsl->dsl_next;
		    kmem_free((char *)dsl, sizeof(*dsl));
		    break;
		}
	    rw_exit(&dp->dp_dsllock);

	    dp_dl_ok_ack(uwq, mp);
	}
	break;

     default:
	DLOG1("dp_dlpi: prim %d unknown\n", dlp->dl_primitive);
	dp_dl_error_ack(uwq, mp, DL_BADPRIM, 0);
    }

dp_dlpi_done:
    mutex_exit(&ds->ds_lock);
   
}

static int
dpuwsrv(uwq)
queue_t *uwq;
{
    mblk_t *mp;
    dl_unitdata_req_t *dlu;
    dpstr_t *ds = (dpstr_t *)uwq->q_ptr;
    dp_t *dp = ds->ds_dp;
    phdr_t *ph;
    mblk_t *pmp;
    int ndwn = 0;

    DLOG("dpuwsrv\n");
    /*
     * Serialize DLPI and ioctl requests on this stream
     */
    mutex_enter(&ds->ds_lock);

    while (mp = getq(uwq)) {
	switch (mp->b_datap->db_type) {
	 case M_PROTO:
	 case M_PCPROTO:
	    dlu = (dl_unitdata_req_t *)mp->b_rptr;
	    break;
	 case M_DATA:
	    if (ds->ds_flags & DSF_RAW) {
		dlu = (dl_unitdata_req_t *)0;
		break;
	    }
	    /* Fall through */
	 default:
	    DLOG1("dpuwsrv: unknown msg type 0x%x\n", mp->b_datap->db_type);
	    freemsg(mp);
	    continue;
	}

	if (dlu)
	    DLOG4("DL_UNITDATA_REQ len 0x%x off 0x%x pri 0x%x - 0x%x\n",
		  dlu->dl_dest_addr_length, dlu->dl_dest_addr_offset,
		  dlu->dl_priority.dl_min, dlu->dl_priority.dl_max);
	else
	    DLOG1("RAW DATA len 0x%x\n", msgdsize(mp));

	if (ds->ds_state != DL_IDLE) {
	    if (dlu)
		dp_dl_uderror_ind(uwq, mp, DL_OUTSTATE, 0);
	    continue;
	}

#ifdef	DEBUGS
	if (dp_debug)
	     dp_dump_msg(mp);
#endif
	/*
	 * Check packet size and address format.
	 */
	if (dlu) {
	    if (msgdsize(mp->b_cont) > dp->dp_mtu) {
		dp_dl_uderror_ind(uwq, mp, DL_BADDATA, 0);
		continue;
	    }
	}
	else {
	    if (msgdsize(mp) > dp->dp_mtu + sizeof(phdr_t))
		continue;
	}
	if (dlu && !dp_ud_addr_ok(dlu)) {
	    dp_dl_uderror_ind(uwq, mp, DL_BADADDR, 0);
	    continue;
	}

	switch (dp->dp_state) {
	 case DPS_WAITING:
	    /*
	     * Normally, the queue would be blocked and we would not be
	     * run during the WAITING state, so this probably shouldn't happen.
	     */
	    if (!(ds->ds_flags & DSF_ENABLED)) {
		DLOG("dpwsrv: DPS_DISCON & not DSF_ENABLED !?!\n");
		putbq(uwq, mp);
		goto dpuwsrv_done;
	    }
	    DLOG("dpwsrv: DPS_WAITING -> DPS_ACTIVE\n");
	    /* DROP THROUGH */
	 case DPS_ACTIVE:
	    /*
	     * Normal operation.  Honor flow control, convert to a PPP
	     * header, and pass packet down.
	     */
	    if (!canputnext(dp->dp_lwq)) {
		DLOG("dpwsrv: DPS_ACTIVE (blocked)\n");
		putbq(uwq, mp);
		goto dpuwsrv_done;
	    }
	    DLOG("dpwsrv: DPS_ACTIVE\n");
	    pmp = (DP_DB_TYPE(mp) == M_DATA) ? mp : dp_ud2ppp(mp, ds);
	    if (!pmp)
		break;
	    dp->dp_opackets++;
	    dp->dp_obytes += msgdsize(pmp);
	    dp_activity(dp, pmp, DC_XMIT_ACTIVE);
	    putnext(dp->dp_lwq, pmp);
	    break;

	 case DPS_DISCON:
	    /*
	     * Initiate a call request.  The control field of the PPP header
	     * of a real packet must always be set to PPP_UI or the packet
	     * will be rejected.  Programs such as "dpd" will look for
	     * packets with the control field set to a special value.
	     */
	    ds->ds_flags &= ~DSF_ENABLED;
	    DLOG("dpwsrv: DPS_DISCON\n");
	    pmp = (DP_DB_TYPE(mp) == M_DATA) ? dupmsg(mp)
					       : dp_ud2ppp(dupmsg(mp), ds);
	    if (!pmp) {
		freemsg(mp);
		break;
	    }
	    ph = (phdr_t *)pmp->b_rptr;
	    ph->ph_protocol |= htons(DPP_DIAL_REQ);
	    if (dp_ppp_input(dp, pmp) == 0)
		goto dp_netdown;

	    putbq(uwq, mp);
	    mutex_enter(&dp->dp_timerlock);
	    dp_state(dp, DPS_WAITING);
	    mutex_exit(&dp->dp_timerlock);
	    goto dpuwsrv_done;

	    
	 case DPS_DOWN:
	 case DPS_FAILCALL:
	    DLOG("dpwsrv: DPS_FAILCALL");
	    /*
	     * If we can't possibly send this packet, return an error,
	     * and drop the packet.  If we are serving a queue with more than
	     * one packet, only send an error upstream once.
	     */
	 dp_netdown:
#if	0
	    /*
	     * This appears to hose up the IP layer which deletes routes
	     * and does other nasty things thus making this interface
	     * "permanently" unusable.
	     */
	    if (dlu && ndwn == 0) {
		ndwn++;
		dp_dl_uderror_ind(uwq, mp, DL_SYSERR, ENETUNREACH);
	    }
	    else
#endif
		freemsg(mp);
	    break;
	}
    }
dpuwsrv_done:
    mutex_exit(&ds->ds_lock);

}

static int
dp_ud_addr_ok(dlu)
dl_unitdata_req_t *dlu;
{
    ulong sap;
    switch (dlu->dl_dest_addr_length) {
     case sizeof(ushort):
	sap = *(ushort *)(((char *)dlu) + dlu->dl_dest_addr_offset);
	break;
     case sizeof(ulong):
	sap = *(ulong *)(((char *)dlu) + dlu->dl_dest_addr_offset);
	break;
     default:
	return 0;
    }
#ifdef	IP_ETHERCENTRIC
    switch (sap) {
     case ETHERTYPE_IP:
     case 0:
	return 1;
    }
#endif
    return (sap & 0x0100) == 0 && (sap & 0x0001) == 1;
}

/*
 * Convert a DL_UNITDATA_REQ message to a PPP packet.
 */
static mblk_t *
dp_ud2ppp(ump, ds)
mblk_t *ump;
dpstr_t *ds;
{
    mblk_t *pmp;
    char *addr;
    phdr_t *ph;
    dl_unitdata_req_t *dlu = (dl_unitdata_req_t *)ump->b_rptr;

    /*
     * Allocate PPP header, and link to copy of msg blocks.
     */
    if (DP_DB_REF(ump) == 1 && DP_MBLK_SIZE(ump) >= sizeof(phdr_t)) {
	pmp = ump;
	DP_DB_TYPE(pmp) = M_DATA;
	pmp->b_wptr = pmp->b_rptr = DP_DB_BASE(pmp);
    }
    else {
	pmp = allocb(PPP_HDR_SIZE, BPRI_MED);
	if (!pmp)
	    return (mblk_t *)0;
	pmp->b_cont = ump->b_cont;
	freeb(ump);
    }

    ph = (phdr_t *)pmp->b_wptr;
    pmp->b_wptr += sizeof (*ph);

    /*
     * Fill in PPP header based on provided address.
     */
    addr = ((char *)dlu) + dlu->dl_dest_addr_offset;
    switch (dlu->dl_dest_addr_length) {
     case sizeof(ushort):
	dp_dlsap2ph(ph, (ulong)(*(short *)addr));
	break;
     case sizeof(ulong):
	dp_dlsap2ph(ph, *(ulong *)addr);
	break;
     case 0:
	*ph = ds->ds_dlsap;
	break;
     default:
	freemsg(pmp);
	return (mblk_t *)0;
    }
    return pmp;
}

/*
 * Convert a PPP packet to a DL_UNITDATA_IND message.
 * If a DLIOCRAW ioctl has been used on this stream, just copy the message in
 * raw form and don't bother with the DLPI stuff.
 */
static mblk_t *
dp_ppp2ud(pmp, ds, reuse)
mblk_t *pmp;
dpstr_t *ds;
int reuse;
{
    mblk_t *mp;
    phdr_t ph;
    dl_unitdata_ind_t *dlu;

#ifdef	DLIOCRAW
    if (ds->ds_flags & DSF_RAW)
	return reuse ? pmp : dupmsg(pmp);
#endif

    ph = *(phdr_t *)pmp->b_rptr;

    /*
     * Convert to DL_UNITDATA_IND message.
     */
    if (reuse) {
	if (DP_DB_REF(pmp) == 1 &&
	    DP_MBLK_SIZE(pmp) >= (DL_UNITDATA_IND_SIZE + 2 * DP_DLSAP_SIZE)) {
	    mp = pmp;
	    mp->b_wptr = mp->b_rptr = DP_DB_BASE(mp);
	}
	else {
	    mp = allocb(DL_UNITDATA_IND_SIZE+2*DP_DLSAP_SIZE, BPRI_MED);
	    if (!mp) {
		freemsg(pmp);
		return (mblk_t *)0;
	    }
	    mp->b_cont = pmp->b_cont;
	    freeb(pmp);
	}
    }
    else {
	mp = allocb(DL_UNITDATA_IND_SIZE+2*DP_DLSAP_SIZE, BPRI_MED);
	if (!mp)
	    return (mblk_t *)0;

	if (pmp->b_cont)
	    mp->b_cont = dupmsg(pmp->b_cont);
    }

    /*
     * Fill in the DL_UNITDATA_IND header.
     */
    DP_DB_TYPE(mp) = M_PROTO;
    dlu = (dl_unitdata_ind_t *)mp->b_rptr;
    dlu->dl_primitive = DL_UNITDATA_IND;
    dlu->dl_dest_addr_length = DP_DLSAP_SIZE;
    dlu->dl_dest_addr_offset = DL_UNITDATA_IND_SIZE;
    dlu->dl_src_addr_length = DP_DLSAP_SIZE;
    dlu->dl_src_addr_offset = DL_UNITDATA_IND_SIZE + DP_DLSAP_SIZE;
    dlu->dl_group_address = 0;
    mp->b_wptr += DL_UNITDATA_IND_SIZE;

    dp_ph2dlsap(mp->b_wptr, &ph);
    mp->b_wptr += DP_DLSAP_SIZE;
    dp_ph2dlsap(mp->b_wptr, &ph);
    mp->b_wptr += DP_DLSAP_SIZE;

    return mp;
}

static mblk_t *
dp_reallocb(mp, size, pri)
mblk_t *mp;
int size;
uint pri;
{
    /*
     * Reuse message block if possible.
     */
    if (DP_DB_REF(mp) == 1 && DP_MBLK_SIZE(mp) >= size) {
	if (mp->b_cont) {
	    freemsg(mp->b_cont);
	    mp->b_cont = NULL;
	}
	mp->b_wptr = mp->b_rptr = DP_DB_BASE(mp);
    }
    else {
	freemsg(mp);
	mp = allocb(size, pri);
    }
    if (mp)
	mp->b_datap->db_type = M_PCPROTO;
    return mp;
}

static void
dp_dl_ok_ack(q, mp)
queue_t *q;
mblk_t *mp;
{
    dl_ok_ack_t *dlo;
    ulong prim = ((union DL_primitives *)mp->b_rptr)->dl_primitive;

    DLOG1("dp_dl_ok_ack: prim 0x%x\n", prim);

    mp = dp_reallocb(mp, DL_OK_ACK_SIZE, BPRI_MED);
    if (!mp)
	return;
    /*
     * Construct OK ack.
     */
    mp->b_datap->db_type = M_PCPROTO;
    dlo = (dl_ok_ack_t *)mp->b_rptr;
    dlo->dl_primitive = DL_OK_ACK;
    dlo->dl_correct_primitive = prim;
    mp->b_wptr += sizeof(*dlo);

    qreply(q, mp);
}

static void
dp_dl_error_ack(q, mp, err, uerr)
queue_t *q;
mblk_t *mp;
ulong err,
      uerr;
{
    dl_error_ack_t *dle;
    ulong prim = ((union DL_primitives *)mp->b_rptr)->dl_primitive;

    DLOG3("dp_dl_error_ack: prim 0x%x err 0x%x uerr %d\n", prim, err, uerr);

    mp = dp_reallocb(mp, DL_ERROR_ACK_SIZE, BPRI_MED);
    if (!mp)
	return;
    /*
     * Construct error ack.
     */
    mp->b_datap->db_type = M_PCPROTO;
    dle = (dl_error_ack_t *)mp->b_rptr;
    dle->dl_primitive = DL_ERROR_ACK;
    dle->dl_error_primitive = prim;
    dle->dl_errno = err;
    dle->dl_unix_errno = uerr;
    mp->b_wptr += sizeof(*dle);

    qreply(q, mp);
}

int uderror_ind_debug = 1;

static void
dp_dl_uderror_ind(q, mp, err, uerr)
queue_t *q;
mblk_t *mp;
ulong err, uerr;
{
    dl_unitdata_req_t *dlu = (dl_unitdata_req_t *)mp->b_rptr;
    dl_uderror_ind_t *due;
    mblk_t *amp;

    if (uderror_ind_debug)
	cmn_err(CE_CONT,
		"dp_dl_uderror_ind: err 0x%x uerr %d addr_len 0x%x addr 0x%x\n",
		err, uerr, dlu->dl_dest_addr_length,
		*(ulong *)(((caddr_t)dlu) + dlu->dl_dest_addr_offset));
    /*
     * Allocate nak message.  We can't re-use the original message
     * since we need to copy out the address.
     */
    amp = allocb(DL_UDERROR_IND_SIZE + dlu->dl_dest_addr_length, BPRI_LO);
    if (!amp) {
	freemsg(mp);
	return;
    }
    /*
     * Construct a nak to an undeliverable packet.
     */
    amp->b_datap->db_type = M_PROTO;
    due = (dl_uderror_ind_t *)amp->b_rptr;
    due->dl_primitive = DL_UDERROR_IND;
    due->dl_dest_addr_length = dlu->dl_dest_addr_length;
    due->dl_dest_addr_offset = DL_UDERROR_IND_SIZE;
    due->dl_unix_errno = uerr;
    due->dl_errno = err;
    amp->b_wptr += sizeof(*due);

    /*
     * Tack on original address
     */
    if (due->dl_dest_addr_length) {
	bcopy(((caddr_t)dlu) + dlu->dl_dest_addr_offset,
	      (caddr_t)amp->b_wptr, due->dl_dest_addr_length);
	amp->b_wptr += due->dl_dest_addr_length;
    }

    freemsg(mp);
    qreply(q, amp);
}

static int
dp_match_sap(patph, valph)
phdr_t *patph,
       *valph;
{
    /*
     * The lower bit of the protocol must always be 1.
     * If it is zero, assume we are trying to match a protocol range.
     */
    if ((ntohs(patph->ph_protocol) & 1) == 0) {
	if ((patph->ph_protocol & htons(DPP_PROTO_MASK)) !=
	    (valph->ph_protocol & htons(DPP_PROTO_MASK)))
	    return -1;
    }
    else {
	if (patph->ph_protocol != valph->ph_protocol)
	    return -1;
    }
    if (patph->ph_control != valph->ph_control ||
	patph->ph_address != valph->ph_address)
	return -1;
    return 0;
}

static int
dp_ppp_input(dp, pmp)
dp_t *dp;
mblk_t *pmp;
{
    dpstr_t *ds;
    dpsaplist_t *dsl;
    mblk_t *mp;
    phdr_t *pph = (phdr_t *)pmp->b_rptr;
    int n = 0;
    dpstr_t *lds = (dpstr_t *)0;

    DLOG1("dp_ppp_input: 0x%x\n", *(ulong *)pmp->b_rptr);

    rw_enter(&dp->dp_dsllock, RW_READER);

    /*
     * Search through all Streams attached to this PPA to find
     * places to send the packet upstream.
     */
    for ( dsl = dp->dp_dsl ; dsl ; dsl = dsl->dsl_next ) {
	ds = dsl->dsl_ds;

	if (ds->ds_state != DL_IDLE)
	    continue;
	/*
	 * Do the matching...
	 * Perhaps the semantics of this could be made simpler.
	 */
	if (dp_match_sap(&dsl->dsl_dlsap, pph))
	    continue;

	DLOG1("dp_ppp_input: matched 0x%x\n", *(ulong *)&dsl->dsl_dlsap);

	/*
	 * Wow, we have found a matching DLSAP.
	 * Construct a DL_UNITDATA_IND message from this PPP packet, and
	 * send it upstream, being careful NOT to reuse the mblk,
	 * since we need it for other read queues.
	 */
	if (lds) {
	    if (mp = dp_ppp2ud(pmp, lds, 0)) {
		putnext(lds->ds_urq, mp);
		n++;
	    }
	}
	lds = ds;
    }
    if (lds) {
	/*
	 * Since this is the last time we need to do this,
	 * we can reuse the mblks.
	 */
	if (mp = dp_ppp2ud(pmp, lds, 1)) {
	    putnext(lds->ds_urq, mp);
	    n++;
	}
    }
    else
	freemsg(pmp);

    rw_exit(&dp->dp_dsllock);
    return n;
}

static void
dp_sap_enable(dp, sap, enable)
dp_t *dp;
ulong sap;
int enable;
{
    dpstr_t *ds;
    dpsaplist_t *dsl;
    phdr_t ph;

    DLOG2("dp_sap_enable: 0x%x %d\n", sap, enable);
    dp_dlsap2ph(&ph, sap);

    /*
     * Search through all Streams attached to this PPA to find
     * any matching saps to enable.
     */
    rw_enter(&dp->dp_dsllock, RW_READER);
    for ( dsl = dp->dp_dsl ; dsl ; dsl = dsl->dsl_next ) {
	ds = dsl->dsl_ds;

	if (ds->ds_state != DL_IDLE)
	    continue;

	if (dp_match_sap(&ph, &dsl->dsl_dlsap))
	    continue;

	DLOG2("dp_sap_enable: %sabling 0x%x\n", enable ? "en" : "dis",
	      *(ulong *)&dsl->dsl_dlsap);
	if (enable) {
	    ds->ds_flags |= DSF_ENABLED;
	    qenable(ds->ds_uwq);
	}
	else
	    ds->ds_flags &= ~DSF_ENABLED;
    }
    rw_exit(&dp->dp_dsllock);
}

static void
dp_send_sigmsg(dp, sap, sig)
dp_t *dp;
ulong sap;
ulong sig;
{
    mblk_t *mp;
    dl_signal_ind_t *dsi;

    DLOG2("dp_send_sigmsg: 0x%x sig 0x%x\n", sap, sig);
    mp = allocb(2 * sizeof(u_long), BPRI_MED);
    if (!mp) {
	DLOG("dp_send_sigmsg: allocb failed\n");
	return;
    }
    mp->b_datap->db_type = M_PROTO;
    dsi = (dl_signal_ind_t *)mp->b_wptr;
    mp->b_wptr += DL_SIGNAL_IND_SIZE;
    dsi->dl_primitive = DL_SIGNAL_IND;
    dsi->dl_signal    = sig;
    dp_mp_input(dp, sap, mp);
}

static void
dp_send_sig(dp, sap, sig)
dp_t *dp;
ulong sap;
int sig;
{
    mblk_t *mp;

    DLOG2("dp_send_sig: 0x%x sig 0x%x\n", sap, sig);
    mp = allocb(1, BPRI_MED);
    if (!mp) {
	DLOG("dp_send_sig: allocb failed");
	return;
    }
    mp->b_datap->db_type = M_PCSIG;
    *mp->b_wptr++ = sig;
    dp_mp_input(dp, sap, mp);
}

static void
dp_mp_input(dp, sap, mp)
dp_t *dp;
ulong sap;
mblk_t *mp;
{
    dpstr_t *ds;
    dpsaplist_t *dsl;
    phdr_t ph;
    mblk_t *dmp;
    queue_t *rq = (queue_t *)0;

    DLOG2("dp_mp_input: 0x%x type 0x%x\n", sap, mp->b_datap->db_type);
    dp_dlsap2ph(&ph, sap);

    /*
     * Search through all Streams attached to this PPA to find
     * any matching saps to send this message upstream.
     */
    rw_enter(&dp->dp_dsllock, RW_READER);
    for ( dsl = dp->dp_dsl ; dsl ; dsl = dsl->dsl_next ) {
	ds = dsl->dsl_ds;

	if (ds->ds_state != DL_IDLE)
	    continue;

	if (dp_match_sap(&ph, &dsl->dsl_dlsap))
	    continue;

	DLOG1("dp_mp_input: matched 0x%x\n", *(ulong *)&dsl->dsl_dlsap);

	if (rq) {
	    if (dmp = dupmsg(mp))
		putnext(rq, dmp);
	}
	rq = ds->ds_urq;
    }
    if (rq)
	putnext(rq, mp);
    else
	freemsg(mp);
    rw_exit(&dp->dp_dsllock);
}

static void
dp_ioc_ack(dp, mp)
dp_t *dp;
mblk_t *mp;
{
    dpstr_t *ds;
    struct iocblk *ioc = (struct iocblk *)mp->b_rptr;

    DLOG1("dp_ioc_ack: %x\n", ioc->ioc_id);

    /*
     * Search through all Streams attached to this PPA to find
     * the outstanding ioctl request.
     */
    rw_enter(&dp->dp_dslock, RW_READER);
    for ( ds = dp->dp_ds ; ds ; ds = ds->ds_ppanext ) {
	if (ds->ds_ioc_id == ioc->ioc_id) {
	    putnext(ds->ds_urq, mp);
	    rw_exit(&dp->dp_dslock);
	    return;
	}
    }
    rw_exit(&dp->dp_dslock);
    DLOG("dp_ioc_ack: no matching M_IOCTL ?!?\n");
    freemsg(mp);
}

static void
dp_rctl(dp, ctl)
dp_t *dp;
int ctl;
{
    switch (ctl) {
     case DP_IF_IERROR:
	dp->dp_ierrors++;
	DLOG1("dp_rctl: input error inc to %d\n", dp->dp_ierrors);
	break;
     case DP_IF_OERROR:
	dp->dp_oerrors++;
	DLOG1("dp_rctl: output error inc to %d\n", dp->dp_oerrors);
	break;
     default:
	DLOG1("dp_rctl: unkown M_CTL message %d\n", ctl);
	break;
    }
}

#ifdef	DEBUGS
static void
dp_dump_msg(mp)
mblk_t *mp;
{
    unsigned char *p;
    char *l;
    char lnbuf[81];
    int n, m;

    if (!dp_debug_dump_msg)
	return;
    p = mp->b_rptr;
    m = 0;
    while (mp) {
	(void)sprintf(lnbuf, "mp%d(%x):", m, mp->b_datap->db_type);
	l = lnbuf + strlen(lnbuf);
	for (n = 0x10 ; n && p < mp->b_wptr ; p++, n--) {
	    if ((p - mp->b_rptr) % 4 == 0)
		*l++ = ' ';
	    (void)sprintf(l, "%x%x", (*p >> 4) & 0xf, *p & 0xf);
	    l += 2;
	}
	*l++ = '\n';
	*l++ = '\0';
	DLOG(lnbuf);
	if (p >= mp->b_wptr && (mp = mp->b_cont)) {
	    p = mp->b_rptr;
	    m++;
	}
    }
}
#endif


static void
dp_dlsap2ph(ph, sap)
phdr_t *ph;
ulong sap;
{
#ifdef	IP_ETHERCENTRIC
    switch (sap) {
     case ETHERTYPE_IP:
     case 0:
	sap = PPP_IP;
	break;
    }
#endif
    ph->ph_protocol = htons(sap & 0xffff);
    ph->ph_control = (sap & 0x00ff0000) ? (sap >> 16) & 0xff : PPP_UI;
    ph->ph_address = (sap & 0xff000000) ? (sap >> 24) & 0xff : PPP_ALLSTATIONS;
}

static void
dp_ph2dlsap(dlsap, ph)
uchar_t *dlsap;
phdr_t *ph;
{
    ulong sap;

    sap = ph->ph_address << 24
	| ph->ph_control << 16
	| ntohs(ph->ph_protocol);

    bcopy((caddr_t)&sap, (caddr_t)dlsap, sizeof(sap));
    
}

#define	DPT_U2K(t)	((dpt->t == DPT_NO_TIMEOUT) ? dpt->t :		\
			 ((dpt->t == DPT_DEF_TIMEOUT) ?			\
			  dp_def_timeouts.t / DPT_HZ : dpt->t / DPT_HZ))
#define	DPT_K2U(t)	((t == DPT_NO_TIMEOUT) ? t : t * DPT_HZ)

static void
dp_settimeos(dp, dpt)
dp_t *dp;
dp_timeo_t *dpt;
{
    mutex_enter(&dp->dp_timerlock);
    dp->dp_timeout[DPT_ACTIVE]   = DPT_U2K(dt_active);
    dp->dp_timeout[DPT_ACTIVE_C] = DPT_U2K(dt_active_c);
    dp->dp_timeout[DPT_ACTIVE_U] = DPT_U2K(dt_active_u);
    dp->dp_timeout[DPT_WAITING]  = DPT_U2K(dt_waiting);
    dp->dp_timeout[DPT_FAILCALL] = DPT_U2K(dt_failcall);
    dp->dp_timeout[DPS_DISCON]   = DPT_NO_TIMEOUT;
    dp->dp_timeout[DPS_DOWN]     = DPT_NO_TIMEOUT;

    dp->dp_flags &= ~(DPF_DOACTIVE|DPF_DOTCP);

    if (dp->dp_timeout[DPT_ACTIVE] > 0)
	dp->dp_flags |= DPF_DOACTIVE;
    if (dp->dp_timeout[DPT_ACTIVE_C] > 0 ||
	dp->dp_timeout[DPT_ACTIVE_U] > 0)
	dp->dp_flags |= (DPF_DOTCP|DPF_DOACTIVE);
    mutex_exit(&dp->dp_timerlock);
}

static void
dp_gettimeos(dp, dpt)
dp_t *dp;
dp_timeo_t *dpt;
{
    mutex_enter(&dp->dp_timerlock);
    dpt->dt_active   = DPT_K2U(dp->dp_timeout[DPT_ACTIVE]);
    dpt->dt_active_c = DPT_K2U(dp->dp_timeout[DPT_ACTIVE_C]);
    dpt->dt_active_u = DPT_K2U(dp->dp_timeout[DPT_ACTIVE_U]);
    dpt->dt_waiting  = DPT_K2U(dp->dp_timeout[DPT_WAITING]);
    dpt->dt_failcall = DPT_K2U(dp->dp_timeout[DPT_FAILCALL]);
    mutex_exit(&dp->dp_timerlock);
}

static void
dp_state(dp, state)
dp_t *dp;
int state;
{
#ifdef	DEBUGS
    u_short ostate = dp->dp_state;
#endif
    
    if (dp->dp_state == state)
	return;

    dp->dp_state = state;
    dp->dp_timer = dp->dp_timeout[state];
    dp->dp_ftimer = DPT_NO_TIMEOUT;

    /*
     * Manipulate the status of the message queues.
     */
    switch (dp->dp_state) {
     case DPS_WAITING:
	dp_sap_enable(dp, DP_PROTO_SAP(PPP_NET), 0);
	break;
     case DPS_ACTIVE:
	dp_uwq_enable(dp, 0);
	dp_active_init(dp);
	break;
     case DPS_FAILCALL:
     case DPS_DISCON:
	dp_flush(dp);
	dp_uwq_enable(dp, 1);
    }

    DLOG4("dp_state: %s -> %s, timer %d ftimer %d\n",
	  dps_str[ostate], dps_str[state], dp->dp_timer, dp->dp_ftimer);
    /*
     * Make sure the timer routine is either active or inactive
     * depending on our current needs.
     */
    if (dp->dp_timer > 0 || dp->dp_ftimer > 0) {
	if ((dp->dp_flags & DPF_TIMEO) == 0)
	    dp_timeout(dp);
    }
    else {
	if (dp->dp_flags & DPF_TIMEO)
	    dp_untimeout(dp);
    }
}

static void
dp_active_init(dp)
dp_t *dp;
{
    dptcpcon_t *dc_tab = dp->dp_tcptab.dt_con;
    dptcpcon_t *dc;

    mutex_enter(&dp->dp_tcptablock);
    bzero((caddr_t)&dp->dp_tcptab, sizeof(dp->dp_tcptab));

    for (dc = dc_tab + DP_DEF_ACTIVE - 1 ; dc >= dc_tab ; dc--) {
	dc->dc_prev = dc - 1;
	dc->dc_next = dc + 1;
    }
    dc_tab[0].dc_prev = &dc_tab[DP_DEF_ACTIVE - 1];
    dc_tab[DP_DEF_ACTIVE - 1].dc_next = &dc_tab[0];

    dp->dp_tcptab.dt_mru = dc_tab;
    dp->dp_nactive = 0;
    mutex_exit(&dp->dp_tcptablock);
}

static void
dp_activity(dp, pmp, direction)
dp_t *dp;
mblk_t *pmp;
int direction;
{
    phdr_t *ph = (phdr_t *)pmp->b_rptr;
    mblk_t *imp;
    struct ip *ip;
    struct tcphdr *th;
    dptcptab_t *dt = &dp->dp_tcptab;
    dptcpcon_t *dc,
	       *dc_last;
    struct in_addr ip_src, ip_dst;
    u_short tcp_src, tcp_dst;
    u_int hlen, len;

    if (!(dp->dp_flags & DPF_DOACTIVE))
	return;
    /*
     * Only concern ourselves with genuine network traffic.
     * PPP may be doing stuff that we don't care about (like LQM).
     */
    if ((ntohs(ph->ph_protocol) & DPP_PROTO_MASK) != PPP_NET)
	return;

    /*
     * Don't do much if we don't have fast timeouts set, or if the
     * packet is some protocol other than IP.
     */

    if (!(dp->dp_flags & DPF_DOTCP)) {
	dp_event(dp, DPT_ACTIVE);
	return;
    }

    /*
     * Do something appropriate based on the protocol involved.
     * IP seems to be the only important protocol at this point.
     */
    switch (ntohs(ph->ph_protocol)) {
     case PPP_IP:
	break;

     default:
	DLOG("dp_activity: non-ip\n");
	dp_event(dp, DPT_ACTIVE_U);
	return;
    }

    imp = pmp->b_cont;
    ip = (struct ip *)imp->b_rptr;
    len = imp->b_wptr - imp->b_rptr;
    if (len < sizeof(struct ip)) {
	DLOG("dp_activity: len\n");
	return;
    }

    if (ip->ip_p != IPPROTO_TCP) {
	DLOG("dp_activity: non-tcp\n");
	dp_event(dp, DPT_ACTIVE_U);
	return;
    }

    hlen = ip->ip_hl << 2;
    if (len < hlen + sizeof(struct tcphdr)) {
	DLOG("dp_activity: len2\n");
	return;
    }

    th = (struct tcphdr *)&((char *)ip)[hlen];
    if (direction == DC_XMIT_ACTIVE) {
	ip_src = ip->ip_src;
	ip_dst = ip->ip_dst;
	tcp_src = th->th_sport;
	tcp_dst = th->th_dport;
    }
    else {
	ip_src = ip->ip_dst;
	ip_dst = ip->ip_src;
	tcp_src = th->th_dport;
	tcp_dst = th->th_sport;
    }
    DLOG5("dp_activity: %x:%d %s %x:%d\n",
	  ip_src.s_addr, tcp_src,
	  (direction == DC_XMIT_ACTIVE) ? "->" : "<-",
	  ip_dst.s_addr, tcp_dst);

    mutex_enter(&dp->dp_tcptablock);

    dc = dt->dt_mru;

    if (ip_src.s_addr == dc->dc_ip_src.s_addr &&
	ip_dst.s_addr == dc->dc_ip_dst.s_addr &&
	tcp_src == dc->dc_th_sport &&
	tcp_dst == dc->dc_th_dport) {
	DLOG("dp_activity: first in list\n");
	goto dc_found;
    }

    /*
     * Not the most recently used TCP connection, look through the
     * circularly linked list.
     */
    dc_last = dc->dc_prev;
    do {
	dc = dc->dc_next;
	if (ip_src.s_addr == dc->dc_ip_src.s_addr &&
	    ip_dst.s_addr == dc->dc_ip_dst.s_addr &&
	    tcp_src == dc->dc_th_sport &&
	    tcp_dst == dc->dc_th_dport) {
	    DLOG("dp_activity: found in list\n");
	    goto dc_found;
	}
    } while (dc != dc_last);

    /*
     *  No TCP connection found, look for the LRU inactive slot.
     */
    for (dc_last = dt->dt_mru ; dc != dc_last ; dc = dc->dc_prev)
	if (!dc->dc_active)
	    goto dc_newstate;
    
    /*
     * Reuse the LRU active slot.  Our table wasn't big enough..
     * Forget about the LRU TCP connection..
     */
    DLOG("dp_activity: table full, reusing LRU slot\n");
    if (dc->dc_active & DC_XMIT_ACTIVE)
	dp->dp_nactive--;
    if (dc->dc_active & DC_RECV_ACTIVE)
	dp->dp_nactive--;
    dc = dt->dt_mru->dc_prev;

    /*
     * Three interesting possibilities exist:
     *	NEW	TCP connection that we have not seen before
     *  SYN	inactive TCP connection opening
     *	FIN|RST	active TCP connection closing
     */
dc_newstate:

    dc->dc_ip_src = ip_src;
    dc->dc_ip_dst = ip_dst;
    dc->dc_th_sport = tcp_src;
    dc->dc_th_dport = tcp_dst;
    dc->dc_active = direction;
    dp->dp_nactive++;
    DLOG1("dp_activity: NEW %d\n", dp->dp_nactive);
    goto dc_mru;

dc_found:

    if (th->th_flags & (TH_SYN|TH_FIN|TH_RST)) {
	if (th->th_flags & TH_FIN) {
	    if (dc->dc_active & direction) {
		/*
		 * Conversation shutting down
		 */
		dp->dp_nactive--;
		dc->dc_active &= ~direction;
		DLOG1("dp_activity: FIN %d\n", dp->dp_nactive);
	    }
	}
	else if (th->th_flags & TH_SYN) {
	    if (!(dc->dc_active & direction)) {
		/*
		 * Conversation (re)starting
		 */
		dp->dp_nactive++;
		dc->dc_active |= direction;
		DLOG1("dp_activity: SYN %d\n", dp->dp_nactive);
	    }
	}
	else if (th->th_flags & TH_RST) {
	    if (dc->dc_active) {
		/*
		 * Conversation terminated.
		 */
		if (dc->dc_active & DC_XMIT_ACTIVE)
		    dp->dp_nactive--;
		if (dc->dc_active & DC_RECV_ACTIVE)
		    dp->dp_nactive--;
		dc->dc_active = 0;
		DLOG1("dp_activity: RST %d\n", dp->dp_nactive);
	    }
	}
    }
    dp_event(dp, dp->dp_nactive ? DPT_ACTIVE : DPT_ACTIVE_C);

dc_mru:

    /*
     * Make sure this TCP connection is first in the MRU list.
     */
    if (dc != dt->dt_mru) {
	if (dt->dt_mru->dc_prev == dc)
	    dt->dt_mru = dc;
	else {
	    dc->dc_prev->dc_next = dc->dc_next;
	    dc->dc_next->dc_prev = dc->dc_prev;

	    dc->dc_next = dt->dt_mru;
	    dc->dc_prev = dt->dt_mru->dc_prev;

	    dc->dc_next->dc_prev = dc;
	    dc->dc_prev->dc_next = dc;

	    dt->dt_mru = dc;
	}
    }
    mutex_exit(&dp->dp_tcptablock);
}

static void
dp_event(dp, timer)
dp_t *dp;
int timer;
{
    int timo;
    mutex_enter(&dp->dp_timerlock);
    dp->dp_timer = dp->dp_timeout[dp->dp_state];
    switch (timer) {
     case DPT_ACTIVE_U:
     case DPT_ACTIVE_C:
	if ((timo = dp->dp_timeout[timer]) >= 0 && timo > dp->dp_ftimer) 
	    dp->dp_ftimer = timo;
	break;
    }
    /*
     * Make sure the timer routine is either active or inactive
     * depending on our current needs.
     */
    if (dp->dp_timer > 0 || dp->dp_ftimer > 0) {
	if ((dp->dp_flags & DPF_TIMEO) == 0)
	    dp_timeout(dp);
    }
    else {
	if (dp->dp_flags & DPF_TIMEO)
	    dp_untimeout(dp);
    }
    mutex_exit(&dp->dp_timerlock);
}

/*
 *
 * dp_callstat() provides for the following state transitions..
 *
 * State	Status 		New State
 * FAILCALL	IN_PROGRESS	WAITING		(dplogin starting..)
 * DISCON	IN_PROGRESS	WAITING		(dplogin starting..)
 * WAITING	FAILURE		FAILCALL	(couldn't complete call)
 * WAITING	NO_MODEM	DISCON		(no modems, immediate retry ok)
 * WAITING	SUCCESS		ACTIVE		(start packets flowing)
 */
	
static void
dp_callstat(dp, stat)
dp_t *dp;
int stat;
{
#ifdef	DEBUGS
    if (dp_debug) {
	if (DP_VALID_STATUS(stat)) {
	    DLOG2("dp_callstat: state %s stat %s\n",
		dps_str[dp->dp_state], dpcs_str[stat]);
	}
	else {
	    DLOG2("dp_callstat: state %s stat %d\n",
		dps_str[dp->dp_state], stat);
	}
    }
#endif
    mutex_enter(&dp->dp_timerlock);
    switch (dp->dp_state) {
     case DPS_DOWN:
     case DPS_ACTIVE:
	DLOG("dp_callstat: unexpected status\n");
	break;

     /*
      * Not connected, so transition to WAITING if dplogin is starting up..
      */
     case DPS_FAILCALL:
     case DPS_DISCON:
	switch (stat) {
	 case DP_IN_PROGRESS:
	    dp->dp_callincoming++;
	    dp_state(dp, DPS_WAITING);
	    break;
	 case DP_SUCCESS:
	    dp_state(dp, DPS_ACTIVE);
	    break;
	 default:
	    DLOG("dp_callstat: unexpected status\n");
	    break;
	}
	break;

     /*
      * Waiting for a connection to be established.
      * Respond properly to the result returned.
      */
     case DPS_WAITING:
	switch (stat) {
	 case DP_SUCCESS:
	    dp->dp_callcomplete++;
	    dp_state(dp, DPS_ACTIVE);
	    break;
	 case DP_FAILURE:
	    dp->dp_callfails++;
	    dp_state(dp, DPS_FAILCALL);
	    break;
	 case DP_NO_MODEM:
	    dp->dp_nomodem++;
	    dp_state(dp, DPS_DISCON);
	    break;
	 case DP_IN_PROGRESS:
	    dp->dp_callattempts++;
	    break;
	 default:
	    DLOG("dp_callstat: unexpected status\n");
	    break;
	}
	break;

     default:
	DLOG("dp_callstat: unexpected state\n");
	break;
    }
    mutex_exit(&dp->dp_timerlock);
}

/*
 * On timeout, the following state transitions are made:
 *
 *	Old State	New State
 *	FAILCALL	DISCON
 *	ACTIVE		DISCON
 *	WAITING		FAILCALL
 */
static void
dp_timer(dp)
dp_t *dp;
{
    mutex_enter(&dp->dp_timerlock);

    DLOG2("dp_timer: timer %d ftimer %d\n", dp->dp_timer, dp->dp_ftimer);
    /*
     * Count down timers
     */
    if (dp->dp_timer > 0)
	dp->dp_timer--;
    if (dp->dp_ftimer > 0)
	dp->dp_ftimer--;

    /*
     * If the fast timer expired, but we have active connections,
     * disable the fast timer.
     */
    if (dp->dp_ftimer == 0 && dp->dp_nactive) {
	DLOG("dp_timer: fast timer ignored\n");
	dp->dp_ftimer = DPT_NO_TIMEOUT;
    }

    /*
     * Normal case: nothing timed out yet.
     */
    if (dp->dp_timer && dp->dp_ftimer) {
	dp->dp_timeid = timeout((void (*)())dp_timer, (caddr_t)dp, dp_ticks);
	mutex_exit(&dp->dp_timerlock);
	return;
    }

    /*
     * A timeout has occured.
     */
    dp->dp_flags &= ~DPF_TIMEO;	/* Indicate no dp_timer call pending. */

    DLOG1("dp_timer: timeout %s\n", dps_str[dp->dp_state]);
    switch (dp->dp_state) {
     case DPS_DOWN:
     case DPS_DISCON:
	/*
	 * Nothing to do here.
	 */
	break;
     case DPS_WAITING:
	/*
	 * Assume that the dialup program hung, and treat as a failed call.
	 */
	dp_state(dp, DPS_FAILCALL);
	dp_send_sigmsg(dp, DP_PROTO_SAP(PPP_LCP), SIGTERM);
	break;

     case DPS_ACTIVE:
	/*
	 * Shut down the line for now by sending a message to the PPP process.
	 */
	dp_send_sigmsg(dp, DP_PROTO_SAP(PPP_LCP), SIGTERM);
	break;

     case DPS_FAILCALL:
	/*
	 * Reenable the line for call attempts.
	 */
	dp_state(dp, DPS_DISCON);
	break;
    }
    mutex_exit(&dp->dp_timerlock);
}

static void
dp_timeout(dp)
dp_t *dp;
{ 
    if (dp->dp_flags & DPF_TIMEO)
	return;
    dp->dp_timeid = timeout((void (*)())dp_timer, (caddr_t)dp, dp_ticks);
    dp->dp_flags |= DPF_TIMEO;
    DLOG2("dp_timeout: (%d) dp_timer in %d ticks\n", dp->dp_timeid, dp_ticks);
}

static void
dp_untimeout(dp)
dp_t *dp;
{
    if (!(dp->dp_flags & DPF_TIMEO))
	return;
    DLOG1("dp_untimeout: %d\n", dp->dp_timeid);
    untimeout(dp->dp_timeid);
    dp->dp_flags &= ~DPF_TIMEO;
}
