/*
 * if_ppp.c - Point-to-Point Protocol (PPP) Asynchronous driver.
 *
 * Copyright (c) 1989 Carnegie Mellon 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 Carnegie Mellon 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.
 *
 * Drew D. Perkins
 * Carnegie Mellon University
 * 4910 Forbes Ave.
 * Pittsburgh, PA 15213
 * (412) 268-8576
 * ddp@andrew.cmu.edu
 *
 * Based on:
 *	@(#)if_sl.c	7.6.1.2 (Berkeley) 2/15/89
 *
 * Copyright (c) 1987 Regents of the University of California.
 * 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 the University of California, Berkeley.  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.
 *
 * Serial Line interface
 *
 * Rick Adams
 * Center for Seismic Studies
 * 1300 N 17th Street, Suite 1450
 * Arlington, Virginia 22209
 * (703)276-7900
 * rick@seismo.ARPA
 * seismo!rick
 *
 * Pounded on heavily by Chris Torek (chris@mimsy.umd.edu, umcp-cs!chris).
 * Converted to 4.3BSD Beta by Chris Torek.
 * Other changes made at Berkeley, based in part on code by Kirk Smith.
 */

/* $Header: if_sl.c,v 1.12 85/12/20 21:54:55 chris Exp $ */
/* from if_sl.c,v 1.11 84/10/04 12:54:47 rick Exp */

#include "ppp.h"
#if NPPP > 0

#include "param.h"
#include "systm.h"
#include "dir.h"
#include "user.h"
#include "ioctl.h"
#include "tty.h"
#include "proc.h"
#include "file.h"
#include "conf.h"
#include "buf.h"
#include "dk.h"
#include "uio.h"
#include "errno.h"
#include "mbuf.h"
#include "socket.h"

#include "if.h"
#include "netisr.h"
#include "route.h"
#if INET
#include "../netinet/in.h"
#include "../netinet/in_systm.h"
#include "../netinet/in_var.h"
#include "../netinet/ip.h"
#endif

#ifndef ibm032
#include "../machine/mtpr.h"
#endif

#include "../net/if_ppp.h"


struct ppp_softc ppp_softc[NPPP];

int pppoutput(), pppioctl();

/*
 * Called from boot code to establish ppp interfaces.
 */
pppattach()
{
	register struct ppp_softc *sc;
	register int i = 0;

	for (sc = ppp_softc; i < NPPP; sc++) {
		sc->sc_if.if_name = "ppp";
		sc->sc_if.if_unit = i++;
		sc->sc_if.if_mtu = PPP_MTU;
		sc->sc_if.if_flags = IFF_POINTOPOINT;
		sc->sc_if.if_ioctl = pppioctl;
		sc->sc_if.if_output = pppoutput;
		sc->sc_if.if_snd.ifq_maxlen = IFQ_MAXLEN;
		sc->sc_inq.ifq_maxlen = IFQ_MAXLEN;
		if_attach(&sc->sc_if);
	}
}

/*
 * Line specific open routine.
 * Attach the given tty to the first available ppp unit.
 */
/* ARGSUSED */
pppopen(dev, tp)
	dev_t dev;
	register struct tty *tp;
{
	register struct ppp_softc *sc;
	register int nppp;

	if (!suser())
		return (EPERM);
	if (tp->t_line == PPPDISC)
		return (EBUSY);

	for (nppp = 0, sc = ppp_softc; nppp < NPPP; nppp++, sc++)
		if (sc->sc_ttyp == NULL) {
			sc->sc_flags = 0;
			sc->sc_ilen = 0;
			sc->sc_asyncmap = 0xffffffff;
			if (pppinit(sc) == 0) {
				sc->sc_if.if_flags &= ~(IFF_UP|IFF_RUNNING);
				return (ENOBUFS);
			}
			tp->t_sc = (caddr_t)sc;
			sc->sc_ttyp = tp;
			ttyflush(tp, FREAD | FWRITE);
			sc->sc_if.if_flags |= IFF_RUNNING;
			return (0);
		}

	return (ENXIO);
}

/*
 * Line specific close routine.
 * Detach the tty from the ppp unit.
 * Mimics part of ttyclose().
 */
pppclose(tp)
	struct tty *tp;
{
	register struct ppp_softc *sc;
	struct mbuf *m;
	int s;

	ttywflush(tp);
	tp->t_line = 0;
	s = splimp();		/* paranoid; splnet probably ok */
	sc = (struct ppp_softc *)tp->t_sc;
	if (sc != NULL) {
		if_down(&sc->sc_if);
		sc->sc_ttyp = NULL;
		tp->t_sc = NULL;
		m_freem(sc->sc_m);
		sc->sc_m = NULL;
		for (;;) {
			IF_DEQUEUE(&sc->sc_inq, m);
			if (m == NULL)
				break;
			m_freem(m);
		}
		sc->sc_if.if_flags &= ~(IFF_UP|IFF_RUNNING);
	}
	splx(s);
}

/*
 * Line specific (tty) read routine.
 */
pppread(tp, uio)
	register struct tty *tp;
	struct uio *uio;
{
	register struct ppp_softc *sc = (struct ppp_softc *)tp->t_sc;
	struct mbuf *m, *m0;
	register int s;
	int error;

	if ((tp->t_state&TS_CARR_ON)==0)
		return (EIO);
	s = splimp();
	while (sc->sc_inq.ifq_head == NULL &&
	       tp->t_line == PPPDISC)
		if (tp->t_state&TS_NBIO) {
			splx(s);
			return (EWOULDBLOCK);
		}
		else
			sleep((caddr_t)&tp->t_rawq, TTIPRI);
	if (tp->t_line != PPPDISC) {
		splx(s);
		return (-1);
	}
	IF_DEQUEUE(&sc->sc_inq, m);
	splx(s);
	IF_ADJ(m);
	for (m0 = m; m0 && uio->uio_resid; m0 = m0->m_next)
		if (error = uiomove(mtod(m, u_char *), m->m_len,
				    UIO_READ, uio))
			break;
	m_freem(m);
	return (error);
}

/*
 * Line specific (tty) write routine.
 */
pppwrite(tp, uio)
	register struct tty *tp;
	struct uio *uio;
{
	register struct ppp_softc *sc = (struct ppp_softc *)tp->t_sc;
	struct mbuf *m, *m0, **mp;
	struct sockaddr dst;
	struct ppp_header *ph1, *ph2;
	int len, error;

	if ((tp->t_state&TS_CARR_ON)==0)
		return (EIO);
	if (tp->t_line != PPPDISC)
		return (-1);
	if (uio->uio_resid > sc->sc_if.if_mtu + sizeof (struct ppp_header) ||
	    uio->uio_resid < sizeof (struct ppp_header))
		return (EMSGSIZE);
	for (mp = &m0; uio->uio_resid; mp = &m->m_next) {
		MGET(m, M_WAIT, MT_DATA);
		if ((*mp = m) == NULL) {
			m_freem(m0);
			return (ENOBUFS);
		}
		if (uio->uio_resid >= CLBYTES / 2) {
			MCLGET(m);
		}
		else
		    m->m_len = MLEN;
		len = MIN(m->m_len, uio->uio_resid);
		if (error = uiomove(mtod(m, u_char *), len, UIO_WRITE, uio)) {
			m_freem(m0);
			return (error);
		}
		m->m_len = len;
	}
	dst.sa_family = AF_UNSPEC;
	ph1 = (struct ppp_header *) &dst.sa_data;
	ph2 = mtod(m0, struct ppp_header *);
	*ph1 = *ph2;
	m0->m_off += sizeof (struct ppp_header);
	m0->m_len -= sizeof (struct ppp_header);
	return (pppoutput(&sc->sc_if, m0, &dst));
}

/*
 * Line specific (tty) select routine.
 */
pppselect(dev, rw)
	dev_t dev;
	int rw;
{
	register struct tty *tp = &cdevsw[major(dev)].d_ttys[minor(dev)];
	register struct ppp_softc *sc = (struct ppp_softc *)tp->t_sc;
	int s = spltty();

	if (tp == 0)
		panic("ttselect");
	switch (rw) {

	case FREAD:
		if (sc->sc_inq.ifq_head || ((tp->t_state & TS_CARR_ON) == 0))
			goto win;
		if (tp->t_rsel && tp->t_rsel->p_wchan == (caddr_t)&selwait)
			tp->t_state |= TS_RCOLL;
		else
			tp->t_rsel = u.u_procp;
		break;

	case FWRITE:
		if (!IF_QFULL(&sc->sc_if.if_snd))
			goto win;
		if (tp->t_wsel && tp->t_wsel->p_wchan == (caddr_t)&selwait)
			tp->t_state |= TS_WCOLL;
		else
			tp->t_wsel = u.u_procp;
		break;
	}
	splx(s);
	return (0);
win:
	splx(s);
	return (1);
}

/*
 * Line specific (tty) ioctl routine.
 * Provide a way to get the ppp unit number.
 * This discipline requires that tty device drivers call
 * the line specific l_ioctl routine from their ioctl routines.
 */
/* ARGSUSED */
ppptioctl(tp, cmd, data, flag)
	struct tty *tp;
	caddr_t data;
{
	register struct ppp_softc *sc = (struct ppp_softc *)tp->t_sc;

	switch (cmd) {
	case TIOCGETD:
		*(int *)data = ((struct ppp_softc *)tp->t_sc)->sc_if.if_unit;
		return (0);
	case FIONREAD:
		*(int *)data = sc->sc_inq.ifq_len;
		return (0);
	}
	return (-1);
}

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

/*
 * Calculate a new FCS given the current FCS and the new data.
 */
u_short pppfcs(fcs, cp, len)
    register u_short fcs;
    register u_char *cp;
    register int len;
{
    while (len--)
	fcs = PPP_FCS(fcs, *cp++);
    return (fcs);
}

/*
 * Queue a packet.  Start transmission if not active.
 * Packet is placed in normally Information field of PPP frame.
 */
pppoutput(ifp, m0, dst)
	struct ifnet *ifp;
	struct mbuf *m0;
	struct sockaddr *dst;
{
	register struct ppp_softc *sc = &ppp_softc[ifp->if_unit];
	struct mbuf *m, *m1;
	struct ppp_header *ph;
	u_short protocol, fcs;
	u_char address, control, *cp;
	int s, error, compac, compprot;

	if (sc->sc_ttyp == NULL ||
	    (ifp->if_flags & IFF_RUNNING) != IFF_RUNNING) {
		error = ENETDOWN;	/* sort of */
		goto bad;
	}
	if ((sc->sc_ttyp->t_state & TS_CARR_ON) == 0) {
		error = EHOSTUNREACH;
		goto bad;
	}

	/*
	 * Compute PPP header.
	 */
	address = PPP_ALLSTATIONS;
	control = PPP_UI;
	switch (dst->sa_family) {
#ifdef INET
	    case AF_INET:
		protocol = PPP_IP;
		break;
#endif
#ifdef NS
	    case AF_NS:
		protocol = PPP_XNS;
		break;
#endif
	    case AF_UNSPEC:
		ph = (struct ppp_header *) dst->sa_data;
		address = ph->ph_address;
		control = ph->ph_control;
		protocol = ntohs(ph->ph_protocol);
		break;
	    default:
		printf("ppp%d: af%d not supported\n", ifp->if_unit,
			dst->sa_family);
		error = EAFNOSUPPORT;
		goto bad;
	}
	compac = sc->sc_if.if_flags & IFF_COMPAC &&
		 address == PPP_ALLSTATIONS &&
		 control == PPP_UI &&
		 protocol != PPP_ALLSTATIONS;
	compprot = sc->sc_if.if_flags & IFF_COMPPROT &&
		 protocol < 0x100;

	/*
	 * Add PPP header.  If no space in first mbuf, allocate another.
	 */
	if (m0->m_off > MMAXOFF ||
	    m0->m_off < MMINOFF + sizeof (struct ppp_header)) {
	    m = m_get(M_DONTWAIT, MT_HEADER);
	    if (m == 0) {
		error = ENOBUFS;
		goto bad;
	    }
	    m->m_next = m0;
	    m->m_len = 0;
	    m0 = m;
	} else
	    m0->m_off -= (compac ? 0 : 2) + (compprot ? 1 : 2);

	cp = mtod(m0, u_char *);
	if (!compac) {
	    *cp++ = address;
	    *cp++ = control;
	    m0->m_len += 2;
	}
	if (!compprot) {
	    *cp++ = protocol >> 8;
	    m0->m_len++;
	}
	*cp++ = protocol & 0xff;
	m0->m_len++;

	/*
	 * Add PPP trailer.  Compute one's complement of FCS over frame
	 * and attach to mbuf chain least significant byte first.
	 */
	fcs = PPP_INITFCS;
	for (m = m0; m; m = m->m_next) {
	    fcs = pppfcs(fcs, mtod(m, u_char *), m->m_len);
	    m1 = m;
	}
	fcs ^= 0xffff;

	if (m1->m_off + m1->m_len > MMAXOFF - sizeof (short) ||
	    m1->m_off + m1->m_len < MMINOFF) {
	    m = m_get(M_DONTWAIT, MT_HEADER);
	    if (m == 0) {
		error = ENOBUFS;
		goto bad;
	    }
	    m->m_next = NULL;
	    m->m_len = 0;
	    m1->m_next = m;
	    m1 = m;
	}
	cp = mtod(m1, u_char *) + m1->m_len;
	*cp++ = fcs & 0xff;
	*cp++ = fcs >> 8;
	m1->m_len += 2;

	s = splimp();
	if (IF_QFULL(&ifp->if_snd)) {
		IF_DROP(&ifp->if_snd);
		sc->sc_if.if_oerrors++;
		splx(s);
		error = ENOBUFS;
		goto bad;
	}
	IF_ENQUEUE(&ifp->if_snd, m0);
	if (sc->sc_ttyp->t_outq.c_cc == 0) {
		splx(s);
		pppstart(sc->sc_ttyp);
	} else
		splx(s);
	return (0);

bad:
	m_freem(m0);
	return (error);
}

/*
 * Start output on interface.  Get another datagram
 * to send from the interface queue and map it to
 * the interface before starting output.
 */
pppstart(tp)
	register struct tty *tp;
{
	register struct ppp_softc *sc = (struct ppp_softc *)tp->t_sc;
	register struct mbuf *m;
	register int len;
	register u_char *start, *stop, *cp;
	int n, s;
	struct mbuf *m2;
	extern int cfreecount;

	for (;;) {
		/*
		 * If there is more in the output queue, just send it now.
		 * We are being called in lieu of ttstart and must do what
		 * it would.
		 */
		if (tp->t_outq.c_cc > 0)
			ttstart(tp);
		if (tp->t_outq.c_cc > PPP_HIWAT)
			return;

		/*
		 * This happens briefly when the line shuts down.
		 */
		if (sc == NULL)
			return;

		/*
		 * If system is getting low on clists
		 * and we have something running already, stop here.
		 */
		if (cfreecount < CLISTRESERVE +
		    sc->sc_if.if_mtu + sizeof (struct ppp_header) &&
		    tp->t_outq.c_cc)
			return;

		/*
		 * Get a packet and send it to the interface.
		 */
		s = splimp();
		IF_DEQUEUE(&sc->sc_if.if_snd, m);
		splx(s);
		if (m == NULL)
			return;

		/*
		 * The extra PPP_FLAG will start up a new packet, and thus
		 * will flush any accumulated garbage.  We do this whenever
		 * the line may have been idle for some time.
		 */
		if (tp->t_outq.c_cc == 0)
			(void) putc(PPP_FLAG, &tp->t_outq);

		while (m) {
			start = mtod(m, u_char *);
			len = m->m_len;
			stop = start + len;
			while (len > 0) {
				/*
				 * Find out how many bytes in the string we can
				 * handle without doing something special.
				 */
				for (cp = start; cp < stop; cp++)
					if ((*cp == PPP_FLAG) ||
					    (*cp == PPP_ESCAPE) ||
					    (*cp < 0x20 &&
					     (sc->sc_asyncmap & (1 << *cp))))
						break;
				n = cp - start;
				if (n) {
					/*
					 * Put n characters at once
					 * into the tty output queue.
					 */
					if (b_to_q((char *)start, n,
						   &tp->t_outq))
						break;
					len -= n;
					start = cp;
				}

				/*
				 * If there are characters left in the mbuf,
				 * the first one must be special..
				 * Put it out in a different form.
				 */
				if (len) {
					if (putc(PPP_ESCAPE, &tp->t_outq))
						break;
					if (putc(*start ^ PPP_TRANS,
						 &tp->t_outq)) {
						(void) unputc(&tp->t_outq);
						break;
					}
					start++;
					len--;
				}
			}
			MFREE(m, m2);
			m = m2;
		}

		if (putc(PPP_FLAG, &tp->t_outq)) {
			/*
			 * Not enough room.  Remove a char to make room
			 * and end the packet normally.
			 * If you get many collisions (more than one or two
			 * a day) you probably do not have enough clists
			 * and you should increase "nclist" in param.c.
			 */
			(void) unputc(&tp->t_outq);
			(void) putc(PPP_FLAG, &tp->t_outq);
			sc->sc_if.if_collisions++;
		} else
			sc->sc_if.if_opackets++;
	}
}

/*
 * Allocate enough mbuf to handle current MTU.
 */
pppinit(sc)
	register struct ppp_softc *sc;
{
	struct mbuf *m, **mp;
	int len = MAX(sc->sc_if.if_mtu, PPP_MTU) +sizeof (struct ifnet *) +
	    sizeof (struct ppp_header) + sizeof (u_short);
	int s;

	s = splimp();
	for (mp = &sc->sc_m; *mp; mp = &(*mp)->m_next)
		if ((len -= M_HASCL(*mp) ? MCLBYTES : MLEN) <= 0) {
			splx(s);
			return (1);
		}

	for (;; mp = &m->m_next) {
		MGET(m, M_DONTWAIT, MT_DATA);
		if (m) {
			*mp = m;
			MCLGET(m);
			if ((len -= M_HASCL(m) ? MCLBYTES : MLEN) <= 0) {
				splx(s);
				return (1);
			}
		}
		else {
			m_freem(sc->sc_m);
			sc->sc_m = NULL;
			splx(s);
			printf("ppp%d: can't allocate mbuf\n", sc - ppp_softc);
			return (0);
		}
	}
}

/*
 * Copy mbuf chain.  Would like to use m_copy(), but we need a real copy
 * of the data, not just copies of pointers to the data.
 */
struct mbuf *
ppp_btom(sc)
	struct ppp_softc *sc;
{
	register struct mbuf *m, **mp;
	struct mbuf *top = sc->sc_m;

	/*
	 * First check current mbuf.  If we have more than MLEN bytes,
	 * set beginning of buffer to the next mbuf.
	 * Else, copy the current mbuf, attach the copy where the current
	 * is attached, and set beginning of buffer to the current mbuf.
	 */
	if (sc->sc_mc->m_len > MLEN) {
		sc->sc_m = sc->sc_mc->m_next;
		sc->sc_mc->m_next = NULL;
	}
	else {
		MGET(m, M_DONTWAIT, MT_DATA);
		if (m == NULL)
			return (NULL);
		bcopy(mtod(sc->sc_mc, caddr_t), mtod(m, caddr_t),
		      sc->sc_mc->m_len);
		m->m_len = sc->sc_mc->m_len;
		for (mp = &top; *mp != sc->sc_mc; mp = &(*mp)->m_next)
			;
		*mp = m;
		sc->sc_m = sc->sc_mc;
	}
	if (pppinit(sc) == 0) {
		m_freem(top);
		if (pppinit(sc) == 0)
			sc->sc_if.if_flags &= ~IFF_UP;
		return (NULL);
	}
	return (top);
}

/*
 * tty interface receiver interrupt.
 */
pppinput(c, tp)
	int c;
	register struct tty *tp;
{
	register struct ppp_softc *sc;
	struct mbuf *m;
	struct ifqueue *inq;
	struct ppp_header *ph;
	int s;

	tk_nin++;
	sc = (struct ppp_softc *)tp->t_sc;
	if (sc == NULL)
		return;

	c &= 0xff;
	if (c == PPP_FLAG) {
		sc->sc_flags &= ~SC_FLUSH;

		if (sc->sc_ilen > 2) {
		    /*
		     * Remove FCS trailer.  Somewhat painful...
		     */
		    sc->sc_ilen -= 2;
		    if (--sc->sc_mc->m_len == 0) {
			for (m = sc->sc_m;
			     m->m_next != sc->sc_mc;
			     m = m->m_next)
			    ;
			sc->sc_mc = m;
		    }
		    sc->sc_mc->m_len--;
		}

		if (sc->sc_ilen < sizeof (struct ppp_header)) {
			if (sc->sc_ilen)
				sc->sc_if.if_ierrors++;
			sc->sc_ilen = 0;
			return;
		}
		if (sc->sc_fcs != PPP_GOODFCS ||
		    ((m = ppp_btom(sc)) == NULL)) {
			sc->sc_if.if_ierrors++;
			sc->sc_ilen = 0;
			return;
		}

		sc->sc_ilen = 0;
		sc->sc_if.if_ipackets++;
		ph = (struct ppp_header *) (mtod(m, struct ifnet **) + 1);
		switch (ntohs(ph->ph_protocol)) {
#ifdef INET
		    case PPP_IP:
			schednetisr(NETISR_IP);
			inq = &ipintrq;
			*(((struct ifnet **) (ph + 1)) - 1) =
			    *mtod(m, struct ifnet **);
			m->m_off += sizeof (struct ppp_header);
			m->m_len -= sizeof (struct ppp_header);
			break;
#endif
		    default:
			ttwakeup(tp);
			inq = &sc->sc_inq;
		}
		s = splimp();
		if (IF_QFULL(inq)) {
			IF_DROP(inq);
			sc->sc_if.if_ierrors++;
			m_freem(m);
		} else
			IF_ENQUEUE(inq, m);

		splx(s);
		return;
	}
	else if (sc->sc_flags & SC_FLUSH)
		return;
	else if (c == PPP_ESCAPE) {
		sc->sc_flags |= SC_ESCAPED;
		return;
	}

	if (sc->sc_flags & SC_ESCAPED) {
		sc->sc_flags &= ~SC_ESCAPED;
		c ^= PPP_TRANS;
	}

	/*
	 * Initialize buffer on first octet received.
	 * First octet could be address or protocol (when compressing
	 * address/control).
	 * Second octet is control.
	 * Third octet is first or second (when compressing protocol)
	 * octet of protocol.
	 * Fourth octet is second octet of protocol.
	 */
	if (sc->sc_ilen == 0) {
		sc->sc_mc = sc->sc_m;
		sc->sc_mp = mtod(sc->sc_m, u_char *);
		*((struct ifnet **) sc->sc_mp) = &sc->sc_if;
		sc->sc_mp += sizeof (struct ifnet *);
		sc->sc_m->m_len = sizeof (struct ifnet *);
		sc->sc_fcs = PPP_INITFCS;
		if (c != PPP_ALLSTATIONS) {
			if (sc->sc_if.if_flags & IFF_COMPAC) {
				*sc->sc_mp++ = PPP_ALLSTATIONS;
				*sc->sc_mp++ = PPP_UI;
				sc->sc_ilen += 2;
				sc->sc_mc->m_len += 2;
			}
			else {
				sc->sc_if.if_ierrors++;
				sc->sc_flags |= SC_FLUSH;
				return;
			}
		}
	}
	if (sc->sc_ilen == 1 &&
	    c != PPP_UI) {
		sc->sc_if.if_ierrors++;
		sc->sc_flags |= SC_FLUSH;
		return;
	}
	if (sc->sc_ilen == 2 &&
	    (c & 1) == 1) {
		if (sc->sc_if.if_flags & IFF_COMPPROT) {
			*sc->sc_mp++ = 0;
			sc->sc_ilen++;
			sc->sc_mc->m_len++;
		}
		else {
			sc->sc_if.if_ierrors++;
			sc->sc_flags |= SC_FLUSH;
			return;
		}
	}
	if (sc->sc_ilen == 3 &&
	    (c & 1) == 0) {
		sc->sc_if.if_ierrors++;
		sc->sc_flags |= SC_FLUSH;
		return;
	}

	if (++sc->sc_ilen > MAX(sc->sc_if.if_mtu, PPP_MTU) +
	    sizeof (struct ppp_header) + sizeof (u_short)) {
		sc->sc_if.if_ierrors++;
		sc->sc_flags |= SC_FLUSH;
		return;
	}
	if (sc->sc_mc->m_len >=
	    (M_HASCL(sc->sc_mc) ? MCLBYTES : MLEN)) {
		sc->sc_mc = sc->sc_mc->m_next;
		sc->sc_mp = mtod(sc->sc_mc, u_char *);
		sc->sc_mc->m_len = 0;
	}
	++sc->sc_mc->m_len;
	*sc->sc_mp++ = c;
	sc->sc_fcs = PPP_FCS(sc->sc_fcs, c);
}

/*
 * Process an ioctl request.
 */
pppioctl(ifp, cmd, data)
	register struct ifnet *ifp;
	int cmd;
	caddr_t data;
{
	register struct ppp_softc *sc = &ppp_softc[ifp->if_unit];
	register struct ifaddr *ifa = (struct ifaddr *)data;
	register struct ifreq *ifr = (struct ifreq *)data;
	int s = splimp(), error = 0;

	switch (cmd) {
	case SIOCSIFFLAGS:
		if ((ifp->if_flags & IFF_RUNNING) == 0)
			ifp->if_flags &= ~IFF_UP;
		break;

	case SIOCSIFADDR:
		if (ifa->ifa_addr.sa_family != AF_INET)
			error = EAFNOSUPPORT;
		break;

	case SIOCSIFDSTADDR:
		if (ifa->ifa_addr.sa_family != AF_INET)
			error = EAFNOSUPPORT;
		break;

	case SIOCSIFMTU:
		if (!suser())
			return (EPERM);
		sc->sc_if.if_mtu = ifr->ifr_mtu;
		if (pppinit(sc) == 0)
			error = ENOBUFS;
		break;

	case SIOCGIFMTU:
		ifr->ifr_mtu = sc->sc_if.if_mtu;
		break;

	case SIOCSIFASYNCMAP:
		if (!suser())
			return (EPERM);
		sc->sc_asyncmap = ifr->ifr_asyncmap;
		break;

	case SIOCGIFASYNCMAP:
		ifr->ifr_asyncmap = sc->sc_asyncmap;
		break;

	default:
		error = EINVAL;
	}
	splx(s);
	return (error);
}
#endif
