Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
diff --git a/drivers/isdn/i4l/isdn_ppp.c b/drivers/isdn/i4l/isdn_ppp.c
new file mode 100644
index 0000000..3c09211
--- /dev/null
+++ b/drivers/isdn/i4l/isdn_ppp.c
@@ -0,0 +1,3020 @@
+/* $Id: isdn_ppp.c,v 1.1.2.3 2004/02/10 01:07:13 keil Exp $
+ *
+ * Linux ISDN subsystem, functions for synchronous PPP (linklevel).
+ *
+ * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de)
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/isdn.h>
+#include <linux/poll.h>
+#include <linux/ppp-comp.h>
+#ifdef CONFIG_IPPP_FILTER
+#include <linux/filter.h>
+#endif
+
+#include "isdn_common.h"
+#include "isdn_ppp.h"
+#include "isdn_net.h"
+
+#ifndef PPP_IPX
+#define PPP_IPX 0x002b
+#endif
+
+/* Prototypes */
+static int isdn_ppp_fill_rq(unsigned char *buf, int len, int proto, int slot);
+static int isdn_ppp_closewait(int slot);
+static void isdn_ppp_push_higher(isdn_net_dev * net_dev, isdn_net_local * lp,
+				 struct sk_buff *skb, int proto);
+static int isdn_ppp_if_get_unit(char *namebuf);
+static int isdn_ppp_set_compressor(struct ippp_struct *is,struct isdn_ppp_comp_data *);
+static struct sk_buff *isdn_ppp_decompress(struct sk_buff *,
+				struct ippp_struct *,struct ippp_struct *,int *proto);
+static void isdn_ppp_receive_ccp(isdn_net_dev * net_dev, isdn_net_local * lp,
+				struct sk_buff *skb,int proto);
+static struct sk_buff *isdn_ppp_compress(struct sk_buff *skb_in,int *proto,
+	struct ippp_struct *is,struct ippp_struct *master,int type);
+static void isdn_ppp_send_ccp(isdn_net_dev *net_dev, isdn_net_local *lp,
+	 struct sk_buff *skb);
+
+/* New CCP stuff */
+static void isdn_ppp_ccp_kickup(struct ippp_struct *is);
+static void isdn_ppp_ccp_xmit_reset(struct ippp_struct *is, int proto,
+				    unsigned char code, unsigned char id,
+				    unsigned char *data, int len);
+static struct ippp_ccp_reset *isdn_ppp_ccp_reset_alloc(struct ippp_struct *is);
+static void isdn_ppp_ccp_reset_free(struct ippp_struct *is);
+static void isdn_ppp_ccp_reset_free_state(struct ippp_struct *is,
+					  unsigned char id);
+static void isdn_ppp_ccp_timer_callback(unsigned long closure);
+static struct ippp_ccp_reset_state *isdn_ppp_ccp_reset_alloc_state(struct ippp_struct *is,
+						      unsigned char id);
+static void isdn_ppp_ccp_reset_trans(struct ippp_struct *is,
+				     struct isdn_ppp_resetparams *rp);
+static void isdn_ppp_ccp_reset_ack_rcvd(struct ippp_struct *is,
+					unsigned char id);
+
+
+
+#ifdef CONFIG_ISDN_MPP
+static ippp_bundle * isdn_ppp_bundle_arr = NULL;
+ 
+static int isdn_ppp_mp_bundle_array_init(void);
+static int isdn_ppp_mp_init( isdn_net_local * lp, ippp_bundle * add_to );
+static void isdn_ppp_mp_receive(isdn_net_dev * net_dev, isdn_net_local * lp, 
+							struct sk_buff *skb);
+static void isdn_ppp_mp_cleanup( isdn_net_local * lp );
+
+static int isdn_ppp_bundle(struct ippp_struct *, int unit);
+#endif	/* CONFIG_ISDN_MPP */
+  
+char *isdn_ppp_revision = "$Revision: 1.1.2.3 $";
+
+static struct ippp_struct *ippp_table[ISDN_MAX_CHANNELS];
+
+static struct isdn_ppp_compressor *ipc_head = NULL;
+
+/*
+ * frame log (debug)
+ */
+static 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);
+	}
+}
+
+/*
+ * 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
+ */
+int
+isdn_ppp_free(isdn_net_local * lp)
+{
+	struct ippp_struct *is;
+
+	if (lp->ppp_slot < 0 || lp->ppp_slot > ISDN_MAX_CHANNELS) {
+		printk(KERN_ERR "%s: ppp_slot(%d) out of range\n",
+			__FUNCTION__, lp->ppp_slot);
+		return 0;
+	}
+
+#ifdef CONFIG_ISDN_MPP
+	spin_lock(&lp->netdev->pb->lock);
+#endif
+	isdn_net_rm_from_bundle(lp);
+#ifdef CONFIG_ISDN_MPP
+	if (lp->netdev->pb->ref_ct == 1)	/* last link in queue? */
+		isdn_ppp_mp_cleanup(lp);
+
+	lp->netdev->pb->ref_ct--;
+	spin_unlock(&lp->netdev->pb->lock);
+#endif /* CONFIG_ISDN_MPP */
+	if (lp->ppp_slot < 0 || lp->ppp_slot > ISDN_MAX_CHANNELS) {
+		printk(KERN_ERR "%s: ppp_slot(%d) now invalid\n",
+			__FUNCTION__, lp->ppp_slot);
+		return 0;
+	}
+	is = ippp_table[lp->ppp_slot];
+	if ((is->state & IPPP_CONNECT))
+		isdn_ppp_closewait(lp->ppp_slot);	/* force wakeup on ippp device */
+	else if (is->state & IPPP_ASSIGNED)
+		is->state = IPPP_OPEN;	/* fallback to 'OPEN but not ASSIGNED' state */
+
+	if (is->debug & 0x1)
+		printk(KERN_DEBUG "isdn_ppp_free %d %lx %lx\n", lp->ppp_slot, (long) lp, (long) is->lp);
+
+	is->lp = NULL;          /* link is down .. set lp to NULL */
+	lp->ppp_slot = -1;      /* is this OK ?? */
+
+	return 0;
+}
+
+/*
+ * bind isdn_net_local <=> ippp-device
+ *
+ * This function is allways called with holding dev->lock so
+ * no additional lock is needed
+ */
+int
+isdn_ppp_bind(isdn_net_local * lp)
+{
+	int i;
+	int unit = 0;
+	struct ippp_struct *is;
+	int retval;
+
+	if (lp->pppbind < 0) {  /* device bounded to ippp device ? */
+		isdn_net_dev *net_dev = dev->netdev;
+		char exclusive[ISDN_MAX_CHANNELS];	/* exclusive flags */
+		memset(exclusive, 0, ISDN_MAX_CHANNELS);
+		while (net_dev) {	/* step through net devices to find exclusive minors */
+			isdn_net_local *lp = net_dev->local;
+			if (lp->pppbind >= 0)
+				exclusive[lp->pppbind] = 1;
+			net_dev = net_dev->next;
+		}
+		/*
+		 * search a free device / slot
+		 */
+		for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
+			if (ippp_table[i]->state == IPPP_OPEN && !exclusive[ippp_table[i]->minor]) {	/* OPEN, but not connected! */
+				break;
+			}
+		}
+	} else {
+		for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
+			if (ippp_table[i]->minor == lp->pppbind &&
+			    (ippp_table[i]->state & IPPP_OPEN) == IPPP_OPEN)
+				break;
+		}
+	}
+
+	if (i >= ISDN_MAX_CHANNELS) {
+		printk(KERN_WARNING "isdn_ppp_bind: Can't find a (free) connection to the ipppd daemon.\n");
+		retval = -1;
+		goto out;
+	}
+	unit = isdn_ppp_if_get_unit(lp->name);	/* get unit number from interface name .. ugly! */
+	if (unit < 0) {
+		printk(KERN_ERR "isdn_ppp_bind: illegal interface name %s.\n", lp->name);
+		retval = -1;
+		goto out;
+	}
+	
+	lp->ppp_slot = i;
+	is = ippp_table[i];
+	is->lp = lp;
+	is->unit = unit;
+	is->state = IPPP_OPEN | IPPP_ASSIGNED;	/* assigned to a netdevice but not connected */
+#ifdef CONFIG_ISDN_MPP
+	retval = isdn_ppp_mp_init(lp, NULL);
+	if (retval < 0)
+		goto out;
+#endif /* CONFIG_ISDN_MPP */
+
+	retval = lp->ppp_slot;
+
+ out:
+	return retval;
+}
+
+/*
+ * kick the ipppd on the device
+ * (wakes up daemon after B-channel connect)
+ */
+
+void
+isdn_ppp_wakeup_daemon(isdn_net_local * lp)
+{
+	if (lp->ppp_slot < 0 || lp->ppp_slot >= ISDN_MAX_CHANNELS) {
+		printk(KERN_ERR "%s: ppp_slot(%d) out of range\n",
+			__FUNCTION__, lp->ppp_slot);
+		return;
+	}
+	ippp_table[lp->ppp_slot]->state = IPPP_OPEN | IPPP_CONNECT | IPPP_NOBLOCK;
+	wake_up_interruptible(&ippp_table[lp->ppp_slot]->wq);
+}
+
+/*
+ * there was a hangup on the netdevice
+ * force wakeup of the ippp device
+ * go into 'device waits for release' state
+ */
+static int
+isdn_ppp_closewait(int slot)
+{
+	struct ippp_struct *is;
+
+	if (slot < 0 || slot >= ISDN_MAX_CHANNELS) {
+		printk(KERN_ERR "%s: slot(%d) out of range\n",
+			__FUNCTION__, slot);
+		return 0;
+	}
+	is = ippp_table[slot];
+	if (is->state)
+		wake_up_interruptible(&is->wq);
+	is->state = IPPP_CLOSEWAIT;
+	return 1;
+}
+
+/*
+ * isdn_ppp_find_slot / isdn_ppp_free_slot
+ */
+
+static int
+isdn_ppp_get_slot(void)
+{
+	int i;
+	for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
+		if (!ippp_table[i]->state)
+			return i;
+	}
+	return -1;
+}
+
+/*
+ * isdn_ppp_open
+ */
+
+int
+isdn_ppp_open(int min, struct file *file)
+{
+	int slot;
+	struct ippp_struct *is;
+
+	if (min < 0 || min > ISDN_MAX_CHANNELS)
+		return -ENODEV;
+
+	slot = isdn_ppp_get_slot();
+	if (slot < 0) {
+		return -EBUSY;
+	}
+	is = file->private_data = ippp_table[slot];
+	
+	printk(KERN_DEBUG "ippp, open, slot: %d, minor: %d, state: %04x\n",
+	       slot, min, is->state);
+
+	/* compression stuff */
+	is->link_compressor   = is->compressor = NULL;
+	is->link_decompressor = is->decompressor = NULL;
+	is->link_comp_stat    = is->comp_stat = NULL;
+	is->link_decomp_stat  = is->decomp_stat = NULL;
+	is->compflags = 0;
+
+	is->reset = isdn_ppp_ccp_reset_alloc(is);
+
+	is->lp = NULL;
+	is->mp_seqno = 0;       /* MP sequence number */
+	is->pppcfg = 0;         /* ppp configuration */
+	is->mpppcfg = 0;        /* mppp configuration */
+	is->last_link_seqno = -1;	/* MP: maybe set to Bundle-MIN, when joining a bundle ?? */
+	is->unit = -1;          /* set, when we have our interface */
+	is->mru = 1524;         /* MRU, default 1524 */
+	is->maxcid = 16;        /* VJ: maxcid */
+	is->tk = current;
+	init_waitqueue_head(&is->wq);
+	is->first = is->rq + NUM_RCV_BUFFS - 1;	/* receive queue */
+	is->last = is->rq;
+	is->minor = min;
+#ifdef CONFIG_ISDN_PPP_VJ
+	/*
+	 * VJ header compression init
+	 */
+	is->slcomp = slhc_init(16, 16);	/* not necessary for 2. link in bundle */
+#endif
+#ifdef CONFIG_IPPP_FILTER
+	is->pass_filter = NULL;
+	is->active_filter = NULL;
+#endif
+	is->state = IPPP_OPEN;
+
+	return 0;
+}
+
+/*
+ * release ippp device
+ */
+void
+isdn_ppp_release(int min, struct file *file)
+{
+	int i;
+	struct ippp_struct *is;
+
+	if (min < 0 || min >= ISDN_MAX_CHANNELS)
+		return;
+	is = file->private_data;
+
+	if (!is) {
+		printk(KERN_ERR "%s: no file->private_data\n", __FUNCTION__);
+		return;
+	}
+	if (is->debug & 0x1)
+		printk(KERN_DEBUG "ippp: release, minor: %d %lx\n", min, (long) is->lp);
+
+	if (is->lp) {           /* a lp address says: this link is still up */
+		isdn_net_dev *p = is->lp->netdev;
+
+		if (!p) {
+			printk(KERN_ERR "%s: no lp->netdev\n", __FUNCTION__);
+			return;
+		}
+		is->state &= ~IPPP_CONNECT;	/* -> effect: no call of wakeup */
+		/*
+		 * isdn_net_hangup() calls isdn_ppp_free()
+		 * isdn_ppp_free() sets is->lp to NULL and lp->ppp_slot to -1
+		 * removing the IPPP_CONNECT flag omits calling of isdn_ppp_wakeup_daemon()
+		 */
+		isdn_net_hangup(&p->dev);
+	}
+	for (i = 0; i < NUM_RCV_BUFFS; i++) {
+		if (is->rq[i].buf) {
+			kfree(is->rq[i].buf);
+			is->rq[i].buf = NULL;
+		}
+	}
+	is->first = is->rq + NUM_RCV_BUFFS - 1;	/* receive queue */
+	is->last = is->rq;
+
+#ifdef CONFIG_ISDN_PPP_VJ
+/* TODO: if this was the previous master: link the slcomp to the new master */
+	slhc_free(is->slcomp);
+	is->slcomp = NULL;
+#endif
+#ifdef CONFIG_IPPP_FILTER
+	if (is->pass_filter) {
+		kfree(is->pass_filter);
+		is->pass_filter = NULL;
+	}
+	if (is->active_filter) {
+		kfree(is->active_filter);
+		is->active_filter = NULL;
+	}
+#endif
+
+/* TODO: if this was the previous master: link the stuff to the new master */
+	if(is->comp_stat)
+		is->compressor->free(is->comp_stat);
+	if(is->link_comp_stat)
+		is->link_compressor->free(is->link_comp_stat);
+	if(is->link_decomp_stat)
+		is->link_decompressor->free(is->link_decomp_stat);
+	if(is->decomp_stat)
+		is->decompressor->free(is->decomp_stat);
+        is->compressor   = is->link_compressor   = NULL;
+        is->decompressor = is->link_decompressor = NULL;
+	is->comp_stat    = is->link_comp_stat    = NULL;
+        is->decomp_stat  = is->link_decomp_stat  = NULL;
+
+	/* Clean up if necessary */
+	if(is->reset)
+		isdn_ppp_ccp_reset_free(is);
+
+	/* this slot is ready for new connections */
+	is->state = 0;
+}
+
+/*
+ * get_arg .. ioctl helper
+ */
+static int
+get_arg(void __user *b, void *val, int len)
+{
+	if (len <= 0)
+		len = sizeof(void *);
+	if (copy_from_user(val, b, len))
+		return -EFAULT;
+	return 0;
+}
+
+/*
+ * set arg .. ioctl helper
+ */
+static int
+set_arg(void __user *b, void *val,int len)
+{
+	if(len <= 0)
+		len = sizeof(void *);
+	if (copy_to_user(b, val, len))
+		return -EFAULT;
+	return 0;
+}
+
+static int get_filter(void __user *arg, struct sock_filter **p)
+{
+	struct sock_fprog uprog;
+	struct sock_filter *code = NULL;
+	int len, err;
+
+	if (copy_from_user(&uprog, arg, sizeof(uprog)))
+		return -EFAULT;
+
+	if (!uprog.len) {
+		*p = NULL;
+		return 0;
+	}
+
+	/* uprog.len is unsigned short, so no overflow here */
+	len = uprog.len * sizeof(struct sock_filter);
+	code = kmalloc(len, GFP_KERNEL);
+	if (code == NULL)
+		return -ENOMEM;
+
+	if (copy_from_user(code, uprog.filter, len)) {
+		kfree(code);
+		return -EFAULT;
+	}
+
+	err = sk_chk_filter(code, uprog.len);
+	if (err) {
+		kfree(code);
+		return err;
+	}
+
+	*p = code;
+	return uprog.len;
+}
+
+/*
+ * ippp device ioctl
+ */
+int
+isdn_ppp_ioctl(int min, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	unsigned long val;
+	int r,i,j;
+	struct ippp_struct *is;
+	isdn_net_local *lp;
+	struct isdn_ppp_comp_data data;
+	void __user *argp = (void __user *)arg;
+
+	is = (struct ippp_struct *) file->private_data;
+	lp = is->lp;
+
+	if (is->debug & 0x1)
+		printk(KERN_DEBUG "isdn_ppp_ioctl: minor: %d cmd: %x state: %x\n", min, cmd, is->state);
+
+	if (!(is->state & IPPP_OPEN))
+		return -EINVAL;
+
+	switch (cmd) {
+		case PPPIOCBUNDLE:
+#ifdef CONFIG_ISDN_MPP
+			if (!(is->state & IPPP_CONNECT))
+				return -EINVAL;
+			if ((r = get_arg(argp, &val, sizeof(val) )))
+				return r;
+			printk(KERN_DEBUG "iPPP-bundle: minor: %d, slave unit: %d, master unit: %d\n",
+			       (int) min, (int) is->unit, (int) val);
+			return isdn_ppp_bundle(is, val);
+#else
+			return -1;
+#endif
+			break;
+		case PPPIOCGUNIT:	/* get ppp/isdn unit number */
+			if ((r = set_arg(argp, &is->unit, sizeof(is->unit) )))
+				return r;
+			break;
+		case PPPIOCGIFNAME:
+			if(!lp)
+				return -EINVAL;
+			if ((r = set_arg(argp, lp->name, strlen(lp->name))))
+				return r;
+			break;
+		case PPPIOCGMPFLAGS:	/* get configuration flags */
+			if ((r = set_arg(argp, &is->mpppcfg, sizeof(is->mpppcfg) )))
+				return r;
+			break;
+		case PPPIOCSMPFLAGS:	/* set configuration flags */
+			if ((r = get_arg(argp, &val, sizeof(val) )))
+				return r;
+			is->mpppcfg = val;
+			break;
+		case PPPIOCGFLAGS:	/* get configuration flags */
+			if ((r = set_arg(argp, &is->pppcfg,sizeof(is->pppcfg) )))
+				return r;
+			break;
+		case PPPIOCSFLAGS:	/* set configuration flags */
+			if ((r = get_arg(argp, &val, sizeof(val) ))) {
+				return r;
+			}
+			if (val & SC_ENABLE_IP && !(is->pppcfg & SC_ENABLE_IP) && (is->state & IPPP_CONNECT)) {
+				if (lp) {
+					/* OK .. we are ready to send buffers */
+					is->pppcfg = val; /* isdn_ppp_xmit test for SC_ENABLE_IP !!! */
+					netif_wake_queue(&lp->netdev->dev);
+					break;
+				}
+			}
+			is->pppcfg = val;
+			break;
+		case PPPIOCGIDLE:	/* get idle time information */
+			if (lp) {
+				struct ppp_idle pidle;
+				pidle.xmit_idle = pidle.recv_idle = lp->huptimer;
+				if ((r = set_arg(argp, &pidle,sizeof(struct ppp_idle))))
+					 return r;
+			}
+			break;
+		case PPPIOCSMRU:	/* set receive unit size for PPP */
+			if ((r = get_arg(argp, &val, sizeof(val) )))
+				return r;
+			is->mru = val;
+			break;
+		case PPPIOCSMPMRU:
+			break;
+		case PPPIOCSMPMTU:
+			break;
+		case PPPIOCSMAXCID:	/* set the maximum compression slot id */
+			if ((r = get_arg(argp, &val, sizeof(val) )))
+				return r;
+			val++;
+			if (is->maxcid != val) {
+#ifdef CONFIG_ISDN_PPP_VJ
+				struct slcompress *sltmp;
+#endif
+				if (is->debug & 0x1)
+					printk(KERN_DEBUG "ippp, ioctl: changed MAXCID to %ld\n", val);
+				is->maxcid = val;
+#ifdef CONFIG_ISDN_PPP_VJ
+				sltmp = slhc_init(16, val);
+				if (!sltmp) {
+					printk(KERN_ERR "ippp, can't realloc slhc struct\n");
+					return -ENOMEM;
+				}
+				if (is->slcomp)
+					slhc_free(is->slcomp);
+				is->slcomp = sltmp;
+#endif
+			}
+			break;
+		case PPPIOCGDEBUG:
+			if ((r = set_arg(argp, &is->debug, sizeof(is->debug) )))
+				return r;
+			break;
+		case PPPIOCSDEBUG:
+			if ((r = get_arg(argp, &val, sizeof(val) )))
+				return r;
+			is->debug = val;
+			break;
+		case PPPIOCGCOMPRESSORS:
+			{
+				unsigned long protos[8] = {0,};
+				struct isdn_ppp_compressor *ipc = ipc_head;
+				while(ipc) {
+					j = ipc->num / (sizeof(long)*8);
+					i = ipc->num % (sizeof(long)*8);
+					if(j < 8)
+						protos[j] |= (0x1<<i);
+					ipc = ipc->next;
+				}
+				if ((r = set_arg(argp,protos,8*sizeof(long) )))
+					return r;
+			}
+			break;
+		case PPPIOCSCOMPRESSOR:
+			if ((r = get_arg(argp, &data, sizeof(struct isdn_ppp_comp_data))))
+				return r;
+			return isdn_ppp_set_compressor(is, &data);
+		case PPPIOCGCALLINFO:
+			{
+				struct pppcallinfo pci;
+				memset((char *) &pci,0,sizeof(struct pppcallinfo));
+				if(lp)
+				{
+					strncpy(pci.local_num,lp->msn,63);
+					if(lp->dial) {
+						strncpy(pci.remote_num,lp->dial->num,63);
+					}
+					pci.charge_units = lp->charge;
+					if(lp->outgoing)
+						pci.calltype = CALLTYPE_OUTGOING;
+					else
+						pci.calltype = CALLTYPE_INCOMING;
+					if(lp->flags & ISDN_NET_CALLBACK)
+						pci.calltype |= CALLTYPE_CALLBACK;
+				}
+				return set_arg(argp,&pci,sizeof(struct pppcallinfo));
+			}
+#ifdef CONFIG_IPPP_FILTER
+		case PPPIOCSPASS:
+			{
+				struct sock_filter *code;
+				int len = get_filter(argp, &code);
+				if (len < 0)
+					return len;
+				kfree(is->pass_filter);
+				is->pass_filter = code;
+				is->pass_len = len;
+				break;
+			}
+		case PPPIOCSACTIVE:
+			{
+				struct sock_filter *code;
+				int len = get_filter(argp, &code);
+				if (len < 0)
+					return len;
+				kfree(is->active_filter);
+				is->active_filter = code;
+				is->active_len = len;
+				break;
+			}
+#endif /* CONFIG_IPPP_FILTER */
+		default:
+			break;
+	}
+	return 0;
+}
+
+unsigned int
+isdn_ppp_poll(struct file *file, poll_table * wait)
+{
+	u_int mask;
+	struct ippp_buf_queue *bf, *bl;
+	u_long flags;
+	struct ippp_struct *is;
+
+	is = file->private_data;
+
+	if (is->debug & 0x2)
+		printk(KERN_DEBUG "isdn_ppp_poll: minor: %d\n",
+				MINOR(file->f_dentry->d_inode->i_rdev));
+
+	/* just registers wait_queue hook. This doesn't really wait. */
+	poll_wait(file, &is->wq, wait);
+
+	if (!(is->state & IPPP_OPEN)) {
+		if(is->state == IPPP_CLOSEWAIT)
+			return POLLHUP;
+		printk(KERN_DEBUG "isdn_ppp: device not open\n");
+		return POLLERR;
+	}
+	/* we're always ready to send .. */
+	mask = POLLOUT | POLLWRNORM;
+
+	spin_lock_irqsave(&is->buflock, flags);
+	bl = is->last;
+	bf = is->first;
+	/*
+	 * if IPPP_NOBLOCK is set we return even if we have nothing to read
+	 */
+	if (bf->next != bl || (is->state & IPPP_NOBLOCK)) {
+		is->state &= ~IPPP_NOBLOCK;
+		mask |= POLLIN | POLLRDNORM;
+	}
+	spin_unlock_irqrestore(&is->buflock, flags);
+	return mask;
+}
+
+/*
+ *  fill up isdn_ppp_read() queue ..
+ */
+
+static int
+isdn_ppp_fill_rq(unsigned char *buf, int len, int proto, int slot)
+{
+	struct ippp_buf_queue *bf, *bl;
+	u_long flags;
+	u_char *nbuf;
+	struct ippp_struct *is;
+
+	if (slot < 0 || slot >= ISDN_MAX_CHANNELS) {
+		printk(KERN_WARNING "ippp: illegal slot(%d).\n", slot);
+		return 0;
+	}
+	is = ippp_table[slot];
+
+	if (!(is->state & IPPP_CONNECT)) {
+		printk(KERN_DEBUG "ippp: device not activated.\n");
+		return 0;
+	}
+	nbuf = (unsigned char *) kmalloc(len + 4, GFP_ATOMIC);
+	if (!nbuf) {
+		printk(KERN_WARNING "ippp: Can't alloc buf\n");
+		return 0;
+	}
+	nbuf[0] = PPP_ALLSTATIONS;
+	nbuf[1] = PPP_UI;
+	nbuf[2] = proto >> 8;
+	nbuf[3] = proto & 0xff;
+	memcpy(nbuf + 4, buf, len);
+
+	spin_lock_irqsave(&is->buflock, flags);
+	bf = is->first;
+	bl = is->last;
+
+	if (bf == bl) {
+		printk(KERN_WARNING "ippp: Queue is full; discarding first buffer\n");
+		bf = bf->next;
+		kfree(bf->buf);
+		is->first = bf;
+	}
+	bl->buf = (char *) nbuf;
+	bl->len = len + 4;
+
+	is->last = bl->next;
+	spin_unlock_irqrestore(&is->buflock, flags);
+	wake_up_interruptible(&is->wq);
+	return len;
+}
+
+/*
+ * read() .. non-blocking: ipppd calls it only after select()
+ *           reports, that there is data
+ */
+
+int
+isdn_ppp_read(int min, struct file *file, char __user *buf, int count)
+{
+	struct ippp_struct *is;
+	struct ippp_buf_queue *b;
+	u_long flags;
+	u_char *save_buf;
+
+	is = file->private_data;
+
+	if (!(is->state & IPPP_OPEN))
+		return 0;
+
+	if (!access_ok(VERIFY_WRITE, buf, count))
+		return -EFAULT;
+
+	spin_lock_irqsave(&is->buflock, flags);
+	b = is->first->next;
+	save_buf = b->buf;
+	if (!save_buf) {
+		spin_unlock_irqrestore(&is->buflock, flags);
+		return -EAGAIN;
+	}
+	if (b->len < count)
+		count = b->len;
+	b->buf = NULL;
+	is->first = b;
+
+	spin_unlock_irqrestore(&is->buflock, flags);
+	copy_to_user(buf, save_buf, count);
+	kfree(save_buf);
+
+	return count;
+}
+
+/*
+ * ipppd wanna write a packet to the card .. non-blocking
+ */
+
+int
+isdn_ppp_write(int min, struct file *file, const char __user *buf, int count)
+{
+	isdn_net_local *lp;
+	struct ippp_struct *is;
+	int proto;
+	unsigned char protobuf[4];
+
+	is = file->private_data;
+
+	if (!(is->state & IPPP_CONNECT))
+		return 0;
+
+	lp = is->lp;
+
+	/* -> push it directly to the lowlevel interface */
+
+	if (!lp)
+		printk(KERN_DEBUG "isdn_ppp_write: lp == NULL\n");
+	else {
+		/*
+		 * Don't reset huptimer for
+		 * LCP packets. (Echo requests).
+		 */
+		if (copy_from_user(protobuf, buf, 4))
+			return -EFAULT;
+		proto = PPP_PROTOCOL(protobuf);
+		if (proto != PPP_LCP)
+			lp->huptimer = 0;
+
+		if (lp->isdn_device < 0 || lp->isdn_channel < 0)
+			return 0;
+
+		if ((dev->drv[lp->isdn_device]->flags & DRV_FLAG_RUNNING) &&
+			lp->dialstate == 0 &&
+		    (lp->flags & ISDN_NET_CONNECTED)) {
+			unsigned short hl;
+			struct sk_buff *skb;
+			/*
+			 * we need to reserve enought space in front of
+			 * sk_buff. old call to dev_alloc_skb only reserved
+			 * 16 bytes, now we are looking what the driver want
+			 */
+			hl = dev->drv[lp->isdn_device]->interface->hl_hdrlen;
+			skb = alloc_skb(hl+count, GFP_ATOMIC);
+			if (!skb) {
+				printk(KERN_WARNING "isdn_ppp_write: out of memory!\n");
+				return count;
+			}
+			skb_reserve(skb, hl);
+			if (copy_from_user(skb_put(skb, count), buf, count))
+			{
+				kfree_skb(skb);
+				return -EFAULT;
+			}
+			if (is->debug & 0x40) {
+				printk(KERN_DEBUG "ppp xmit: len %d\n", (int) skb->len);
+				isdn_ppp_frame_log("xmit", skb->data, skb->len, 32,is->unit,lp->ppp_slot);
+			}
+
+			isdn_ppp_send_ccp(lp->netdev,lp,skb); /* keeps CCP/compression states in sync */
+
+			isdn_net_write_super(lp, skb);
+		}
+	}
+	return count;
+}
+
+/*
+ * init memory, structures etc.
+ */
+
+int
+isdn_ppp_init(void)
+{
+	int i,
+	 j;
+	 
+#ifdef CONFIG_ISDN_MPP
+	if( isdn_ppp_mp_bundle_array_init() < 0 )
+		return -ENOMEM;
+#endif /* CONFIG_ISDN_MPP */
+
+	for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
+		if (!(ippp_table[i] = (struct ippp_struct *)
+		      kmalloc(sizeof(struct ippp_struct), GFP_KERNEL))) {
+			printk(KERN_WARNING "isdn_ppp_init: Could not alloc ippp_table\n");
+			for (j = 0; j < i; j++)
+				kfree(ippp_table[j]);
+			return -1;
+		}
+		memset((char *) ippp_table[i], 0, sizeof(struct ippp_struct));
+		spin_lock_init(&ippp_table[i]->buflock);
+		ippp_table[i]->state = 0;
+		ippp_table[i]->first = ippp_table[i]->rq + NUM_RCV_BUFFS - 1;
+		ippp_table[i]->last = ippp_table[i]->rq;
+
+		for (j = 0; j < NUM_RCV_BUFFS; j++) {
+			ippp_table[i]->rq[j].buf = NULL;
+			ippp_table[i]->rq[j].last = ippp_table[i]->rq +
+			    (NUM_RCV_BUFFS + j - 1) % NUM_RCV_BUFFS;
+			ippp_table[i]->rq[j].next = ippp_table[i]->rq + (j + 1) % NUM_RCV_BUFFS;
+		}
+	}
+	return 0;
+}
+
+void
+isdn_ppp_cleanup(void)
+{
+	int i;
+
+	for (i = 0; i < ISDN_MAX_CHANNELS; i++)
+		kfree(ippp_table[i]);
+
+#ifdef CONFIG_ISDN_MPP
+	if (isdn_ppp_bundle_arr)
+		kfree(isdn_ppp_bundle_arr);
+#endif /* CONFIG_ISDN_MPP */
+
+}
+
+/*
+ * check for address/control field and skip if allowed
+ * retval != 0 -> discard packet silently
+ */
+static int isdn_ppp_skip_ac(struct ippp_struct *is, struct sk_buff *skb) 
+{
+	if (skb->len < 1)
+		return -1;
+
+	if (skb->data[0] == 0xff) {
+		if (skb->len < 2)
+			return -1;
+
+		if (skb->data[1] != 0x03)
+			return -1;
+
+		// skip address/control (AC) field
+		skb_pull(skb, 2);
+	} else { 
+		if (is->pppcfg & SC_REJ_COMP_AC)
+			// if AC compression was not negotiated, but used, discard packet
+			return -1;
+	}
+	return 0;
+}
+
+/*
+ * get the PPP protocol header and pull skb
+ * retval < 0 -> discard packet silently
+ */
+static int isdn_ppp_strip_proto(struct sk_buff *skb) 
+{
+	int proto;
+	
+	if (skb->len < 1)
+		return -1;
+
+	if (skb->data[0] & 0x1) {
+		// protocol field is compressed
+		proto = skb->data[0];
+		skb_pull(skb, 1);
+	} else {
+		if (skb->len < 2)
+			return -1;
+		proto = ((int) skb->data[0] << 8) + skb->data[1];
+		skb_pull(skb, 2);
+	}
+	return proto;
+}
+
+
+/*
+ * handler for incoming packets on a syncPPP interface
+ */
+void isdn_ppp_receive(isdn_net_dev * net_dev, isdn_net_local * lp, struct sk_buff *skb)
+{
+	struct ippp_struct *is;
+	int slot;
+	int proto;
+
+	if (net_dev->local->master)
+		BUG(); // we're called with the master device always
+
+	slot = lp->ppp_slot;
+	if (slot < 0 || slot >= ISDN_MAX_CHANNELS) {
+		printk(KERN_ERR "isdn_ppp_receive: lp->ppp_slot(%d)\n",
+			lp->ppp_slot);
+		kfree_skb(skb);
+		return;
+	}
+	is = ippp_table[slot];
+
+	if (is->debug & 0x4) {
+		printk(KERN_DEBUG "ippp_receive: is:%08lx lp:%08lx slot:%d unit:%d len:%d\n",
+		       (long)is,(long)lp,lp->ppp_slot,is->unit,(int) skb->len);
+		isdn_ppp_frame_log("receive", skb->data, skb->len, 32,is->unit,lp->ppp_slot);
+	}
+
+ 	if (isdn_ppp_skip_ac(is, skb) < 0) {
+ 		kfree_skb(skb);
+ 		return;
+ 	}
+  	proto = isdn_ppp_strip_proto(skb);
+ 	if (proto < 0) {
+ 		kfree_skb(skb);
+ 		return;
+ 	}
+  
+#ifdef CONFIG_ISDN_MPP
+ 	if (is->compflags & SC_LINK_DECOMP_ON) {
+ 		skb = isdn_ppp_decompress(skb, is, NULL, &proto);
+ 		if (!skb) // decompression error
+ 			return;
+ 	}
+	
+ 	if (!(is->mpppcfg & SC_REJ_MP_PROT)) { // we agreed to receive MPPP
+  		if (proto == PPP_MP) {
+  			isdn_ppp_mp_receive(net_dev, lp, skb);
+ 			return;
+ 		}
+ 	} 
+#endif
+ 	isdn_ppp_push_higher(net_dev, lp, skb, proto);
+}
+
+/*
+ * we receive a reassembled frame, MPPP has been taken care of before.
+ * address/control and protocol have been stripped from the skb
+ * note: net_dev has to be master net_dev
+ */
+static void
+isdn_ppp_push_higher(isdn_net_dev * net_dev, isdn_net_local * lp, struct sk_buff *skb, int proto)
+{
+	struct net_device *dev = &net_dev->dev;
+ 	struct ippp_struct *is, *mis;
+	isdn_net_local *mlp = NULL;
+	int slot;
+
+	slot = lp->ppp_slot;
+	if (slot < 0 || slot >= ISDN_MAX_CHANNELS) {
+		printk(KERN_ERR "isdn_ppp_push_higher: lp->ppp_slot(%d)\n",
+			lp->ppp_slot);
+		goto drop_packet;
+	}
+	is = ippp_table[slot];
+ 	
+ 	if (lp->master) { // FIXME?
+		mlp = (isdn_net_local *) lp->master->priv;
+ 		slot = mlp->ppp_slot;
+ 		if (slot < 0 || slot > ISDN_MAX_CHANNELS) {
+ 			printk(KERN_ERR "isdn_ppp_push_higher: master->ppp_slot(%d)\n",
+ 				lp->ppp_slot);
+			goto drop_packet;
+ 		}
+ 	}
+ 	mis = ippp_table[slot];
+
+	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, 32,is->unit,lp->ppp_slot);
+	}
+	if (mis->compflags & SC_DECOMP_ON) {
+		skb = isdn_ppp_decompress(skb, is, mis, &proto);
+		if (!skb) // decompression error
+  			return;
+  	}
+	switch (proto) {
+		case PPP_IPX:  /* untested */
+			if (is->debug & 0x20)
+				printk(KERN_DEBUG "isdn_ppp: IPX\n");
+			skb->protocol = htons(ETH_P_IPX);
+			break;
+		case PPP_IP:
+			if (is->debug & 0x20)
+				printk(KERN_DEBUG "isdn_ppp: IP\n");
+			skb->protocol = htons(ETH_P_IP);
+			break;
+		case PPP_COMP:
+		case PPP_COMPFRAG:
+			printk(KERN_INFO "isdn_ppp: unexpected compressed frame dropped\n");
+			goto drop_packet;
+#ifdef CONFIG_ISDN_PPP_VJ
+		case PPP_VJC_UNCOMP:
+			if (is->debug & 0x20)
+				printk(KERN_DEBUG "isdn_ppp: VJC_UNCOMP\n");
+			if (net_dev->local->ppp_slot < 0) {
+				printk(KERN_ERR "%s: net_dev->local->ppp_slot(%d) out of range\n",
+					__FUNCTION__, net_dev->local->ppp_slot);
+				goto drop_packet;
+			}
+			if (slhc_remember(ippp_table[net_dev->local->ppp_slot]->slcomp, skb->data, skb->len) <= 0) {
+				printk(KERN_WARNING "isdn_ppp: received illegal VJC_UNCOMP frame!\n");
+				goto drop_packet;
+			}
+			skb->protocol = htons(ETH_P_IP);
+			break;
+		case PPP_VJC_COMP:
+			if (is->debug & 0x20)
+				printk(KERN_DEBUG "isdn_ppp: VJC_COMP\n");
+			{
+				struct sk_buff *skb_old = skb;
+				int pkt_len;
+				skb = dev_alloc_skb(skb_old->len + 128);
+
+				if (!skb) {
+					printk(KERN_WARNING "%s: Memory squeeze, dropping packet.\n", dev->name);
+					skb = skb_old;
+					goto drop_packet;
+				}
+				skb_put(skb, skb_old->len + 128);
+				memcpy(skb->data, skb_old->data, skb_old->len);
+				if (net_dev->local->ppp_slot < 0) {
+					printk(KERN_ERR "%s: net_dev->local->ppp_slot(%d) out of range\n",
+						__FUNCTION__, net_dev->local->ppp_slot);
+					goto drop_packet;
+				}
+				pkt_len = slhc_uncompress(ippp_table[net_dev->local->ppp_slot]->slcomp,
+						skb->data, skb_old->len);
+				kfree_skb(skb_old);
+				if (pkt_len < 0)
+					goto drop_packet;
+
+				skb_trim(skb, pkt_len);
+				skb->protocol = htons(ETH_P_IP);
+			}
+			break;
+#endif
+		case PPP_CCP:
+		case PPP_CCPFRAG:
+			isdn_ppp_receive_ccp(net_dev,lp,skb,proto);
+			/* 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)
+				break;
+			/* fall through */
+		default:
+			isdn_ppp_fill_rq(skb->data, skb->len, proto, lp->ppp_slot);	/* push data to pppd device */
+			kfree_skb(skb);
+			return;
+	}
+
+#ifdef CONFIG_IPPP_FILTER
+	/* check if the packet passes the pass and active filters
+	 * the filter instructions are constructed assuming
+	 * a four-byte PPP header on each packet (which is still present) */
+	skb_push(skb, 4);
+
+	{
+		u_int16_t *p = (u_int16_t *) skb->data;
+
+		*p = 0;	/* indicate inbound in DLT_LINUX_SLL */
+	}
+
+	if (is->pass_filter
+	    && sk_run_filter(skb, is->pass_filter, is->pass_len) == 0) {
+		if (is->debug & 0x2)
+			printk(KERN_DEBUG "IPPP: inbound frame filtered.\n");
+		kfree_skb(skb);
+		return;
+	}
+	if (!(is->active_filter
+	      && sk_run_filter(skb, is->active_filter,
+	                       is->active_len) == 0)) {
+		if (is->debug & 0x2)
+			printk(KERN_DEBUG "IPPP: link-active filter: reseting huptimer.\n");
+		lp->huptimer = 0;
+		if (mlp)
+			mlp->huptimer = 0;
+	}
+	skb_pull(skb, 4);
+#else /* CONFIG_IPPP_FILTER */
+	lp->huptimer = 0;
+	if (mlp)
+		mlp->huptimer = 0;
+#endif /* CONFIG_IPPP_FILTER */
+	skb->dev = dev;
+	skb->input_dev = dev;
+	skb->mac.raw = skb->data;
+	netif_rx(skb);
+	/* net_dev->local->stats.rx_packets++; done in isdn_net.c */
+	return;
+
+ drop_packet:
+	net_dev->local->stats.rx_dropped++;
+	kfree_skb(skb);
+}
+
+/*
+ * isdn_ppp_skb_push ..
+ * checks whether we have enough space at the beginning of the skb
+ * and allocs a new SKB if necessary
+ */
+static unsigned char *isdn_ppp_skb_push(struct sk_buff **skb_p,int len)
+{
+	struct sk_buff *skb = *skb_p;
+
+	if(skb_headroom(skb) < len) {
+		struct sk_buff *nskb = skb_realloc_headroom(skb, len);
+
+		if (!nskb) {
+			printk(KERN_ERR "isdn_ppp_skb_push: can't realloc headroom!\n");
+			dev_kfree_skb(skb);
+			return NULL;
+		}
+		printk(KERN_DEBUG "isdn_ppp_skb_push:under %d %d\n",skb_headroom(skb),len);
+		dev_kfree_skb(skb);
+		*skb_p = nskb;
+		return skb_push(nskb, len);
+	}
+	return skb_push(skb,len);
+}
+
+/*
+ * 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!!
+ */
+
+int
+isdn_ppp_xmit(struct sk_buff *skb, struct net_device *netdev)
+{
+	isdn_net_local *lp,*mlp;
+	isdn_net_dev *nd;
+	unsigned int proto = PPP_IP;     /* 0x21 */
+	struct ippp_struct *ipt,*ipts;
+	int slot, retval = 0;
+
+	mlp = (isdn_net_local *) (netdev->priv);
+	nd = mlp->netdev;       /* get master lp */
+
+	slot = mlp->ppp_slot;
+	if (slot < 0 || slot >= ISDN_MAX_CHANNELS) {
+		printk(KERN_ERR "isdn_ppp_xmit: lp->ppp_slot(%d)\n",
+			mlp->ppp_slot);
+		kfree_skb(skb);
+		goto out;
+	}
+	ipts = ippp_table[slot];
+
+	if (!(ipts->pppcfg & SC_ENABLE_IP)) {	/* PPP connected ? */
+		if (ipts->debug & 0x1)
+			printk(KERN_INFO "%s: IP frame delayed.\n", netdev->name);
+		retval = 1;
+		goto out;
+	}
+
+	switch (ntohs(skb->protocol)) {
+		case ETH_P_IP:
+			proto = PPP_IP;
+			break;
+		case ETH_P_IPX:
+			proto = PPP_IPX;	/* untested */
+			break;
+		default:
+			printk(KERN_ERR "isdn_ppp: skipped unsupported protocol: %#x.\n", 
+			       skb->protocol);
+			dev_kfree_skb(skb);
+			goto out;
+	}
+
+	lp = isdn_net_get_locked_lp(nd);
+	if (!lp) {
+		printk(KERN_WARNING "%s: all channels busy - requeuing!\n", netdev->name);
+		retval = 1;
+		goto out;
+	}
+	/* we have our lp locked from now on */
+
+	slot = lp->ppp_slot;
+	if (slot < 0 || slot > ISDN_MAX_CHANNELS) {
+		printk(KERN_ERR "isdn_ppp_xmit: lp->ppp_slot(%d)\n",
+			lp->ppp_slot);
+		kfree_skb(skb);
+		goto unlock;
+	}
+	ipt = ippp_table[slot];
+
+	/*
+	 * after this line .. requeueing in the device queue is no longer allowed!!!
+	 */
+
+	/* Pull off the fake header we stuck on earlier to keep
+	 * the fragmentation code happy.
+	 */
+	skb_pull(skb,IPPP_MAX_HEADER);
+
+#ifdef CONFIG_IPPP_FILTER
+	/* check if we should pass this packet
+	 * the filter instructions are constructed assuming
+	 * a four-byte PPP header on each packet */
+	skb_push(skb, 4);
+
+	{
+		u_int16_t *p = (u_int16_t *) skb->data;
+
+		*p++ = htons(4); /* indicate outbound in DLT_LINUX_SLL */
+		*p   = htons(proto);
+	}
+
+	if (ipt->pass_filter
+	    && sk_run_filter(skb, ipt->pass_filter, ipt->pass_len) == 0) {
+		if (ipt->debug & 0x4)
+			printk(KERN_DEBUG "IPPP: outbound frame filtered.\n");
+		kfree_skb(skb);
+		goto unlock;
+	}
+	if (!(ipt->active_filter
+	      && sk_run_filter(skb, ipt->active_filter,
+		               ipt->active_len) == 0)) {
+		if (ipt->debug & 0x4)
+			printk(KERN_DEBUG "IPPP: link-active filter: reseting huptimer.\n");
+		lp->huptimer = 0;
+	}
+	skb_pull(skb, 4);
+#else /* CONFIG_IPPP_FILTER */
+	lp->huptimer = 0;
+#endif /* CONFIG_IPPP_FILTER */
+
+	if (ipt->debug & 0x4)
+		printk(KERN_DEBUG "xmit skb, len %d\n", (int) skb->len);
+        if (ipts->debug & 0x40)
+                isdn_ppp_frame_log("xmit0", skb->data, skb->len, 32,ipts->unit,lp->ppp_slot);
+
+#ifdef CONFIG_ISDN_PPP_VJ
+	if (proto == PPP_IP && ipts->pppcfg & SC_COMP_TCP) {	/* ipts here? probably yes, but check this again */
+		struct sk_buff *new_skb;
+	        unsigned short hl;
+		/*
+		 * we need to reserve enought space in front of
+		 * sk_buff. old call to dev_alloc_skb only reserved
+		 * 16 bytes, now we are looking what the driver want.
+		 */
+		hl = dev->drv[lp->isdn_device]->interface->hl_hdrlen + IPPP_MAX_HEADER;
+		/* 
+		 * Note: hl might still be insufficient because the method
+		 * above does not account for a possibible MPPP slave channel
+		 * which had larger HL header space requirements than the
+		 * master.
+		 */
+		new_skb = alloc_skb(hl+skb->len, GFP_ATOMIC);
+		if (new_skb) {
+			u_char *buf;
+			int pktlen;
+
+			skb_reserve(new_skb, hl);
+			new_skb->dev = skb->dev;
+			skb_put(new_skb, skb->len);
+			buf = skb->data;
+
+			pktlen = slhc_compress(ipts->slcomp, skb->data, skb->len, new_skb->data,
+				 &buf, !(ipts->pppcfg & SC_NO_TCP_CCID));
+
+			if (buf != skb->data) {	
+				if (new_skb->data != buf)
+					printk(KERN_ERR "isdn_ppp: FATAL error after slhc_compress!!\n");
+				dev_kfree_skb(skb);
+				skb = new_skb;
+			} else {
+				dev_kfree_skb(new_skb);
+			}
+
+			skb_trim(skb, pktlen);
+			if (skb->data[0] & SL_TYPE_COMPRESSED_TCP) {	/* cslip? style -> PPP */
+				proto = PPP_VJC_COMP;
+				skb->data[0] ^= SL_TYPE_COMPRESSED_TCP;
+			} else {
+				if (skb->data[0] >= SL_TYPE_UNCOMPRESSED_TCP)
+					proto = PPP_VJC_UNCOMP;
+				skb->data[0] = (skb->data[0] & 0x0f) | 0x40;
+			}
+		}
+	}
+#endif
+
+	/*
+	 * normal (single link) or bundle compression
+	 */
+	if(ipts->compflags & SC_COMP_ON) {
+		/* We send compressed only if both down- und upstream
+		   compression is negotiated, that means, CCP is up */
+		if(ipts->compflags & SC_DECOMP_ON) {
+			skb = isdn_ppp_compress(skb,&proto,ipt,ipts,0);
+		} else {
+			printk(KERN_DEBUG "isdn_ppp: CCP not yet up - sending as-is\n");
+		}
+	}
+
+	if (ipt->debug & 0x24)
+		printk(KERN_DEBUG "xmit2 skb, len %d, proto %04x\n", (int) skb->len, proto);
+
+#ifdef CONFIG_ISDN_MPP
+	if (ipt->mpppcfg & SC_MP_PROT) {
+		/* we get mp_seqno from static isdn_net_local */
+		long mp_seqno = ipts->mp_seqno;
+		ipts->mp_seqno++;
+		if (ipt->mpppcfg & SC_OUT_SHORT_SEQ) {
+			unsigned char *data = isdn_ppp_skb_push(&skb, 3);
+			if(!data)
+				goto unlock;
+			mp_seqno &= 0xfff;
+			data[0] = MP_BEGIN_FRAG | MP_END_FRAG | ((mp_seqno >> 8) & 0xf);	/* (B)egin & (E)ndbit .. */
+			data[1] = mp_seqno & 0xff;
+			data[2] = proto;	/* PID compression */
+		} else {
+			unsigned char *data = isdn_ppp_skb_push(&skb, 5);
+			if(!data)
+				goto unlock;
+			data[0] = MP_BEGIN_FRAG | MP_END_FRAG;	/* (B)egin & (E)ndbit .. */
+			data[1] = (mp_seqno >> 16) & 0xff;	/* sequence number: 24bit */
+			data[2] = (mp_seqno >> 8) & 0xff;
+			data[3] = (mp_seqno >> 0) & 0xff;
+			data[4] = proto;	/* PID compression */
+		}
+		proto = PPP_MP; /* MP Protocol, 0x003d */
+	}
+#endif
+
+	/*
+	 * 'link in bundle' compression  ...
+	 */
+	if(ipt->compflags & SC_LINK_COMP_ON)
+		skb = isdn_ppp_compress(skb,&proto,ipt,ipts,1);
+
+	if( (ipt->pppcfg & SC_COMP_PROT) && (proto <= 0xff) ) {
+		unsigned char *data = isdn_ppp_skb_push(&skb,1);
+		if(!data)
+			goto unlock;
+		data[0] = proto & 0xff;
+	}
+	else {
+		unsigned char *data = isdn_ppp_skb_push(&skb,2);
+		if(!data)
+			goto unlock;
+		data[0] = (proto >> 8) & 0xff;
+		data[1] = proto & 0xff;
+	}
+	if(!(ipt->pppcfg & SC_COMP_AC)) {
+		unsigned char *data = isdn_ppp_skb_push(&skb,2);
+		if(!data)
+			goto unlock;
+		data[0] = 0xff;    /* All Stations */
+		data[1] = 0x03;    /* Unnumbered information */
+	}
+
+	/* tx-stats are now updated via BSENT-callback */
+
+	if (ipts->debug & 0x40) {
+		printk(KERN_DEBUG "skb xmit: len: %d\n", (int) skb->len);
+		isdn_ppp_frame_log("xmit", skb->data, skb->len, 32,ipt->unit,lp->ppp_slot);
+	}
+	
+	isdn_net_writebuf_skb(lp, skb);
+
+ unlock:
+	spin_unlock_bh(&lp->xmit_lock);
+ out:
+	return retval;
+}
+
+#ifdef CONFIG_IPPP_FILTER
+/*
+ * check if this packet may trigger auto-dial.
+ */
+
+int isdn_ppp_autodial_filter(struct sk_buff *skb, isdn_net_local *lp)
+{
+	struct ippp_struct *is = ippp_table[lp->ppp_slot];
+	u_int16_t proto;
+	int drop = 0;
+
+	switch (ntohs(skb->protocol)) {
+	case ETH_P_IP:
+		proto = PPP_IP;
+		break;
+	case ETH_P_IPX:
+		proto = PPP_IPX;
+		break;
+	default:
+		printk(KERN_ERR "isdn_ppp_autodial_filter: unsupported protocol 0x%x.\n",
+		       skb->protocol);
+		return 1;
+	}
+
+	/* the filter instructions are constructed assuming
+	 * a four-byte PPP header on each packet. we have to
+	 * temporarily remove part of the fake header stuck on
+	 * earlier.
+	 */
+	skb_pull(skb, IPPP_MAX_HEADER - 4);
+
+	{
+		u_int16_t *p = (u_int16_t *) skb->data;
+
+		*p++ = htons(4);	/* indicate outbound in DLT_LINUX_SLL */
+		*p   = htons(proto);
+	}
+	
+	drop |= is->pass_filter
+	        && sk_run_filter(skb, is->pass_filter, is->pass_len) == 0;
+	drop |= is->active_filter
+	        && sk_run_filter(skb, is->active_filter, is->active_len) == 0;
+	
+	skb_push(skb, IPPP_MAX_HEADER - 4);
+	return drop;
+}
+#endif
+#ifdef CONFIG_ISDN_MPP
+
+/* this is _not_ rfc1990 header, but something we convert both short and long
+ * headers to for convinience's sake:
+ * 	byte 0 is flags as in rfc1990
+ *	bytes 1...4 is 24-bit seqence number converted to host byte order 
+ */
+#define MP_HEADER_LEN	5
+
+#define MP_LONGSEQ_MASK		0x00ffffff
+#define MP_SHORTSEQ_MASK	0x00000fff
+#define MP_LONGSEQ_MAX		MP_LONGSEQ_MASK
+#define MP_SHORTSEQ_MAX		MP_SHORTSEQ_MASK
+#define MP_LONGSEQ_MAXBIT	((MP_LONGSEQ_MASK+1)>>1)
+#define MP_SHORTSEQ_MAXBIT	((MP_SHORTSEQ_MASK+1)>>1)
+
+/* sequence-wrap safe comparisions (for long sequence)*/ 
+#define MP_LT(a,b)	((a-b)&MP_LONGSEQ_MAXBIT)
+#define MP_LE(a,b) 	!((b-a)&MP_LONGSEQ_MAXBIT)
+#define MP_GT(a,b) 	((b-a)&MP_LONGSEQ_MAXBIT)
+#define MP_GE(a,b)	!((a-b)&MP_LONGSEQ_MAXBIT)
+
+#define MP_SEQ(f)	((*(u32*)(f->data+1)))
+#define MP_FLAGS(f)	(f->data[0])
+
+static int isdn_ppp_mp_bundle_array_init(void)
+{
+	int i;
+	int sz = ISDN_MAX_CHANNELS*sizeof(ippp_bundle);
+	if( (isdn_ppp_bundle_arr = (ippp_bundle*)kmalloc(sz, 
+							GFP_KERNEL)) == NULL )
+		return -ENOMEM;
+	memset(isdn_ppp_bundle_arr, 0, sz);
+	for( i = 0; i < ISDN_MAX_CHANNELS; i++ )
+		spin_lock_init(&isdn_ppp_bundle_arr[i].lock);
+	return 0;
+}
+
+static ippp_bundle * isdn_ppp_mp_bundle_alloc(void)
+{
+	int i;
+	for( i = 0; i < ISDN_MAX_CHANNELS; i++ )
+		if (isdn_ppp_bundle_arr[i].ref_ct <= 0)
+			return (isdn_ppp_bundle_arr + i);
+	return NULL;
+}
+
+static int isdn_ppp_mp_init( isdn_net_local * lp, ippp_bundle * add_to )
+{
+	struct ippp_struct * is;
+
+	if (lp->ppp_slot < 0) {
+		printk(KERN_ERR "%s: lp->ppp_slot(%d) out of range\n",
+			__FUNCTION__, lp->ppp_slot);
+		return(-EINVAL);
+	}
+
+	is = ippp_table[lp->ppp_slot];
+	if (add_to) {
+		if( lp->netdev->pb )
+			lp->netdev->pb->ref_ct--;
+		lp->netdev->pb = add_to;
+	} else {		/* first link in a bundle */
+		is->mp_seqno = 0;
+		if ((lp->netdev->pb = isdn_ppp_mp_bundle_alloc()) == NULL)
+			return -ENOMEM;
+		lp->next = lp->last = lp;	/* nobody else in a queue */
+		lp->netdev->pb->frags = NULL;
+		lp->netdev->pb->frames = 0;
+		lp->netdev->pb->seq = UINT_MAX;
+	}
+	lp->netdev->pb->ref_ct++;
+	
+	is->last_link_seqno = 0;
+	return 0;
+}
+
+static u32 isdn_ppp_mp_get_seq( int short_seq, 
+					struct sk_buff * skb, u32 last_seq );
+static struct sk_buff * isdn_ppp_mp_discard( ippp_bundle * mp,
+			struct sk_buff * from, struct sk_buff * to );
+static void isdn_ppp_mp_reassembly( isdn_net_dev * net_dev, isdn_net_local * lp,
+				struct sk_buff * from, struct sk_buff * to );
+static void isdn_ppp_mp_free_skb( ippp_bundle * mp, struct sk_buff * skb );
+static void isdn_ppp_mp_print_recv_pkt( int slot, struct sk_buff * skb );
+
+static void isdn_ppp_mp_receive(isdn_net_dev * net_dev, isdn_net_local * lp, 
+							struct sk_buff *skb)
+{
+	struct ippp_struct *is;
+	isdn_net_local * lpq;
+	ippp_bundle * mp;
+	isdn_mppp_stats * stats;
+	struct sk_buff * newfrag, * frag, * start, *nextf;
+	u32 newseq, minseq, thisseq;
+	unsigned long flags;
+	int slot;
+
+	spin_lock_irqsave(&net_dev->pb->lock, flags);
+    	mp = net_dev->pb;
+        stats = &mp->stats;
+	slot = lp->ppp_slot;
+	if (slot < 0 || slot > ISDN_MAX_CHANNELS) {
+		printk(KERN_ERR "%s: lp->ppp_slot(%d)\n",
+			__FUNCTION__, lp->ppp_slot);
+		stats->frame_drops++;
+		dev_kfree_skb(skb);
+		spin_unlock_irqrestore(&mp->lock, flags);
+		return;
+	}
+	is = ippp_table[slot];
+    	if( ++mp->frames > stats->max_queue_len )
+		stats->max_queue_len = mp->frames;
+	
+	if (is->debug & 0x8)
+		isdn_ppp_mp_print_recv_pkt(lp->ppp_slot, skb);
+
+	newseq = isdn_ppp_mp_get_seq(is->mpppcfg & SC_IN_SHORT_SEQ, 
+						skb, is->last_link_seqno);
+
+
+	/* if this packet seq # is less than last already processed one,
+	 * toss it right away, but check for sequence start case first 
+	 */
+	if( mp->seq > MP_LONGSEQ_MAX && (newseq & MP_LONGSEQ_MAXBIT) ) {
+		mp->seq = newseq;	/* the first packet: required for
+					 * rfc1990 non-compliant clients --
+					 * prevents constant packet toss */
+	} else if( MP_LT(newseq, mp->seq) ) {
+		stats->frame_drops++;
+		isdn_ppp_mp_free_skb(mp, skb);
+		spin_unlock_irqrestore(&mp->lock, flags);
+		return;
+	}
+	
+	/* find the minimum received sequence number over all links */
+	is->last_link_seqno = minseq = newseq;
+	for (lpq = net_dev->queue;;) {
+		slot = lpq->ppp_slot;
+		if (slot < 0 || slot > ISDN_MAX_CHANNELS) {
+			printk(KERN_ERR "%s: lpq->ppp_slot(%d)\n",
+				__FUNCTION__, lpq->ppp_slot);
+		} else {
+			u32 lls = ippp_table[slot]->last_link_seqno;
+			if (MP_LT(lls, minseq))
+				minseq = lls;
+		}
+		if ((lpq = lpq->next) == net_dev->queue)
+			break;
+	}
+	if (MP_LT(minseq, mp->seq))
+		minseq = mp->seq;	/* can't go beyond already processed
+					 * packets */
+	newfrag = skb;
+
+  	/* if this new fragment is before the first one, then enqueue it now. */
+  	if ((frag = mp->frags) == NULL || MP_LT(newseq, MP_SEQ(frag))) {
+		newfrag->next = frag;
+    		mp->frags = frag = newfrag;
+    		newfrag = NULL;
+  	}
+
+  	start = MP_FLAGS(frag) & MP_BEGIN_FRAG &&
+				MP_SEQ(frag) == mp->seq ? frag : NULL;
+
+	/* 
+	 * main fragment traversing loop
+	 *
+	 * try to accomplish several tasks:
+	 * - insert new fragment into the proper sequence slot (once that's done
+	 *   newfrag will be set to NULL)
+	 * - reassemble any complete fragment sequence (non-null 'start'
+	 *   indicates there is a continguous sequence present)
+	 * - discard any incomplete sequences that are below minseq -- due
+	 *   to the fact that sender always increment sequence number, if there
+	 *   is an incomplete sequence below minseq, no new fragments would
+	 *   come to complete such sequence and it should be discarded
+	 *
+	 * loop completes when we accomplished the following tasks:
+	 * - new fragment is inserted in the proper sequence ('newfrag' is 
+	 *   set to NULL)
+	 * - we hit a gap in the sequence, so no reassembly/processing is 
+	 *   possible ('start' would be set to NULL)
+	 *
+	 * algorightm for this code is derived from code in the book
+	 * 'PPP Design And Debugging' by James Carlson (Addison-Wesley)
+	 */
+  	while (start != NULL || newfrag != NULL) {
+
+    		thisseq = MP_SEQ(frag);
+    		nextf = frag->next;
+
+    		/* drop any duplicate fragments */
+    		if (newfrag != NULL && thisseq == newseq) {
+      			isdn_ppp_mp_free_skb(mp, newfrag);
+      			newfrag = NULL;
+    		}
+
+    		/* insert new fragment before next element if possible. */
+    		if (newfrag != NULL && (nextf == NULL || 
+						MP_LT(newseq, MP_SEQ(nextf)))) {
+      			newfrag->next = nextf;
+      			frag->next = nextf = newfrag;
+      			newfrag = NULL;
+    		}
+
+    		if (start != NULL) {
+	    		/* check for misplaced start */
+      			if (start != frag && (MP_FLAGS(frag) & MP_BEGIN_FRAG)) {
+				printk(KERN_WARNING"isdn_mppp(seq %d): new "
+				      "BEGIN flag with no prior END", thisseq);
+				stats->seqerrs++;
+				stats->frame_drops++;
+				start = isdn_ppp_mp_discard(mp, start,frag);
+				nextf = frag->next;
+      			}
+    		} else if (MP_LE(thisseq, minseq)) {		
+      			if (MP_FLAGS(frag) & MP_BEGIN_FRAG)
+				start = frag;
+      			else {
+				if (MP_FLAGS(frag) & MP_END_FRAG)
+	  				stats->frame_drops++;
+				if( mp->frags == frag )
+					mp->frags = nextf;	
+				isdn_ppp_mp_free_skb(mp, frag);
+				frag = nextf;
+				continue;
+      			}
+		}
+		
+		/* if start is non-null and we have end fragment, then
+		 * we have full reassembly sequence -- reassemble 
+		 * and process packet now
+		 */
+    		if (start != NULL && (MP_FLAGS(frag) & MP_END_FRAG)) {
+      			minseq = mp->seq = (thisseq+1) & MP_LONGSEQ_MASK;
+      			/* Reassemble the packet then dispatch it */
+			isdn_ppp_mp_reassembly(net_dev, lp, start, nextf);
+      
+      			start = NULL;
+      			frag = NULL;
+
+      			mp->frags = nextf;
+    		}
+
+		/* check if need to update start pointer: if we just
+		 * reassembled the packet and sequence is contiguous
+		 * then next fragment should be the start of new reassembly
+		 * if sequence is contiguous, but we haven't reassembled yet,
+		 * keep going.
+		 * if sequence is not contiguous, either clear everyting
+		 * below low watermark and set start to the next frag or
+		 * clear start ptr.
+		 */ 
+    		if (nextf != NULL && 
+		    ((thisseq+1) & MP_LONGSEQ_MASK) == MP_SEQ(nextf)) {
+      			/* if we just reassembled and the next one is here, 
+			 * then start another reassembly. */
+
+      			if (frag == NULL) {
+				if (MP_FLAGS(nextf) & MP_BEGIN_FRAG)
+	  				start = nextf;
+				else
+				{
+	  				printk(KERN_WARNING"isdn_mppp(seq %d):"
+						" END flag with no following "
+						"BEGIN", thisseq);
+					stats->seqerrs++;
+				}
+			}
+
+    		} else {
+			if ( nextf != NULL && frag != NULL &&
+						MP_LT(thisseq, minseq)) {
+				/* we've got a break in the sequence
+				 * and we not at the end yet
+				 * and we did not just reassembled
+				 *(if we did, there wouldn't be anything before)
+				 * and we below the low watermark 
+			 	 * discard all the frames below low watermark 
+				 * and start over */
+				stats->frame_drops++;
+				mp->frags = isdn_ppp_mp_discard(mp,start,nextf);
+			}
+			/* break in the sequence, no reassembly */
+      			start = NULL;
+    		}
+	  			
+    		frag = nextf;
+  	}	/* while -- main loop */
+	
+  	if (mp->frags == NULL)
+    		mp->frags = frag;
+		
+	/* rather straighforward way to deal with (not very) possible 
+	 * queue overflow */
+	if (mp->frames > MP_MAX_QUEUE_LEN) {
+		stats->overflows++;
+		while (mp->frames > MP_MAX_QUEUE_LEN) {
+			frag = mp->frags->next;
+			isdn_ppp_mp_free_skb(mp, mp->frags);
+			mp->frags = frag;
+		}
+	}
+	spin_unlock_irqrestore(&mp->lock, flags);
+}
+
+static void isdn_ppp_mp_cleanup( isdn_net_local * lp )
+{
+	struct sk_buff * frag = lp->netdev->pb->frags;
+	struct sk_buff * nextfrag;
+    	while( frag ) {
+		nextfrag = frag->next;
+		isdn_ppp_mp_free_skb(lp->netdev->pb, frag);
+		frag = nextfrag;
+	}
+	lp->netdev->pb->frags = NULL;
+}
+
+static u32 isdn_ppp_mp_get_seq( int short_seq, 
+					struct sk_buff * skb, u32 last_seq )
+{
+	u32 seq;
+	int flags = skb->data[0] & (MP_BEGIN_FRAG | MP_END_FRAG);
+   
+   	if( !short_seq )
+	{
+		seq = ntohl(*(u32*)skb->data) & MP_LONGSEQ_MASK;
+		skb_push(skb,1);
+	}
+	else
+	{
+		/* convert 12-bit short seq number to 24-bit long one 
+	 	*/
+		seq = ntohs(*(u16*)skb->data) & MP_SHORTSEQ_MASK;
+	
+		/* check for seqence wrap */
+		if( !(seq &  MP_SHORTSEQ_MAXBIT) && 
+		     (last_seq &  MP_SHORTSEQ_MAXBIT) && 
+		     (unsigned long)last_seq <= MP_LONGSEQ_MAX )
+			seq |= (last_seq + MP_SHORTSEQ_MAX+1) & 
+					(~MP_SHORTSEQ_MASK & MP_LONGSEQ_MASK);
+		else
+			seq |= last_seq & (~MP_SHORTSEQ_MASK & MP_LONGSEQ_MASK);
+		
+		skb_push(skb, 3);	/* put converted seqence back in skb */
+	}
+	*(u32*)(skb->data+1) = seq; 	/* put seqence back in _host_ byte
+					 * order */
+	skb->data[0] = flags;	        /* restore flags */
+	return seq;
+}
+
+struct sk_buff * isdn_ppp_mp_discard( ippp_bundle * mp,
+			struct sk_buff * from, struct sk_buff * to )
+{
+	if( from )
+		while (from != to) {
+	  		struct sk_buff * next = from->next;
+			isdn_ppp_mp_free_skb(mp, from);
+	  		from = next;
+		}
+	return from;
+}
+
+void isdn_ppp_mp_reassembly( isdn_net_dev * net_dev, isdn_net_local * lp,
+				struct sk_buff * from, struct sk_buff * to )
+{
+	ippp_bundle * mp = net_dev->pb;
+	int proto;
+	struct sk_buff * skb;
+	unsigned int tot_len;
+
+	if (lp->ppp_slot < 0 || lp->ppp_slot >= ISDN_MAX_CHANNELS) {
+		printk(KERN_ERR "%s: lp->ppp_slot(%d) out of range\n",
+			__FUNCTION__, lp->ppp_slot);
+		return;
+	}
+	if( MP_FLAGS(from) == (MP_BEGIN_FRAG | MP_END_FRAG) ) {
+		if( ippp_table[lp->ppp_slot]->debug & 0x40 )
+			printk(KERN_DEBUG "isdn_mppp: reassembly: frame %d, "
+					"len %d\n", MP_SEQ(from), from->len );
+		skb = from;
+		skb_pull(skb, MP_HEADER_LEN);
+		mp->frames--;	
+	} else {
+		struct sk_buff * frag;
+		int n;
+
+		for(tot_len=n=0, frag=from; frag != to; frag=frag->next, n++)
+			tot_len += frag->len - MP_HEADER_LEN;
+
+		if( ippp_table[lp->ppp_slot]->debug & 0x40 )
+			printk(KERN_DEBUG"isdn_mppp: reassembling frames %d "
+				"to %d, len %d\n", MP_SEQ(from), 
+				(MP_SEQ(from)+n-1) & MP_LONGSEQ_MASK, tot_len );
+		if( (skb = dev_alloc_skb(tot_len)) == NULL ) {
+			printk(KERN_ERR "isdn_mppp: cannot allocate sk buff "
+					"of size %d\n", tot_len);
+			isdn_ppp_mp_discard(mp, from, to);
+			return;
+		}
+
+		while( from != to ) {
+			unsigned int len = from->len - MP_HEADER_LEN;
+
+			memcpy(skb_put(skb,len), from->data+MP_HEADER_LEN, len);
+			frag = from->next;
+			isdn_ppp_mp_free_skb(mp, from);
+			from = frag; 
+		}
+	}
+   	proto = isdn_ppp_strip_proto(skb);
+	isdn_ppp_push_higher(net_dev, lp, skb, proto);
+}
+
+static void isdn_ppp_mp_free_skb(ippp_bundle * mp, struct sk_buff * skb)
+{
+	dev_kfree_skb(skb);
+	mp->frames--;
+}
+
+static void isdn_ppp_mp_print_recv_pkt( int slot, struct sk_buff * skb )
+{
+	printk(KERN_DEBUG "mp_recv: %d/%d -> %02x %02x %02x %02x %02x %02x\n", 
+		slot, (int) skb->len, 
+		(int) skb->data[0], (int) skb->data[1], (int) skb->data[2],
+		(int) skb->data[3], (int) skb->data[4], (int) skb->data[5]);
+}
+
+static int
+isdn_ppp_bundle(struct ippp_struct *is, int unit)
+{
+	char ifn[IFNAMSIZ + 1];
+	isdn_net_dev *p;
+	isdn_net_local *lp, *nlp;
+	int rc;
+	unsigned long flags;
+
+	sprintf(ifn, "ippp%d", unit);
+	p = isdn_net_findif(ifn);
+	if (!p) {
+		printk(KERN_ERR "ippp_bundle: cannot find %s\n", ifn);
+		return -EINVAL;
+	}
+
+    	spin_lock_irqsave(&p->pb->lock, flags);
+
+	nlp = is->lp;
+	lp = p->queue;
+	if( nlp->ppp_slot < 0 || nlp->ppp_slot >= ISDN_MAX_CHANNELS ||
+		lp->ppp_slot < 0 || lp->ppp_slot >= ISDN_MAX_CHANNELS ) {
+		printk(KERN_ERR "ippp_bundle: binding to invalid slot %d\n",
+			nlp->ppp_slot < 0 || nlp->ppp_slot >= ISDN_MAX_CHANNELS ? 
+			nlp->ppp_slot : lp->ppp_slot );
+		rc = -EINVAL;
+		goto out;
+ 	}
+
+	isdn_net_add_to_bundle(p, nlp);
+
+	ippp_table[nlp->ppp_slot]->unit = ippp_table[lp->ppp_slot]->unit;
+
+	/* maybe also SC_CCP stuff */
+	ippp_table[nlp->ppp_slot]->pppcfg |= ippp_table[lp->ppp_slot]->pppcfg &
+		(SC_ENABLE_IP | SC_NO_TCP_CCID | SC_REJ_COMP_TCP);
+	ippp_table[nlp->ppp_slot]->mpppcfg |= ippp_table[lp->ppp_slot]->mpppcfg &
+		(SC_MP_PROT | SC_REJ_MP_PROT | SC_OUT_SHORT_SEQ | SC_IN_SHORT_SEQ);
+	rc = isdn_ppp_mp_init(nlp, p->pb);
+out:
+	spin_unlock_irqrestore(&p->pb->lock, flags);
+	return rc;
+}
+  
+#endif /* CONFIG_ISDN_MPP */
+  
+/*
+ * network device ioctl handlers
+ */
+
+static int
+isdn_ppp_dev_ioctl_stats(int slot, struct ifreq *ifr, struct net_device *dev)
+{
+	struct ppp_stats __user *res = ifr->ifr_data;
+	struct ppp_stats t;
+	isdn_net_local *lp = (isdn_net_local *) dev->priv;
+
+	if (!access_ok(VERIFY_WRITE, res, sizeof(struct ppp_stats)))
+		return -EFAULT;
+
+	/* 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
+		if (slot >= 0 && ippp_table[slot]->slcomp) {
+			struct slcompress *slcomp = ippp_table[slot]->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;
+	int len;
+	isdn_net_local *lp = (isdn_net_local *) dev->priv;
+
+
+	if (lp->p_encap != ISDN_NET_ENCAP_SYNCPPP)
+		return -EINVAL;
+
+	switch (cmd) {
+#define PPP_VERSION "2.3.7"
+		case SIOCGPPPVER:
+			len = strlen(PPP_VERSION) + 1;
+			if (copy_to_user(ifr->ifr_data, PPP_VERSION, len))
+				error = -EFAULT;
+			break;
+
+		case SIOCGPPPSTATS:
+			error = isdn_ppp_dev_ioctl_stats(lp->ppp_slot, 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;
+}
+
+
+int
+isdn_ppp_dial_slave(char *name)
+{
+#ifdef CONFIG_ISDN_MPP
+	isdn_net_dev *ndev;
+	isdn_net_local *lp;
+	struct net_device *sdev;
+
+	if (!(ndev = isdn_net_findif(name)))
+		return 1;
+	lp = ndev->local;
+	if (!(lp->flags & ISDN_NET_CONNECTED))
+		return 5;
+
+	sdev = lp->slave;
+	while (sdev) {
+		isdn_net_local *mlp = (isdn_net_local *) sdev->priv;
+		if (!(mlp->flags & ISDN_NET_CONNECTED))
+			break;
+		sdev = mlp->slave;
+	}
+	if (!sdev)
+		return 2;
+
+	isdn_net_dial_req((isdn_net_local *) sdev->priv);
+	return 0;
+#else
+	return -1;
+#endif
+}
+
+int
+isdn_ppp_hangup_slave(char *name)
+{
+#ifdef CONFIG_ISDN_MPP
+	isdn_net_dev *ndev;
+	isdn_net_local *lp;
+	struct net_device *sdev;
+
+	if (!(ndev = isdn_net_findif(name)))
+		return 1;
+	lp = ndev->local;
+	if (!(lp->flags & ISDN_NET_CONNECTED))
+		return 5;
+
+	sdev = lp->slave;
+	while (sdev) {
+		isdn_net_local *mlp = (isdn_net_local *) sdev->priv;
+
+		if (mlp->slave) { /* find last connected link in chain */
+			isdn_net_local *nlp = (isdn_net_local *) mlp->slave->priv;
+
+			if (!(nlp->flags & ISDN_NET_CONNECTED))
+				break;
+		} else if (mlp->flags & ISDN_NET_CONNECTED)
+			break;
+		
+		sdev = mlp->slave;
+	}
+	if (!sdev)
+		return 2;
+
+	isdn_net_hangup(sdev);
+	return 0;
+#else
+	return -1;
+#endif
+}
+
+/*
+ * 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_ccp_kickup(struct ippp_struct *is)
+{
+	isdn_ppp_fill_rq(NULL, 0, PPP_COMP, is->lp->ppp_slot);
+}
+
+/* In-kernel handling of CCP Reset-Request and Reset-Ack is necessary,
+   but absolutely nontrivial. The most abstruse problem we are facing is
+   that the generation, reception and all the handling of timeouts and
+   resends including proper request id management should be entirely left
+   to the (de)compressor, but indeed is not covered by the current API to
+   the (de)compressor. The API is a prototype version from PPP where only
+   some (de)compressors have yet been implemented and all of them are
+   rather simple in their reset handling. Especially, their is only one
+   outstanding ResetAck at a time with all of them and ResetReq/-Acks do
+   not have parameters. For this very special case it was sufficient to
+   just return an error code from the decompressor and have a single
+   reset() entry to communicate all the necessary information between
+   the framework and the (de)compressor. Bad enough, LZS is different
+   (and any other compressor may be different, too). It has multiple
+   histories (eventually) and needs to Reset each of them independently
+   and thus uses multiple outstanding Acks and history numbers as an
+   additional parameter to Reqs/Acks.
+   All that makes it harder to port the reset state engine into the
+   kernel because it is not just the same simple one as in (i)pppd but
+   it must be able to pass additional parameters and have multiple out-
+   standing Acks. We are trying to achieve the impossible by handling
+   reset transactions independent by their id. The id MUST change when
+   the data portion changes, thus any (de)compressor who uses more than
+   one resettable state must provide and recognize individual ids for
+   each individual reset transaction. The framework itself does _only_
+   differentiate them by id, because it has no other semantics like the
+   (de)compressor might.
+   This looks like a major redesign of the interface would be nice,
+   but I don't have an idea how to do it better. */
+
+/* Send a CCP Reset-Request or Reset-Ack directly from the kernel. This is
+   getting that lengthy because there is no simple "send-this-frame-out"
+   function above but every wrapper does a bit different. Hope I guess
+   correct in this hack... */
+
+static void isdn_ppp_ccp_xmit_reset(struct ippp_struct *is, int proto,
+				    unsigned char code, unsigned char id,
+				    unsigned char *data, int len)
+{
+	struct sk_buff *skb;
+	unsigned char *p;
+	int hl;
+	int cnt = 0;
+	isdn_net_local *lp = is->lp;
+
+	/* Alloc large enough skb */
+	hl = dev->drv[lp->isdn_device]->interface->hl_hdrlen;
+	skb = alloc_skb(len + hl + 16,GFP_ATOMIC);
+	if(!skb) {
+		printk(KERN_WARNING
+		       "ippp: CCP cannot send reset - out of memory\n");
+		return;
+	}
+	skb_reserve(skb, hl);
+
+	/* We may need to stuff an address and control field first */
+	if(!(is->pppcfg & SC_COMP_AC)) {
+		p = skb_put(skb, 2);
+		*p++ = 0xff;
+		*p++ = 0x03;
+	}
+
+	/* Stuff proto, code, id and length */
+	p = skb_put(skb, 6);
+	*p++ = (proto >> 8);
+	*p++ = (proto & 0xff);
+	*p++ = code;
+	*p++ = id;
+	cnt = 4 + len;
+	*p++ = (cnt >> 8);
+	*p++ = (cnt & 0xff);
+
+	/* Now stuff remaining bytes */
+	if(len) {
+		p = skb_put(skb, len);
+		memcpy(p, data, len);
+	}
+
+	/* skb is now ready for xmit */
+	printk(KERN_DEBUG "Sending CCP Frame:\n");
+	isdn_ppp_frame_log("ccp-xmit", skb->data, skb->len, 32, is->unit,lp->ppp_slot);
+
+	isdn_net_write_super(lp, skb);
+}
+
+/* Allocate the reset state vector */
+static struct ippp_ccp_reset *isdn_ppp_ccp_reset_alloc(struct ippp_struct *is)
+{
+	struct ippp_ccp_reset *r;
+	r = kmalloc(sizeof(struct ippp_ccp_reset), GFP_KERNEL);
+	if(!r) {
+		printk(KERN_ERR "ippp_ccp: failed to allocate reset data"
+		       " structure - no mem\n");
+		return NULL;
+	}
+	memset(r, 0, sizeof(struct ippp_ccp_reset));
+	printk(KERN_DEBUG "ippp_ccp: allocated reset data structure %p\n", r);
+	is->reset = r;
+	return r;
+}
+
+/* Destroy the reset state vector. Kill all pending timers first. */
+static void isdn_ppp_ccp_reset_free(struct ippp_struct *is)
+{
+	unsigned int id;
+
+	printk(KERN_DEBUG "ippp_ccp: freeing reset data structure %p\n",
+	       is->reset);
+	for(id = 0; id < 256; id++) {
+		if(is->reset->rs[id]) {
+			isdn_ppp_ccp_reset_free_state(is, (unsigned char)id);
+		}
+	}
+	kfree(is->reset);
+	is->reset = NULL;
+}
+
+/* Free a given state and clear everything up for later reallocation */
+static void isdn_ppp_ccp_reset_free_state(struct ippp_struct *is,
+					  unsigned char id)
+{
+	struct ippp_ccp_reset_state *rs;
+
+	if(is->reset->rs[id]) {
+		printk(KERN_DEBUG "ippp_ccp: freeing state for id %d\n", id);
+		rs = is->reset->rs[id];
+		/* Make sure the kernel will not call back later */
+		if(rs->ta)
+			del_timer(&rs->timer);
+		is->reset->rs[id] = NULL;
+		kfree(rs);
+	} else {
+		printk(KERN_WARNING "ippp_ccp: id %d is not allocated\n", id);
+	}
+}
+
+/* The timer callback function which is called when a ResetReq has timed out,
+   aka has never been answered by a ResetAck */
+static void isdn_ppp_ccp_timer_callback(unsigned long closure)
+{
+	struct ippp_ccp_reset_state *rs =
+		(struct ippp_ccp_reset_state *)closure;
+
+	if(!rs) {
+		printk(KERN_ERR "ippp_ccp: timer cb with zero closure.\n");
+		return;
+	}
+	if(rs->ta && rs->state == CCPResetSentReq) {
+		/* We are correct here */
+		if(!rs->expra) {
+			/* Hmm, there is no Ack really expected. We can clean
+			   up the state now, it will be reallocated if the
+			   decompressor insists on another reset */
+			rs->ta = 0;
+			isdn_ppp_ccp_reset_free_state(rs->is, rs->id);
+			return;
+		}
+		printk(KERN_DEBUG "ippp_ccp: CCP Reset timed out for id %d\n",
+		       rs->id);
+		/* Push it again */
+		isdn_ppp_ccp_xmit_reset(rs->is, PPP_CCP, CCP_RESETREQ, rs->id,
+					rs->data, rs->dlen);
+		/* Restart timer */
+		rs->timer.expires = jiffies + HZ*5;
+		add_timer(&rs->timer);
+	} else {
+		printk(KERN_WARNING "ippp_ccp: timer cb in wrong state %d\n",
+		       rs->state);
+	}
+}
+
+/* Allocate a new reset transaction state */
+static struct ippp_ccp_reset_state *isdn_ppp_ccp_reset_alloc_state(struct ippp_struct *is,
+						      unsigned char id)
+{
+	struct ippp_ccp_reset_state *rs;
+	if(is->reset->rs[id]) {
+		printk(KERN_WARNING "ippp_ccp: old state exists for id %d\n",
+		       id);
+		return NULL;
+	} else {
+		rs = kmalloc(sizeof(struct ippp_ccp_reset_state), GFP_KERNEL);
+		if(!rs)
+			return NULL;
+		memset(rs, 0, sizeof(struct ippp_ccp_reset_state));
+		rs->state = CCPResetIdle;
+		rs->is = is;
+		rs->id = id;
+		rs->timer.data = (unsigned long)rs;
+		rs->timer.function = isdn_ppp_ccp_timer_callback;
+		is->reset->rs[id] = rs;
+	}
+	return rs;
+}
+
+
+/* A decompressor wants a reset with a set of parameters - do what is
+   necessary to fulfill it */
+static void isdn_ppp_ccp_reset_trans(struct ippp_struct *is,
+				     struct isdn_ppp_resetparams *rp)
+{
+	struct ippp_ccp_reset_state *rs;
+
+	if(rp->valid) {
+		/* The decompressor defines parameters by itself */
+		if(rp->rsend) {
+			/* And he wants us to send a request */
+			if(!(rp->idval)) {
+				printk(KERN_ERR "ippp_ccp: decompressor must"
+				       " specify reset id\n");
+				return;
+			}
+			if(is->reset->rs[rp->id]) {
+				/* There is already a transaction in existence
+				   for this id. May be still waiting for a
+				   Ack or may be wrong. */
+				rs = is->reset->rs[rp->id];
+				if(rs->state == CCPResetSentReq && rs->ta) {
+					printk(KERN_DEBUG "ippp_ccp: reset"
+					       " trans still in progress"
+					       " for id %d\n", rp->id);
+				} else {
+					printk(KERN_WARNING "ippp_ccp: reset"
+					       " trans in wrong state %d for"
+					       " id %d\n", rs->state, rp->id);
+				}
+			} else {
+				/* Ok, this is a new transaction */
+				printk(KERN_DEBUG "ippp_ccp: new trans for id"
+				       " %d to be started\n", rp->id);
+				rs = isdn_ppp_ccp_reset_alloc_state(is, rp->id);
+				if(!rs) {
+					printk(KERN_ERR "ippp_ccp: out of mem"
+					       " allocing ccp trans\n");
+					return;
+				}
+				rs->state = CCPResetSentReq;
+				rs->expra = rp->expra;
+				if(rp->dtval) {
+					rs->dlen = rp->dlen;
+					memcpy(rs->data, rp->data, rp->dlen);
+				}
+				/* HACK TODO - add link comp here */
+				isdn_ppp_ccp_xmit_reset(is, PPP_CCP,
+							CCP_RESETREQ, rs->id,
+							rs->data, rs->dlen);
+				/* Start the timer */
+				rs->timer.expires = jiffies + 5*HZ;
+				add_timer(&rs->timer);
+				rs->ta = 1;
+			}
+		} else {
+			printk(KERN_DEBUG "ippp_ccp: no reset sent\n");
+		}
+	} else {
+		/* The reset params are invalid. The decompressor does not
+		   care about them, so we just send the minimal requests
+		   and increase ids only when an Ack is received for a
+		   given id */
+		if(is->reset->rs[is->reset->lastid]) {
+			/* There is already a transaction in existence
+			   for this id. May be still waiting for a
+			   Ack or may be wrong. */
+			rs = is->reset->rs[is->reset->lastid];
+			if(rs->state == CCPResetSentReq && rs->ta) {
+				printk(KERN_DEBUG "ippp_ccp: reset"
+				       " trans still in progress"
+				       " for id %d\n", rp->id);
+			} else {
+				printk(KERN_WARNING "ippp_ccp: reset"
+				       " trans in wrong state %d for"
+				       " id %d\n", rs->state, rp->id);
+			}
+		} else {
+			printk(KERN_DEBUG "ippp_ccp: new trans for id"
+			       " %d to be started\n", is->reset->lastid);
+			rs = isdn_ppp_ccp_reset_alloc_state(is,
+							    is->reset->lastid);
+			if(!rs) {
+				printk(KERN_ERR "ippp_ccp: out of mem"
+				       " allocing ccp trans\n");
+				return;
+			}
+			rs->state = CCPResetSentReq;
+			/* We always expect an Ack if the decompressor doesn't
+			   know	better */
+			rs->expra = 1;
+			rs->dlen = 0;
+			/* HACK TODO - add link comp here */
+			isdn_ppp_ccp_xmit_reset(is, PPP_CCP, CCP_RESETREQ,
+						rs->id, NULL, 0);
+			/* Start the timer */
+			rs->timer.expires = jiffies + 5*HZ;
+			add_timer(&rs->timer);
+			rs->ta = 1;
+		}
+	}
+}
+
+/* An Ack was received for this id. This means we stop the timer and clean
+   up the state prior to calling the decompressors reset routine. */
+static void isdn_ppp_ccp_reset_ack_rcvd(struct ippp_struct *is,
+					unsigned char id)
+{
+	struct ippp_ccp_reset_state *rs = is->reset->rs[id];
+
+	if(rs) {
+		if(rs->ta && rs->state == CCPResetSentReq) {
+			/* Great, we are correct */
+			if(!rs->expra)
+				printk(KERN_DEBUG "ippp_ccp: ResetAck received"
+				       " for id %d but not expected\n", id);
+		} else {
+			printk(KERN_INFO "ippp_ccp: ResetAck received out of"
+			       "sync for id %d\n", id);
+		}
+		if(rs->ta) {
+			rs->ta = 0;
+			del_timer(&rs->timer);
+		}
+		isdn_ppp_ccp_reset_free_state(is, id);
+	} else {
+		printk(KERN_INFO "ippp_ccp: ResetAck received for unknown id"
+		       " %d\n", id);
+	}
+	/* Make sure the simple reset stuff uses a new id next time */
+	is->reset->lastid++;
+}
+
+/* 
+ * decompress packet
+ *
+ * if master = 0, we're trying to uncompress an per-link compressed packet,
+ * as opposed to an compressed reconstructed-from-MPPP packet.
+ * proto is updated to protocol field of uncompressed packet.
+ *
+ * retval: decompressed packet,
+ *         same packet if uncompressed,
+ *	   NULL if decompression error
+ */
+
+static struct sk_buff *isdn_ppp_decompress(struct sk_buff *skb,struct ippp_struct *is,struct ippp_struct *master,
+	int *proto)
+{
+	void *stat = NULL;
+	struct isdn_ppp_compressor *ipc = NULL;
+	struct sk_buff *skb_out;
+	int len;
+	struct ippp_struct *ri;
+	struct isdn_ppp_resetparams rsparm;
+	unsigned char rsdata[IPPP_RESET_MAXDATABYTES];
+
+	if(!master) {
+		// per-link decompression 
+		stat = is->link_decomp_stat;
+		ipc = is->link_decompressor;
+		ri = is;
+	} else {
+		stat = master->decomp_stat;
+		ipc = master->decompressor;
+		ri = master;
+	}
+
+	if (!ipc) {
+		// no decompressor -> we can't decompress.
+		printk(KERN_DEBUG "ippp: no decompressor defined!\n");
+		return skb;
+	}
+	if (!stat) // if we have a compressor, stat has been set as well
+		BUG();
+
+	if((master && *proto == PPP_COMP) || (!master && *proto == PPP_COMPFRAG) ) {
+		// compressed packets are compressed by their protocol type
+
+		// Set up reset params for the decompressor
+  		memset(&rsparm, 0, sizeof(rsparm));
+  		rsparm.data = rsdata;
+  		rsparm.maxdlen = IPPP_RESET_MAXDATABYTES;
+  
+  		skb_out = dev_alloc_skb(is->mru + PPP_HDRLEN);
+		len = ipc->decompress(stat, skb, skb_out, &rsparm);
+		kfree_skb(skb);
+		if (len <= 0) {
+			switch(len) {
+			case DECOMP_ERROR:
+				printk(KERN_INFO "ippp: decomp wants reset %s params\n",
+				       rsparm.valid ? "with" : "without");
+				
+				isdn_ppp_ccp_reset_trans(ri, &rsparm);
+				break;
+			case DECOMP_FATALERROR:
+				ri->pppcfg |= SC_DC_FERROR;
+				/* Kick ipppd to recognize the error */
+				isdn_ppp_ccp_kickup(ri);
+				break;
+			}
+			kfree_skb(skb_out);
+			return NULL;
+		}
+		*proto = isdn_ppp_strip_proto(skb_out);
+		if (*proto < 0) {
+			kfree_skb(skb_out);
+			return NULL;
+		}
+		return skb_out;
+	} else { 
+		// uncompressed packets are fed through the decompressor to
+		// update the decompressor state
+		ipc->incomp(stat, skb, *proto);
+		return skb;
+	}
+}
+
+/*
+ * compress a frame 
+ *   type=0: normal/bundle compression
+ *       =1: link compression
+ * returns original skb if we haven't compressed the frame
+ * and a new skb pointer if we've done it
+ */
+static struct sk_buff *isdn_ppp_compress(struct sk_buff *skb_in,int *proto,
+	struct ippp_struct *is,struct ippp_struct *master,int type)
+{
+    int ret;
+    int new_proto;
+    struct isdn_ppp_compressor *compressor;
+    void *stat;
+    struct sk_buff *skb_out;
+
+	/* we do not compress control protocols */
+    if(*proto < 0 || *proto > 0x3fff) {
+	    return skb_in;
+    }
+
+	if(type) { /* type=1 => Link compression */
+		return skb_in;
+	}
+	else {
+		if(!master) {
+			compressor = is->compressor;
+			stat = is->comp_stat;
+		}
+		else {
+			compressor = master->compressor;
+			stat = master->comp_stat;
+		}
+		new_proto = PPP_COMP;
+	}
+
+	if(!compressor) {
+		printk(KERN_ERR "isdn_ppp: No compressor set!\n");
+		return skb_in;
+	}
+	if(!stat) {
+		printk(KERN_ERR "isdn_ppp: Compressor not initialized?\n");
+		return skb_in;
+	}
+
+	/* Allow for at least 150 % expansion (for now) */
+	skb_out = alloc_skb(skb_in->len + skb_in->len/2 + 32 +
+		skb_headroom(skb_in), GFP_ATOMIC);
+	if(!skb_out)
+		return skb_in;
+	skb_reserve(skb_out, skb_headroom(skb_in));
+
+	ret = (compressor->compress)(stat,skb_in,skb_out,*proto);
+	if(!ret) {
+		dev_kfree_skb(skb_out);
+		return skb_in;
+	}
+	
+	dev_kfree_skb(skb_in);
+	*proto = new_proto;
+	return skb_out;
+}
+
+/*
+ * we received a CCP frame .. 
+ * not a clean solution, but we MUST handle a few cases in the kernel
+ */
+static void isdn_ppp_receive_ccp(isdn_net_dev *net_dev, isdn_net_local *lp,
+	 struct sk_buff *skb,int proto)
+{
+	struct ippp_struct *is;
+	struct ippp_struct *mis;
+	int len;
+	struct isdn_ppp_resetparams rsparm;
+	unsigned char rsdata[IPPP_RESET_MAXDATABYTES];	
+
+	printk(KERN_DEBUG "Received CCP frame from peer slot(%d)\n",
+		lp->ppp_slot);
+	if (lp->ppp_slot < 0 || lp->ppp_slot > ISDN_MAX_CHANNELS) {
+		printk(KERN_ERR "%s: lp->ppp_slot(%d) out of range\n",
+			__FUNCTION__, lp->ppp_slot);
+		return;
+	}
+	is = ippp_table[lp->ppp_slot];
+	isdn_ppp_frame_log("ccp-rcv", skb->data, skb->len, 32, is->unit,lp->ppp_slot);
+
+	if(lp->master) {
+		int slot = ((isdn_net_local *) (lp->master->priv))->ppp_slot;
+		if (slot < 0 || slot > ISDN_MAX_CHANNELS) {
+			printk(KERN_ERR "%s: slot(%d) out of range\n",
+				__FUNCTION__, slot);
+			return;
+		}	
+		mis = ippp_table[slot];
+	} else
+		mis = is;
+
+	switch(skb->data[0]) {
+	case CCP_CONFREQ:
+		if(is->debug & 0x10)
+			printk(KERN_DEBUG "Disable compression here!\n");
+		if(proto == PPP_CCP)
+			mis->compflags &= ~SC_COMP_ON;		
+		else
+			is->compflags &= ~SC_LINK_COMP_ON;		
+		break;
+	case CCP_TERMREQ:
+	case CCP_TERMACK:
+		if(is->debug & 0x10)
+			printk(KERN_DEBUG "Disable (de)compression here!\n");
+		if(proto == PPP_CCP)
+			mis->compflags &= ~(SC_DECOMP_ON|SC_COMP_ON);		
+		else
+			is->compflags &= ~(SC_LINK_DECOMP_ON|SC_LINK_COMP_ON);		
+		break;
+	case CCP_CONFACK:
+		/* if we RECEIVE an ackowledge we enable the decompressor */
+		if(is->debug & 0x10)
+			printk(KERN_DEBUG "Enable decompression here!\n");
+		if(proto == PPP_CCP) {
+			if (!mis->decompressor)
+				break;
+			mis->compflags |= SC_DECOMP_ON;
+		} else {
+			if (!is->decompressor)
+				break;
+			is->compflags |= SC_LINK_DECOMP_ON;
+		}
+		break;
+
+	case CCP_RESETACK:
+		printk(KERN_DEBUG "Received ResetAck from peer\n");
+		len = (skb->data[2] << 8) | skb->data[3];
+		len -= 4;
+
+		if(proto == PPP_CCP) {
+			/* If a reset Ack was outstanding for this id, then
+			   clean up the state engine */
+			isdn_ppp_ccp_reset_ack_rcvd(mis, skb->data[1]);
+			if(mis->decompressor && mis->decomp_stat)
+				mis->decompressor->
+					reset(mis->decomp_stat,
+					      skb->data[0],
+					      skb->data[1],
+					      len ? &skb->data[4] : NULL,
+					      len, NULL);
+			/* TODO: This is not easy to decide here */
+			mis->compflags &= ~SC_DECOMP_DISCARD;
+		}
+		else {
+			isdn_ppp_ccp_reset_ack_rcvd(is, skb->data[1]);
+			if(is->link_decompressor && is->link_decomp_stat)
+				is->link_decompressor->
+					reset(is->link_decomp_stat,
+					      skb->data[0],
+					      skb->data[1],
+					      len ? &skb->data[4] : NULL,
+					      len, NULL);
+			/* TODO: neither here */
+			is->compflags &= ~SC_LINK_DECOMP_DISCARD;
+		}
+		break;
+
+	case CCP_RESETREQ:
+		printk(KERN_DEBUG "Received ResetReq from peer\n");
+		/* Receiving a ResetReq means we must reset our compressor */
+		/* Set up reset params for the reset entry */
+		memset(&rsparm, 0, sizeof(rsparm));
+		rsparm.data = rsdata;
+		rsparm.maxdlen = IPPP_RESET_MAXDATABYTES; 
+		/* Isolate data length */
+		len = (skb->data[2] << 8) | skb->data[3];
+		len -= 4;
+		if(proto == PPP_CCP) {
+			if(mis->compressor && mis->comp_stat)
+				mis->compressor->
+					reset(mis->comp_stat,
+					      skb->data[0],
+					      skb->data[1],
+					      len ? &skb->data[4] : NULL,
+					      len, &rsparm);
+		}
+		else {
+			if(is->link_compressor && is->link_comp_stat)
+				is->link_compressor->
+					reset(is->link_comp_stat,
+					      skb->data[0],
+					      skb->data[1],
+					      len ? &skb->data[4] : NULL,
+					      len, &rsparm);
+		}
+		/* Ack the Req as specified by rsparm */
+		if(rsparm.valid) {
+			/* Compressor reset handler decided how to answer */
+			if(rsparm.rsend) {
+				/* We should send a Frame */
+				isdn_ppp_ccp_xmit_reset(is, proto, CCP_RESETACK,
+							rsparm.idval ? rsparm.id
+							: skb->data[1],
+							rsparm.dtval ?
+							rsparm.data : NULL,
+							rsparm.dtval ?
+							rsparm.dlen : 0);
+			} else {
+				printk(KERN_DEBUG "ResetAck suppressed\n");
+			}
+		} else {
+			/* We answer with a straight reflected Ack */
+			isdn_ppp_ccp_xmit_reset(is, proto, CCP_RESETACK,
+						skb->data[1],
+						len ? &skb->data[4] : NULL,
+						len);
+		}
+		break;
+	}
+}
+
+
+/*
+ * Daemon sends a CCP frame ...
+ */
+
+/* TODO: Clean this up with new Reset semantics */
+
+/* I believe the CCP handling as-is is done wrong. Compressed frames
+ * should only be sent/received after CCP reaches UP state, which means
+ * both sides have sent CONF_ACK. Currently, we handle both directions
+ * independently, which means we may accept compressed frames too early
+ * (supposedly not a problem), but may also mean we send compressed frames
+ * too early, which may turn out to be a problem.
+ * This part of state machine should actually be handled by (i)pppd, but
+ * that's too big of a change now. --kai
+ */
+
+/* Actually, we might turn this into an advantage: deal with the RFC in
+ * the old tradition of beeing generous on what we accept, but beeing
+ * strict on what we send. Thus we should just
+ * - accept compressed frames as soon as decompression is negotiated
+ * - send compressed frames only when decomp *and* comp are negotiated
+ * - drop rx compressed frames if we cannot decomp (instead of pushing them
+ *   up to ipppd)
+ * and I tried to modify this file according to that. --abp
+ */
+
+static void isdn_ppp_send_ccp(isdn_net_dev *net_dev, isdn_net_local *lp, struct sk_buff *skb)
+{
+	struct ippp_struct *mis,*is;
+	int proto, slot = lp->ppp_slot;
+	unsigned char *data;
+
+	if(!skb || skb->len < 3)
+		return;
+	if (slot < 0 || slot >= ISDN_MAX_CHANNELS) {
+		printk(KERN_ERR "%s: lp->ppp_slot(%d) out of range\n",
+			__FUNCTION__, slot);
+		return;
+	}	
+	is = ippp_table[slot];
+	/* Daemon may send with or without address and control field comp */
+	data = skb->data;
+	if(!(is->pppcfg & SC_COMP_AC) && data[0] == 0xff && data[1] == 0x03) {
+		data += 2;
+		if(skb->len < 5)
+			return;
+	}
+
+	proto = ((int)data[0]<<8)+data[1];
+	if(proto != PPP_CCP && proto != PPP_CCPFRAG)
+		return;
+
+	printk(KERN_DEBUG "Received CCP frame from daemon:\n");
+	isdn_ppp_frame_log("ccp-xmit", skb->data, skb->len, 32, is->unit,lp->ppp_slot);
+
+	if (lp->master) {
+		slot = ((isdn_net_local *) (lp->master->priv))->ppp_slot;
+		if (slot < 0 || slot > ISDN_MAX_CHANNELS) {
+			printk(KERN_ERR "%s: slot(%d) out of range\n",
+				__FUNCTION__, slot);
+			return;
+		}	
+		mis = ippp_table[slot];
+	} else
+		mis = is;
+	if (mis != is)
+		printk(KERN_DEBUG "isdn_ppp: Ouch! Master CCP sends on slave slot!\n");
+	
+        switch(data[2]) {
+	case CCP_CONFREQ:
+		if(is->debug & 0x10)
+			printk(KERN_DEBUG "Disable decompression here!\n");
+		if(proto == PPP_CCP)
+			is->compflags &= ~SC_DECOMP_ON;
+		else
+			is->compflags &= ~SC_LINK_DECOMP_ON;
+		break;
+	case CCP_TERMREQ:
+	case CCP_TERMACK:
+		if(is->debug & 0x10)
+			printk(KERN_DEBUG "Disable (de)compression here!\n");
+		if(proto == PPP_CCP)
+			is->compflags &= ~(SC_DECOMP_ON|SC_COMP_ON);
+		else
+			is->compflags &= ~(SC_LINK_DECOMP_ON|SC_LINK_COMP_ON);
+		break;
+	case CCP_CONFACK:
+		/* if we SEND an ackowledge we can/must enable the compressor */
+		if(is->debug & 0x10)
+			printk(KERN_DEBUG "Enable compression here!\n");
+		if(proto == PPP_CCP) {
+			if (!is->compressor)
+				break;
+			is->compflags |= SC_COMP_ON;
+		} else {
+			if (!is->compressor)
+				break;
+			is->compflags |= SC_LINK_COMP_ON;
+		}
+		break;
+	case CCP_RESETACK:
+		/* If we send a ACK we should reset our compressor */
+		if(is->debug & 0x10)
+			printk(KERN_DEBUG "Reset decompression state here!\n");
+		printk(KERN_DEBUG "ResetAck from daemon passed by\n");
+		if(proto == PPP_CCP) {
+			/* link to master? */
+			if(is->compressor && is->comp_stat)
+				is->compressor->reset(is->comp_stat, 0, 0,
+						      NULL, 0, NULL);
+			is->compflags &= ~SC_COMP_DISCARD;	
+		}
+		else {
+			if(is->link_compressor && is->link_comp_stat)
+				is->link_compressor->reset(is->link_comp_stat,
+							   0, 0, NULL, 0, NULL);
+			is->compflags &= ~SC_LINK_COMP_DISCARD;	
+		}
+		break;
+	case CCP_RESETREQ:
+		/* Just let it pass by */
+		printk(KERN_DEBUG "ResetReq from daemon passed by\n");
+		break;
+	}
+}
+
+int isdn_ppp_register_compressor(struct isdn_ppp_compressor *ipc)
+{
+	ipc->next = ipc_head;
+	ipc->prev = NULL;
+	if(ipc_head) {
+		ipc_head->prev = ipc;
+	}
+	ipc_head = ipc;
+	return 0;
+}
+
+int isdn_ppp_unregister_compressor(struct isdn_ppp_compressor *ipc)
+{
+	if(ipc->prev)
+		ipc->prev->next = ipc->next;
+	else
+		ipc_head = ipc->next;
+	if(ipc->next)
+		ipc->next->prev = ipc->prev;
+	ipc->prev = ipc->next = NULL;
+	return 0;
+}
+
+static int isdn_ppp_set_compressor(struct ippp_struct *is, struct isdn_ppp_comp_data *data)
+{
+	struct isdn_ppp_compressor *ipc = ipc_head;
+	int ret;
+	void *stat;
+	int num = data->num;
+
+	if(is->debug & 0x10)
+		printk(KERN_DEBUG "[%d] Set %s type %d\n",is->unit,
+			(data->flags&IPPP_COMP_FLAG_XMIT)?"compressor":"decompressor",num);
+
+	/* If is has no valid reset state vector, we cannot allocate a
+	   decompressor. The decompressor would cause reset transactions
+	   sooner or later, and they need that vector. */
+
+	if(!(data->flags & IPPP_COMP_FLAG_XMIT) && !is->reset) {
+		printk(KERN_ERR "ippp_ccp: no reset data structure - can't"
+		       " allow decompression.\n");
+		return -ENOMEM;
+	}
+
+	while(ipc) {
+		if(ipc->num == num) {
+			stat = ipc->alloc(data);
+			if(stat) {
+				ret = ipc->init(stat,data,is->unit,0);
+				if(!ret) {
+					printk(KERN_ERR "Can't init (de)compression!\n");
+					ipc->free(stat);
+					stat = NULL;
+					break;
+				}
+			}
+			else {
+				printk(KERN_ERR "Can't alloc (de)compression!\n");
+				break;
+			}
+
+                        if(data->flags & IPPP_COMP_FLAG_XMIT) {
+				if(data->flags & IPPP_COMP_FLAG_LINK) {
+					if(is->link_comp_stat)
+						is->link_compressor->free(is->link_comp_stat);
+					is->link_comp_stat = stat;
+                                	is->link_compressor = ipc;
+				}
+				else {
+					if(is->comp_stat)
+						is->compressor->free(is->comp_stat);
+					is->comp_stat = stat;
+                                	is->compressor = ipc;
+				}
+			}
+                        else {
+				if(data->flags & IPPP_COMP_FLAG_LINK) {
+					if(is->link_decomp_stat)
+						is->link_decompressor->free(is->link_decomp_stat);
+					is->link_decomp_stat = stat;
+        	                        is->link_decompressor = ipc;
+				}
+				else {
+					if(is->decomp_stat)
+						is->decompressor->free(is->decomp_stat);
+					is->decomp_stat = stat;
+        	                        is->decompressor = ipc;
+				}
+			}
+			return 0;
+		}
+		ipc = ipc->next;
+	}
+	return -EINVAL;
+}