/* Linux ISDN subsystem, functions for synchronous PPP (linklevel).
 *
 * Copyright 1995,96    by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de)
 *           1999-2002  by Kai Germaschewski <kai@germaschewski.name>
 *
 * This software may be used and distributed according to the terms
 * of the GNU General Public License, incorporated herein by reference.
 */

#include <linux/module.h>
#include <linux/isdn.h>
#include <linux/smp_lock.h>
#include <linux/poll.h>
#include <linux/ppp-comp.h>
#include <linux/if_arp.h>

#include "isdn_common.h"
#include "isdn_net_lib.h"
#include "isdn_ppp.h"
#include "isdn_ppp_ccp.h"
#include "isdn_ppp_vj.h"
#include "isdn_ppp_mp.h"

/* ====================================================================== */

#define IPPP_MAX_RQ_LEN 8 /* max #frames queued for ipppd to read */

static int
isdn_ppp_set_compressor(isdn_net_dev *idev, struct isdn_ppp_comp_data *);

/* ====================================================================== */
/* IPPPD handling                                                         */
/* ====================================================================== */

/* We use reference counting for struct ipppd. It is alloced on
 * open() on /dev/ipppX and saved into file->private, making for one
 * reference. release() will release this reference, after all other
 * references are gone, the destructor frees it.
 *
 * Another reference is taken by isdn_ppp_bind() and freed by
 * isdn_ppp_unbind(). The callbacks from isdn_net_lib.c happen only
 * between isdn_ppp_bind() and isdn_ppp_unbind(), i.e. access to 
 * idev->ipppd is safe without further locking.
 */

#undef IPPPD_DEBUG

#ifdef IPPPD_DEBUG
#define ipppd_debug(i, fmt, arg...) \
        printk(KERN_DEBUG "ipppd %p minor %d state %#x %s: " fmt "\n", (i), \
               (i)->minor, (i)->state, __FUNCTION__ , ## arg)
#else
#define ipppd_debug(...) do { } while (0)
#endif

/* ipppd::flags */
enum {
	IPPPD_FL_HUP    = 0x01,
	IPPPD_FL_WAKEUP = 0x02,
};

/* ipppd::state */
enum {
	IPPPD_ST_OPEN,
	IPPPD_ST_ASSIGNED,
	IPPPD_ST_CONNECTED,
};

struct ipppd {
	struct list_head ipppds;
	int state;
	int flags;
	struct sk_buff_head rq;
	wait_queue_head_t wq;
	struct isdn_net_dev_s *idev;
	int unit;
	int minor;
	unsigned long debug;
	atomic_t refcnt;
};

/* ====================================================================== */

static spinlock_t ipppds_lock = SPIN_LOCK_UNLOCKED;
static LIST_HEAD(ipppds);

static void
ipppd_destroy(struct ipppd *ipppd)
{
	HERE;

	skb_queue_purge(&ipppd->rq);
	kfree(ipppd);
}

static inline struct ipppd *
ipppd_get(struct ipppd *ipppd)
{
	atomic_inc(&ipppd->refcnt);
	printk("%s: %d\n", __FUNCTION__, atomic_read(&ipppd->refcnt));
	return ipppd;
}

static inline void 
ipppd_put(struct ipppd *ipppd)
{
	printk("%s: %d\n", __FUNCTION__, atomic_read(&ipppd->refcnt));

	if (atomic_dec_and_test(&ipppd->refcnt))
		ipppd_destroy(ipppd);
}

/* ====================================================================== */
/* char dev ops                                                           */

/* --- open ------------------------------------------------------------- */

static int
ipppd_open(struct inode *ino, struct file *file)
{
	unsigned long flags;
	unsigned int minor = minor(ino->i_rdev) - ISDN_MINOR_PPP;
	struct ipppd *ipppd;

	ipppd = kmalloc(sizeof(*ipppd), GFP_KERNEL);
	if (!ipppd)
		return -ENOMEM;

	memset(ipppd, 0, sizeof(*ipppd));
	atomic_set(&ipppd->refcnt, 0);
	
	/* file->private_data holds a reference */
	file->private_data = ipppd_get(ipppd);

	ipppd->unit = -1;          /* set by isdn_ppp_bind */
	ipppd->minor = minor;
	ipppd->state = IPPPD_ST_OPEN;
	init_waitqueue_head(&ipppd->wq);
	skb_queue_head_init(&ipppd->rq);

	spin_lock_irqsave(&ipppds_lock, flags);
	list_add(&ipppd->ipppds, &ipppds);
	spin_unlock_irqrestore(&ipppds_lock, flags);
	
	ipppd_debug(ipppd, "minor %d", minor);

	return 0;
}

/* --- release  --------------------------------------------------------- */

static int
ipppd_release(struct inode *ino, struct file *file)
{
	unsigned long flags;
	struct ipppd *ipppd = file->private_data;

	ipppd_debug(ipppd, "");

	if (ipppd->state == IPPPD_ST_CONNECTED)
		isdn_net_hangup(ipppd->idev);

	spin_lock_irqsave(&ipppds_lock, flags);
	list_del(&ipppd->ipppds);
	spin_unlock_irqrestore(&ipppds_lock, flags);

	ipppd_put(ipppd);

	return 0;
}

/* --- read ------------------------------------------------------------- */

/* read() is always non blocking */
static ssize_t
ipppd_read(struct file *file, char *buf, size_t count, loff_t *off)
{
	struct ipppd *is;
	struct sk_buff *skb;
	int retval;

	if (off != &file->f_pos)
		return -ESPIPE;
	
	is = file->private_data;

	skb = skb_dequeue(&is->rq);
	if (!skb) {
		retval = -EAGAIN;
		goto out;
	}
	if (skb->len > count) {
		retval = -EMSGSIZE;
		goto out_free;
	}
	if (copy_to_user(buf, skb->data, skb->len)) {
		retval = -EFAULT;
		goto out_free;
	}
	retval = skb->len;

 out_free:
	dev_kfree_skb(skb);
 out:
	return retval;
}

/* --- write ------------------------------------------------------------ */

/* write() is always non blocking */
static ssize_t
ipppd_write(struct file *file, const char *buf, size_t count, loff_t *off)
{
	isdn_net_dev *idev;
	struct inl_ppp *inl_ppp;
	struct ind_ppp *ind_ppp;
	struct ipppd *ipppd;
	struct sk_buff *skb;
	char *p;
	int retval;
	u16 proto;

	if (off != &file->f_pos)
		return -ESPIPE;

	ipppd = file->private_data;
	ipppd_debug(ipppd, "count = %d", count);

	if (ipppd->state != IPPPD_ST_CONNECTED) {
		retval = -ENOTCONN;
		goto out;
	}

	idev = ipppd->idev;
	if (!idev) {
		isdn_BUG();
		retval = -ENODEV;
		goto out;
	}
	ind_ppp = idev->ind_priv;
	inl_ppp = idev->mlp->inl_priv;
	/* Daemon needs to send at least full header, AC + proto */
	if (count < 4) {
		retval = -EMSGSIZE;
		goto out;
	}
	skb = isdn_ppp_dev_alloc_skb(idev, count, GFP_KERNEL);
	if (!skb) {
		retval = -ENOMEM;
		goto out;
	}
	p = skb_put(skb, count);
	if (copy_from_user(p, buf, count)) {
		kfree_skb(skb);
		retval = -EFAULT;
		goto out;
	}
	/* Don't reset huptimer for LCP packets. (Echo requests). */
	proto = PPP_PROTOCOL(p);
	if (proto != PPP_LCP)
		idev->huptimer = 0;
	
	/* Keeps CCP/compression states in sync */
	switch (proto) {
	case PPP_CCP:
		ippp_ccp_send_ccp(inl_ppp->ccp, skb);
		break;
	case PPP_CCPFRAG:
		ippp_ccp_send_ccp(ind_ppp->ccp, skb);
		break;
	}
	/* FIXME: Somewhere we need protection against the
	 * queue growing too large */
	isdn_net_write_super(idev, skb);

	retval = count;
	
 out:
	return retval;
}

/* --- poll ------------------------------------------------------------- */

static unsigned int
ipppd_poll(struct file *file, poll_table * wait)
{
	unsigned int mask;
	struct ipppd *is;

	is = file->private_data;

	ipppd_debug(is, "");

	/* just registers wait_queue hook. This doesn't really wait. */
	poll_wait(file, &is->wq, wait);

	if (is->flags & IPPPD_FL_HUP) {
		mask = POLLHUP;
		goto out;
	}
	/* we're always ready to send .. */
	mask = POLLOUT | POLLWRNORM;

	/*
	 * if IPPP_FL_WAKEUP is set we return even if we have nothing to read
	 */
	if (!skb_queue_empty(&is->rq) || is->flags & IPPPD_FL_WAKEUP) {
		is->flags &= ~IPPPD_FL_WAKEUP;
		mask |= POLLIN | POLLRDNORM;
	}

 out:
	return mask;
}

/* --- ioctl ------------------------------------------------------------ */

/* get_arg .. ioctl helper */
static int
get_arg(unsigned long arg, void *val, int len)
{
	if (copy_from_user((void *) val, (void *) arg, len))
		return -EFAULT;
	return 0;
}

/* set arg .. ioctl helper */
static int
set_arg(unsigned long arg, void *val,int len)
{
	if (copy_to_user((void *) arg, (void *) val, len))
		return -EFAULT;
	return 0;
}

static int
ipppd_ioctl(struct inode *ino, struct file *file, unsigned int cmd,
	    unsigned long arg)
{
	isdn_net_dev *idev;
	struct ind_ppp *ind_ppp = NULL;
	struct inl_ppp *inl_ppp = NULL;
	unsigned long val;
	int r;
	struct ipppd *is;
	struct isdn_ppp_comp_data data;
	unsigned int cfg;

	is = file->private_data;

	ipppd_debug(is, "cmd %#x", cmd);

	// FIXME that needs locking?
	idev = is->idev;
	if (idev) {
		ind_ppp = idev->ind_priv;
		inl_ppp = idev->mlp->inl_priv;
	}
	switch (cmd) {
	case PPPIOCGUNIT:	/* get ppp/isdn unit number */
		r = set_arg(arg, &is->unit, sizeof(is->unit));
		break;
	case PPPIOCGDEBUG:
		r = set_arg(arg, &is->debug, sizeof(is->debug));
		break;
	case PPPIOCSDEBUG:
		r = get_arg(arg, &val, sizeof(val));
		if (r)
			break;
		is->debug = val;
		if (idev) {
			ind_ppp->debug = val;
			inl_ppp->debug = val;
		}
		break;
	case PPPIOCGCOMPRESSORS:
	{
		unsigned long protos[8];
		ippp_ccp_get_compressors(protos);
		r = set_arg(arg, protos, sizeof(protos));
		break;
	}
	default:
		r = -ENOTTY;
		break;
	}

	if (r != -ENOTTY)
		goto out;

	if (!idev) {
		r = -ENODEV;
		goto out;
	}

	switch (cmd) {
	case PPPIOCBUNDLE:
		r = get_arg(arg, &val, sizeof(val));
		if (r)
			break;

		r = ippp_mp_bundle(idev, val);
		break;
	case PPPIOCGIFNAME:
		r = set_arg(arg, idev->name, strlen(idev->name)+1);
		break;
	case PPPIOCGMPFLAGS:	/* get configuration flags */
		r = set_arg(arg, &inl_ppp->mp_cfg, sizeof(inl_ppp->mp_cfg));
		break;
	case PPPIOCSMPFLAGS:	/* set configuration flags */
		r = get_arg(arg, &val, sizeof(val));
		if (r)
			break;
		inl_ppp->mp_cfg = val;
		break;
	case PPPIOCGFLAGS:	/* get configuration flags */
		cfg = ind_ppp->pppcfg | ippp_ccp_get_flags(ind_ppp->ccp);
		r = set_arg(arg, &cfg, sizeof(cfg));
		break;
	case PPPIOCSFLAGS:	/* set configuration flags */
		r = get_arg(arg, &val, sizeof(val));
		if (r)
			break;
		if ((val & SC_ENABLE_IP) && !(ind_ppp->pppcfg & SC_ENABLE_IP)) {
			ind_ppp->pppcfg = val;
			/* OK .. we are ready to send buffers */
			isdn_net_online(idev);
			break;
		}
		ind_ppp->pppcfg = val;
		break;
	case PPPIOCGIDLE:	/* get idle time information */
	{
		struct ppp_idle pidle;
		pidle.xmit_idle = pidle.recv_idle = idev->huptimer;
		r = set_arg(arg, &pidle,sizeof(pidle));
		break;
	}
	case PPPIOCSMRU:	/* set receive unit size for PPP */
		r = get_arg(arg, &val, sizeof(val));
		if (r)
			break;
		r = ippp_ccp_set_mru(ind_ppp->ccp, val);
		break;
	case PPPIOCSMPMRU:
		break;
	case PPPIOCSMPMTU:
		break;
	case PPPIOCSMAXCID:	/* set the maximum compression slot id */
		r = get_arg(arg, &val, sizeof(val));
		if (r)
			break;
		r = ippp_vj_set_maxcid(idev, val);
		break;
	case PPPIOCSCOMPRESSOR:
		r = get_arg(arg, &data, sizeof(data));
		if (r)
			break;
		r = isdn_ppp_set_compressor(idev, &data);
		break;
	case PPPIOCGCALLINFO:
	{
		isdn_net_local *mlp;
		struct isdn_net_phone *phone;
		struct pppcallinfo pci;
		int i;
		memset(&pci, 0, sizeof(pci));

		mlp = idev->mlp;
		strncpy(pci.local_num, mlp->msn, 63);
		i = 0;
		list_for_each_entry(phone, &mlp->phone[1], list) {
			if (i++ == idev->dial) {
				strncpy(pci.remote_num,phone->num,63);
				break;
			}
		}
		pci.charge_units = idev->charge;
		if (idev->outgoing)
			pci.calltype = CALLTYPE_OUTGOING;
		else
			pci.calltype = CALLTYPE_INCOMING;
		if (mlp->flags & ISDN_NET_CALLBACK)
			pci.calltype |= CALLTYPE_CALLBACK;
		r = set_arg(arg, &pci, sizeof(pci));
		break;
	}
	default:
		r = -ENOTTY;
		break;
	}
 out:
	return r;
}

/* --- fops ------------------------------------------------------------- */

struct file_operations isdn_ppp_fops =
{
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,
	.read		= ipppd_read,
	.write		= ipppd_write,
	.poll		= ipppd_poll,
	.ioctl		= ipppd_ioctl,
	.open		= ipppd_open,
	.release	= ipppd_release,
};

/* --- ipppd_queue_read ------------------------------------------------- */

/* Queue packets for ipppd to read(). */

static int
ipppd_queue_read(struct ipppd *is, u16 proto, unsigned char *buf, int len)
{
	struct sk_buff *skb;
	unsigned char *p;
	int retval;

	if (is->state != IPPPD_ST_CONNECTED) {
		printk(KERN_DEBUG "ippp: device not connected.\n");
		retval = -ENOTCONN;
		goto out;
	}
	if (skb_queue_len(&is->rq) > IPPP_MAX_RQ_LEN) {
		printk(KERN_WARNING "ippp: Queue is full\n");
		retval = -EBUSY;
		goto out;
	}
	skb = dev_alloc_skb(len + 4);
	if (!skb) {
		printk(KERN_WARNING "ippp: Can't alloc buf\n");
		retval = -ENOMEM;
		goto out;
	}
	p = skb_put(skb, 4);
	p += put_u8(p, PPP_ALLSTATIONS);
	p += put_u8(p, PPP_UI);
	p += put_u16(p, proto);
	memcpy(skb_put(skb, len), buf, len);

	skb_queue_tail(&is->rq, skb);
	wake_up(&is->wq);

	retval = len;
 out:
	return retval;
}

/* ====================================================================== */
/* interface to isdn_net_lib                                            */
/* ====================================================================== */


/* Prototypes */
static int
isdn_ppp_if_get_unit(char *namebuf);

static void
isdn_ppp_dev_xmit(void *priv, struct sk_buff *skb, u16 proto);

static struct sk_buff *
isdn_ppp_lp_alloc_skb(void *priv, int len, int gfp_mask);

/* New CCP stuff */
static void
isdn_ppp_dev_kick_up(void *priv);

/*
 * frame log (debug)
 */
void
isdn_ppp_frame_log(char *info, char *data, int len, int maxlen,int unit,int slot)
{
	int cnt,
	 j,
	 i;
	char buf[80];

	if (len < maxlen)
		maxlen = len;

	for (i = 0, cnt = 0; cnt < maxlen; i++) {
		for (j = 0; j < 16 && cnt < maxlen; j++, cnt++)
			sprintf(buf + j * 3, "%02x ", (unsigned char) data[cnt]);
		printk(KERN_DEBUG "[%d/%d].%s[%d]: %s\n",unit,slot, info, i, buf);
	}
}

void
ippp_push_proto(struct ind_ppp *ind_ppp, struct sk_buff *skb, u16 proto)
{
	if (skb_headroom(skb) < 2) {
		isdn_BUG();
		return;
	}
	if ((ind_ppp->pppcfg & SC_COMP_PROT) && proto <= 0xff)
		put_u8(skb_push(skb, 1), proto);
	else
		put_u16(skb_push(skb, 2), proto);

}

static void
ippp_push_ac(struct ind_ppp *ind_ppp, struct sk_buff *skb)
{
	unsigned char *p;

	if (skb_headroom(skb) < 2) {
		isdn_BUG();
		return;
	}
	if (ind_ppp->pppcfg & SC_COMP_AC)
		return;

	p = skb_push(skb, 2);	
	p += put_u8(p, PPP_ALLSTATIONS);
	p += put_u8(p, PPP_UI);
}

/*
 * unbind isdn_net_local <=> ippp-device
 * note: it can happen, that we hangup/free the master before the slaves
 *       in this case we bind another lp to the master device
 */
static void
isdn_ppp_unbind(isdn_net_dev *idev)
{
	struct ind_ppp *ind_ppp = idev->ind_priv;
	struct ipppd *is = ind_ppp->ipppd;
	
	if (!is) {
		isdn_BUG();
		return;
	}
	ipppd_debug(is, "");

	if (is->state != IPPPD_ST_ASSIGNED)
		isdn_BUG();

	is->state = IPPPD_ST_OPEN;

	/* is->idev will be invalid shortly */
	ippp_ccp_free(ind_ppp->ccp);

	is->idev = NULL;
	/* lose the reference we took on isdn_ppp_bind */
	ipppd_put(is); 
	ind_ppp->ipppd = NULL;

	kfree(ind_ppp);
	idev->ind_priv = NULL;

	return;
}

/*
 * bind isdn_net_local <=> ippp-device
 */
int
isdn_ppp_bind(isdn_net_dev *idev)
{
	struct ind_ppp *ind_ppp;
	int unit = 0;
	unsigned long flags;
	int retval = 0;
	struct ipppd *ipppd;

	if (idev->ind_priv) {
		isdn_BUG();
		return -EIO;
	}
	ind_ppp = kmalloc(sizeof(struct ind_ppp), GFP_KERNEL);
	if (!ind_ppp)
		return -ENOMEM;

	spin_lock_irqsave(&ipppds_lock, flags);
	if (idev->pppbind < 0) {  /* device not bound to ippp device ? */
		struct list_head *l;
		char exclusive[ISDN_MAX_CHANNELS];	/* exclusive flags */
		memset(exclusive, 0, ISDN_MAX_CHANNELS);
		/* step through net devices to find exclusive minors */
		list_for_each(l, &isdn_net_devs) {
			isdn_net_dev *p = list_entry(l, isdn_net_dev, global_list);
			if (p->pppbind >= 0 && p->pppbind < ISDN_MAX_CHANNELS)
				exclusive[p->pppbind] = 1;
		}
		/*
		 * search a free device / slot
		 */
		list_for_each_entry(ipppd, &ipppds, ipppds) {
			if (!ipppd)
				continue;
			if (ipppd->state != IPPPD_ST_OPEN)
				continue;
			if (!exclusive[ipppd->minor])
				goto found;
		}
	} else {
		list_for_each_entry(ipppd, &ipppds, ipppds) {
			if (!ipppd)
				continue;
			if (ipppd->state != IPPPD_ST_OPEN)
				continue;
			if (ipppd->minor == idev->pppbind)
				goto found;
		}
	}

	printk(KERN_INFO "isdn_ppp_bind: no ipppd\n");
	retval = -ESRCH;
	goto err;

 found:
	unit = isdn_ppp_if_get_unit(idev->name);	/* get unit number from interface name .. ugly! */
	if (unit < 0) {
		printk(KERN_INFO "isdn_ppp_bind: illegal interface name %s.\n", idev->name);
		retval = -ENODEV;
		goto err;
	}
	
	ipppd->unit = unit;
	ipppd->state = IPPPD_ST_ASSIGNED;
	ipppd->idev = idev;
	/* we hold a reference until isdn_ppp_unbind() */
	ipppd_get(ipppd);
	spin_unlock_irqrestore(&ipppds_lock, flags);

	idev->ind_priv = ind_ppp;
	ind_ppp->pppcfg = 0;         /* config flags */
	ind_ppp->ipppd = ipppd;
	ind_ppp->ccp = ippp_ccp_alloc();
	if (!ind_ppp->ccp) {
		retval = -ENOMEM;
		goto out;
	}
	ind_ppp->ccp->proto       = PPP_COMPFRAG;
	ind_ppp->ccp->priv        = idev;
	ind_ppp->ccp->alloc_skb   = isdn_ppp_dev_alloc_skb;
	ind_ppp->ccp->xmit        = isdn_ppp_dev_xmit;
	ind_ppp->ccp->kick_up     = isdn_ppp_dev_kick_up;

	retval = ippp_mp_bind(idev);
	if (retval)
		goto out;
	
	return 0;

 out:
	ipppd->state = IPPPD_ST_OPEN;
	ipppd_put(ipppd);
	ind_ppp->ipppd = NULL;
	kfree(ind_ppp);
	idev->ind_priv = NULL;
	return retval;

 err:
	spin_unlock_irqrestore(&ipppds_lock, flags);
	kfree(ind_ppp);
	return retval;
}

/*
 * kick the ipppd on the device
 * (wakes up daemon after B-channel connect)
 */

static void
isdn_ppp_connected(isdn_net_dev *idev)
{
	struct ind_ppp *ind_ppp = idev->ind_priv;
	struct ipppd *ipppd = ind_ppp->ipppd;

	ipppd_debug(ipppd, "");

	ipppd->state  = IPPPD_ST_CONNECTED;
	ipppd->flags |= IPPPD_FL_WAKEUP;
	wake_up(&ipppd->wq);
}

static void
isdn_ppp_disconnected(isdn_net_dev *idev)
{
	struct ind_ppp *ind_ppp = idev->ind_priv;
	struct ipppd *ipppd = ind_ppp->ipppd;

	ipppd_debug(ipppd, "");

	if (ind_ppp->pppcfg & SC_ENABLE_IP)
		isdn_net_offline(idev);

	if (ipppd->state != IPPPD_ST_CONNECTED)
		isdn_BUG();
	
	ipppd->state  = IPPPD_ST_ASSIGNED;
	ipppd->flags |= IPPPD_FL_HUP;
	wake_up(&ipppd->wq);

	ippp_mp_disconnected(idev);
}

/*
 * init memory, structures etc.
 */

int
isdn_ppp_init(void)
{
	return 0;
}

void
isdn_ppp_cleanup(void)
{
}

/*
 * check for address/control field and skip if allowed
 * retval != 0 -> discard packet silently
 */
static int
isdn_ppp_skip_ac(struct ind_ppp *ind_ppp, struct sk_buff *skb) 
{
	u8 val;

	if (skb->len < 1)
		return -EINVAL;

	get_u8(skb->data, &val);
	if (val != PPP_ALLSTATIONS) {
		/* if AC compression was not negotiated, but no AC present,
		   discard packet */
		if (ind_ppp->pppcfg & SC_REJ_COMP_AC)
			return -EINVAL;

		return 0;
	}
	if (skb->len < 2)
		return -EINVAL;

	get_u8(skb->data + 1, &val);
	if (val != PPP_UI)
		return -EINVAL;

	/* skip address/control (AC) field */
	skb_pull(skb, 2);
	return 0;
}

/*
 * get the PPP protocol header and pull skb
 * retval < 0 -> discard packet silently
 */
int
isdn_ppp_strip_proto(struct sk_buff *skb, u16 *proto) 
{
	u8 val;

	if (skb->len < 1)
		return -EINVAL;

	get_u8(skb->data, &val);
	if (val & 0x1) {
		/* protocol field is compressed */
		*proto = val;
		skb_pull(skb, 1);
	} else {
		if (skb->len < 2)
			return -EINVAL;
		get_u16(skb->data, proto);
		skb_pull(skb, 2);
	}
	return 0;
}

/*
 * handler for incoming packets on a syncPPP interface
 */
static void
isdn_ppp_receive(isdn_net_local *lp, isdn_net_dev *idev, struct sk_buff *skb)
{
	struct ind_ppp *ind_ppp = idev->ind_priv;
	struct ipppd *is = ind_ppp->ipppd;
	u16 proto;

	if (!is) 
		goto err;

	if (is->debug & 0x4) {
		printk(KERN_DEBUG "ippp_receive: is:%p lp:%p unit:%d len:%d\n",
		       is, lp, is->unit, skb->len);
		isdn_ppp_frame_log("receive", skb->data, skb->len, 32,is->unit,-1);
	}

 	if (isdn_ppp_skip_ac(ind_ppp, skb) < 0)
		goto err;

  	if (isdn_ppp_strip_proto(skb, &proto))
		goto err;

	ippp_mp_receive(idev, skb, proto);
	return;

 err:
	lp->stats.rx_dropped++;
	kfree_skb(skb);
}

/*
 * address/control and protocol have been stripped from the skb
 */
void
ippp_receive(isdn_net_dev *idev, struct sk_buff *skb, u16 proto)
{
	isdn_net_local *lp = idev->mlp;
	struct inl_ppp *inl_ppp = lp->inl_priv;
	struct ind_ppp *ind_ppp = idev->ind_priv;
 	struct ipppd *is = ind_ppp->ipppd;

	if (is->debug & 0x10) {
		printk(KERN_DEBUG "push, skb %d %04x\n", (int) skb->len, proto);
		isdn_ppp_frame_log("rpush", skb->data, skb->len, 256, is->unit, -1);
	}
	/* all packets need to be passed through the compressor */
	skb = ippp_ccp_decompress(inl_ppp->ccp, skb, &proto);
	if (!skb) /* decompression error */
		goto error;

	switch (proto) {
		case PPP_IPX:  /* untested */
			if (is->debug & 0x20)
				printk(KERN_DEBUG "isdn_ppp: IPX\n");
			isdn_netif_rx(idev, skb, htons(ETH_P_IPX));
			break;
		case PPP_IP:
			if (is->debug & 0x20)
				printk(KERN_DEBUG "isdn_ppp: IP\n");
			isdn_netif_rx(idev, skb, htons(ETH_P_IP));
			break;
		case PPP_COMP:
		case PPP_COMPFRAG:
			printk(KERN_INFO "isdn_ppp: unexpected compressed frame dropped\n");
			goto drop;
		case PPP_VJC_UNCOMP:
		case PPP_VJC_COMP:
			ippp_vj_decompress(idev, skb, proto);
			break;
		case PPP_CCPFRAG:
			ippp_ccp_receive_ccp(ind_ppp->ccp, skb);
			goto ccp;
		case PPP_CCP:
			ippp_ccp_receive_ccp(inl_ppp->ccp, skb);
	ccp:
			/* Dont pop up ResetReq/Ack stuff to the daemon any
			   longer - the job is done already */
			if(skb->data[0] == CCP_RESETREQ ||
			   skb->data[0] == CCP_RESETACK)
				goto free;
			/* fall through */
		default:
			// FIXME use skb directly
			ipppd_queue_read(is, proto, skb->data, skb->len);
			goto free;
	}
	return;

 drop:
	lp->stats.rx_dropped++;
 free:
	kfree_skb(skb);
	return;

 error:
	lp->stats.rx_dropped++;
}

/*
 * send ppp frame .. we expect a PIDCOMPressable proto --
 *  (here: currently always PPP_IP,PPP_VJC_COMP,PPP_VJC_UNCOMP)
 *
 * VJ compression may change skb pointer!!! .. requeue with old
 * skb isn't allowed!!
 */

static int
isdn_ppp_start_xmit(struct sk_buff *skb, struct net_device *ndev)
{
	isdn_net_local *mlp = ndev->priv;
	struct inl_ppp *inl_ppp = mlp->inl_priv;
	struct ind_ppp *ind_ppp;
	isdn_net_dev *idev = list_entry(mlp->online.next, isdn_net_dev, online);
	u16 proto = PPP_IP;     /* 0x21 */
	struct ipppd *ipppd;

	ndev->trans_start = jiffies;

	if (list_empty(&mlp->online))
		return isdn_net_autodial(skb, ndev);

	switch (ntohs(skb->protocol)) {
		case ETH_P_IP:
			proto = PPP_IP;
			break;
		case ETH_P_IPX:
			proto = PPP_IPX;	/* untested */
			break;
		default:
			printk(KERN_INFO "isdn_ppp: skipped unsupported protocol: %#x.\n", 
			       skb->protocol);
			goto drop;
	}

	idev = isdn_net_get_xmit_dev(mlp);
	if (!idev) {
		printk(KERN_INFO "%s: IP frame delayed.\n", ndev->name);
		goto stop;
	}
	ind_ppp = idev->ind_priv;
	if (!(ind_ppp->pppcfg & SC_ENABLE_IP)) {	/* PPP connected ? */
		isdn_BUG();
		goto stop;
	}
	ipppd = ind_ppp->ipppd;
	idev->huptimer = 0;

        if (ipppd->debug & 0x40)
                isdn_ppp_frame_log("xmit0", skb->data, skb->len, 256, ipppd->unit, -1);

	/* after this line,requeueing is no longer allowed! */
	skb = ippp_vj_compress(idev, skb, &proto);

	/* normal (single link) or bundle compression */
	skb = ippp_ccp_compress(inl_ppp->ccp, skb, &proto);

	if (ipppd->debug & 0x40)
                isdn_ppp_frame_log("xmit1", skb->data, skb->len, 32, ipppd->unit, -1);

	ippp_push_proto(ind_ppp, skb, proto);
	ippp_mp_xmit(idev, skb);
	return 0;

 drop:
	kfree_skb(skb);
	mlp->stats.tx_dropped++;
	return 0;

 stop:
	netif_stop_queue(ndev);
	return 1;
}

void
ippp_xmit(isdn_net_dev *idev, struct sk_buff *skb)
{
	struct ind_ppp *ind_ppp = idev->ind_priv;
	struct ipppd *ipppd = ind_ppp->ipppd;

	ippp_push_ac(ind_ppp, skb);

	if (ipppd->debug & 0x40) {
		isdn_ppp_frame_log("xmit3", skb->data, skb->len, 32, ipppd->unit, -1);
	}
	
	isdn_net_writebuf_skb(idev, skb);
}

/*
 * network device ioctl handlers
 */

static int
isdn_ppp_dev_ioctl_stats(struct ifreq *ifr, struct net_device *dev)
{
	struct ppp_stats *res, t;
	isdn_net_local *lp = (isdn_net_local *) dev->priv;
	struct inl_ppp *inl_ppp = lp->inl_priv;
	struct slcompress *slcomp;
	int err;

	res = (struct ppp_stats *) ifr->ifr_ifru.ifru_data;
	err = verify_area(VERIFY_WRITE, res, sizeof(struct ppp_stats));

	if (err)
		return err;

	/* build a temporary stat struct and copy it to user space */

	memset(&t, 0, sizeof(struct ppp_stats));
	if (dev->flags & IFF_UP) {
		t.p.ppp_ipackets = lp->stats.rx_packets;
		t.p.ppp_ibytes = lp->stats.rx_bytes;
		t.p.ppp_ierrors = lp->stats.rx_errors;
		t.p.ppp_opackets = lp->stats.tx_packets;
		t.p.ppp_obytes = lp->stats.tx_bytes;
		t.p.ppp_oerrors = lp->stats.tx_errors;
#ifdef CONFIG_ISDN_PPP_VJ
		slcomp = inl_ppp->slcomp;
		if (slcomp) {
			t.vj.vjs_packets = slcomp->sls_o_compressed + slcomp->sls_o_uncompressed;
			t.vj.vjs_compressed = slcomp->sls_o_compressed;
			t.vj.vjs_searches = slcomp->sls_o_searches;
			t.vj.vjs_misses = slcomp->sls_o_misses;
			t.vj.vjs_errorin = slcomp->sls_i_error;
			t.vj.vjs_tossed = slcomp->sls_i_tossed;
			t.vj.vjs_uncompressedin = slcomp->sls_i_uncompressed;
			t.vj.vjs_compressedin = slcomp->sls_i_compressed;
		}
#endif
	}
	if( copy_to_user(res, &t, sizeof(struct ppp_stats))) return -EFAULT;
	return 0;
}

int
isdn_ppp_dev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
	int error=0;
	char *r;
	int len;
	isdn_net_local *lp = (isdn_net_local *) dev->priv;


	if (lp->p_encap != ISDN_NET_ENCAP_SYNCPPP)
		return -EINVAL;

	switch (cmd) {
	case SIOCGPPPVER:
		r = (char *) ifr->ifr_ifru.ifru_data;
		len = strlen(PPP_VERSION) + 1;
		if (copy_to_user(r, PPP_VERSION, len))
			error = -EFAULT;
		break;
	case SIOCGPPPSTATS:
		error = isdn_ppp_dev_ioctl_stats(ifr, dev);
		break;
	default:
		error = -EINVAL;
		break;
	}
	return error;
}

static int
isdn_ppp_if_get_unit(char *name)
{
	int len,
	 i,
	 unit = 0,
	 deci;

	len = strlen(name);

	if (strncmp("ippp", name, 4) || len > 8)
		return -1;

	for (i = 0, deci = 1; i < len; i++, deci *= 10) {
		char a = name[len - i - 1];
		if (a >= '0' && a <= '9')
			unit += (a - '0') * deci;
		else
			break;
	}
	if (!i || len - i != 4)
		unit = -1;

	return unit;
}

/*
 * PPP compression stuff
 */


/* Push an empty CCP Data Frame up to the daemon to wake it up and let it
   generate a CCP Reset-Request or tear down CCP altogether */

static void isdn_ppp_dev_kick_up(void *priv)
{
	isdn_net_dev *idev = priv;
	struct ind_ppp *ind_ppp = idev->ind_priv;

	ipppd_queue_read(ind_ppp->ipppd, PPP_COMPFRAG, NULL, 0);
}

static void isdn_ppp_lp_kick_up(void *priv)
{
	isdn_net_local *lp = priv;
	isdn_net_dev *idev;
	struct ind_ppp *ind_ppp;

	if (list_empty(&lp->online)) {
		isdn_BUG();
		return;
	}
	idev = list_entry(lp->online.next, isdn_net_dev, online);
	ind_ppp = idev->ind_priv;
	ipppd_queue_read(ind_ppp->ipppd, PPP_COMP, NULL, 0);
}

/* Send a CCP Reset-Request or Reset-Ack directly from the kernel. */

static struct sk_buff *
__isdn_ppp_alloc_skb(isdn_net_dev *idev, int len, unsigned int gfp_mask)
{
	int hl = IPPP_MAX_HEADER + isdn_slot_hdrlen(idev->isdn_slot); 
	struct sk_buff *skb;

	skb = alloc_skb(hl + len, gfp_mask);
	if (!skb)
		return NULL;

	skb_reserve(skb, hl);
	return skb;
}

struct sk_buff *
isdn_ppp_dev_alloc_skb(void *priv, int len, int gfp_mask)
{
	isdn_net_dev *idev = priv;

	return __isdn_ppp_alloc_skb(idev, len, gfp_mask);
}

static struct sk_buff *
isdn_ppp_lp_alloc_skb(void *priv, int len, int gfp_mask)
{
	isdn_net_local *lp = priv;
	isdn_net_dev *idev;

	if (list_empty(&lp->online)) {
		isdn_BUG();
		return NULL;
	}
	idev = list_entry(lp->online.next, isdn_net_dev, online);
	return __isdn_ppp_alloc_skb(idev, len, gfp_mask);
}

static void
isdn_ppp_dev_xmit(void *priv, struct sk_buff *skb, u16 proto)
{
	isdn_net_dev *idev = priv;
	struct ind_ppp *ind_ppp = idev->ind_priv;

	ippp_push_proto(ind_ppp, skb, proto);
	ippp_push_ac(ind_ppp, skb);
	isdn_net_write_super(idev, skb);
}

static void
isdn_ppp_lp_xmit(void *priv, struct sk_buff *skb, u16 proto)
{
	isdn_net_local *lp = priv;
	isdn_net_dev *idev;
	struct ind_ppp *ind_ppp;

	if (list_empty(&lp->online)) {
		isdn_BUG();
		return;
	}
	idev = list_entry(lp->online.next, isdn_net_dev, online);
	ind_ppp = idev->ind_priv;
	ippp_push_proto(ind_ppp, skb, proto);
	ippp_push_ac(ind_ppp, skb);
	isdn_net_write_super(idev, skb);
}

static int
isdn_ppp_set_compressor(isdn_net_dev *idev, struct isdn_ppp_comp_data *data)
{
	struct ippp_ccp *ccp;
	struct inl_ppp *inl_ppp = idev->mlp->inl_priv;
	struct ind_ppp *ind_ppp = idev->ind_priv;

	if (data->flags & IPPP_COMP_FLAG_LINK)
		ccp = ind_ppp->ccp;
	else
		ccp = inl_ppp->ccp;

	return ippp_ccp_set_compressor(ccp, ind_ppp->ipppd->unit, data);
}

// ISDN_NET_ENCAP_SYNCPPP
// ======================================================================

static int
isdn_ppp_open(isdn_net_local *lp)
{
	struct inl_ppp *inl_ppp;

	inl_ppp = kmalloc(sizeof(*inl_ppp), GFP_KERNEL);
	if (!inl_ppp)
		return -ENOMEM;

	lp->inl_priv = inl_ppp;

	inl_ppp->slcomp = ippp_vj_alloc();
	if (!inl_ppp->slcomp)
		goto err;

	inl_ppp->ccp = ippp_ccp_alloc();
	if (!inl_ppp->ccp)
		goto err_vj;

	inl_ppp->ccp->proto       = PPP_COMP;
	inl_ppp->ccp->priv        = lp;
	inl_ppp->ccp->alloc_skb   = isdn_ppp_lp_alloc_skb;
	inl_ppp->ccp->xmit        = isdn_ppp_lp_xmit;
	inl_ppp->ccp->kick_up     = isdn_ppp_lp_kick_up;
	
	return 0;

 err_vj:
	ippp_vj_free(inl_ppp->slcomp);
 err:
	kfree(inl_ppp);
	lp->inl_priv = NULL;
	return -ENOMEM;
}

static void
isdn_ppp_close(isdn_net_local *lp)
{
	struct inl_ppp *inl_ppp = lp->inl_priv;
	
	ippp_ccp_free(inl_ppp->ccp);
	ippp_vj_free(inl_ppp->slcomp);

	kfree(inl_ppp);
	lp->inl_priv = NULL;
}

struct isdn_netif_ops isdn_ppp_ops = {
	.hard_start_xmit     = isdn_ppp_start_xmit,
	.do_ioctl            = isdn_ppp_dev_ioctl,
	.flags               = IFF_NOARP | IFF_POINTOPOINT,
	.type                = ARPHRD_PPP,
	.receive             = isdn_ppp_receive,
	.connected           = isdn_ppp_connected,
	.disconnected        = isdn_ppp_disconnected,
	.bind                = isdn_ppp_bind,
	.unbind              = isdn_ppp_unbind,
	.open                = isdn_ppp_open,
	.close               = isdn_ppp_close,
};

