/*
   PPP for Linux
*/

/*
   Sources:

   slip.c

   RFC1331: The Point-to-Point Protocol (PPP) for the Transmission of
   Multi-protocol Datagrams over Point-to-Point Links

   RFC1332: IPCP

   ppp-1.2

*/

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/interrupt.h>
#include <linux/ptrace.h>
#include <linux/ioport.h>
#include <linux/in.h>
#include <linux/malloc.h>
#include <linux/tty.h>
#include <linux/errno.h>
#include <asm/system.h>
#include <asm/bitops.h>
#include <asm/segment.h>
#include <dev.h>
#include <inet.h>
#include <skbuff.h>

#include <ip.h>
#include <tcp.h>
#include "slhc.h"
#include <linux/ppp.h>

#define ASSERT(p) if (!p) printk ("assertion failed: " # p);
#define PRINTK(p) printk p ;
#define PRINTKN(n,p) {if (ppp_debug >= n) printk p;}
#define CHECK_PPP(a)  if (!ppp->inuse) { printk ("PPP: ALERT! not INUSE! %d\n", __LINE__); return a;}
#define CHECK_PPP_VOID()  if (!ppp->inuse) { printk ("PPP: ALERT! not INUSE! %d\n", __LINE__); return;}

int ppp_debug = 2;
int ppp_debug_netpackets = 0;

int ppp_init(struct device *);
void ppp_init_ctrl_blk(struct ppp *);
int ppp_dev_open(struct device *);
int ppp_dev_close(struct device *);
void ppp_kick_tty(struct ppp *);
void ppp_output_done(struct ppp *);
void ppp_unesc(struct ppp *, unsigned char *, int);
void ppp_doframe(struct ppp *);
int ppp_do_ip(struct ppp *, unsigned short, unsigned char *, int);
int ppp_us_queue(struct ppp *, unsigned short, unsigned char *, int);
int ppp_xmit(struct sk_buff *, struct device *);
static unsigned short ppp_type_trans(struct sk_buff *, struct device *);
static int ppp_header(unsigned char *, struct device *, unsigned short,
	       unsigned long, unsigned long, unsigned int);
static void ppp_add_arp(unsigned long, struct sk_buff *, struct device *);
static int ppp_rebuild_header(void *, struct device *);
static struct enet_statistics *ppp_get_stats (struct device *);
struct ppp *ppp_find(struct tty_struct *);
struct ppp *ppp_alloc(void);
int ppp_lock(struct ppp *);
void ppp_unlock(struct ppp *);
void ppp_add_fcs(struct ppp *);
int ppp_check_fcs(struct ppp *);

int ppp_open(struct tty_struct *);
void ppp_close(struct tty_struct *);
int ppp_read(struct tty_struct *, struct file *, char *, int);
int ppp_write(struct tty_struct *, struct file *, char *, int);
int ppp_ioctl(struct tty_struct *, struct file *, unsigned int, unsigned long);
void ppp_tty_input_ready(struct tty_struct *);


/* FCS table from RFC1331 */

static unsigned 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
  };

struct tty_ldisc ppp_ldisc;
static struct ppp ppp_ctrl[PPP_NRUNIT];



/*************************************************************
 * INITIALIZATION
 *************************************************************/

static int first_time = 1;

/* called at boot time for each ppp device */

int
ppp_init(struct device *dev)
{
  struct ppp *ppp;
  int i;

  ppp = &ppp_ctrl[dev->base_addr];

  if (first_time) {
    first_time = 0;
    printk("PPP: version %s (%d channels)\n", PPP_VERSION, PPP_NRUNIT);
    printk("TCP compression code copyright 1989 Regents of the University of California\n");

    (void) memset(&ppp_ldisc, 0, sizeof(ppp_ldisc));
    ppp_ldisc.open    = ppp_open;
    ppp_ldisc.close   = ppp_close;
    ppp_ldisc.read    = ppp_read;
    ppp_ldisc.write   = ppp_write;
    ppp_ldisc.ioctl   = ppp_ioctl;
    ppp_ldisc.handler = ppp_tty_input_ready;

    if ((i = tty_register_ldisc(N_PPP, &ppp_ldisc)) == 0)
      printk("PPP line discipline registered.\n");
    else
      printk("error registering line discipline: %d\n", i);
  }

  /* initialize PPP control block */
  ppp_init_ctrl_blk (ppp);
  ppp->inuse = 0;
  ppp->line = dev->base_addr;
  ppp->tty = NULL;
  ppp->dev = dev;

  /* clear statistics */
  ppp->rcount = 0;
  ppp->rpacket = 0;
  ppp->roverrun = 0;
  ppp->spacket = 0;
  ppp->sbusy = 0;
  ppp->errors = 0;

  /* device INFO */
  dev->mtu = PPP_MTU;
  dev->hard_start_xmit = ppp_xmit;
  dev->open = ppp_dev_open;
  dev->stop = ppp_dev_close;
  dev->get_stats = ppp_get_stats;
  /* inapplicable fields follow */
  dev->hard_header = ppp_header;
  dev->add_arp = ppp_add_arp;
  dev->type_trans = ppp_type_trans;
  dev->hard_header_len = 0;
  dev->addr_len = 0;
  dev->type = 0;
  dev->queue_xmit = dev_queue_xmit;
  dev->rebuild_header = ppp_rebuild_header;
  for (i = 0; i < DEV_NUMBUFFS; i++)
    dev->buffs[i] = NULL;

  /* New-style flags */
  dev->flags = IFF_POINTOPOINT;
  dev->family = AF_INET;
  dev->pa_addr = 0;
  dev->pa_brdaddr = 0;
  dev->pa_mask = 0;
  dev->pa_alen = sizeof(unsigned long);

  return 0;
}

void
ppp_init_ctrl_blk(struct ppp *ppp)
{
  ppp->sending = 0;
  ppp->escape = 0;
  ppp->toss = 0;

  ppp->flags = 0;
  ppp->async_map = 0xffffffff;
  ppp->mtu = PPP_MTU;
  ppp->fcs = 0;

  ppp->slcomp = NULL;

  ppp->rbuff = NULL;
  ppp->xbuff = NULL;
  ppp->cbuff = NULL;

  ppp->rhead = NULL;
  ppp->rend = NULL;
  ppp->rcount = 0;
  ppp->xhead = NULL;

  ppp->us_rbuff = NULL;
  ppp->us_rbuff_end = NULL;
  ppp->us_rbuff_head = NULL;
  ppp->us_rbuff_tail = NULL;
  ppp->us_read_waitq = NULL;
  ppp->us_write_waitq = NULL;
  ppp->us_rbuff_lock = 0;
  ppp->inp_sig = 0;
  ppp->inp_sig_pid = 0;
}


/* called when PPP line discipline is selected on a tty */
int
ppp_open(struct tty_struct *tty)
{
  struct ppp *ppp;
  int l;
  unsigned char *p;

  if ((ppp = ppp_find(tty)) != NULL) {
    PRINTKN (1,("ppp_open: gack! tty already associated to %s!\n",
	   ppp->dev->name));
    return -EEXIST;
  }

  if ((ppp = ppp_alloc()) == NULL) {
    PRINTKN (1,("ppp_open: couldn't allocate ppp channel\n"));
    return -ENFILE;
  }

  ppp->tty = tty;
  tty_read_flush(tty);
  tty_write_flush(tty);

  /* make sure the channel is actually open */
  ppp_init_ctrl_blk(ppp);

  if((ppp->slcomp = slhc_init(16, 16)) == NULL) {
    PRINTKN (1,("ppp: no space for compression buffers!\n"));
    return -ENOMEM;
  }

  /* use the MTU field as set by the ifconfig ioctl in pppd */
  ppp->mtu = ppp->dev->mtu;
  l = (ppp->mtu + 10) * 2;
  p = (unsigned char *) kmalloc(l, GFP_KERNEL);
  if (p == NULL) {
    PRINTKN (1,("ppp: no memory for XMIT buffer!\n"));
    return -ENOMEM;
  }
  ppp->dev->mem_start = (unsigned long) p;
  ppp->dev->mem_end = (unsigned long) (p + l);

  p = (unsigned char *) kmalloc(l, GFP_KERNEL);
  if (p == NULL) {
    PRINTKN (1,("ppp: no memory for RECV buffer!\n"));
    return -ENOMEM;
  }
  ppp->dev->rmem_start = (unsigned long) p;
  ppp->dev->rmem_end = (unsigned long) (p + l);

  ppp->xbuff = (unsigned char *) ppp->dev->mem_start;
  ppp->xhead = ppp->xbuff;
  ppp->rbuff = (unsigned char *) ppp->dev->rmem_start;
  ppp->rhead = ppp->rbuff;
  ppp->rend = ppp->rbuff + l;
  ppp->rcount = 0;

  p = (unsigned char *) kmalloc(l, GFP_KERNEL);
  if (p == NULL) {
    PRINTKN (1,("ppp: no memory for COMPRESS buffer!\n"));
    return -ENOMEM;
  }
  ppp->cbuff = p;

  p = kmalloc (RBUFSIZE, GFP_KERNEL);
  if (p == NULL) {
    PRINTKN (1,("ppp: no space for user receive buffer\n"));
    return -ENOMEM;
  }
  ppp->us_rbuff = p;
  ppp->us_rbuff_end = p + RBUFSIZE;
  ppp->us_rbuff_head = p;
  ppp->us_rbuff_tail = p;

  ppp->us_read_waitq = NULL;
  ppp->us_write_waitq = NULL;
  ppp->us_rbuff_lock = 0;

  PRINTKN (2,("ppp: channel %s open\n", ppp->dev->name));

  return(ppp->line);
}


/* called when we abandon the PPP line discipline */
void
ppp_close(struct tty_struct *tty)
{
  struct ppp *ppp;

  if ((ppp = ppp_find(tty)) == NULL) {
    PRINTKN (1,("ppp: trying to close unopened tty!\n"));
    return;
  }

  CHECK_PPP_VOID();

  kfree(ppp->xbuff);
  kfree(ppp->cbuff);
  kfree(ppp->rbuff);
  kfree(ppp->us_rbuff);
  if (ppp->slcomp) slhc_free(ppp->slcomp);
  ppp->tty = 0;
/*  clear_bit (0, &ppp->inuse); */
  ppp->inuse = 0;
  
  PRINTKN (2,("ppp: channel %s closing.\n", ppp->dev->name));
}

/* called when ppp interface goes "up".  here this just means we start
   passing IP packets */
int
ppp_dev_open(struct device *dev)
{
  struct ppp *ppp;

  /* reset POINTOPOINT every time, since dev_close zaps it! */
  dev->flags |= IFF_POINTOPOINT;

  ppp = &ppp_ctrl[dev->base_addr];
  if (ppp->tty == NULL) {
    PRINTKN (1,("ppp: %s not connected to a TTY! can't go open!\n", dev->name));
    return -ENXIO;
  }
  PRINTKN (2,("ppp: channel %s going up for IP packets!\n", dev->name));

  CHECK_PPP(-ENXIO);

  return 0;
}

int
ppp_dev_close(struct device *dev)
{
  struct ppp *ppp;

  ppp = &ppp_ctrl[dev->base_addr];
  if (ppp->tty == NULL) {
    PRINTKN (1,("ppp: %s not connected to a TTY! can't go down!\n", dev->name));
    return -ENXIO;
  }
  PRINTKN (2,("ppp: channel %s going down for IP packets!", dev->name));
  CHECK_PPP(-ENXIO);

  return 0;
}

/*************************************************************
 * TTY OUTPUT
 *    The following function delivers a fully-formed PPP
 *    frame in ppp->xbuff to the TTY for output.
 *************************************************************/

void
ppp_kick_tty (struct ppp *ppp)
{
  if (tty_write_data(ppp->tty, (char *) ppp->xbuff, 
		     ppp->xhead - ppp->xbuff,
		     (void (*)(void *)) ppp_output_done, (void *) ppp) 
      == 0)
    ppp_output_done(ppp);
  ppp->spacket++;
}

void
ppp_output_done(struct ppp *ppp)
{
  ppp_unlock (ppp);
  if (ppp->dev->flags & IFF_UP)
    dev_tint (ppp->dev);
  wake_up_interruptible (&ppp->us_write_waitq);
}

/*************************************************************
 * TTY INPUT
 *    The following functions handle input that arrives from
 *    the TTY.  It recognizes PPP frames and either hands them
 *    to the network layer or queues them for delivery to a
 *    user process reading this TTY.
 *************************************************************/

void
ppp_dump_inqueue(struct tty_struct *tty)
{
  int head = tty->read_q.head, tail = tty->read_q.tail, i;
  printk ("INQUEUE: head %d tail %d imode %x:\n", head, tail, 
	  (unsigned int) tty->termios->c_iflag);
  i = tail; 
  while (i != head) {
    printk (" %x", tty->read_q.buf[i]);
    i = (i + 1) & (TTY_BUF_SIZE - 1);}
  printk ("\n  flags: ");
  i = tail/32;
  do {
    printk ("%8x ", (unsigned int) tty->readq_flags[i]);
    i++;
    if (i == TTY_BUF_SIZE/32) i = 0;
  } while (i != (head/32));
  printk ("\n");
}

/* called by lower levels of TTY driver when data becomes available.
   all incoming data comes through this function. */

void ppp_tty_input_ready(struct tty_struct *tty)
{
  struct ppp *ppp;
  int n, error;
  unsigned char buff[128];

/*  PRINTK( ("PPP: handler called.\n") ); */
  if ((ppp = ppp_find(tty)) == NULL) {
    PRINTKN (1,("PPP: handler called but couldn't find PPP struct.\n"));
    return;
  }

  CHECK_PPP_VOID();

  /* ZZZ */
  if (ppp_debug >= 5) ppp_dump_inqueue(ppp->tty);

  do {
    n = tty_read_raw_data(tty, buff, 128);
    if (ppp_debug >= 5) {
      int i;
      printk ("{<%d> ", n);
      for (i = 0; i < (n > 0 ? n : -n); i++) printk ("%x ", buff[i]);
      printk ("}\n");
    }
    if ( n == 0 )		/* nothing there */
      break;
    if ( n < 0 ) {
      /* Last character is error flag.
	 Process the previous characters, then set toss flag. */
      n = (-n) - 1;
      error = buff[n];
    } else error = 0;
    ppp_unesc(ppp,buff,n);
    if (error) ppp->toss = error;
  } while (1);
}


/* stuff a single character into the receive buffer */

static inline void
ppp_enqueue(struct ppp *ppp, unsigned char c)
{
  unsigned long flags;

  save_flags(flags);
  cli();
  if (ppp->rhead < ppp->rend) {
    *ppp->rhead = c;
    ppp->rhead++;
    ppp->rcount++;
  } else ppp->roverrun++;
  restore_flags(flags);
}

/* recover frame by undoing PPP escape mechanism;
   copies N chars of input data from C into PPP->rbuff
   calls ppp_doframe to dispose of any frames it finds
*/

void
ppp_unesc(struct ppp *ppp, unsigned char *c, int n)
{
  int i;

  for (i = 0; i < n; i++, c++) {
    PRINTKN (6,("(%x)", (unsigned int) *c));
    switch (*c) {
    case PPP_ESC:		/* PPP_ESC: invert 0x20 in next character */
      ppp->escape = 0x20;
      break;
    case PPP_FLAG:		/* PPP_FLAG: end of frame */
      if (ppp->escape)		/* PPP_ESC just before PPP_FLAG is illegal */
	ppp->toss = -1;
      ppp_doframe(ppp);		/* pass frame on to next layers */
      ppp->rcount = 0;
      ppp->rhead = ppp->rbuff;
      ppp->escape = 0;
      break;
    default:			/* regular character */
      if ((*c < 32) && (ppp->async_map & (1 << *c)))
	continue;		/* throw away control characters in asyncmap */
      ppp_enqueue (ppp, *c ^ ppp->escape);
      ppp->escape = 0;
    }
  }
}


/* on entry, a received frame is in ppp->rbuff
   check it and dispose as appropriate */
void
ppp_doframe(struct ppp *ppp)
{
  unsigned char *c = ppp->rbuff;
  unsigned short proto;
  int count = ppp->rcount;

  /* forget it if we've already noticed an error */
  if (ppp->toss) {
    PRINTKN (1, ("ppp_toss: tossing frame, reason = %d\n", ppp->toss));
    ppp->toss = 0;
    ppp->errors++;
    return;
  }
  ppp->toss = 0;

  if (ppp_debug >= 3) {
    int i;
    printk ("ppp: got <%d>.", count);
    for (i = 0; i < count; i++) printk ("%x.", c[i]);
    printk ("\n");
  }

  /* big enough? */
  if (count == 0)
    return;
  if (count < 4) {
    PRINTKN (1,("ppp: got runt ppp frame, %d chars\n", count));
    return;
  }

  /* check PPP error detection field */
  if (!ppp_check_fcs(ppp)) {
    PRINTKN (1,("ppp: frame with bad fcs\n"));
    ppp->errors++;
    return;
  }
  count -= 2;			/* ignore last two characters */

  /* now we have a good frame */
  /* figure out the protocol field */
  if ((c[0] == PPP_ADDRESS) && (c[1] == PPP_CONTROL)) {
    c = c + 2;			/* ADDR/CTRL not compressed, so skip */
    count -= 2;
  }

  if (*c & 1) {
    proto = *c++;		/* PROTO compressed */
    count--;
  } else {
    proto = (c[0] << 8) +  c[1]; /* PROTO uncompressed */
    c += 2;
    count -= 2;
  }

  /* handle the cases that go to the net layer */
  if ((ppp->dev->flags & IFF_UP) && ppp_do_ip(ppp, proto, c, count)) {
    ppp->rpacket++;
    return;
  }

  /* If we got here, it has to go to a user process doing a read,
     so queue it.
     User process expects to get whole frame (for some reason), so
     use count+2 so as to include FCS field. */
  if (ppp_us_queue(ppp, proto, c, count+2)) {
    ppp->rpacket++;
    return;
  }

  /* couldn't cope. */
  PRINTKN (1,("ppp: dropping packet on the floor: nobody could take it.\n"));
  ppp->errors++;
}


/* Examine packet at C, attempt to pass up to net layer. 
   PROTO is the protocol field from the PPP frame.
   Return 1 if could handle it, 0 otherwise.  */

int
ppp_do_ip (struct ppp *ppp, unsigned short proto, unsigned char *c,
	  int count)
{
  int flags, done;

  PRINTKN (4,("ppp_do_ip: proto %x len %d first byte %x\n", proto, count, c[0]));

  if (ppp_debug_netpackets)
    printk ("%s <-- proto %x len %d\n", ppp->dev->name, proto, count);
    
  if (proto == PROTO_IP)
    goto sendit;
  
  if ((proto == PROTO_VJCOMP) && !(ppp->flags & SC_REJ_COMP_TCP)) {
    /* get space for uncompressing the header */
    save_flags (flags);
    cli();
    if ((ppp->rhead + 80) < ppp->rend) {
      ppp->rhead += 80;
      ppp->rcount += 80;
      done = 1;
    } else {
      ppp->roverrun++;
      done = 0;
    }
    restore_flags(flags);

    if (! done)	{
      PRINTKN (1,("ppp: no space to decompress VJ compressed TCP header.\n"));
      return 1;
    }

    count = slhc_uncompress(ppp->slcomp, c, count);
    if (count <= 0) {
      ppp->errors++;
      PRINTKN (1,("ppp: error in VJ decompression\n"));
      return 1;
    }
    goto sendit;
  }
  
  if ((proto == PROTO_VJUNCOMP) && !(ppp->flags & SC_REJ_COMP_TCP)) {
    if (slhc_remember(ppp->slcomp, c, count) <= 0) {
      ppp->errors++;
      PRINTKN (1,("ppp: error in VJ memorizing\n"));
      return 1;
    }
    goto sendit;
  }

  /* not ours */
  return 0;

 sendit:
  if (ppp_debug_netpackets) {
    struct iphdr *iph = (struct iphdr *) c;
    printk ("%s <--    src %x dst %x len %d\n", ppp->dev->name, 
	    iph->saddr, iph->daddr, count);
  }

  do {
    done = dev_rint(c, count, 0, ppp->dev);
    if (done == 0 || done == 1) break;
  } while (1);

  return 1;
}

/* stuff packet at BUF, length LEN, into the us_rbuff buffer
   prepend PROTO information */

#define PUTC(c,label) *ppp->us_rbuff_head++ = c; \
                if (ppp->us_rbuff_head == ppp->us_rbuff_end) \
                     ppp->us_rbuff_head = ppp->us_rbuff; \
                if (ppp->us_rbuff_head == ppp->us_rbuff_tail) \
                     goto label;
#define GETC(c) c = *ppp->us_rbuff_tail++; \
                if (ppp->us_rbuff_tail == ppp->us_rbuff_end) \
                     ppp->us_rbuff_tail = ppp->us_rbuff;

int
ppp_us_queue(struct ppp *ppp, unsigned short proto, 
	     unsigned char *buf, int len)
{
  int totlen;
  unsigned char *saved_head;

  totlen = len+2;		/* including protocol */

  if (set_bit(1, &ppp->us_rbuff_lock)) {
    PRINTKN (1, ("ppp_us_queue: can't get lock\n"));
    return 0;
  }
  saved_head = ppp->us_rbuff_head;
  PUTC((totlen & 0xff00) >> 8, failure);
  PUTC(totlen & 0x00ff, failure);
  PUTC((proto & 0xff00) >> 8, failure);
  PUTC(proto & 0x00ff, failure);
  while (len-- > 0) {
    PUTC(*buf++, failure);
  }
  PRINTKN (3, ("ppp: successfully queued %d bytes\n", totlen));
  clear_bit(1, &ppp->us_rbuff_lock);
  wake_up_interruptible (&ppp->us_read_waitq);
  if (ppp->inp_sig && ppp->inp_sig_pid)
    if (kill_proc (ppp->inp_sig_pid, ppp->inp_sig, 1) != 0) {
      /* process is gone */
      PRINTKN (2,("ppp: process that requested notification is gone\n"));
      ppp->inp_sig = 0;
      ppp->inp_sig_pid = 0;
    }
  return 1;

 failure:
  ppp->us_rbuff_head = saved_head;
  clear_bit(1, &ppp->us_rbuff_lock);

  PRINTKN (1, ("ppp_us_queue: ran out of buffer space.\n"));

  return 0;
}


/*************************************************************
 * LINE DISCIPLINE SUPPORT
 *    The following functions form support user programs
 *    which read and write data on a TTY with the PPP line
 *    discipline.  Reading is done from a circular queue,
 *    filled by the lower TTY levels.
 *************************************************************/

/* read a PPP frame from the us_rbuff circular buffer, 
   waiting if necessary
*/

int
ppp_read(struct tty_struct *tty, struct file *file, char *buf, int nr)
{
  struct ppp *ppp;
  unsigned char c;
  int len, i;

  if ((ppp = ppp_find(tty)) == NULL) {
    PRINTKN (1,("ppp_read: cannnot find ppp channel\n"));
    return -EIO;
  }

  CHECK_PPP(-ENXIO);

  PRINTKN (4,("ppp_read: called %x num %d\n", (unsigned int) buf, nr));

  do {
    /* try to acquire read lock */
    if (set_bit(0, &ppp->us_rbuff_lock) == 0) {
      /* got lock */
      if (ppp->us_rbuff_head == ppp->us_rbuff_tail) {
	/* no data */
	PRINTKN (4,("ppp_read: no data\n"));
	clear_bit(0, &ppp->us_rbuff_lock);
        if (ppp->inp_sig) {
	  PRINTKN (4,("ppp_read: EWOULDBLOCK\n"));
	  return -EWOULDBLOCK;
        } else goto wait;
      }

      GETC (c); len = c << 8; GETC (c); len += c;

      PRINTKN (4,("ppp_read: len = %d\n", len));

      if (len + 2 > nr) {
	/* frame too big; can't copy it, but do update us_rbuff_head */
	PRINTKN (1,("ppp: read of %d bytes too small for %d frame\n",
		nr, len+2));
	ppp->us_rbuff_head += len;
	if (ppp->us_rbuff_head > ppp->us_rbuff_end)
	  ppp->us_rbuff_head += - (ppp->us_rbuff_end - ppp->us_rbuff);
	clear_bit(0, &ppp->us_rbuff_lock);
	wake_up_interruptible (&ppp->us_read_waitq);
	return -EOVERFLOW;		/* ZZZ; HACK! */
      } else {
	/* have the space: copy the packet, faking the first two bytes */
	put_fs_byte (PPP_ADDRESS, buf++);
	put_fs_byte (PPP_CONTROL, buf++);
	i = len;
	while (i-- > 0) {
	  GETC (c);
	  put_fs_byte (c, buf++);
	}
      }
      clear_bit(0, &ppp->us_rbuff_lock);
      wake_up_interruptible (&ppp->us_read_waitq);
      PRINTKN (3,("ppp_read: passing %d bytes up\n", len + 2));
      return len + 2;
    }

    /* need to wait */
  wait:
    current->timeout = 0;
    PRINTKN (3,("ppp_read: sleeping\n"));
    interruptible_sleep_on (&ppp->us_read_waitq);
    if (current->signal & ~current->blocked)
      return -EINTR;
  } while (1);
}

/* stuff a character into the transmit buffer, using PPP's way of escaping
   special characters.
   also, update ppp->fcs to take account of new character */
static inline void
ppp_stuff_char(struct ppp *ppp, unsigned char c)
{
  int curpt = ppp->xhead - ppp->xbuff;
  if ((curpt < 0) || (curpt > 3000)) printk ("ppp_stuff_char: %x %x %d\n",
 	(unsigned int) ppp->xbuff, (unsigned int) ppp->xhead, curpt);
  if ((c == PPP_FLAG) ||
      (c == PPP_ESC) ||
      ((c < 32) && (ppp->async_map & (1<<c)))) {
    *ppp->xhead++ = PPP_ESC;
    *ppp->xhead++ = c ^ 0x20;
  } else *ppp->xhead++ = c;
  ppp->fcs = (ppp->fcs >> 8) ^ fcstab[(ppp->fcs ^ c) & 0xff];
}


/* write a frame with NR chars from BUF to TTY
   we have to put the FCS field on ourselves
*/

int
ppp_write(struct tty_struct *tty, struct file *file, char *buf, int nr)
{
  struct ppp *ppp;
  int i;

  if ((ppp = ppp_find(tty)) == NULL) {
    PRINTKN (1,("ppp_write: cannot find ppp unit\n"));
    return -EIO;
  }

  CHECK_PPP(-ENXIO);
  if (ppp_debug >= 3) { int j;
    printk ("ppp_write: <%d>,", nr);
    for (j = 0; j < nr; j++) printk ("%X,", (unsigned int) get_fs_byte(&buf[j]));
    printk ("\n");}

  if (nr > ppp->mtu) {
    printk("ppp_write: truncating user packet from %d to mtu %d\n",
	   nr, ppp->mtu);
    nr = ppp->mtu;
  }

  /* lock this PPP unit so we will be the only writer;
     sleep if necessary */
  while ((ppp->sending == 1) || !ppp_lock(ppp)) {
    current->timeout = 0;
    PRINTKN (3,("ppp_write: sleeping\n"));
    interruptible_sleep_on(&ppp->us_write_waitq);
    if (current->signal & ~current->blocked)
      return -EINTR;
  }

  /* OK, locked.  Stuff the given bytes into the buffer. */

  PRINTKN(4,("ppp_write: acquired write lock\n"));
  ppp->xhead = ppp->xbuff;

  *ppp->xhead++ = PPP_FLAG; 

  ppp->fcs = PPP_FCS_INIT;
  i = nr;
  while (i-- > 0)
    ppp_stuff_char(ppp,get_fs_byte(buf++));

  ppp_add_fcs(ppp);		/* concatenate FCS at end */

  *ppp->xhead++ = PPP_FLAG;

  PRINTKN (4,("ppp_write: writing %d chars\n", ppp->xhead - ppp->xbuff));

  /* packet is ready-to-go */
  ppp_kick_tty(ppp);

  return nr;
}
 
int
ppp_ioctl(struct tty_struct *tty, struct file *file, unsigned int i,
	  unsigned long l)
{
  struct ppp *ppp;
  int fl;

  if ((ppp = ppp_find(tty)) == NULL) {
    printk ("ppp_ioctl: can't find PPP block from tty!\n");
    return -EBADF;
  }

  CHECK_PPP(-ENXIO);

  switch (i) {
  case PPPIOCGFLAGS:
    fl = (ppp->flags & SC_MASK);
    put_fs_long (fl, l);
    PRINTKN (3,("ppp_ioctl: get flags: addr %x flags %x\n", l, fl));
    return 0;
  case PPPIOCSFLAGS:
    fl = get_fs_long (l) & SC_MASK;
    ppp->flags = (ppp->flags & ~SC_MASK) | fl;
    PRINTKN (1,("ppp_ioctl: set flags: flags %x\n", fl));
    return 0;
  case PPPIOCGASYNCMAP:
    put_fs_long (ppp->async_map, l);
    PRINTKN (3,("ppp_ioctl: get asyncmap: addr %x asyncmap %x\n",
	    l, ppp->async_map));
    return 0;
  case PPPIOCSASYNCMAP:
    ppp->async_map = get_fs_long (l);
    PRINTKN (3,("ppp_ioctl: set asyncmap %x\n", ppp->async_map));
    return 0;
  case PPPIOCGUNIT:
    put_fs_long (ppp->dev->base_addr, l);
    PRINTKN (3,("ppp_ioctl: get unit: %d", ppp->dev->base_addr));
    return 0;
  case PPPIOCSINPSIG:
    ppp->inp_sig = get_fs_long (l);
    ppp->inp_sig_pid = current->pid;
    PRINTKN (3,("ppp_ioctl: set input signal %d\n", ppp->inp_sig));
    return 0;
  case PPPIOCSDEBUG:
    ppp_debug = get_fs_long (l);
    ppp_debug_netpackets = (ppp_debug & 0xff00) >> 8;
    ppp_debug &= 0xff;
    printk ("ppp_ioctl: set debug level %d, netpacket %d\n", 
	    ppp_debug,ppp_debug_netpackets);
    return 0;
  case PPPIOCGDEBUG:
    put_fs_long (ppp_debug | (ppp_debug_netpackets << 8), l);
    PRINTKN (3,("ppp_ioctl: get debug level %d\n", 
       ppp_debug | (ppp_debug_netpackets << 8)));
    return 0;
  default:
    PRINTKN (1,("ppp_ioctl: invalid ioctl: %x, addr %x\n", i, l));
    return -EINVAL;
  }
}


/*************************************************************
 * NETWORK OUTPUT
 *    This routine accepts requests from the network layer
 *    and attempts to deliver the packets.
 *    It also includes various routines we are compelled to
 *    have to make the network layer work (arp, etc...).
 *************************************************************/

int
ppp_xmit(struct sk_buff *skb, struct device *dev)
{
  struct tty_struct *tty;
  struct ppp *ppp;
  unsigned char *p;
  unsigned short proto;
  int len;

  /* Locate PPP structure */
  ppp = &ppp_ctrl[dev->base_addr];
  tty = ppp->tty;
  PRINTKN(4,("ppp_xmit [%s]: skb %X busy %d\n", dev->name, 
	     (unsigned int) skb, ppp->sending));

  CHECK_PPP(0);

  if (!(ppp->dev->flags & IFF_UP)) {
    PRINTKN(1,("ppp_xmit: packet sent on interface %s, which is down for IP\n",
	       dev->name));
    goto done;
  }

  if (tty == NULL) {
    PRINTKN(1,("ppp_xmit: %s not connected to a TTY!\n", dev->name));
    goto done;
  }

  if (skb == NULL) {
    PRINTKN(3,("ppp_xmit: null packet!\n"));
    return 0;
  }

  /* Attempt to acquire send lock */
  if (ppp->sending || !ppp_lock(ppp)) {
    PRINTKN(3,("ppp_xmit: busy\n"));
    ppp->sbusy++;
    return 1;
  }

  p = (unsigned char *) (skb + 1);
  len = skb->len;
  ppp->xhead = ppp->xbuff;

  /* try to compress, if VJ compression mode is on */
  if (ppp->flags & SC_COMP_TCP) {
    /* NOTE: last 0 argument says never to compress connection ID */
    len = slhc_compress(ppp->slcomp, p, len, ppp->cbuff, &p, 0);
    if (p[0] & SL_TYPE_COMPRESSED_TCP)
      proto = PROTO_VJCOMP;
    else if (p[0] >= SL_TYPE_UNCOMPRESSED_TCP) {
      proto = PROTO_VJUNCOMP;
      p[0] = (p[0] & 0x0f) | 0x40; 
    } else
      proto = PROTO_IP;
  } else proto = PROTO_IP;

  if (ppp_debug_netpackets) {
    struct iphdr *iph = (struct iphdr *) (skb + 1);
    printk ("%s ==> proto %x len %d src %x dst %x proto %d\n",
	    dev->name, proto, len, iph->saddr, iph->daddr, iph->protocol);
  }
    
  /* start of frame:   FLAG  ALL_STATIONS  CONTROL  <protohi> <protolo> */
  *ppp->xhead++ = PPP_FLAG;

  ppp->fcs = PPP_FCS_INIT;
  if (!(ppp->flags & SC_COMP_AC)) { 
    ppp_stuff_char(ppp, PPP_ADDRESS);
    ppp_stuff_char(ppp, PPP_CONTROL);
  }

  if (!(ppp->flags & SC_COMP_PROT) || (proto & 0xff00))
    ppp_stuff_char(ppp, proto>>8);
  ppp_stuff_char(ppp, proto&0xff);

  /* data part */
  while (len-- > 0)
    ppp_stuff_char(ppp, *p++);

  /* fcs and flag */
  ppp_add_fcs(ppp);
  *ppp->xhead++ = PPP_FLAG;

  /* send it! */
  PRINTKN (4,("ppp_xmit: sending %d bytes\n", ppp->xhead - ppp->xbuff));
  ppp_kick_tty(ppp);

 done:
  if (skb->free) 
    kfree_skb(skb, FREE_WRITE);
  return 0;
}
  
static unsigned short
ppp_type_trans (struct sk_buff *skb, struct device *dev)
{
  return(NET16(ETH_P_IP));
}

static int
ppp_header(unsigned char *buff, struct device *dev, unsigned short type,
	   unsigned long daddr, unsigned long saddr, unsigned len)
{
  return(0);
}

static void
ppp_add_arp(unsigned long addr, struct sk_buff *skb, struct device *dev)
{
}

static int
ppp_rebuild_header(void *buff, struct device *dev)
{
  return(0);
}

static struct enet_statistics ppp_stats;

static struct enet_statistics *
ppp_get_stats (struct device *dev)
{
  struct ppp *ppp = &ppp_ctrl[dev->base_addr];
  ppp_stats.rx_packets = ppp->rpacket;
  ppp_stats.rx_errors = ppp->errors;
  ppp_stats.rx_dropped = 0;
  ppp_stats.rx_fifo_errors = 0;
  ppp_stats.rx_length_errors = 0;
  ppp_stats.rx_over_errors = ppp->roverrun;
  ppp_stats.rx_crc_errors = 0;
  ppp_stats.rx_frame_errors = 0;
  ppp_stats.tx_packets = ppp->spacket;
  ppp_stats.tx_errors = 0;
  ppp_stats.tx_dropped = 0;
  ppp_stats.tx_fifo_errors = 0;
  ppp_stats.collisions = ppp->sbusy;
  ppp_stats.tx_carrier_errors = 0;
  ppp_stats.tx_aborted_errors = 0;
  ppp_stats.tx_window_errors = 0;
  ppp_stats.tx_heartbeat_errors = 0;
  PRINTKN (3, ("ppp_get_stats called"));
  return &ppp_stats;
}


/*************************************************************
 * UTILITIES
 *    Miscellany called by various functions above.
 *************************************************************/


/* find a PPP channel given a TTY */
struct ppp *
ppp_find(struct tty_struct *tty)
{
  int i;
  for (i = 0; i < PPP_NRUNIT; i++)
    if (ppp_ctrl[i].inuse && (ppp_ctrl[i].tty == tty)) return &ppp_ctrl[i];

  return NULL;
}

/* allocate a PPP channel */
struct ppp *
ppp_alloc(void)
{
  int i;
  for (i = 0; i < PPP_NRUNIT; i++)
    if (!set_bit(0, &ppp_ctrl[i].inuse)) return &ppp_ctrl[i];

  return NULL;
}

/* marks a PPP interface 'busy'.  user processes will wait, if
   they try to write, and the network code will refrain from sending
   return nonzero if succeeded in acquiring lock
*/

int
ppp_lock(struct ppp *ppp)
{
  int flags, locked;
  save_flags(flags);
  cli();
  locked = ppp->sending;
  ppp->sending = 1;
  if (ppp->dev->flags & IFF_UP)
    ppp->dev->tbusy = 1;
  restore_flags(flags);
  return locked == 0;
}

void
ppp_unlock(struct ppp *ppp)
{
  int flags;
  save_flags(flags);
  cli();
  ppp->sending = 0;
  if (ppp->dev->flags & IFF_UP)
    ppp->dev->tbusy = 0;
  restore_flags(flags);
}

/* FCS support functions */

void
ppp_add_fcs(struct ppp *ppp)
{
  unsigned short fcs = ppp->fcs;

  fcs ^= 0xffff;
  ppp_stuff_char(ppp, fcs & 0x00ff);
  ppp_stuff_char(ppp, (fcs & 0xff00) >> 8);
  ASSERT (ppp->fcs == PPP_FCS_GOOD);
  PRINTKN (4,("ppp_add_fcs: fcs is %d\n", (int) fcs));
}

int
ppp_check_fcs(struct ppp *ppp)
{
  unsigned short fcs = PPP_FCS_INIT, msgfcs;
  unsigned char *c = ppp->rbuff;
  int i;

  for (i = 0; i < ppp->rcount - 2; i++, c++)
    fcs = (fcs >> 8) ^ fcstab[(fcs ^ *c) & 0xff];

  fcs ^= 0xffff;
  msgfcs = (c[1] << 8) + c[0];
  PRINTKN (4,("ppp_check_fcs: got %x want %x\n", (int) msgfcs, (int) fcs));
  return fcs == msgfcs;
}
