/*
 * ppp_async.c - Streams async functions Also does FCS
 *
 * Copyright (C) 1990  Brad K. Clements, All Rights Reserved
 * fcstab and some ideas nicked from if_ppp.c from cmu.
 * See copyright notice in if_ppp.h and NOTES
 *
 * $Id: pppasync.c,v 1.25 1994/04/27 18:40:05 ks Exp $
 *
 *
 * 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.
 *
 * Note: this copyright applies to portions of this software developed
 * at Purdue beyond the software covered by the original copyright.
 */


#include <sys/param.h>
#include <sys/types.h>
#include <sys/stream.h>
#include <sys/stropts.h>

#ifdef	SOLARIS1
#include <sys/user.h>
#include <sys/kmem_alloc.h>
#else
#include <sys/kmem.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#endif
#include <sys/errno.h>

#include "ppp.h"
#include "dpio.h"

#ifdef	DEBUGS
#include <sys/syslog.h>
#define	static	
#ifdef	SOLARIS1
#define	DLOG(s)		if (ppp_async_debug) log(LOG_INFO, (s))
#define	DLOG1(s, a)	if (ppp_async_debug) log(LOG_INFO, (s), (a))
#define	DLOG2(s, a, b)	if (ppp_async_debug) log(LOG_INFO, (s), (a), (b))
#else
#define	DLOG(s)		if (ppp_async_debug) cmn_err(CE_CONT, (s))
#define	DLOG1(s, a)	if (ppp_async_debug) cmn_err(CE_CONT, (s), (a))
#define	DLOG2(s, a, b)	if (ppp_async_debug) cmn_err(CE_CONT, (s), (a), (b))
#endif

int	ppp_async_debug=0;
int	ppp_async_input_debug=0;
#define MAX_DUMP_BYTES 1504
#else
#define	DLOG(s)		{}
#define	DLOG1(s, a)	{}
#define	DLOG2(s, a, b)	{}
#endif

#ifdef	SOLARIS1
/*
 * Define a number of facilities to allow the code to look more
 * like Solaris 2.X.
 */
#define D_MP 0
#define qprocson(x)
#define qprocsoff(x)
#define canputnext(q)   (canput((q)->q_next))
#define KM_SLEEP        KMEM_SLEEP
#define KM_NOSLEEP      KMEM_NOSLEEP
#define	const
#endif


#ifdef	__STDC__
static int ppp_async_open(queue_t *, dev_t *, int, int, cred_t *);
static int ppp_async_close(queue_t *, cred_t *);
static int ppp_async_rput(queue_t *, mblk_t *);
static int ppp_async_rsrv(queue_t *);
static int ppp_async_wput(queue_t *, mblk_t *);
static int ppp_async_wsrv(queue_t *);
#else
static int ppp_async_open(/* queue_t *, dev_t *, int, int, cred_t * */);
static int ppp_async_close(/* queue_t *, cred_t * */);
static int ppp_async_rput(/* queue_t *, mblk_t * */);
static int ppp_async_rsrv(/* queue_t * */);
static int ppp_async_wput(/* queue_t *, mblk_t * */);
static int ppp_async_wsrv(/* queue_t * */);
#endif

static 	struct	module_info	minfo ={
    0xabcd, "pppasync", 0, INFPSZ, 16384, 4096
};

static	struct	qinit	r_init = {
    ppp_async_rput, ppp_async_rsrv, ppp_async_open, ppp_async_close,
    NULL, &minfo, NULL
};
static	struct	qinit	w_init = {
    ppp_async_wput, ppp_async_wsrv, ppp_async_open, ppp_async_close,
    NULL, &minfo, NULL
};
struct	streamtab	pppasyncinfo = {
    &r_init, &w_init, NULL, NULL
};

/*
 * FCS lookup table as calculated by genfcstab.
 */
static const u_short fcstab[256] = {
	0x0000,	0x1189,	0x2312,	0x329b,	0x4624,	0x57ad,	0x6536,	0x74bf,
	0x8c48,	0x9dc1,	0xaf5a,	0xbed3,	0xca6c,	0xdbe5,	0xe97e,	0xf8f7,
	0x1081,	0x0108,	0x3393,	0x221a,	0x56a5,	0x472c,	0x75b7,	0x643e,
	0x9cc9,	0x8d40,	0xbfdb,	0xae52,	0xdaed,	0xcb64,	0xf9ff,	0xe876,
	0x2102,	0x308b,	0x0210,	0x1399,	0x6726,	0x76af,	0x4434,	0x55bd,
	0xad4a,	0xbcc3,	0x8e58,	0x9fd1,	0xeb6e,	0xfae7,	0xc87c,	0xd9f5,
	0x3183,	0x200a,	0x1291,	0x0318,	0x77a7,	0x662e,	0x54b5,	0x453c,
	0xbdcb,	0xac42,	0x9ed9,	0x8f50,	0xfbef,	0xea66,	0xd8fd,	0xc974,
	0x4204,	0x538d,	0x6116,	0x709f,	0x0420,	0x15a9,	0x2732,	0x36bb,
	0xce4c,	0xdfc5,	0xed5e,	0xfcd7,	0x8868,	0x99e1,	0xab7a,	0xbaf3,
	0x5285,	0x430c,	0x7197,	0x601e,	0x14a1,	0x0528,	0x37b3,	0x263a,
	0xdecd,	0xcf44,	0xfddf,	0xec56,	0x98e9,	0x8960,	0xbbfb,	0xaa72,
	0x6306,	0x728f,	0x4014,	0x519d,	0x2522,	0x34ab,	0x0630,	0x17b9,
	0xef4e,	0xfec7,	0xcc5c,	0xddd5,	0xa96a,	0xb8e3,	0x8a78,	0x9bf1,
	0x7387,	0x620e,	0x5095,	0x411c,	0x35a3,	0x242a,	0x16b1,	0x0738,
	0xffcf,	0xee46,	0xdcdd,	0xcd54,	0xb9eb,	0xa862,	0x9af9,	0x8b70,
	0x8408,	0x9581,	0xa71a,	0xb693,	0xc22c,	0xd3a5,	0xe13e,	0xf0b7,
	0x0840,	0x19c9,	0x2b52,	0x3adb,	0x4e64,	0x5fed,	0x6d76,	0x7cff,
	0x9489,	0x8500,	0xb79b,	0xa612,	0xd2ad,	0xc324,	0xf1bf,	0xe036,
	0x18c1,	0x0948,	0x3bd3,	0x2a5a,	0x5ee5,	0x4f6c,	0x7df7,	0x6c7e,
	0xa50a,	0xb483,	0x8618,	0x9791,	0xe32e,	0xf2a7,	0xc03c,	0xd1b5,
	0x2942,	0x38cb,	0x0a50,	0x1bd9,	0x6f66,	0x7eef,	0x4c74,	0x5dfd,
	0xb58b,	0xa402,	0x9699,	0x8710,	0xf3af,	0xe226,	0xd0bd,	0xc134,
	0x39c3,	0x284a,	0x1ad1,	0x0b58,	0x7fe7,	0x6e6e,	0x5cf5,	0x4d7c,
	0xc60c,	0xd785,	0xe51e,	0xf497,	0x8028,	0x91a1,	0xa33a,	0xb2b3,
	0x4a44,	0x5bcd,	0x6956,	0x78df,	0x0c60,	0x1de9,	0x2f72,	0x3efb,
	0xd68d,	0xc704,	0xf59f,	0xe416,	0x90a9,	0x8120,	0xb3bb,	0xa232,
	0x5ac5,	0x4b4c,	0x79d7,	0x685e,	0x1ce1,	0x0d68,	0x3ff3,	0x2e7a,
	0xe70e,	0xf687,	0xc41c,	0xd595,	0xa12a,	0xb0a3,	0x8238,	0x93b1,
	0x6b46,	0x7acf,	0x4854,	0x59dd,	0x2d62,	0x3ceb,	0x0e70,	0x1ff9,
	0xf78f,	0xe606,	0xd49d,	0xc514,	0xb1ab,	0xa022,	0x92b9,	0x8330,
	0x7bc7,	0x6a4e,	0x58d5,	0x495c,	0x3de3,	0x2c6a,	0x1ef1,	0x0f78
};


typedef struct  ppp_async_info {
    u_short pai_flags;		/* status flags */
    u_short pai_fcs;		/* the current fcs */
    u_long  pai_asyncmap;	/* current outgoing asyncmap */
    int     pai_buffsize;	/* how big of an input buffer to alloc */
    int     pai_buffcount;	/* # chars currently in the input buffer */
    mblk_t  *pai_buffer;	/* pointer to the current buffer list */
    mblk_t  *pai_bufftail;	/* pointer to the current input block */
    struct ppp_async_info
	    *next_pai;		/* next PAI in active list */
} pai_t;

/*
 * Values for pai_flags
 */
#define	PAI_FLAGS_FLUSH		0x01
#define	PAI_FLAGS_ESCAPED 	0x02
#define	PAI_FLAGS_COMPPROT	0x04
#define	PAI_FLAGS_COMPAC	0x08

static pai_t *pais;		/* Our list of async control structures */
static kmutex_t pais_lock;

#define	ALLOCBSIZE	128	/* Maximum possible IP + TCP header */

#ifdef	SOLARIS1
static int
ppp_async_open(rq, dev, flag, sflag)
queue_t *rq;
dev_t dev;
int flag,
    sflag;
#else
static int
ppp_async_open(rq, devp, flag, sflag, credp)
queue_t *rq;
dev_t *devp;
int flag,
    sflag;
cred_t *credp;
#endif
{
    register pai_t	*p;
    register int x;
    int	s;

    DLOG("ppp_async_open\n");
    if (rq->q_ptr)
	return 0;

#ifdef	SOLARIS1
    s = splstr();
#endif
    if ((p = (pai_t *)kmem_zalloc(sizeof(pai_t), KM_SLEEP)) == NULL) {
#ifdef	SOLARIS1
	splx(s);
	u.u_error = ENOMEM;
	return OPENFAIL;
#else
	return ENOMEM;
#endif
    }

    rq->q_ptr = WR(rq)->q_ptr = (caddr_t)p;
    p->pai_asyncmap = 0xffffffff; /* default async map */
    p->pai_buffsize = PPP_MTU + PPP_HDR_SIZE + sizeof(u_short) + 1;
    p->pai_flags = PAI_FLAGS_FLUSH;
    DLOG("ppp_async: opening\n");
    mutex_enter(&pais_lock);
    p->next_pai = pais;
    pais = p;
    mutex_exit(&pais_lock);
    qprocson(rq);
#ifdef	SOLARIS1
    splx(s);
#endif
    return(0);
}

#ifdef	SOLARIS1
static int
ppp_async_close(rq)
queue_t *rq;
#else
static int
ppp_async_close(rq, credp)
queue_t *rq;
cred_t *credp;
#endif
{
    int	s;
    register pai_t 	*p, **pp;

    DLOG("ppp_async_close\n");
#ifdef	SOLARIS1
    s = splstr();
#endif
    qprocsoff(rq);

    mutex_enter(&pais_lock);

    for (pp = &pais ; p = *pp ; pp = &p->next_pai)
	if (p == (pai_t *)rq->q_ptr)
	    break;
    *pp = p->next_pai;

    mutex_exit(&pais_lock);

    /*
     * Free associated memory.
     */
    if (p->pai_buffer)
	freemsg(p->pai_buffer);
    kmem_free((caddr_t)p, sizeof(pai_t));

    rq->q_ptr = WR(rq)->q_ptr = (caddr_t)0;


    DLOG("ppp_async: closing\n");
#ifdef	SOLARIS1
    splx(s);
#endif
    return(0);			
}


/*
 * M_IOCTL processing is performed at this level. There is some 
 * weirdness here, but I couldn't think of an easier way to handle it.
 *
 * DPIOCSASYNCMAP and DPIOCGASYNCMAP is handled here.
 *
 * DPIOCSCOMPAC and DPIOCSCOMPPROT are both handled here, rather
 * than jamming new flag bits into the if_ interface.  However, older
 * versions of the upper modules may note these ioctls as they go by.
 *
 * DPIOCSMRU and DPIOCGMRU (Max Receive Unit) are both handled here.
 * Rather than using the MTU to set the MRU, we have a seperate IOCTL for it.
 *
 */
static int
ppp_async_wput(q, mp)
queue_t  *q;
register mblk_t *mp;
{
    switch (mp->b_datap->db_type) {
     case M_FLUSH:
	if (*mp->b_rptr & FLUSHW)
	    flushq(q, FLUSHDATA);
	putnext(q, mp);			/* send it along too */
	break;

      case M_DATA:
	putq(q, mp);			/* queue it for my service routine */
	break;

      case M_IOCTL:
	ppp_async_ioc(q, mp);
	break;

     default:
	putnext(q,mp);	/* don't know what to do with this, so send it along*/
    }
}
	

static int
ppp_async_ioc(q, mp)
queue_t *q;
mblk_t *mp;
{
    register struct iocblk	*ioc;
    register pai_t	*p;
    int	x, flags;
    int err = 0;

    ioc = (struct iocblk *) mp->b_rptr;
    p =  (pai_t *) q->q_ptr;

    if (ioc->ioc_count == TRANSPARENT) {
	/*
	 * No TRANSPARENT ioctl support.
	 */
	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 pa_ioc_ret;
    }
    switch (ioc->ioc_cmd) {
     case DPIOCSCOMPAC:	/* enable or disable AC compression */
	if (ioc->ioc_count != sizeof(u_char)) {
	    err = EINVAL;
	    break;
	}
	DLOG1("ppp_async: SCOMPAC %d\n", *(u_char *)mp->b_cont->b_rptr);
	if ( *(u_char *) mp->b_cont->b_rptr) 
	    p->pai_flags |= PAI_FLAGS_COMPAC;
	else
	    p->pai_flags &= ~PAI_FLAGS_COMPAC;
	break;

     case DPIOCSCOMPPROT:	/* enable or disable PROT  compression */
	if (ioc->ioc_count != sizeof(u_char)) {
	    err = EINVAL;
	    break;
	}
	DLOG1("ppp_async: SCOMPPROT %d\n", *(u_char *)mp->b_cont->b_rptr);
	if ( *(u_char *) mp->b_cont->b_rptr) 
	    p->pai_flags |= PAI_FLAGS_COMPPROT;
	else
	    p->pai_flags &= ~PAI_FLAGS_COMPPROT;
	break;

     case DPIOCSMRU:
	if (ioc->ioc_count != sizeof(int)) {
	    err = EINVAL;
	    break;
	}
	x = *(int *) mp->b_cont->b_rptr;
	if (x < PPP_MTU)
	    x = PPP_MTU;
	x += PPP_HDR_SIZE + sizeof(u_short) + 1;
	if (x > 4096) {		/* couldn't allocb something this big */
	    err = EINVAL;
	    break;
	}
	p->pai_buffsize = x;
	break;

     case DPIOCGMRU:
	if (ioc->ioc_count != sizeof(int)) {
	    err = EINVAL;
	    break;
	}
	*(int *)mp->b_cont->b_wptr = 
		p->pai_buffsize - (PPP_HDR_SIZE + sizeof(u_short) + 1);
	goto pa_ioc_data_ret;

     case DPIOCGASYNCMAP:
	if (ioc->ioc_count != sizeof(u_long)) {
	    err = EINVAL;
	    break;
	}
	*(u_long *) mp->b_cont->b_wptr = p->pai_asyncmap;
	goto pa_ioc_data_ret;

     case DPIOCSASYNCMAP:
	if (ioc->ioc_count != sizeof(u_long)) {
	    err = EINVAL;
	    break;
	}
	DLOG1("ppp_async: SASYNCMAP %x\n",
	      *(u_long *)mp->b_cont->b_rptr);

	p->pai_asyncmap = *(u_long *) mp->b_cont->b_rptr;
	break;

     case DPIOCGASDEBUG :
	if (ioc->ioc_count != sizeof(int)) {
	    err = EINVAL;
	    break;
	}
	*(int *)mp->b_cont->b_wptr = (ppp_async_debug ? 1 : 0)
				   | (ppp_async_input_debug ? 2 : 0);
	goto pa_ioc_data_ret;

     case DPIOCSASDEBUG :
	if (ioc->ioc_count != sizeof(int)) {
	    err = EINVAL;
	    break;
	}
	flags = *(int *)mp->b_cont->b_rptr;
	DLOG1("ppp_async: SASDEBUG %x\n", flags);

	ppp_async_debug = flags & 1;
	ppp_async_input_debug = flags & 2;
	break;

     default:
	putnext(q, mp);
	return;
    }

pa_ioc_ret:
    if (mp->b_cont) {
	freemsg(mp->b_cont);
	mp->b_cont = NULL;
    }
    ioc->ioc_count = 0;
pa_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(q, mp);
}

/*
 * This is an incredibly ugly routine. If you see a better way of
 * doing this, feel free to improve it. I'm hoping that the buffer
 * management routines are efficient, in terms of dup'ing and adjusting
 * message blocks. Hopefully we won't run out of small message blocks.
 * Perhaps some counters should be kept to record how efficient this
 * routine is, and how much break up of messages is required.  I'm not
 * doing anything funny with stuffing PPP_ESCAPES into the actual data
 * buffers.  If you get a lot of outgoing errors (under Solaris 1.X)..
 * you might need to up your NBLK4 parameter  by quite a bit.... (see param.c)
 */
static int
ppp_async_wsrv(q)
queue_t	*q;
{
    register u_char	*cp;
    register pai_t	*p = (pai_t *)q->q_ptr;
    register u_short	fcs;
    register mblk_t	*mp;
    u_char		*start, *stop,c;
    mblk_t		*m1, *m0, *outgoing;

    while ((mp = getq(q)) != NULL) {
	/*
	 * we can only get M_DATA types into our Queue,
	 * due to our Put function
	 */
	if (!canputnext(q)) {
	    putbq(q, mp);
	    return;
	}

	/*
	 * at least a ppp header and FCS required
	 */
	if (msgdsize(mp) < (PPP_HDR_SIZE + sizeof(u_short))) {	
	    freemsg(mp);		/* discard the message */
	    putctl1(OTHERQ(q), M_CTL, DP_IF_OERROR); /* indicate output err */
	    continue;
	}
	m0 = mp;			/* remember first message block */

	/*
	 * Compress PPP header, if allowed.
	 */
	if ((p->pai_flags & PAI_FLAGS_COMPAC) &&
	    *mp->b_rptr == PPP_ALLSTATIONS &&
	    *(mp->b_rptr+1) == PPP_UI)
	    mp->b_rptr += 2;
	if ((p->pai_flags & PAI_FLAGS_COMPPROT) &&
	    *mp->b_rptr == 0)
	    mp->b_rptr++;
	outgoing = NULL;
	fcs = PPP_INITFCS;

#define	SPECIAL(p,c)	((c) == PPP_FLAG) || ((c) == PPP_ESCAPE) ||  \
	((c) < 0x20 && ((p)->pai_asyncmap & (1 << (c))))

	/*
	 * For each block in the message, scan between the start and stop
	 * markers for escaped chars. dup the message block and chain in
	 * the PPP_ESCAPE char and the PPP_TRANS'd character. Continue
	 * processing for all of this data block, then for all remaining blocks
         */
	while (mp) {
	    start = mp->b_rptr;
	    stop = mp->b_wptr;
	    while (start < stop ) {
		for (cp = start; cp < stop; cp++) {
		    if (SPECIAL(p,*cp))
			break;
		    fcs = PPP_FCS(fcs, *cp);
		}

		if (cp - start) {
		    /* dup the message block, up to len chars */
#define	DB_REF_MAX	254	/* Workaround for a bug in SunOS4 */
#ifdef	DB_REF_MAX
		    m1 = (mp->b_datap->db_ref < DB_REF_MAX)
		       ? dupb(mp)
		       : copyb(mp);
#else
		    m1 = dupb(mp);
#endif
		    if (!m1)
			goto nobuffs;
		    /* discard chars at front */
		    adjmsg(m1, start - mp->b_rptr);

		    /* throw away remaining chars */
		    adjmsg(m1,  cp - stop);
		    if (outgoing)
			linkb(outgoing, m1);
		    else
			outgoing = m1;
		} 
		if (cp < stop) {	/* a special char must follow */
		    m1 = allocb(2 * sizeof(u_char),BPRI_LO);
		    if (!m1)
			goto nobuffs;
		    *m1->b_wptr++ = PPP_ESCAPE;
		    *m1->b_wptr++ = *cp ^ PPP_TRANS;
		    fcs = PPP_FCS(fcs, *cp);
		    if (outgoing)
			linkb(outgoing, m1);
		    else
			outgoing = m1;
		}
		else
		    break;	/* no sense in doing another add and compare */
		start = cp + 1;
	    }			/* end while start < stop */
	    mp = mp->b_cont;	/* look at the next block */
	}					/* end while (mp) */
	m1 = allocb(sizeof(u_char) + 2 * sizeof(u_short), BPRI_LO);
	if (!m1)
	    goto nobuffs;
	fcs ^= 0xffff;				/* XOR the resulting FCS */
	c = fcs & 0xff;
	if (SPECIAL(p,c)) {
	    *m1->b_wptr++ = PPP_ESCAPE;
	    *m1->b_wptr++ = c ^ PPP_TRANS;
	}
	else
	    *m1->b_wptr++ = c;
	c = fcs >> 8;
	if (SPECIAL(p,c)) {
	    *m1->b_wptr++ = PPP_ESCAPE;
	    *m1->b_wptr++ = c ^ PPP_TRANS;
	}
	else
	    *m1->b_wptr++  = c;

	*m1->b_wptr++ = PPP_FLAG;  /* add trailing PPP_FLAG */
	linkb(outgoing, m1);	   /* gee, we better have an outgoing by now */

	/*
	 * now we check to see if the lower queue has entries, if so, we assume
	 * that we don't need a leading PPP_FLAG because these packets
	 * will be sent back to back
	 */
	if (!qsize(q->q_next)) {
	    /* no entries in next queue, have to add a leading PPP_FLAG */
	    m1 = allocb(sizeof(u_char), BPRI_LO);
	    if (!m1)
		goto nobuffs;
	    *m1->b_wptr++ = PPP_FLAG;
	    linkb(m1, outgoing);
	    outgoing = m1;
	}

	/* phew, ready to ship */
	putnext(q, outgoing);
	freemsg(m0);		/* discard original message block pointers */
	continue;

       nobuffs:;		/* well, we ran out of memory somewhere */
	if (outgoing)
	    freemsg(outgoing);	/* throw away what we have already */
	putbq(q, m0);		/* put back the original message */
	putctl1(OTHERQ(q), M_CTL, DP_IF_OERROR);
	qenable(q);		/* reschedule ourselves for later */
	return;
    } /* end while (getq()) */
}

static int
ppp_async_rput(q, mp)
queue_t *q;
register mblk_t *mp;
{
    switch (mp->b_datap->db_type) {

     case M_FLUSH:
	if (*mp->b_rptr & FLUSHR)
	    flushq(q, FLUSHDATA);
	putnext(q, mp);			/* send it along too */
	break;

     case M_DATA:
	putq(q, mp);			/* queue it for my service routine */
	break;

     default:
	putnext(q,mp);	/* don't know what to do with this, so send it along */
    }
}

#ifdef	DEBUGS
static const char ppp_digits[] = "0123456789abcdef";
#endif

#define	INPUT_ERROR(q)	putctl1(q, M_CTL, DP_IF_IERROR)
#define	STUFF_CHAR(p,c)	(*(p)->pai_bufftail->b_wptr++ = (c), (p)->pai_buffcount++, mleft--)

static void
ppp_flush_input(q, p)
queue_t	*q;
register pai_t *p;
{
    INPUT_ERROR(q);
    p->pai_flags |= PAI_FLAGS_FLUSH;
}

static int
ppp_async_rsrv(q)
queue_t	*q;
{
    register mblk_t	*mp;
    register u_char	*cp,c;
    int			mleft;
    register pai_t	*p;
    mblk_t		*m0;

    p = (pai_t *) q->q_ptr;

    while ((mp = getq(q)) != NULL) {
	/*
	 * Our put function limits messages to type M_DATA.
	 */
	if (!canputnext(q)) {
	    putbq(q, mp);
	    return;
	}
	m0 = mp;	/* remember first message block */
	for ( ; mp != NULL; mp = mp->b_cont) {	/* for each message block */
	    cp = mp->b_rptr;
	    if (p->pai_flags & PAI_FLAGS_FLUSH) {
flush_input:
		while (cp < mp->b_wptr)
		    if (*cp++ == PPP_FLAG) {
			p->pai_flags &= ~PAI_FLAGS_FLUSH;
			if (p->pai_buffer) {
			    freemsg(p->pai_buffer);
			    p->pai_buffer = NULL;
			    p->pai_buffcount = 0;
			}
			goto hdr_alloc;
		    }
		continue;
	    }
	    if (p->pai_buffcount < PPP_HDR_SIZE) {
		/*
		 * Collect Header
		 */
		if (!p->pai_buffer) {
hdr_alloc:
		    if (!(p->pai_buffer = allocb(PPP_HDR_SIZE, BPRI_MED))) {
			DLOG("ppp_async_rsrv: can't alloc PPP header\n");
			ppp_flush_input(q, p);
			goto flush_input;
		    }
		    p->pai_bufftail = p->pai_buffer;
		    p->pai_buffcount = 0;
		}
		mleft = PPP_HDR_SIZE - p->pai_buffcount;
		while (cp < mp->b_wptr && mleft > 0) {
		    switch (c = *cp++) {
		     case PPP_FLAG:
			if (p->pai_buffcount == 0)
			    continue;
			DLOG("ppp_async_rsrv: short packet\n");
			INPUT_ERROR(q);
			p->pai_bufftail->b_wptr = p->pai_bufftail->b_rptr;
			p->pai_buffcount = 0;
			mleft = PPP_HDR_SIZE;
			continue;
		     case PPP_ESCAPE:
			p->pai_flags |= PAI_FLAGS_ESCAPED;
			continue;
		    }
		    if (p->pai_flags & PAI_FLAGS_ESCAPED) {
			p->pai_flags &= ~PAI_FLAGS_ESCAPED;
			c ^= PPP_TRANS;
		    }
		    if (p->pai_buffcount == 0) {
			p->pai_fcs = PPP_INITFCS;
			if (c != PPP_ALLSTATIONS) {
			    if (p->pai_flags & PAI_FLAGS_COMPAC) {
				STUFF_CHAR(p, PPP_ALLSTATIONS);
				STUFF_CHAR(p, PPP_UI);
			    }
			    else {
				DLOG("ppp_async_rsrv: missed ALLSTATIONS\n");
				ppp_flush_input(q,p);
				goto flush_input;
			    }
			}
		    }
		    if (p->pai_buffcount == 1 && c != PPP_UI) {
			DLOG("ppp_async_rsrv: missed UI\n");
			ppp_flush_input(q,p);
			goto flush_input;
		    }
		    if (p->pai_buffcount == 2 && (c & 1) == 1) {
			if (p->pai_flags & PAI_FLAGS_COMPPROT)
			    STUFF_CHAR(p, 0);
			else {
			    DLOG("ppp_async_rsrv: bad protocol high byte\n");
			    ppp_flush_input(q,p);
			    goto flush_input;
			}
		    }
		    if (p->pai_buffcount == 3 && (c & 1) == 0) {
			DLOG("ppp_async_rsrv: bad protocol low byte\n");
			ppp_flush_input(q,p);
			goto flush_input;
		    }
		    STUFF_CHAR(p, c);
		    p->pai_fcs = PPP_FCS(p->pai_fcs, c);
		}
		if (!mleft) {
		    if (p->pai_buffcount != PPP_HDR_SIZE)
			DLOG("ppp_async_rsrv: incomplete header ?!?\n");
		    goto data_alloc;
		}
		/* cp >= mp->b_wptr */
		continue;
	    }

	    mleft = p->pai_bufftail->b_datap->db_lim
		  - p->pai_bufftail->b_wptr;
	    if (!mleft) {
data_alloc:
		if (!(p->pai_bufftail = allocb(ALLOCBSIZE, BPRI_MED))) {
		    DLOG("ppp_async_rsrv: couldn't alloc data buffer\n");
		    ppp_flush_input(q,p);	/* discard all of it */
		    goto flush_input;
		}
		linkb(p->pai_buffer, p->pai_bufftail);
		mleft = p->pai_bufftail->b_datap->db_lim
		      - p->pai_bufftail->b_wptr;
	    }
	    if (mleft > p->pai_buffsize - p->pai_buffcount) {
		mleft = p->pai_buffsize - p->pai_buffcount;
		if (mleft == 0) {
		    DLOG1("ppp_async_rsrv: too many chars in input buffer %d\n",
			  p->pai_buffcount);
		    ppp_flush_input(q, p);
		    goto flush_input;
		}
	    }
	    /*
	     * With any luck, 95% of the input processing will happen
	     * in the next 7 lines of minimal C code.
	     */
	    for ( ; cp < mp->b_wptr && mleft ; ) {
		c = *cp++;
		if (!PPP_SPECIAL(c)) {
		    if (!(p->pai_flags & PAI_FLAGS_ESCAPED)) {
			STUFF_CHAR(p, c);
			p->pai_fcs = PPP_FCS(p->pai_fcs, c);
			continue;
		    }
		    c ^= PPP_TRANS;
		    p->pai_flags &= ~PAI_FLAGS_ESCAPED;
		    STUFF_CHAR(p, c);
		    p->pai_fcs = PPP_FCS(p->pai_fcs, c);
		    continue;
		}
		else
		    switch (c) {
		     case PPP_ESCAPE:
			if (cp < mp->b_wptr) {
			    c = *cp++ ^ PPP_TRANS;
			    STUFF_CHAR(p, c);
			    p->pai_fcs = PPP_FCS(p->pai_fcs, c);
			}
			else
			    p->pai_flags |= PAI_FLAGS_ESCAPED;
			break;

		     case PPP_FLAG:
#if DEBUGS
			if (ppp_async_input_debug)
			    ppp_input_dump(p);
#endif
			if (p->pai_fcs == PPP_GOODFCS &&
			    p->pai_buffcount > PPP_HDR_SIZE + sizeof(u_short)) {
			    adjmsg(p->pai_buffer, -sizeof(u_short));
			    putnext(q, p->pai_buffer);
			}
			else {
			    INPUT_ERROR(q);
			    freemsg(p->pai_buffer);
			    DLOG("ppp_async_rsrv: FCS Error\n");
			}
			if (cp < mp->b_wptr)
			    goto hdr_alloc;
			p->pai_buffer = NULL;
			p->pai_buffcount = 0;
			break;
		    }
	    }
	    if (!mleft)
		goto data_alloc;
	    /*
	     * Done with this message block. Go on to the next one.
	     */
	}
	/* discard this message now */
	freemsg(m0);
    }	/* end while  getq */
}

#ifdef	DEBUGS
/*
 * here is where we will dump out a frame in hex using the log() 
 * function if ppp_async_input_debug is non-zero. As this function is
 * a pig, we only print up to the number of bytes specified by the value of
 * the ppp_async_input_debug variable so as to not cause too many
 * timeouts.   <gmc@quotron.com> 
 */

ppp_input_dump(p)
register pai_t	*p;
{
    register mblk_t *mptr = p->pai_buffer;
    register u_char *rptr;
    register u_int i, mlen;
    u_int frame_length = (unsigned) p->pai_buffcount;
    char buf[2*MAX_DUMP_BYTES+4];	/* tmp buffer */
    char *bp = buf;

#ifdef	SOLARIS1
    log(LOG_INFO, "ppp_async: got input frame of %d bytes\n", frame_length); 
#else
    cmn_err(CE_CONT, "ppp_async: got input frame of %d bytes\n", frame_length); 
#endif
    rptr = mptr->b_rptr; /* get pointer to beginning  */
    mlen = mptr->b_wptr - rptr; /* get length of this dblock */

    /* only dump up to MAX_DUMP_BYTES */

    i = (frame_length < ppp_async_input_debug) ? frame_length :
	  ppp_async_input_debug;   

    while (i--) {	/* convert to ascii hex */
	if (mlen-- == 0) {/* get next dblock */
	    mptr = mptr->b_cont;
	    if (mptr) { /* are we done? */
		rptr = mptr->b_rptr; /* nope, get next dblock */
		mlen = mptr->b_wptr - rptr;
	    }
	    else {	/* no more dblocks */
		if (i != 0)
#ifdef	SOLARIS1
		    log(LOG_ERR,
			"ppp_async: ran out of data! (this shouldn't happen\n");
#else
		    cmn_err(CE_CONT,
			"ppp_async: ran out of data! (this shouldn't happen\n");
#endif
		break;
	    }
	}
	*bp++ = ppp_digits[*rptr >> 4]; /* convert to ascii hex */
	*bp++ = ppp_digits[*rptr++ & 0xf];
    }

    /* add a '>' to show that frame was truncated*/
    if (ppp_async_input_debug < frame_length)
	*bp++ = '>';
    *bp = 0;
#ifdef	SOLARIS1
    log(LOG_INFO,"ppp_async: %s\n", buf); 
#else
    cmn_err(CE_CONT,"ppp_async: %s\n", buf); 
#endif
}

nmsgb(mp)
mblk_t	*mp;
{
    int n;
    for (n = 0 ; mp ; mp = mp->b_cont)
	n++;
    return n;
}
#endif /*DEBUGS*/

#ifdef	SOLARIS1
#include <sys/conf.h>
#include <sys/buf.h>
#include <sundev/mbvar.h>
#include <sun/autoconf.h>
#include <sun/vddrv.h>

static struct vdldrv ppp_async_vd = {
    VDMAGIC_PSEUDO,
    "PPP Async",
};
static struct fmodsw *fmod_ppp_async;

xxxinit(fc,vdp,vdi,vds)
unsigned int fc;
struct vddrv *vdp;
addr_t vdi;
struct vdstat *vds;
{

    switch (fc) {
     case VDLOAD:
	{
	    int dev,i;
	    for (dev = 0; dev < fmodcnt; dev++) {
		if (fmodsw[dev].f_str == NULL)
		    break;
	    }
	    if (dev == fmodcnt)
		return ENODEV;
	    fmod_ppp_async = &fmodsw[dev];
	    for (i = 0; i <= FMNAMESZ; i++)
		fmod_ppp_async->f_name[i] = minfo.mi_idname[i];
	    fmod_ppp_async->f_str = &pppasyncinfo;
	    vdp->vdd_vdtab = (struct vdlinkage *) &ppp_async_vd;
	}
	return 0;
     case VDUNLOAD:
	if (pais)
	    return EBUSY;

	fmod_ppp_async->f_name[0] = '\0';
	fmod_ppp_async->f_str = NULL;
	return 0;
     case VDSTAT:
	return 0;
     default:
	return EIO;
    }
}
#else
/*
 * Solaris 2.X version
 */
#include <sys/modctl.h>

extern struct streamtab pppasyncinfo;

static struct fmodsw pppasyncfsw = {
    "pppasync",
    &pppasyncinfo,
    D_NEW | D_MP
};
	
extern struct mod_ops mod_strmodops;

static struct modlstrmod pppasynclstrmod = {
    &mod_strmodops,
    "PPP Async Module v3.1.2",
    &pppasyncfsw
};

static struct modlinkage pppasyncmodlinkage = {
    MODREV_1,
    (void *)&pppasynclstrmod,
    NULL
};

/*
 * Module Config Entry Points.
 */

_init()
{
    mutex_init(&pais_lock, "pppasync module list lock",
	       MUTEX_DRIVER, (void *)0);
    return (mod_install(&pppasyncmodlinkage));
}

_fini()
{
    register int ret;

    if ((ret = mod_remove(&pppasyncmodlinkage)) == DDI_SUCCESS) {
	mutex_destroy(&pais_lock);
	return ret;
    }
    else
	return EBUSY;
}

_info(modinfop)
struct modinfo *modinfop;
{
    return mod_info(&pppasyncmodlinkage, modinfop);
}
#endif
