/*
 * spppasyn.c - STREAMS module for doing PPP asynchronous HDLC.
 *
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation is hereby granted, provided that the above copyright
 * notice appears in all copies.
 *
 * SUN MAKES NO REPRESENTATION OR WARRANTIES ABOUT THE SUITABILITY OF
 * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, OR NON-INFRINGEMENT.  SUN SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES
 *
 * Copyright (c) 1994 The Australian National University.
 * All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation is hereby granted, provided that the above copyright
 * notice appears in all copies.  This software is provided without any
 * warranty, express or implied. The Australian National University
 * makes no representations about the suitability of this software for
 * any purpose.
 *
 * IN NO EVENT SHALL THE AUSTRALIAN NATIONAL UNIVERSITY BE LIABLE TO ANY
 * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
 * THE AUSTRALIAN NATIONAL UNIVERSITY HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * THE AUSTRALIAN NATIONAL UNIVERSITY SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE AUSTRALIAN NATIONAL UNIVERSITY HAS NO
 * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
 * OR MODIFICATIONS.
 *
 * $Id: ppp_ahdlc.c,v 1.16 2000/03/06 19:38:12 masputra Exp $
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/types.h>
#include <sys/param.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/sysmacros.h>
#include <sys/errno.h>
#include <sys/conf.h>
#include <sys/kmem.h>
#include <sys/crc32.h>
#include <sys/cmn_err.h>
#include <sys/ddi.h>

#include <net/ppp_defs.h>
#include <net/pppio.h>

#include "s_common.h"

#ifdef DEBUG
#define	REPORT_CRC_TYPE
#endif
#include "spppasyn.h"

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

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

static int	spppasyn_open(queue_t *, dev_t *, int, int, cred_t *);
static int	spppasyn_close(queue_t *, int, cred_t *);
static int	spppasyn_wput(queue_t *, mblk_t *);
static int	spppasyn_rput(queue_t *, mblk_t *);
static mblk_t	*ahdlc_encode(queue_t *, mblk_t *);
static mblk_t	*ahdlc_decode(queue_t *, mblk_t *);
static void	spppasyn_timer(void *);
static mblk_t	*spppasyn_inpkt(queue_t *, mblk_t *);
static mblk_t	*spppasyn_muxencode(queue_t *, mblk_t *);

#define	RESET_MUX_VALUES(x)	{	\
	x->sa_mqhead = x->sa_mqtail = NULL;	\
	x->sa_proto = 0;			\
	x->sa_mqlen = 0;			\
}
#define	IS_XMUX_ENABLED(x)	\
	((x)->sa_flags & X_MUXMASK)
#define	IS_RMUX_ENABLED(x)	\
	((x)->sa_flags & R_MUXMASK)
#define	IS_COMP_AC(x)	\
	((x)->sa_flags & SAF_XCOMP_AC)
#define	IS_COMP_PROT(x)	\
	((x)->sa_flags & SAF_XCOMP_PROT)
#define	IS_DECOMP_PROT(x)	\
	((x)->sa_flags & SAF_RDECOMP_PROT)

/*
 * Don't send HDLC start flag if last transmit is within 1.5 seconds -
 * FLAG_TIME is defined in nanoseconds.
 */
#define	FLAG_TIME	1500000000ul

/*
 * The usual AHDLC implementation enables the default escaping for all
 * LCP frames.  LCP_USE_DFLT() is used in this implementation to
 * modify this rule slightly.  If the code number happens to be
 * Echo-Request, Echo-Reply, or Discard-Request (each of which may be
 * sent only when LCP is in Opened state), then one may also use the
 * negotiated ACCM; the RFC is silent on this.  The theory is that
 * pppd can construct Echo-Request messages that are guaranteed to
 * fail if the negotiated ACCM is bad.
 */
#define	LCP_USE_DFLT(mp)	((code = MSG_BYTE((mp), 4)) < 9 || code > 11)

/*
 * Extract bit c from map m, to determine if character c needs to be
 * escaped.  Map 'm' is a pointer to a 256 bit map; 8 words of 32 bits
 * each.
 */
#define	IN_TX_MAP(c, m)	\
	((m)[(c) >> 5] & (1 << ((c) & 0x1f)))

/*
 * Checks the 32-bit receive ACCM to see if the byte should have been
 * escaped by peer.
 */
#define	IN_RX_MAP(c, m)		(((c) < 0x20) && ((m) & (1 << (c))))

static struct module_info spppasyn_modinfo = {
	AHDLC_MOD_ID,		/* mi_idnum */
	AHDLC_MOD_NAME,		/* mi_idname */
	0,			/* mi_minpsz */
	INFPSZ,			/* mi_maxpsz */
	0,			/* mi_hiwat */
	0			/* mi_lowat */
};

static struct qinit spppasyn_rinit = {
	spppasyn_rput,		/* qi_putp */
	NULL,			/* qi_srvp */
	spppasyn_open,		/* qi_qopen */
	spppasyn_close,		/* qi_qclose */
	NULL,			/* qi_qadmin */
	&spppasyn_modinfo,	/* qi_minfo */
	NULL			/* qi_mstat */
};

static struct qinit spppasyn_winit = {
	spppasyn_wput,		/* qi_putp */
	NULL,			/* qi_srvp */
	NULL,			/* qi_qopen */
	NULL,			/* qi_qclose */
	NULL,			/* qi_qadmin */
	&spppasyn_modinfo,	/* qi_minfo */
	NULL			/* qi_mstat */
};

struct streamtab spppasyn_tab = {
	&spppasyn_rinit,	/* st_rdinit */
	&spppasyn_winit,	/* st_wrinit */
	NULL,			/* st_muxrinit */
	NULL,			/* st_muxwinit */
};

/* Matches above structure. */
static const char *kstat_names[] = {
	"ioctls", "ioctlsfwd", "ioctlserr", "ctls",
	"ctlsfwd", "ctlserr", "inbadchars", "inbadcharmask",
	"inaborts", "inrunts", "inallocfails", "intoolongs",
	"outrunts", "outallocfails", "incrcerrs", "unknownwrs",
	"unknownrds", "hangups", "datain", "dataout",
	"extrabufs", "sentmux", "recvmux", "inmuxerrs",
#ifdef REPORT_CRC_TYPE
	"incrctype", "outcrctype",
#endif
};

/* So.  This is why we have optimizing compilers. */
#define	KVAL(vn)	state->sa_kstats.vn.value.ui32
#define	KSET(vn, v)	KVAL(vn) = (v)
#define	KADD(vn, v)	KSET(vn, KVAL(vn) + (v))
#define	KOR(vn, v)	KSET(vn, KVAL(vn) | (v))
#define	KINCR(vn)	KADD(vn, 1)

static void ppp_dump_frame(sppp_ahdlc_t *state, mblk_t *mptr,
    const char *msg);

/*
 * RCV_B7_1, etc., defined in net/pppio.h, are stored in flags also.
 */
#define	RCV_FLAGS	(RCV_B7_1 | RCV_B7_0 | RCV_ODDP | RCV_EVNP)

/*
 * FCS lookup table as calculated by genfcstab.
 */
static ushort_t 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
};

/*
 * Per-character flags for accumulating input errors.  Flags are
 * accumulated for bit 7 set to 0, bit 7 set to 1, even parity
 * characters, and odd parity characters.  The link should see all
 * four in the very first LCP Configure-Request if all is ok.  (C0 is
 * even parity and has bit 7 set to 1, and 23 is odd parity and has
 * bit 7 set to 0.)
 */
static uchar_t charflags[256] = {
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_ODDP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_0|RCV_EVNP,
	RCV_B7_0|RCV_EVNP, RCV_B7_0|RCV_ODDP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_EVNP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_EVNP,
	RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_EVNP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_EVNP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_EVNP,
	RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_EVNP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_EVNP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_EVNP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP, RCV_B7_1|RCV_ODDP, RCV_B7_1|RCV_ODDP,
	RCV_B7_1|RCV_EVNP
};

/*
 * Append two lists; preserve message boundaries.
 * Warning: uses b_next.
 */
static mblk_t *
sppp_mappend(mblk_t *m1, mblk_t *m2)
{
	mblk_t *mret;

	if (m1 == NULL)
		return (m2);
	if (m2 == NULL)
		return (m1);

	mret = m1;
	while (m1->b_next != NULL)
		m1 = m1->b_next;
	m1->b_next = m2;
	return (mret);
}

/*
 * Concatenate two mblk lists.
 */
static mblk_t *
sppp_mcat(mblk_t *m1, mblk_t *m2)
{
	mblk_t *mret;

	if (m1 == NULL)
		return (m2);
	if (m2 == NULL)
		return (m1);

	mret = m1;
	while (m1->b_cont != NULL)
		m1 = m1->b_cont;
	m1->b_cont = m2;
	return (mret);
}

/*
 * spppasyn_open()
 *
 * STREAMS module open (entry) point.  Called when spppasyn is pushed
 * onto an asynchronous serial stream.
 */
/* ARGSUSED */
static int
spppasyn_open(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *credp)
{
	sppp_ahdlc_t	*state;

	ASSERT(q != NULL);

	if (q->q_ptr != NULL) {
		return (0);		/* return if already opened */
	}

	if (sflag != MODOPEN) {
		return (EINVAL);	/* only open as a module */
	}

	state = (sppp_ahdlc_t *)kmem_zalloc(sizeof (sppp_ahdlc_t), KM_SLEEP);
	ASSERT(state != NULL);

	q->q_ptr = (caddr_t)state;
	WR(q)->q_ptr = (caddr_t)state;

	state->sa_xaccm[0] = 0xffffffff;	/* escape 0x00 through 0x1f */
	state->sa_xaccm[3] = 0x60000000;   /* escape 0x7d and 0x7e */
	state->sa_mru = PPP_MRU;		/* default of 1500 bytes */

	qprocson(q);

	return (0);
}

/*
 * spppasyn_close()
 *
 * STREAMS module close (exit) point
 */
/* ARGSUSED */
static int
spppasyn_close(queue_t *q, int flag, cred_t *credp)
{
	sppp_ahdlc_t	*state;

	ASSERT(q != NULL);
	state = (sppp_ahdlc_t *)q->q_ptr;
	ASSERT(state != NULL);

	/* We're leaving now.  No more calls, please. */
	qprocsoff(q);

	if (state->sa_rx_buf != NULL) {
		freemsg(state->sa_rx_buf);
		state->sa_rx_buf = NULL;
	}

	if (state->sa_ksp != NULL) {
		kstat_delete(state->sa_ksp);
		state->sa_ksp = NULL;
	}

	if (state->sa_mqhead != NULL)
		freemsg(state->sa_mqhead);
	/* remove the time out routine */
	if (state->sa_timeout_id != 0)
		(void) quntimeout(q, state->sa_timeout_id);

	q->q_ptr = NULL;
	WR(q)->q_ptr = NULL;
	kmem_free(state, sizeof (sppp_ahdlc_t));

	return (0);
}

/*
 * Create the standard kernel statistics structure and attach it to
 * the current state structure.  This can be called only after
 * assigning the unit number.
 */
static void
create_kstats(sppp_ahdlc_t *state)
{
	kstat_t *ksp;
	char unitname[KSTAT_STRLEN];
	int nstat, i;
	kstat_named_t *knt;

	nstat = sizeof (state->sa_kstats) / sizeof (kstat_named_t);
	knt = (kstat_named_t *)&state->sa_kstats;
	for (i = 0; i < nstat; i++, knt++) {
#ifdef DEBUG
		/* Just in case I do something silly here. */
		if (i >= sizeof (kstat_names) / sizeof (kstat_names[0]))
			(void) sprintf(knt->name, "unknown%d", i);
		else
#endif
			(void) strncpy(knt->name, kstat_names[i],
			    sizeof (knt->name));
		knt->data_type = KSTAT_DATA_UINT32;
	}
	/*
	 * sprintf is known to be safe here because KSTAT_STRLEN is
	 * 31, the maximum module name length is 8, and the maximum
	 * string length from %d is 11.  This was once snprintf, but
	 * that's not backward-compatible with Solaris 2.6.
	 */
	(void) sprintf(unitname, "%s" "%d", AHDLC_MOD_NAME, state->sa_unit);
	ksp = kstat_create(AHDLC_MOD_NAME, state->sa_unit, unitname, "net",
	    KSTAT_TYPE_NAMED, nstat, KSTAT_FLAG_VIRTUAL);
	if (ksp != NULL) {
		ksp->ks_data = (void *)&state->sa_kstats;
		kstat_install(ksp);
	}
	state->sa_ksp = ksp;
#ifdef REPORT_CRC_TYPE
	KSET(pks_outcrctype, 16);
	KSET(pks_incrctype, 16);
#endif
}

/*
 * spppasyn_inner_ioctl
 *
 * MT-Perimeters:
 *	exclusive inner
 *
 * Handle state-affecting ioctls.
 */
static void
spppasyn_inner_ioctl(queue_t *q, mblk_t *mp)
{
	sppp_ahdlc_t		*state;
	struct iocblk		*iop;
	int			error;
	int			flagval;
	int			len;
	uint32_t		mux_flags;
	uint32_t		mask;
	int			flagmask;

	ASSERT(q != NULL && mp != NULL);
	state = (sppp_ahdlc_t *)q->q_ptr;
	iop = (struct iocblk *)mp->b_rptr;
	ASSERT(state != NULL && iop != NULL);

	error = EINVAL;
	len = 0;

	switch (iop->ioc_cmd) {
	case PPPIO_XFCS:
		/* Check for valid option length */
		if (iop->ioc_count != sizeof (uint32_t) || mp->b_cont == NULL)
			break;

		/* Grab flag value */
		flagval = *(uint32_t *)mp->b_cont->b_rptr;
		if (flagval < PPPFCS_16 || flagval > PPPFCS_NONE)
			break;
		state->sa_flags &= ~SAF_XMITCRC32 & ~SAF_XMITCRCNONE;
		if (flagval == PPPFCS_32) {
#ifdef REPORT_CRC_TYPE
			KSET(pks_outcrctype, 32);
#endif
			state->sa_flags |= SAF_XMITCRC32;
		} else if (flagval == PPPFCS_NONE) {
#ifdef REPORT_CRC_TYPE
			KSET(pks_outcrctype, 0);
#endif
			state->sa_flags |= SAF_XMITCRCNONE;
		}
#ifdef REPORT_CRC_TYPE
		else {
			KSET(pks_outcrctype, 16);
		}
#endif

		/* Return success */
		error = 0;
		break;

	case PPPIO_RFCS:
		/* Check for valid option length */
		if (iop->ioc_count != sizeof (uint32_t) || mp->b_cont == NULL)
			break;

		/* Grab flag value */
		flagval = *(uint32_t *)mp->b_cont->b_rptr;
		if (flagval < PPPFCS_16 || flagval > PPPFCS_NONE)
			break;
		state->sa_flags &= ~SAF_RECVCRC32 & ~SAF_RECVCRCNONE;
		if (flagval == PPPFCS_32) {
#ifdef REPORT_CRC_TYPE
			KSET(pks_incrctype, 32);
#endif
			state->sa_flags |= SAF_RECVCRC32;
		} else if (flagval == PPPFCS_NONE) {
#ifdef REPORT_CRC_TYPE
			KSET(pks_incrctype, 0);
#endif
			state->sa_flags |= SAF_RECVCRCNONE;
		}
#ifdef REPORT_CRC_TYPE
		else {
			KSET(pks_incrctype, 16);
		}
#endif

		/* Return success */
		error = 0;
		break;

	case PPPIO_XACCM:
		/* Check for valid asyncmap length */
		if (iop->ioc_count < sizeof (uint32_t) ||
		    iop->ioc_count > sizeof (ext_accm) ||
		    mp->b_cont == NULL)
			break;

		/* Copy user's asyncmap into our state structure. */
		bcopy((caddr_t)mp->b_cont->b_rptr,
		    (caddr_t)state->sa_xaccm, iop->ioc_count);

		state->sa_xaccm[2] &= ~0x40000000;	/* don't escape 0x5e */
		state->sa_xaccm[3] |= 0x60000000;	/* escape 0x7d, 0x7e */

		error = 0;
		break;

	case PPPIO_RACCM:
		/* Check for valid asyncmap length (only ctrl chars) */
		if (iop->ioc_count != sizeof (uint32_t) ||
		    mp->b_cont == NULL)
			break;

		state->sa_raccm = *(uint32_t *)mp->b_cont->b_rptr;

		error = 0;
		break;

	case PPPIO_LASTMOD:
		/* We already know this. */
		state->sa_flags |= SAF_LASTMOD;
		error = 0;
		break;

	case PPPIO_MUX:
		/* set the compression flags */
		if (iop->ioc_count != 2 * sizeof (uint32_t) ||
		    mp->b_cont == NULL)
			break;

		/* set the mux flags */
		mux_flags = ((uint32_t *)mp->b_cont->b_rptr)[0];
		mask = ((uint32_t *)mp->b_cont->b_rptr)[1];
		if (mux_flags != 0)
			state->sa_flags = (state->sa_flags & ~mask) | (mask);

		/* set the multiplexing timer value */
		if (mask & R_MUXMASK)
			state->sa_timeout_usec = mux_flags;

		error = 0;
		break;

	case PPPIO_CFLAGS:
		if (iop->ioc_count != 2 * sizeof (uint32_t) ||
		    mp->b_cont == NULL)
			break;

		flagval = (((uint32_t *)mp->b_cont->b_rptr)[0] << 20) &
		    (SAF_RDECOMP_PROT | SAF_RDECOMP_AC | SAF_XCOMP_PROT |
		    SAF_XCOMP_AC);
		flagmask = (((uint32_t *)mp->b_cont->b_rptr)[1] << 20) &
		    (SAF_RDECOMP_PROT | SAF_RDECOMP_AC | SAF_XCOMP_PROT |
		    SAF_XCOMP_AC);
		state->sa_flags = flagval | (state->sa_flags & ~flagmask);
		*(uint32_t *)mp->b_cont->b_rptr = state->sa_flags >> 20;
		len = sizeof (uint32_t);
		error = 0;
		break;

	case PPPIO_DEBUG:
		if (iop->ioc_count != sizeof (uint32_t) || mp->b_cont == NULL)
			break;

		flagval = *(uint32_t *)mp->b_cont->b_rptr;
		if (flagval != PPPDBG_LOG + PPPDBG_AHDLC) {
			putnext(q, mp);
			return;
		}
		cmn_err(CE_CONT, AHDLC_MOD_NAME "%d: debug log enabled\n",
		    state->sa_unit);
		state->sa_flags |= SAF_XMITDUMP | SAF_RECVDUMP;
		error = 0;
		break;
	}

	if (error == 0) {
		/* Success; tell the user */
		if (mp->b_cont == NULL)
			len = 0;
		else
			mp->b_cont->b_wptr = mp->b_cont->b_rptr + len;
		miocack(q, mp, len, 0);
	} else {
		/* Failure; send error back upstream. */
		KINCR(pks_ioctlserr);
		miocnak(q, mp, 0, error);
	}
}

/*
 * spppasyn_inner_mctl
 *
 * MT-Perimeters:
 *	exclusive inner
 *
 * Handle state-affecting M_CTL messages.
 */
static void
spppasyn_inner_mctl(queue_t *q, mblk_t *mp)
{
	sppp_ahdlc_t	*state;
	int		msglen;
	int		error;

	ASSERT(q != NULL && mp != NULL);
	state = (sppp_ahdlc_t *)q->q_ptr;
	ASSERT(state != NULL);

	msglen = MBLKL(mp);
	error = 0;
	switch (*mp->b_rptr) {
	case PPPCTL_MTU:
				/* Just ignore the MTU */
		break;

	case PPPCTL_MRU:
		if (msglen != 4)
			error = EINVAL;
		else
			state->sa_mru =
			    ((ushort_t *)mp->b_rptr)[1];
		break;

	case PPPCTL_UNIT:
		if (state->sa_ksp != NULL) {
			error = EINVAL;
			break;
		}
		if (msglen == 2)
			state->sa_unit = mp->b_rptr[1];
		else if (msglen == 8)
			state->sa_unit =
			    ((uint32_t *)mp->b_rptr)[1];
		else
			error = EINVAL;
		if (error == 0 && state->sa_ksp == NULL)
			create_kstats(state);
		break;
	}

	if (error > 0) {
		KINCR(pks_ctlserr);
	}
	if (state->sa_flags & SAF_LASTMOD) {
		freemsg(mp);
	} else {
		KINCR(pks_ctlsfwd);
		putnext(q, mp);
	}
}

/*
 * spppasyn_wput()
 *
 * MT-Perimeters:
 *	exclusive inner.
 *
 * Write side put routine.  This called by the modules above us (likely to
 * be the compression module) to transmit data or pass along ioctls.
 */
static int
spppasyn_wput(queue_t *q, mblk_t *mp)
{
	sppp_ahdlc_t		*state;
	struct iocblk		*iop;
	int			error;
	mblk_t			*np;
	struct ppp_stats64	*psp;
	int			msglen;

	ASSERT(q != NULL && mp != NULL);
	state = (sppp_ahdlc_t *)q->q_ptr;
	ASSERT(state != NULL);

	switch (MTYPE(mp)) {

	case M_DATA:
		/*
		 * A data packet - do character-stuffing and FCS, and
		 * send it onwards.  The blocks are freed as we go.
		 */
		if (IS_XMUX_ENABLED(state))
			mp = spppasyn_muxencode(q, mp);
		else
			mp = ahdlc_encode(q, mp);
		if (mp != NULL)
			putnext(q, mp);
		break;

	case M_IOCTL:

		KINCR(pks_ioctls);
		iop = (struct iocblk *)mp->b_rptr;

		msglen = 0;

		switch (iop->ioc_cmd) {
		case PPPIO_XFCS:
		case PPPIO_RFCS:
		case PPPIO_XACCM:
		case PPPIO_RACCM:
		case PPPIO_LASTMOD:
		case PPPIO_DEBUG:
		case PPPIO_MUX:
		case PPPIO_CFLAGS:
			spppasyn_inner_ioctl(q, mp);
			return (0);

		case PPPIO_GCLEAN:
			np = allocb(sizeof (uint32_t), BPRI_HI);
			if (np == NULL) {
				error = ENOSR;
				break;
			}
			if (mp->b_cont != NULL) {
				freemsg(mp->b_cont);
			}
			mp->b_cont = np;

			*(uint32_t *)np->b_wptr = state->sa_flags & RCV_FLAGS;

			msglen = sizeof (uint32_t);
			np->b_wptr += msglen;
			error = 0;
			break;

		case PPPIO_GETSTAT:
			error = EINVAL;
			break;

		case PPPIO_GETSTAT64:
			np = allocb(sizeof (*psp), BPRI_HI);
			if (np == NULL) {
				error = ENOSR;
				break;
			}
			if (mp->b_cont != NULL) {
				freemsg(mp->b_cont);
			}
			mp->b_cont = np;

			psp = (struct ppp_stats64 *)np->b_wptr;
			bzero((caddr_t)psp, sizeof (*psp));
			psp->p = state->sa_stats;

			msglen = sizeof (*psp);
			np->b_wptr += msglen;
			error = 0;
			break;

		case PPPIO_GTYPE:
			np = allocb(sizeof (uint32_t), BPRI_HI);
			if (np == NULL) {
				error = ENOSR;
				break;
			}
			if (mp->b_cont != NULL) {
				freemsg(mp->b_cont);
			}
			mp->b_cont = np;

			*(uint32_t *)np->b_wptr = PPPTYP_AHDLC;

			msglen = sizeof (uint32_t);
			np->b_wptr += msglen;
			error = 0;
			break;

		default:
			/* Unknown ioctl -- forward along */
			KINCR(pks_ioctlsfwd);
			putnext(q, mp);
			return (0);
		}

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

		break;

	case M_CTL:
		KINCR(pks_ctls);
		spppasyn_inner_mctl(q, mp);
		break;

	default:
		if (state->sa_flags & (SAF_XMITDUMP|SAF_RECVDUMP))
			cmn_err(CE_CONT,
			    "spppasyn_wpur:  unknown buffer type %d",
			    MTYPE(mp));
		KINCR(pks_unknownwrs);
		putnext(q, mp);
		break;
	}

	return (0);
}

/*
 * spppasyn_rput()
 *
 * MT-Perimeters:
 *	exclusive inner.
 *
 * Read side put routine.  This is called by the async serial driver
 * below us to handle received data and returned signals (like
 * hang-up).
 */
static int
spppasyn_rput(queue_t *q, mblk_t  *mp)
{
	sppp_ahdlc_t	*state;
	mblk_t		*mpnext;

	ASSERT(q != NULL && mp != NULL);
	state = (sppp_ahdlc_t *)q->q_ptr;
	ASSERT(state != NULL);

	switch (MTYPE(mp)) {

	case M_DATA:
		/* Note -- decoder frees the buffers */
		mp = ahdlc_decode(q, mp);
		while (mp != NULL) {
			mpnext = mp->b_next;
			mp->b_next = NULL;
			putnext(q, mp);
			mp = mpnext;
		}
		break;

	case M_HANGUP:
		KINCR(pks_hangups);
		state->sa_flags |= SAF_IFLUSH;
		putnext(q, mp);
		break;

	default:
		if (state->sa_flags & (SAF_XMITDUMP|SAF_RECVDUMP)) {
			if (MTYPE(mp) == M_IOCTL)
				cmn_err(CE_CONT,
				    "spppasyn_rput:  unexpected ioctl %X",
				    ((struct iocblk *)mp->b_rptr)->ioc_cmd);
			else
				cmn_err(CE_CONT,
				    "spppasyn_rput:  unknown buffer type %d",
				    MTYPE(mp));
		}
		KINCR(pks_unknownrds);
		putnext(q, mp);
		break;
	}

	return (0);
}

/*
 * ahdlc_encode
 *
 * Perform asynchronous HDLC framing on a given buffer and transmit
 * the result.  The state structure must be valid.  The input buffers
 * are freed as we go.
 *
 * This function is called by wput and just encodes the data.  Wput
 * then calls putnext directly.  There's no service routine for this
 * module, so flow control is asserted by the module below us up to
 * our caller by the STREAMS framework.  This is by design -- this
 * module does not queue anything so that other modules can make QoS
 * decisions.
 */
static mblk_t *
ahdlc_encode(queue_t *q, mblk_t	*mp)
{
	sppp_ahdlc_t	*state;
	uint32_t	loc_xaccm[8];
	ushort_t	fcs16;
	uint32_t	fcs32;
	size_t		msglen;
	size_t		outmp_len;
	mblk_t		*outmp;
	mblk_t		*curout;
	mblk_t		*tmp;
	uchar_t		*ep;
	uchar_t		*dp;
	uchar_t		*tp;
	uchar_t		*tpmax;
#if defined(lint) || defined(_lint)
	uchar_t		chr;	/* lint likes this */
#else
	int		chr;	/* not uchar_t; more efficient this way */
				/* with WorkShop compiler */
#endif
	int		is_lcp, is_ctrl;
	int		code;
	hrtime_t	hrtime;
	uint32_t	flags;	/* sampled copy of flags */

	state = (sppp_ahdlc_t *)q->q_ptr;

	/* Don't transmit anything obviously silly. */
	msglen = msgsize(mp);
	if (msglen < 4) {
		KINCR(pks_outrunts);
		freemsg(mp);
		(void) putnextctl1(RD(q), M_CTL, PPPCTL_OERROR);
		return (NULL);
	}

	/*
	 * Allocate an output buffer just large enough for most cases.
	 * Based on original work in the ppp-2.2 AIX PPP driver, we
	 * estimate the output size as 1.25 * input message length
	 * plus 16.  If this turns out to be too small, then we'll
	 * allocate exactly one additional buffer with two times the
	 * remaining input length (the maximum that could possibly be
	 * required).
	 */
	outmp_len = msglen + (msglen >> 2) + 16;
	outmp = allocb(outmp_len, BPRI_MED);
	if (outmp == NULL)
		goto outallocfail;

	tp = outmp->b_wptr;

	/*
	 * Check if our last transmit happened within FLAG_TIME, using
	 * the system's hrtime.
	 */
	hrtime = gethrtime();
	if (ABS(hrtime - state->sa_hrtime) > FLAG_TIME) {
		*tp++ = PPP_FLAG;
	}
	state->sa_hrtime = hrtime;
	bcopy((caddr_t)state->sa_xaccm, (caddr_t)loc_xaccm, sizeof (loc_xaccm));
	flags = state->sa_flags;

	/*
	 * LCP messages must be sent using the default escaping
	 * (ACCM).  We bend this rule a little to allow LCP
	 * Echo-Request through with the negotiated escaping so that
	 * we can detect bad negotiated ACCM values.  If the ACCM is
	 * bad, echos will fail and take down the link.
	 */
	is_lcp = is_ctrl = 0;
	code = MSG_BYTE(mp, 0);
	if (code == PPP_ALLSTATIONS) {
		if (MSG_BYTE(mp, 1) == PPP_UI) {
			code = MSG_BYTE(mp, 2);
			if (code == (PPP_LCP >> 8) &&
			    MSG_BYTE(mp, 3) == (PPP_LCP & 0xFF)) {
				if (LCP_USE_DFLT(mp))
					is_lcp = 2;
				else
					is_lcp = 1;	/* Echo-Request */
			} else if (!(code & 1) && code > 0x3F)
				is_ctrl = 1;
		}
	} else if (!(code & 1) && code > 0x3F)
		is_ctrl = 1;

	/*
	 * If it's LCP and not just an LCP Echo-Request, then we need
	 * to drop back to default escaping rules temporarily.
	 */
	if (is_lcp > 1) {
		/*
		 * force escape on 0x00 through 0x1f
		 * and, for RFC 1662 (and ISO 3309:1991), 0x80-0x9f.
		 */
		loc_xaccm[0] = 0xffffffff;
		loc_xaccm[4] = 0xffffffff;
	}

	fcs16 = PPPINITFCS16;		/* Initial FCS is 0xffff */
	fcs32 = PPPINITFCS32;

	/*
	 * Process this block and the rest (if any) attached to this
	 * one.  Note that we quite intentionally ignore the type of
	 * the buffer.  The caller has checked that the first buffer
	 * is M_DATA; all others must be so, and any that are not are
	 * harmless driver errors.
	 */
	curout = outmp;
	tpmax = outmp->b_datap->db_lim;
	do {
		dp = mp->b_rptr;
		while (dp < (ep = mp->b_wptr)) {
			/*
			 * Calculate maximum safe run length for inner loop,
			 * regardless of escaping.
			 */
			outmp_len = (tpmax - tp) / 2;
			if (dp + outmp_len < ep)
				ep = dp + outmp_len;

			/*
			 * Select out on CRC type here to make the
			 * inner byte loop more efficient.  (We could
			 * do both CRCs at all times if we wanted, but
			 * that ends up taking an extra 8 cycles per
			 * byte -- 47% overhead!)
			 */
			if (flags & SAF_XMITCRC32) {
				while (dp < ep) {
					chr = *dp++;
					fcs32 = PPPFCS32(fcs32, chr);
					if (IN_TX_MAP(chr, loc_xaccm)) {
						*tp++ = PPP_ESCAPE;
						chr ^= PPP_TRANS;
					}
					*tp++ = chr;
				}
			} else {
				while (dp < ep) {
					chr = *dp++;
					fcs16 = PPPFCS16(fcs16, chr);
					if (IN_TX_MAP(chr, loc_xaccm)) {
						*tp++ = PPP_ESCAPE;
						chr ^= PPP_TRANS;
					}
					*tp++ = chr;
				}
			}

			/*
			 * If we limited our run length and we're now low
			 * on output space, then allocate a new output buffer.
			 * This should rarely happen, unless the output data
			 * has a lot of escapes.
			 */
			if (ep != mp->b_wptr && tpmax - tp < 5) {
				KINCR(pks_extrabufs);
				/* Get remaining message length */
				outmp_len = (mp->b_wptr - dp) +
				    msgsize(mp->b_cont);
				/* Calculate maximum required space */
				outmp_len = (outmp_len + PPP_FCS32LEN) * 2 + 1;
				curout = allocb(outmp_len, BPRI_MED);
				if ((outmp->b_cont = curout) == NULL)
					goto outallocfail;
				outmp->b_wptr = tp;
				tp = curout->b_wptr;
				tpmax = curout->b_datap->db_lim;
			}
		}
		tmp = mp->b_cont;
		freeb(mp);
		mp = tmp;
	} while (mp != NULL);

	/*
	 * Make sure we have enough remaining room to add the CRC (if
	 * any) and a trailing flag byte.
	 */
	outmp_len = PPP_FCS32LEN * 2 + 1;
	if (tpmax - tp < outmp_len) {
		KINCR(pks_extrabufs);
		curout = allocb(outmp_len, BPRI_MED);
		if ((outmp->b_cont = curout) == NULL)
			goto outallocfail;
		outmp->b_wptr = tp;
		tp = curout->b_wptr;
		tpmax = curout->b_datap->db_lim;
	}

	/*
	 * Network layer data is the only thing that can be sent with
	 * no CRC at all.
	 */
	if ((flags & SAF_XMITCRCNONE) && !is_lcp && !is_ctrl)
		goto nocrc;

	if (!(flags & SAF_XMITCRC32))
		fcs32 = fcs16;

	/*
	 * Append the HDLC FCS, making sure that escaping is done on any
	 * necessary bytes. Note that the FCS bytes are in little-endian.
	 */
	fcs32 = ~fcs32;
	chr = fcs32 & 0xff;
	if (IN_TX_MAP(chr, loc_xaccm)) {
		*tp++ = PPP_ESCAPE;
		chr ^= PPP_TRANS;
	}
	*tp++ = chr;

	chr = (fcs32 >> 8) & 0xff;
	if (IN_TX_MAP(chr, loc_xaccm)) {
		*tp++ = PPP_ESCAPE;
		chr ^= PPP_TRANS;
	}
	*tp++ = chr;

	if (flags & SAF_XMITCRC32) {
		chr = (fcs32 >> 16) & 0xff;
		if (IN_TX_MAP(chr, loc_xaccm)) {
			*tp++ = PPP_ESCAPE;
			chr ^= PPP_TRANS;
		}
		*tp++ = chr;

		chr = (fcs32 >> 24) & 0xff;
		if (IN_TX_MAP(chr, loc_xaccm)) {
			*tp++ = PPP_ESCAPE;
			chr ^= PPP_TRANS;
		}
		*tp++ = chr;
	}

nocrc:
	/*
	 * And finally append the HDLC flag, and send it away
	 */
	*tp++ = PPP_FLAG;
	ASSERT(tp < tpmax);
	curout->b_wptr = tp;

	state->sa_stats.ppp_obytes += msgsize(outmp);
	state->sa_stats.ppp_opackets++;

	if (state->sa_flags & SAF_XMITDUMP)
		ppp_dump_frame(state, outmp, "sent");

	KINCR(pks_dataout);
	return (outmp);

outallocfail:
	KINCR(pks_outallocfails);
	state->sa_stats.ppp_oerrors++;
	freemsg(outmp);
	freemsg(mp);
	(void) putnextctl1(RD(q), M_CTL, PPPCTL_OERROR);
	return (NULL);
}

/*
 * Handle end-of-frame excitement.  This is here mostly because the Solaris
 * C style rules require tab for indent and prohibit excessive indenting.
 */
static mblk_t *
receive_frame(queue_t *q, mblk_t *outmp, ushort_t fcs16, uint32_t fcs32)
{
	sppp_ahdlc_t *state = (sppp_ahdlc_t *)q->q_ptr;
	uchar_t *cp, *ep;
	int is_lcp, is_ctrl, crclen;
	ushort_t 	proto;
	int i;

	cp = outmp->b_rptr;
	if (cp[0] == PPP_ALLSTATIONS && cp[1] == PPP_UI)
		cp += 2;
	proto = *cp++;
	if ((proto & 1) == 0)
		proto = (proto << 8) + *cp++;
	is_lcp = (proto == PPP_LCP);
	is_ctrl = (proto >= 0x4000);

	/*
	 * To allow for renegotiation, LCP accepts good CRCs of either
	 * type at any time.  Other control (non-network) packets must
	 * have either CRC-16 or CRC-32, as negotiated.  Network layer
	 * packets may additionally omit the CRC entirely, if that was
	 * negotiated.
	 */
	if ((is_lcp && (fcs16 == PPPGOODFCS16 || fcs32 == PPPGOODFCS32)) ||
	    ((fcs16 == PPPGOODFCS16 && !(state->sa_flags & SAF_RECVCRC32)) ||
	    (fcs32 == PPPGOODFCS32 &&
	    (state->sa_flags & SAF_RECVCRC32))) ||
	    (!is_ctrl && !is_lcp && (state->sa_flags & SAF_RECVCRCNONE))) {

		state->sa_stats.ppp_ipackets++;
		if (is_lcp) {
			crclen = (fcs16 == PPPGOODFCS16) ?
			    PPP_FCSLEN : PPP_FCS32LEN;
		} else {
			crclen = (state->sa_flags & SAF_RECVCRC32) ?
			    PPP_FCS32LEN : PPP_FCSLEN;
			if (!is_ctrl && (state->sa_flags & SAF_RECVCRCNONE))
				crclen = 0;
		}
		if (crclen != 0) {
			i = adjmsg(outmp, -crclen);
			ASSERT(i != 0);
#if defined(lint) || defined(_lint)
			/* lint is happier this way in a non-DEBUG build */
			i = i;
#endif
		}

		if (proto == PPP_MUX) {
			/* spppasyn_inpkt checks for PPP_MUX packets */
			KINCR(pks_recvmux);
			/* Remove headers */
			outmp->b_rptr = cp;
			return (spppasyn_inpkt(q, outmp));
		}

		/*
		 * Sniff the received data stream.  If we see an LCP
		 * Configure-Ack, then pick out the ACCM setting, if
		 * any, and configure now.  This allows us to stay in
		 * sync in case the peer is already out of Establish
		 * phase.
		 */
		if (is_lcp && *cp == 2) {
			ep = outmp->b_wptr;
			i = (cp[2] << 8) | cp[3];
			if (i > ep - cp)
				ep = cp;	/* Discard junk */
			else if (i < ep - cp)
				ep = cp + i;
			cp += 4;
			while (cp + 2 < ep) {
				if ((i = cp[1]) < 2)
					i = 2;
				if (cp + i > ep)
					i = ep - cp;
				if (cp[0] == 2 && i >= 6) {
					state->sa_raccm = (cp[2] << 24) |
					    (cp[3] << 16) | (cp[4] << 8) |
					    cp[5];
					break;
				}
				cp += i;
			}
		}
		return (outmp);
	} else {
		KINCR(pks_incrcerrs);
		cmn_err(CE_CONT, PPP_DRV_NAME "%d: bad fcs (len=%ld)\n",
		    state->sa_unit, msgsize(outmp));

		if (state->sa_flags & SAF_RECVDUMP)
			ppp_dump_frame(state, outmp, "bad data");

		freemsg(outmp);

		state->sa_stats.ppp_ierrors++;

		(void) putnextctl1(q, M_CTL, PPPCTL_IERROR);
		return (NULL);
	}
}

/*
 * ahdlc_decode()
 *
 * Process received characters.
 *
 * This is handled as exclusive inner so that we don't get confused
 * about the state.  Returns a list of packets linked by b_next.
 */
static mblk_t *
ahdlc_decode(queue_t *q, mblk_t  *mp)
{
	sppp_ahdlc_t	*state;
	mblk_t		*retmp;		/* list of packets to return */
	mblk_t		*outmp;		/* buffer for decoded data */
	mblk_t		*mpnext;	/* temporary ptr for unlinking */
	uchar_t		*dp;		/* pointer to input data */
	uchar_t		*dpend;		/* end of input data */
	uchar_t		*tp;		/* pointer to decoded output data */
	uchar_t		*tpmax;		/* output buffer limit */
	int		flagtmp;	/* temporary cache of flags */
#if defined(lint) || defined(_lint)
	uchar_t		chr;	/* lint likes this */
#else
	int		chr;	/* not uchar_t; more efficient this way */
				/* with WorkShop compiler */
#endif
	ushort_t	fcs16;		/* running CRC-16 */
	uint32_t	fcs32;		/* running CRC-32 */
#ifdef HANDLE_ZERO_LENGTH
	size_t		nprocessed;
#endif

	state = (sppp_ahdlc_t *)q->q_ptr;

	KINCR(pks_datain);

	state->sa_stats.ppp_ibytes += msgsize(mp);

	if (state->sa_flags & SAF_RECVDUMP)
		ppp_dump_frame(state, mp, "rcvd");

	flagtmp = state->sa_flags;
	fcs16 = state->sa_infcs16;
	fcs32 = state->sa_infcs32;
	outmp = state->sa_rx_buf;
	if (outmp == NULL) {
		tp = tpmax = NULL;
	} else {
		tp = outmp->b_wptr;
		tpmax = outmp->b_datap->db_lim;
	}
#ifdef HANDLE_ZERO_LENGTH
	nprocessed = 0;
#endif

	/*
	 * Main input processing loop.  Loop over received buffers and
	 * each byte in each buffer.  Note that we quite intentionally
	 * ignore the type of the buffer.  The caller has checked that
	 * the first buffer is M_DATA; all others must be so, and any
	 * that are not are harmless driver errors.
	 */
	retmp = NULL;
	while (mp != NULL) {

		/* Innermost loop -- examine bytes in buffer. */
		dpend = mp->b_wptr;
		dp = mp->b_rptr;
#ifdef HANDLE_ZERO_LENGTH
		nprocessed += dpend - dp;
#endif
		for (; dp < dpend; dp++) {
			chr = *dp;

			/*
			 * This should detect the lack of an 8-bit
			 * communication channel, which is necessary
			 * for PPP to work.
			 */
			flagtmp |= charflags[chr];

			/*
			 * So we have a HDLC flag ...
			 */
			if (chr == PPP_FLAG) {

				/*
				 * If there's no received buffer, then
				 * just ignore this frame marker.
				 */
				if ((flagtmp & SAF_IFLUSH) || outmp == NULL) {
					flagtmp &= ~SAF_IFLUSH & ~SAF_ESCAPED;
					continue;
				}

				/*
				 * Per RFC 1662 -- silently discard
				 * runt frames (fewer than 4 octets
				 * with 16 bit CRC) and frames that
				 * end in 7D 7E (abort sequence).
				 * These are not counted as errors.
				 *
				 * (We could just reset the pointers
				 * and reuse the buffer, but this is a
				 * rarely used error path and not
				 * worth the optimization.)
				 */
				if ((flagtmp & SAF_ESCAPED) ||
				    tp - outmp->b_rptr < 2 + PPP_FCSLEN) {
					if (flagtmp & SAF_ESCAPED)
						KINCR(pks_inaborts);
					else
						KINCR(pks_inrunts);
					if (state->sa_flags & SAF_RECVDUMP) {
						outmp->b_wptr = tp;
						ppp_dump_frame(state, outmp,
						    "runt");
					}
					freemsg(outmp);
					flagtmp &= ~SAF_ESCAPED;
				} else {
					/* Handle the received frame */
					outmp->b_wptr = tp;
					outmp = receive_frame(q, outmp, fcs16,
					    fcs32);
					retmp = sppp_mappend(retmp, outmp);
				}

				outmp = NULL;
				tp = tpmax = NULL;

				continue;
			}

			/* If we're waiting for a new frame, then drop data. */
			if (flagtmp & SAF_IFLUSH) {
				continue;
			}

			/*
			 * Start of new frame.  Allocate a receive
			 * buffer large enough to store a frame (after
			 * un-escaping) of at least 1500 octets plus
			 * the CRC.  If MRU is negotiated to be more
			 * than the default, then allocate that much.
			 * In addition, we add an extra 32-bytes for a
			 * fudge factor, in case the peer doesn't do
			 * arithmetic very well.
			 */
			if (outmp == NULL) {
				int maxlen;

				if ((maxlen = state->sa_mru) < PPP_MRU)
					maxlen = PPP_MRU;
				maxlen += PPP_FCS32LEN + 32;
				outmp = allocb(maxlen, BPRI_MED);

				/*
				 * If allocation fails, try again on
				 * the next frame.  (Go into discard
				 * mode.)
				 */
				if (outmp == NULL) {
					KINCR(pks_inallocfails);
					flagtmp |= SAF_IFLUSH;
					continue;
				}

				tp = outmp->b_wptr;
				tpmax = outmp->b_datap->db_lim;

				/* Neither flag can possibly be set here. */
				flagtmp &= ~(SAF_IFLUSH | SAF_ESCAPED);
				fcs16 = PPPINITFCS16;
				fcs32 = PPPINITFCS32;
			}

			/*
			 * If the peer sends us a character that's in
			 * our receive character map, then that's
			 * junk.  Discard it without changing state.
			 * If he previously sent us an escape
			 * character, then toggle this one and
			 * continue.  Otherwise, if he's now sending
			 * escape, set the flag for next time.
			 */
			if (IN_RX_MAP(chr, state->sa_raccm)) {
				KINCR(pks_inbadchars);
				KOR(pks_inbadcharmask, 1 << chr);
				continue;
			}
			if (flagtmp & SAF_ESCAPED) {
				chr ^= PPP_TRANS;
				flagtmp &= ~SAF_ESCAPED;
			} else if (chr == PPP_ESCAPE) {
				flagtmp |= SAF_ESCAPED;
				continue;
			}

			/*
			 * Unless the peer is confused about the
			 * negotiated MRU, we should never get a frame
			 * that is too long.  If it happens, toss it
			 * away and begin discarding data until we see
			 * the end of the frame.
			 */
			if (tp < tpmax) {
				fcs16 = PPPFCS16(fcs16, chr);
				fcs32 = PPPFCS32(fcs32, chr);
				*tp++ = chr;
			} else {
				KINCR(pks_intoolongs);
				cmn_err(CE_CONT, PPP_DRV_NAME
				    "%d: frame too long (%d bytes)\n",
				    state->sa_unit,
				    (int)(tpmax - outmp->b_rptr));

				freemsg(outmp);
				outmp = NULL;
				tp = tpmax = NULL;
				flagtmp |= SAF_IFLUSH;
			}
		}

		/*
		 * Free the buffer we just processed and move on to
		 * the next one.
		 */
		mpnext = mp->b_cont;
		freeb(mp);
		mp = mpnext;
	}
	state->sa_flags = flagtmp;
	if ((state->sa_rx_buf = outmp) != NULL)
		outmp->b_wptr = tp;
	state->sa_infcs16 = fcs16;
	state->sa_infcs32 = fcs32;

#ifdef HANDLE_ZERO_LENGTH
	if (nprocessed <= 0) {
		outmp = allocb(0, BPRI_MED);
		if (outmp != NULL) {
			outmp->b_datap->db_type = M_HANGUP;
			retmp = sppp_mappend(retmp, outmp);
		}
	}
#endif
	return (retmp);
}

/*
 * Nifty packet dumper; copied from AIX 4.1 port.  This routine dumps
 * the raw received and transmitted data through syslog.  This allows
 * debug of communications problems without resorting to a line
 * analyzer.
 *
 * The expression "3*BYTES_PER_LINE" used frequently here represents
 * the size of each hex value printed -- two hex digits and a space.
 */
#define	BYTES_PER_LINE	8
static void
ppp_dump_frame(sppp_ahdlc_t *state, mblk_t *mptr, const char *msg)
{
	/*
	 * Buffer is big enough for hex digits, two spaces, ASCII output,
	 * and one NUL byte.
	 */
	char buf[3 * BYTES_PER_LINE + 2 + BYTES_PER_LINE + 1];
	uchar_t *rptr, *eptr;
	int i, chr;
	char *bp;
	static const char digits[] = "0123456789abcdef";

	cmn_err(CE_CONT, "!ppp_async%d: %s %ld bytes\n", state->sa_unit,
	    msg, msgsize(mptr));
	i = 0;
	bp = buf;
	/* Add filler spaces between hex output and ASCII */
	buf[3 * BYTES_PER_LINE] = ' ';
	buf[3 * BYTES_PER_LINE + 1] = ' ';
	/* Add NUL byte at end */
	buf[sizeof (buf) - 1] = '\0';
	while (mptr != NULL) {
		rptr = mptr->b_rptr; /* get pointer to beginning  */
		eptr = mptr->b_wptr;
		while (rptr < eptr) {
			chr = *rptr++;
			/* convert byte to ascii hex */
			*bp++ = digits[chr >> 4];
			*bp++ = digits[chr & 0xf];
			*bp++ = ' ';
			/* Insert ASCII past hex output and filler */
			buf[3 * BYTES_PER_LINE + 2 + i] =
			    (chr >= 0x20 && chr <= 0x7E) ? (char)chr : '.';
			i++;
			if (i >= BYTES_PER_LINE) {
				cmn_err(CE_CONT, "!ppp%d: %s\n", state->sa_unit,
				    buf);
				bp = buf;
				i = 0;
			}
		}
		mptr = mptr->b_cont;
	}
	if (bp > buf) {
		/* fill over unused hex display positions */
		while (bp < buf + 3 * BYTES_PER_LINE)
			*bp++ = ' ';
		/* terminate ASCII string at right position */
		buf[3 * BYTES_PER_LINE + 2 + i] = '\0';
		cmn_err(CE_CONT, "!ppp%d: %s\n", state->sa_unit, buf);
	}
}

static mblk_t *
spppasyn_muxencode(queue_t *q, mblk_t *mp)
{
	sppp_ahdlc_t	*state = (sppp_ahdlc_t *)q->q_ptr;
	uint32_t	len;
	uint32_t	nlen;
	ushort_t	protolen;
	uint32_t	hdrlen;
	ushort_t	proto;
	mblk_t		*new_frame;
	mblk_t		*tmp;
	mblk_t		*send_frame;
	ushort_t	i;

	len = msgdsize(mp);
	i = 0;
	protolen = 1;
	proto = MSG_BYTE(mp, i);

	if (proto == PPP_ALLSTATIONS) {
		len -= 2;
		i += 2;
		proto = MSG_BYTE(mp, i);
	}

	++i;
	if ((proto & 1) == 0) {
		proto = (proto << 8) + MSG_BYTE(mp, i);
		protolen++;
	}

	hdrlen = i - 1;

	send_frame = NULL;
	if (len > PPP_MAX_MUX_LEN || (proto & 0x8000)) {

		/* send the queued frames */
		if (state->sa_mqhead != NULL) {
			/* increment counter if it is MUX pkt */
			if (state->sa_mqtail != NULL)
				KINCR(pks_sentmux);
			send_frame = ahdlc_encode(q, state->sa_mqhead);
		}

		/* send the current frame */
		mp = ahdlc_encode(q, mp);
		send_frame = sppp_mcat(send_frame, mp);

		/* reset the state values over here */
		RESET_MUX_VALUES(state);
		return (send_frame);
	}

	/* len + 1 , since we add the mux overhead */
	nlen = len + 1;
	/* subtract the protocol length if protocol matches */
	if (state->sa_proto == proto)
		nlen -= protolen;

	send_frame = NULL;
	if ((state->sa_mqlen + nlen) >= state->sa_mru) {

		/* send the existing queued frames */
		if (state->sa_mqhead != NULL) {
			/* increment counter if it is MUX pkt */
			if (state->sa_mqtail != NULL)
				KINCR(pks_sentmux);
			send_frame = ahdlc_encode(q, state->sa_mqhead);
		}

		/* reset state values */
		RESET_MUX_VALUES(state);
	}

	/* add the current frame to the queue */
	if (state->sa_mqhead != NULL) {

		if (state->sa_mqtail == NULL) {

			/*
			 * this is the first mblk in the queue create
			 * a new frame to hold the PPP MUX header
			 */
			if ((new_frame = allocb(PPP_HDRLEN+1,
			    BPRI_MED)) == NULL) {
				return (send_frame);
			}

			if (!IS_COMP_AC(state)) {
				/* add the header */
				*new_frame->b_wptr++ = PPP_ALLSTATIONS;
				*new_frame->b_wptr++ = PPP_UI;
			}

			/* do protocol compression */
			if (IS_COMP_PROT(state)) {
				*new_frame->b_wptr++ = PPP_MUX;
			} else {
				*new_frame->b_wptr++ = 0;
				*new_frame->b_wptr++ = PPP_MUX;
			}

			*new_frame->b_wptr++ = PFF |
			    (state->sa_mqlen - protolen - 1);

			if (DB_REF(mp) > 1) {
				tmp = copymsg(state->sa_mqhead);
				freemsg(state->sa_mqhead);
				if ((state->sa_mqhead = tmp) == NULL) {
					return (send_frame);
				}
			}

			if (state->sa_mqhead->b_rptr[0] == PPP_ALLSTATIONS)
				state->sa_mqhead->b_rptr += 2;

			linkb(new_frame, state->sa_mqhead);
			state->sa_mqtail = state->sa_mqhead;
			/* point mqtail to the last mblk_t */
			while (state->sa_mqtail->b_cont != NULL)
				state->sa_mqtail = state->sa_mqtail->b_cont;

			/* change state->sa_mqhead */
			state->sa_mqhead = new_frame;

		}

		if (state->sa_proto == proto) {

			/* Check if the mblk_t is being referenced */
			if (DB_REF(mp) > 1) {
				tmp = copymsg(mp);
				freemsg(mp);
				if ((mp = tmp) == NULL) {
					return (send_frame);
				}
			}

			/*
			 * match,can remove the protocol field
			 * and write data there
			 */
			mp->b_rptr += hdrlen;
			/*
			 * protolen - 1 ,because the the byte with
			 * the PFF bit and the length field have
			 * to added
			 */
			mp->b_rptr += (protolen - 1);
			*mp->b_rptr = (len - protolen) & 0xff;

		} else {
			/*
			 * no match, there are three options
			 * 1. write in mp
			 * 2. write in mqtail
			 * 3. alloc a new blk for just one byte
			 */
			/* Check if the mblk_t is being referenced */
			if (DB_REF(mp) > 1) {
				tmp = copymsg(mp);
				freemsg(mp);
				if ((mp = tmp) == NULL) {
					return (send_frame);
				}
			}

			if (hdrlen != 0) {

				mp->b_rptr += (hdrlen-1);
				*mp->b_rptr = PFF | (len);

			} else if (state->sa_mqtail->b_wptr <
			    DB_LIM(state->sa_mqtail)) {
					*state->sa_mqtail->b_wptr++ = PFF |len;
			} else {
				/* allocate a new mblk & add the byte */
				/* write the data */
				if ((new_frame = allocb(1, BPRI_MED))
				    == NULL) {
					freemsg(mp);
					return (send_frame);
				}
				*new_frame->b_wptr++ = PFF | (len);
				linkb(state->sa_mqtail, new_frame);
			}

			/* update proto */
			state->sa_proto = proto;
		}

		linkb(state->sa_mqtail, mp);
		state->sa_mqtail = mp;
		while (state->sa_mqtail->b_cont != NULL)
			state->sa_mqtail = state->sa_mqtail->b_cont;
		state->sa_mqlen += nlen;

	} else {
		state->sa_mqhead = mp;
		state->sa_mqlen = len + protolen + 1;
		state->sa_proto = proto;
	}

	if (state->sa_timeout_id == 0) {
		state->sa_timeout_id = qtimeout(q, spppasyn_timer, q,
		    (drv_usectohz(state->sa_timeout_usec)));
	}
	return (send_frame);
}

/*
 * Called from receive frame, this routine checks if it is a PPP_MUX
 * packet and demuxes it.  The returned pointer is a chain of mblks
 * using b_next and representing the demultiplexed packets.
 */
static mblk_t *
spppasyn_inpkt(queue_t *q, mblk_t *mp)
{
	sppp_ahdlc_t	*state = (sppp_ahdlc_t *)q->q_ptr;
	ushort_t	proto;
	ushort_t	prev_proto;
	uint32_t	len;		/* length of subframe */
	uchar_t		muxhdr;
	mblk_t		*hdrmp;
	mblk_t		*subframe;
	mblk_t		*retmp;

	if (!(mp->b_rptr[0] & PFF)) {
		KINCR(pks_inmuxerrs);
		(void) putnextctl1(q, M_CTL, PPPCTL_IERROR);
		freemsg(mp);
		return (NULL);
	}

	/* initialise the Last protocol and protocol length */
	prev_proto = 0;

	/*
	 * Taking into granted that the decoded frame is contiguous
	 */
	retmp = NULL;
	while (mp->b_rptr < mp->b_wptr) {

		/*
		 * get the last protocol, protocol length
		 * and the length of the message
		 */

		/* protocol field flag and length */
		muxhdr = mp->b_rptr[0];
		len = muxhdr & ~PFF;

		mp->b_rptr++;

		/* check if there and enough bytes left in pkt */
		if (MBLKL(mp) < len) {
			KINCR(pks_inmuxerrs);
			(void) putnextctl1(q, M_CTL, PPPCTL_IERROR);
			break;
		}

		/* allocate memory for the header length */
		if ((hdrmp = allocb(PPP_HDRLEN, BPRI_MED)) == NULL) {
			KINCR(pks_inallocfails);
			break;
		}

		/* add the ppp header to the pkt */
		*hdrmp->b_wptr++ = PPP_ALLSTATIONS;
		*hdrmp->b_wptr++ = PPP_UI;

		/* check if the protocol field flag is set */
		if (muxhdr & PFF) {

			/* get the protocol */
			proto = MSG_BYTE(mp, 0);
			if ((proto & 1) == 0)
				proto = (proto << 8) + MSG_BYTE(mp, 1);

			/* reset values */
			prev_proto = proto;
		} else {
			if (!IS_DECOMP_PROT(state))
				*hdrmp->b_wptr++ = prev_proto >> 8;
			*hdrmp->b_wptr++ = (prev_proto & 0xff);
		}

		/* get the payload from the MUXed packet */
		subframe = dupmsg(mp);
		subframe->b_wptr = mp->b_rptr + len;

		/* link the subframe to the new frame */
		linkb(hdrmp, subframe);

		/* do a putnext */
		retmp = sppp_mappend(retmp, hdrmp);

		/* move the read pointer beyond this subframe */
		mp->b_rptr += len;
	}

	freemsg(mp);
	return (retmp);
}


/*
 * timer routine which sends out the queued pkts *
 */
static void
spppasyn_timer(void *arg)
{
	queue_t *q;
	sppp_ahdlc_t *state;
	mblk_t *mp;

	ASSERT(arg);
	q = (queue_t *)arg;
	state = (sppp_ahdlc_t *)q->q_ptr;

	if (state->sa_mqhead != NULL) {
		/* increment counter */
		if (state->sa_mqtail != NULL)
			KINCR(pks_sentmux);
		if ((mp = ahdlc_encode(q, state->sa_mqhead)) != NULL)
			putnext(q, mp);
		/* reset the state values over here */
		RESET_MUX_VALUES(state);
	}
	/* clear timeout_id */
	state->sa_timeout_id = 0;
}