OpenCores
URL https://opencores.org/ocsvn/or1k/or1k/trunk

Subversion Repositories or1k

[/] [or1k/] [trunk/] [rc203soc/] [sw/] [uClinux/] [drivers/] [isdn/] [isdn_ppp.c] - Rev 1777

Go to most recent revision | Compare with Previous | Blame | View Log

/* $Id: isdn_ppp.c,v 1.1 2005-12-20 10:16:56 jcastillo Exp $
 *
 * Linux ISDN subsystem, functions for synchronous PPP (linklevel).
 *
 * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Log: not supported by cvs2svn $
 * Revision 1.1.1.1  2001/09/10 07:44:18  simons
 * Initial import
 *
 * Revision 1.1.1.1  2001/07/02 17:58:31  simons
 * Initial revision
 *
 * Revision 1.28.2.3  1998/12/30 17:49:00  paul
 * fixed syncPPP callback out
 *
 * Revision 1.28.2.2  1998/11/03 14:31:23  fritz
 * Reduced stack usage in various functions.
 * Adapted statemachine to work with certified HiSax.
 * Some fixes in callback handling.
 *
 * Revision 1.28.2.1  1998/03/16 09:56:02  cal
 * Merged in TimRu-patches. Still needs validation in conjunction with ABC-patches.
 *
 * Revision 1.28  1997/06/17 13:05:57  hipp
 * Applied Eric's underflow-patches (slightly modified)
 * more compression changes (but disabled at the moment)
 * changed one copy_to_user() to run with enabled IRQs
 * a few MP changes
 * changed 'proto' handling in the isdn_ppp receive code
 *
 * Revision 1.27  1997/03/30 16:51:17  calle
 * changed calls to copy_from_user/copy_to_user and removed verify_area
 * were possible.
 *
 * Revision 1.26  1997/02/23 16:53:44  hipp
 * minor cleanup
 * some initial changes for future PPP compresion
 * added AC,PC compression for outgoing frames
 *
 * Revision 1.25  1997/02/12 20:37:35  hipp
 * New ioctl() PPPIOCGCALLINFO, minor cleanup
 *
 * Revision 1.24  1997/02/11 18:32:56  fritz
 * Bugfix in isdn_ppp_free_mpqueue().
 *
 * Revision 1.23  1997/02/10 11:12:19  fritz
 * More changes for Kernel 2.1.X compatibility.
 *
 * Revision 1.22  1997/02/06 15:03:51  hipp
 * changed GFP_KERNEL kmalloc to GFP_ATOMIC in isdn_ppp_fill_mpqueue()
 *
 * Revision 1.21  1997/02/03 23:29:38  fritz
 * Reformatted according CodingStyle
 * Bugfix: removed isdn_ppp_skb_destructor, used by upper layers.
 * Misc changes for Kernel 2.1.X compatibility.
 *
 * Revision 1.20  1996/10/30 12:21:58  fritz
 * Cosmetic fix: Compiler warning when compiling without MPP.
 *
 * Revision 1.19  1996/10/25 19:03:21  hipp
 * changed/added some defines to (re)allow compilation without MP/VJ
 *
 * Revision 1.18  1996/10/22 23:14:00  fritz
 * Changes for compatibility to 2.0.X and 2.1.X kernels.
 *
 * Revision 1.17  1996/10/22 09:39:49  hipp
 * a few MP changes and bugfixes
 *
 * Revision 1.16  1996/09/23 01:58:10  fritz
 * Fix: With syncPPP encapsulation, discard LCP packets
 *      when calculating hangup timeout.
 *
 * Revision 1.15  1996/09/07 12:50:12  hipp
 * bugfixes (unknown device after failed dial attempt, minor bugs)
 *
 * Revision 1.14  1996/08/12 16:26:47  hipp
 * code cleanup
 * changed connection management from minors to slots
 *
 * Revision 1.13  1996/07/01 19:47:24  hipp
 * Fixed memory leak in VJ handling and more VJ changes
 *
 * Revision 1.12  1996/06/24 17:42:03  fritz
 * Minor bugfixes.
 *
 * Revision 1.11  1996/06/16 17:46:05  tsbogend
 * changed unsigned long to u32 to make Alpha people happy
 *
 * Revision 1.10  1996/06/11 14:50:29  hipp
 * Lot of changes and bugfixes.
 * New scheme to resend packets to busy LL devices.
 *
 * Revision 1.9  1996/05/18 01:37:01  fritz
 * Added spelling corrections and some minor changes
 * to stay in sync with kernel.
 *
 * Revision 1.8  1996/05/06 11:34:55  hipp
 * fixed a few bugs
 *
 * Revision 1.7  1996/04/30 11:07:42  fritz
 * Added Michael's ippp-bind patch.
 *
 * Revision 1.6  1996/04/30 09:33:09  fritz
 * Removed compatibility-macros.
 *
 * Revision 1.5  1996/04/20 16:32:32  fritz
 * Changed ippp_table to an array of pointers, allocating each part
 * separately.
 *
 * Revision 1.4  1996/02/19 15:25:50  fritz
 * Bugfix: Sync-PPP packets got compressed twice, when resent due to
 * send-queue-full reject.
 *
 * Revision 1.3  1996/02/11 02:27:12  fritz
 * Lot of Bugfixes my Michael.
 * Moved calls to skb_push() into isdn_net_header()
 * Fixed a possible race-condition in isdn_ppp_timer_timeout().
 *
 * Revision 1.2  1996/01/22 05:08:06  fritz
 * Merged in Michael's patches for MP.
 * Minor changes in isdn_ppp_xmit.
 *
 * Revision 1.1  1996/01/09 04:11:29  fritz
 * Initial revision
 *
 */
 
/* TODO: right tbusy handling when using MP */
 
/*
 * experimental for dynamic addressing: readdress IP frames
 */
#undef ISDN_SYNCPPP_READDRESS
 
#include <linux/config.h>
#define __NO_VERSION__
#include <linux/module.h>
#include <linux/version.h>
#include <linux/isdn.h>
#if (LINUX_VERSION_CODE >= 0x020117)
#include <asm/poll.h>
#endif
#include "isdn_common.h"
#include "isdn_ppp.h"
#include "isdn_net.h"
 
#ifndef PPP_IPX
#define PPP_IPX 0x002b
#endif
 
/* set this if you use dynamic addressing */
 
/* 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,int num);
static struct sk_buff *isdn_ppp_decompress(struct sk_buff *,
				struct ippp_struct *,struct ippp_struct *);
static void isdn_ppp_receive_ccp(isdn_net_dev * net_dev, isdn_net_local * lp,
				struct sk_buff *skb);
static struct sk_buff *isdn_ppp_compress(struct sk_buff *skb_in,int *proto,
	struct ippp_struct *is,struct ippp_struct *master,int type);
 
#ifdef CONFIG_ISDN_MPP
static int isdn_ppp_bundle(struct ippp_struct *, int unit);
static void isdn_ppp_mask_queue(isdn_net_dev * dev, long mask);
static void isdn_ppp_cleanup_mpqueue(isdn_net_dev * dev, long min);
static void isdn_ppp_cleanup_sqqueue(isdn_net_dev * dev, isdn_net_local *, long min);
static void isdn_ppp_free_sqqueue(isdn_net_dev *);
static int isdn_ppp_fill_mpqueue(isdn_net_dev *, struct sk_buff **skb,
				 int BEbyte, long *sqno, int min_sqno);
static void isdn_ppp_free_mpqueue(isdn_net_dev *);
#endif
 
char *isdn_ppp_revision = "$Revision: 1.1 $";
 
static struct ippp_struct *ippp_table[ISDN_MAX_CHANNELS];
static struct isdn_ppp_compressor *ipc_head = NULL;
 
extern int isdn_net_force_dial_lp(isdn_net_local *);
 
/*
 * frame log (debug)
 */
static void
isdn_ppp_frame_log(char *info, char *data, int len, int maxlen)
{
	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 "%s[%d]: %s\n", info, i, buf);
	}
}
 
/*
 * unbind isdn_net_local <=> ippp-device
 * note: it can happen, that we hangup/free the master before the slaves
 */
int
isdn_ppp_free(isdn_net_local * lp)
{
#ifdef CONFIG_ISDN_MPP
	isdn_net_local *master_lp = lp;
#endif
	unsigned long flags;
	struct ippp_struct *is;
 
	if (lp->ppp_slot < 0 || lp->ppp_slot > ISDN_MAX_CHANNELS)
		return 0;
 
	is = ippp_table[lp->ppp_slot];
 
	save_flags(flags);
	cli();
#ifdef CONFIG_ISDN_MPP
	if (lp->master)
		master_lp = (isdn_net_local *) lp->master->priv;
 
	lp->last->next = lp->next;
	lp->next->last = lp->last;
	if (master_lp->netdev->queue == lp) {
		master_lp->netdev->queue = lp->next;
		if (lp->next == lp) {	/* last link in queue? */
			master_lp->netdev->ib.bundled = 0;
			isdn_ppp_free_mpqueue(master_lp->netdev);
			isdn_ppp_free_sqqueue(master_lp->netdev);
		}
	}
	lp->next = lp->last = lp;	/* (re)set own pointers */
#endif
 
	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 ASSIGEND' staet */
 
 
	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 */
#ifdef ISDN_SYNCPPP_READDRESS
	is->old_pa_addr = 0x0;
	is->old_pa_dstaddr = 0x0;
#endif
	lp->ppp_slot = -1;      /* is this OK ?? */
	restore_flags(flags);
 
	return 0;
}
 
/*
 * bind isdn_net_local <=> ippp-device
 */
int
isdn_ppp_bind(isdn_net_local * lp)
{
	int i;
	int unit = 0;
	long flags;
	struct ippp_struct *is;
 
	if (lp->p_encap != ISDN_NET_ENCAP_SYNCPPP)
		return -1;
 
	save_flags(flags);
	cli();
 
	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) {
		restore_flags(flags);
		printk(KERN_WARNING "isdn_ppp_bind: Can't find usable ippp device.\n");
		return -1;
	}
	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);
		return -1;
	}
	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 */
 
	restore_flags(flags);
 
	return lp->ppp_slot;
}
 
/*
 * 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)
		return;
 
	ippp_table[lp->ppp_slot]->state = IPPP_OPEN | IPPP_CONNECT | IPPP_NOBLOCK;
 
	if (ippp_table[lp->ppp_slot]->wq)
		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)
		return 0;
	is = ippp_table[slot];
 
	if (is->state && is->wq)
		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];
 
	if (is->debug & 0x1)
		printk(KERN_DEBUG "ippp, open, slot: %d, minor: %d, state: %04x\n", slot, min, is->state);
 
	/* compression stuff */
	is->compressor = NULL;
	is->decomp_stat = is->comp_stat = NULL;
	is->link_compressor = NULL;
	is->link_decomp_stat = is->link_comp_stat = NULL;
 
	is->lp = NULL;
	is->mp_seqno = 0;       /* MP sequence number */
	is->pppcfg = 0;         /* ppp configuration */
	is->mpppcfg = 0;        /* mppp configuration */
	is->range = 0x1000000;  /* MP: 24 bit range */
	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;
	is->wq = NULL;          /* read() wait queue */
	is->wq1 = NULL;         /* select() wait queue */
	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
 
	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->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;
 
		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
	slhc_free(is->slcomp);
	is->slcomp = NULL;
#endif
 
	is->state = 0;
}
 
/*
 * get_arg .. ioctl helper
 */
static int
get_arg(void *b, void *val, int len)
{
	int r;
	if (len <= 0)
		len = sizeof(unsigned long);
	if ((r = copy_from_user((void *) val, b, len)))
		return r;
	return 0;
}
 
/*
 * set arg .. ioctl helper
 */
static int
set_arg(void *b, unsigned long val, void *str)
{
	int r;
	if (!str) {
		if ((r = copy_to_user(b, (void *) &val, 4)))
			return r;
	} else {
		if ((r = copy_to_user(b, str, val)))
			return r;
	}
	return 0;
}
 
/*
 * ippp device ioctl
 */
int
isdn_ppp_ioctl(int min, struct file *file, unsigned int cmd, unsigned long arg)
{
	unsigned long val;
	int num,r;
	struct ippp_struct *is;
	isdn_net_local *lp;
 
	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((void *) arg, &val, 0)))
				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((void *) arg, is->unit, NULL)))
				return r;
			break;
		case PPPIOCGMPFLAGS:	/* get configuration flags */
			if ((r = set_arg((void *) arg, is->mpppcfg, NULL)))
				return r;
			break;
		case PPPIOCSMPFLAGS:	/* set configuration flags */
			if ((r = get_arg((void *) arg, &val, 0)))
				return r;
			is->mpppcfg = val;
			break;
		case PPPIOCGFLAGS:	/* get configuration flags */
			if ((r = set_arg((void *) arg, is->pppcfg, NULL)))
				return r;
			break;
		case PPPIOCSFLAGS:	/* set configuration flags */
			if ((r = get_arg((void *) arg, &val, 0))) {
				return r;
			}
			if (val & SC_ENABLE_IP && !(is->pppcfg & SC_ENABLE_IP) && (is->state & IPPP_CONNECT)) {
				if (lp) {
					lp->netdev->dev.tbusy = 0;
					mark_bh(NET_BH);	/* OK .. we are ready to send buffers */
				}
			}
			is->pppcfg = val;
			break;
#if 0
		case PPPIOCGSTAT:	/* read PPP statistic information */
			break;
#endif
		case PPPIOCGIDLE:	/* get idle time information */
			if (lp) {
				struct ppp_idle pidle;
				pidle.xmit_idle = pidle.recv_idle = lp->huptimer;
				if ((r = set_arg((void *) arg, sizeof(struct ppp_idle), &pidle)))
					 return r;
			}
			break;
		case PPPIOCSMRU:	/* set receive unit size for PPP */
			if ((r = get_arg((void *) arg, &val, 0)))
				return r;
			is->mru = val;
			break;
		case PPPIOCSMPMRU:
			break;
		case PPPIOCSMPMTU:
			break;
		case PPPIOCSMAXCID:	/* set the maximum compression slot id */
			if ((r = get_arg((void *) arg, &val, 0)))
				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((void *) arg, is->debug, 0)))
				return r;
			break;
		case PPPIOCSDEBUG:
			if ((r = get_arg((void *) arg, &val, 0)))
				return r;
			is->debug = val;
			break;
		case PPPIOCGCOMPRESSORS:
			{
				unsigned long protos = 0;
				struct isdn_ppp_compressor *ipc = ipc_head;
				while(ipc) {
					protos |= (0x1<<ipc->num);
					ipc = ipc->next;
				}
				if ((r = set_arg((void *) arg, protos, 0)))
					return r;
			}
			break;
		case PPPIOCSCOMPRESSOR:
			if ((r = get_arg((void *) arg, &num, sizeof(int))))
				return r;
			return isdn_ppp_set_compressor(is, num);
			break;
		case PPPIOCGCALLINFO:
			{
				struct pppcallinfo *pci;
 
				if ((pci = (struct pppcallinfo *)kmalloc(sizeof(struct pppcallinfo), GFP_KERNEL)) == NULL)
					return -ENOMEM;
				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;
				}
				r = set_arg((void *)arg,sizeof(struct pppcallinfo),pci);
				kfree(pci);
				return r;
			}
		default:
			break;
	}
	return 0;
}
 
#if (LINUX_VERSION_CODE < 0x020117)
int
isdn_ppp_select(int min, struct file *file, int type, select_table * st)
{
	struct ippp_buf_queue *bf,
	*bl;
	unsigned long flags;
	struct ippp_struct *is;
 
	is = file->private_data;
 
	if (is->debug & 0x2)
		printk(KERN_DEBUG "isdn_ppp_select: minor: %d, type: %d \n", min, type);
 
	if (!(is->state & IPPP_OPEN))
		return -EINVAL;
 
	switch (type) {
		case SEL_IN:
			save_flags(flags);
			cli();
			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)) {
				select_wait(&is->wq, st);
				restore_flags(flags);
				return 0;
			}
			is->state &= ~IPPP_NOBLOCK;
			restore_flags(flags);
			return 1;
		case SEL_OUT:
			/* we're always ready to send .. */
			return 1;
		case SEL_EX:
			select_wait(&is->wq1, st);
			return 0;
	}
	return 1;
}
#else
unsigned int
isdn_ppp_poll(struct file *file, poll_table * wait)
{
	unsigned int mask;
	struct ippp_buf_queue *bf;
	struct ippp_buf_queue *bl;
	unsigned 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_inode->i_rdev));
 
	poll_wait(&is->wq, wait);
 
	if (!(is->state & IPPP_OPEN)) {
		printk(KERN_DEBUG "isdn_ppp: device not open\n");
		return POLLERR;
	}
	/* we're always ready to send .. */
	mask = POLLOUT | POLLWRNORM;
 
	save_flags(flags);
	cli();
	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;
	}
	restore_flags(flags);
	return mask;
}
#endif
 
 
/*
 *  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;
	unsigned long flags;
	unsigned char *nbuf;
	struct ippp_struct *is;
 
	if (slot < 0 || slot >= ISDN_MAX_CHANNELS) {
		printk(KERN_WARNING "ippp: illegal slot.\n");
		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);
 
	save_flags(flags);
	cli();
 
	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;
	restore_flags(flags);
 
	if (is->wq)
		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 *buf, int count)
{
	struct ippp_struct *is;
	struct ippp_buf_queue *b;
	int r;
	unsigned long flags;
	unsigned char *save_buf;
 
	is = file->private_data;
 
	if (!(is->state & IPPP_OPEN))
		return 0;
 
	if ((r = verify_area(VERIFY_WRITE, (void *) buf, count)))
		return r;
 
	save_flags(flags);
	cli();
 
	b = is->first->next;
	save_buf = b->buf;
	if (!save_buf) {
		restore_flags(flags);
		return -EAGAIN;
	}
	if (b->len < count)
		count = b->len;
	b->buf = NULL;
	is->first = b;
 
	restore_flags(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 *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)) {
			int cnt;
			struct sk_buff *skb;
			skb = dev_alloc_skb(count);
			if (!skb) {
				printk(KERN_WARNING "isdn_ppp_write: out of memory!\n");
				return count;
			}
			SET_SKB_FREE(skb);
			if (copy_from_user(skb_put(skb, count), buf, count))
				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);
			}
 
			if ((cnt = isdn_writebuf_skb_stub(lp->isdn_device, lp->isdn_channel, skb)) != count) {
				if (lp->sav_skb) {
					dev_kfree_skb(lp->sav_skb, FREE_WRITE);
					printk(KERN_INFO "isdn_ppp_write: freeing sav_skb (%d,%d)!\n", cnt, count);
				} else
					printk(KERN_INFO "isdn_ppp_write: Can't write PPP frame to LL (%d,%d)!\n", cnt, count);
				lp->sav_skb = skb;
			}
		}
	}
	return count;
}
 
/*
 * init memory, structures etc.
 */
 
int
isdn_ppp_init(void)
{
	int i,
	 j;
 
	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[i]);
			return -1;
		}
		memset((char *) ippp_table[i], 0, sizeof(struct ippp_struct));
		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]);
}
 
/*
 * get the PPP protocol header and pull skb
 */
static int isdn_ppp_strip_proto(struct sk_buff *skb) 
{
	int proto;
	if (skb->data[0] & 0x1) {
		proto = skb->data[0];
		skb_pull(skb, 1);   /* protocol ID is only 8 bit */
	} else {
		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 proto;
 
	is = ippp_table[lp->ppp_slot];
 
	if (is->debug & 0x4) {
		printk(KERN_DEBUG "ippp_receive: len: %d\n", (int) skb->len);
		isdn_ppp_frame_log("receive", skb->data, skb->len, 32);
	}
	if (net_dev->local.master) {
		printk(KERN_WARNING "isdn_ppp_receice: net_dev != master\n");
		net_dev = ((isdn_net_local *) net_dev->local.master->priv)->netdev;
	}
	if (skb->data[0] == 0xff && skb->data[1] == 0x03)
		skb_pull(skb, 2);
	else if (is->pppcfg & SC_REJ_COMP_AC) {
		SET_SKB_FREE(skb);
		dev_kfree_skb(skb, 0 /* FREE_READ */ );
		return;         /* discard it silently */
	}
 
	proto = isdn_ppp_strip_proto(skb);
 
#ifdef CONFIG_ISDN_MPP
	if (!(is->mpppcfg & SC_REJ_MP_PROT)) {
		int sqno_end;
 
		if(proto == PPP_LINK_COMP) {
			printk(KERN_DEBUG "received single link compressed frame\n");
			skb = isdn_ppp_decompress(skb,is,NULL);
			if(!skb)
				return;
			proto = isdn_ppp_strip_proto(skb);
		}
 
		if (proto == PPP_MP) {
			isdn_net_local *lpq;
			long sqno, min_sqno, tseq;
 
			u_char BEbyte = skb->data[0];
			if (is->debug & 0x8)
				printk(KERN_DEBUG "recv: %d/%04x/%d -> %02x %02x %02x %02x %02x %02x\n", lp->ppp_slot, proto,
				       (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]);
			if (!(is->mpppcfg & SC_IN_SHORT_SEQ)) {
				sqno = ((int) skb->data[1] << 16) + ((int) skb->data[2] << 8) + (int) skb->data[3];
				skb_pull(skb, 4);
			} else {
				sqno = (((int) skb->data[0] & 0xf) << 8) + (int) skb->data[1];
				skb_pull(skb, 2);
			}
 
			/*
			 * new sequence number lower than last number? (this is only allowed
			 * for overflow case)
			 */
			if ((tseq = is->last_link_seqno) >= sqno) {
				int range = is->range;
				if (tseq + 1024 < range + sqno)	/* redundancy check .. not MP conform */
					printk(KERN_WARNING "isdn_ppp_receive, MP, detected overflow with sqno: %ld, last: %ld !!!\n", sqno, tseq);
				else {
					sqno += range;
					is->last_link_seqno = sqno;
				}
			} else {
				/* here, we should also add an redundancy check */
				is->last_link_seqno = sqno;
			}
 
			/* 
			 * step over all links to find lowest link number
			 */
			for (min_sqno = LONG_MAX, lpq = net_dev->queue;;) {
				long lls = ippp_table[lpq->ppp_slot]->last_link_seqno;
				if (lls >= 0 && lls < min_sqno)
					min_sqno = lls;
				lpq = lpq->next;
				if (lpq == net_dev->queue)
					break;
			}
 
			/*
			 * for the case, that the last frame numbers of all 
			 * links are overflowed: mask/reduce the sequenece number to
			 * 'normal' numbering.
			 */
			if (min_sqno >= ippp_table[lpq->ppp_slot]->range) {
				int mask = ippp_table[lpq->ppp_slot]->range-1;	/* range is power of two, so a mask will do the job */
				isdn_ppp_mask_queue(net_dev, mask);
				net_dev->ib.next_num &= mask;
				{
					struct sqqueue *q = net_dev->ib.sq;
					while (q) {
						q->sqno_start &= mask;
						q->sqno_end &= mask;
					}
				}
				min_sqno &= mask;
				for (lpq = net_dev->queue;;) {
					ippp_table[lpq->ppp_slot]->last_link_seqno &= mask;
					lpq = lpq->next;
					if (lpq == net_dev->queue)
						break;
				}
			}
			if ((BEbyte & (MP_BEGIN_FRAG | MP_END_FRAG)) != (MP_BEGIN_FRAG | MP_END_FRAG)) {
				static int dmes = 0;
				if( !dmes ) {
					printk(KERN_DEBUG "ippp: trying ;) to fill mp_queue %d .. UNTESTED!!\n", lp->ppp_slot);
					dmes = 1;
				}
				if ((sqno_end = isdn_ppp_fill_mpqueue(net_dev, &skb, BEbyte, &sqno, min_sqno)) < 0) {
					net_dev->ib.modify = 1;	/* block timeout-timer */
					isdn_ppp_cleanup_sqqueue(net_dev, lp, min_sqno);
					net_dev->ib.modify = 0;
					return;	/* no packet complete */
				}
			} else
				sqno_end = sqno;
 
			if (is->debug & 0x40)
				printk(KERN_DEBUG "min_sqno: %ld sqno_end %d next: %ld\n", min_sqno, sqno_end, net_dev->ib.next_num);
 
			/*
			 * MP buffer management .. reorders incoming packets ..
			 * lotsa mem-copies and not heavily tested.
			 *
			 * first check whether there is more than one link in the bundle
			 * then check whether the number is in order
			 */
			net_dev->ib.modify = 1;	/* block timeout-timer */
			if (net_dev->ib.bundled && net_dev->ib.next_num != sqno) {
				/*
				 * packet is not 'in order'
				 */
				struct sqqueue *q;
 
				q = (struct sqqueue *) kmalloc(sizeof(struct sqqueue), GFP_ATOMIC);
				if (!q) {
					net_dev->ib.modify = 0;
					printk(KERN_WARNING "ippp/MPPP: Bad! Can't alloc sq node!\n");
					SET_SKB_FREE(skb);
					dev_kfree_skb(skb, 0 /* FREE_READ */ );
					return;	/* discard */
				}
				q->skb = skb;
				q->sqno_end = sqno_end;
				q->sqno_start = sqno;
				q->timer = jiffies + (ISDN_TIMER_1SEC) * 5;	/* timeout after 5 seconds */
 
				if (!net_dev->ib.sq) {
					net_dev->ib.sq = q;
					q->next = NULL;
				} else {
					struct sqqueue *ql = net_dev->ib.sq;
					if (ql->sqno_start > q->sqno_start) {
						q->next = ql;
						net_dev->ib.sq = q;
					} else {
						while (ql->next && ql->next->sqno_start < q->sqno_start)
							ql = ql->next;
						q->next = ql->next;
						ql->next = q;
					}
				}
			} else {
				/*
				 * packet was 'in order' .. push it higher
				 */
				net_dev->ib.next_num = sqno_end + 1;
				proto = isdn_ppp_strip_proto(skb);
				isdn_ppp_push_higher(net_dev, lp, skb, proto);
			}
			isdn_ppp_cleanup_sqqueue(net_dev, lp, min_sqno);
			net_dev->ib.modify = 0;
 
		} else
			isdn_ppp_push_higher(net_dev, lp, skb, proto);
	} else
#endif
		isdn_ppp_push_higher(net_dev, lp, skb, proto);
}
 
/*
 * push frame to higher layers
 * 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 device *dev = &net_dev->dev;
	struct ippp_struct *is = ippp_table[lp->ppp_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);
	}
 
	if(proto == PPP_COMP) {
		if(!lp->master)
			skb = isdn_ppp_decompress(skb,is,is);
		else
			skb = isdn_ppp_decompress(skb,is,ippp_table[((isdn_net_local *) (lp->master->priv))->ppp_slot]);
		if(!skb)
			return;
		proto = isdn_ppp_strip_proto(skb);
	}
 
	switch (proto) {
		case PPP_IPX:  /* untested */
			if (is->debug & 0x20)
				printk(KERN_DEBUG "isdn_ppp: IPX\n");
			skb->dev = dev;
			skb->mac.raw = skb->data;
			skb->protocol = htons(ETH_P_IPX);
			break;
#ifdef CONFIG_ISDN_PPP_VJ
		case PPP_VJC_UNCOMP:
			if (is->debug & 0x20)
				printk(KERN_DEBUG "isdn_ppp: VJC_UNCOMP\n");
			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");
				net_dev->local.stats.rx_dropped++;
				SET_SKB_FREE(skb);
				dev_kfree_skb(skb, 0 /* FREE_READ */ );
				return;
			}
#endif
		case PPP_IP:
			if (is->debug & 0x20)
				printk(KERN_DEBUG "isdn_ppp: IP\n");
			skb->dev = dev;
			skb->mac.raw = skb->data;
			skb->protocol = htons(ETH_P_IP);
			break;
		case PPP_VJC_COMP:
			if (is->debug & 0x20)
				printk(KERN_DEBUG "isdn_ppp: VJC_COMP\n");
#ifdef CONFIG_ISDN_PPP_VJ
			{
				struct sk_buff *skb_old = skb;
				int pkt_len;
				skb = dev_alloc_skb(skb_old->len + 40);
 
				SET_SKB_FREE(skb_old);
 
				if (!skb) {
					printk(KERN_WARNING "%s: Memory squeeze, dropping packet.\n", dev->name);
					net_dev->local.stats.rx_dropped++;
					dev_kfree_skb(skb_old, 0 /* FREE_READ */ );
					return;
				}
				skb->dev = dev;
				skb_put(skb, skb_old->len + 40);
				memcpy(skb->data, skb_old->data, skb_old->len);
				skb->mac.raw = skb->data;
				pkt_len = slhc_uncompress(ippp_table[net_dev->local.ppp_slot]->slcomp,
						skb->data, skb_old->len);
				dev_kfree_skb(skb_old, 0 /* FREE_READ */ );
				if (pkt_len < 0) {
					SET_SKB_FREE(skb);
					dev_kfree_skb(skb, 0 /* FREE_READ */ );
					lp->stats.rx_dropped++;
					return;
				}
				skb_trim(skb, pkt_len);
				skb->protocol = htons(ETH_P_IP);
			}
#else
			printk(KERN_INFO "isdn: Ooopsa .. VJ-Compression support not compiled into isdn driver.\n");
			lp->stats.rx_dropped++;
			SET_SKB_FREE(skb);
			dev_kfree_skb(skb, 0 /* FREE_READ */ );
			return;
#endif
			break;
		case PPP_CCP:
			isdn_ppp_receive_ccp(net_dev,lp,skb);
			/* fall through */
		default:
			isdn_ppp_fill_rq(skb->data, skb->len, proto, lp->ppp_slot);	/* push data to pppd device */
			SET_SKB_FREE(skb);
			dev_kfree_skb(skb, 0 /* FREE_READ */ );
			return;
	}
 
	/* Reset hangup-timer */
	lp->huptimer = 0;
	netif_rx(skb);
	/* net_dev->local.stats.rx_packets++; *//* done in isdn_net.c */
 
	return;
}
 
/*
 * 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) {
		printk(KERN_ERR "isdn_ppp_skb_push:under %d %d\n",skb_headroom(skb),len);
		dev_kfree_skb(skb,FREE_WRITE);
		return NULL;
	}
	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 device *dev)
{
	struct device *mdev = ((isdn_net_local *) (dev->priv))->master;	/* get master (for redundancy) */
	isdn_net_local *lp,*mlp;
	isdn_net_dev *nd;
	unsigned int proto = PPP_IP;     /* 0x21 */
	struct ippp_struct *ipt,*ipts;
 
	if (mdev)
		mlp = (isdn_net_local *) (mdev->priv);
	else {
		mdev = dev;
		mlp = (isdn_net_local *) (dev->priv);
	}
	nd = mlp->netdev;       /* get master lp */
	ipts = ippp_table[mlp->ppp_slot];
 
	if (!(ipts->pppcfg & SC_ENABLE_IP)) {	/* PPP connected ? */
#ifdef ISDN_SYNCPPP_READDRESS
		if (!ipts->old_pa_addr)
			ipts->old_pa_addr = mdev->pa_addr;
		if (!ipts->old_pa_dstaddr)
			ipts->old_pa_dstaddr = mdev->pa_dstaddr;
#endif
		if (ipts->debug & 0x1)
			printk(KERN_INFO "%s: IP frame delayed.\n", dev->name);
		return 1;
	}
 
	switch (ntohs(skb->protocol)) {
		case ETH_P_IP:
			proto = PPP_IP;
#ifdef ISDN_SYNCPPP_READDRESS
			if (ipts->old_pa_addr != mdev->pa_addr) {
				struct iphdr *ipfr;
				ipfr = (struct iphdr *) skb->data;
				if(ipts->debug & 0x4)
					printk(KERN_DEBUG "IF-address changed from %lx to %lx\n", ipts->old_pa_addr, mdev->pa_addr);
				if (ipfr->version == 4) {
					if (ipfr->saddr == ipts->old_pa_addr) {
						printk(KERN_DEBUG "readdressing %lx to %lx\n", ipfr->saddr, mdev->pa_addr);
						ipfr->saddr = mdev->pa_addr;
					}
				}
			}
			/* dstaddr change not so important */
#endif
			break;
		case ETH_P_IPX:
			proto = PPP_IPX;	/* untested */
			break;
		default:
			dev_kfree_skb(skb, FREE_WRITE);
			printk(KERN_ERR "isdn_ppp: skipped frame with unsupported protocoll: %#x.\n", skb->protocol);
			return 0;
	}
 
	lp = nd->queue;         /* get lp on top of queue */
 
	if (lp->sav_skb) {      /* find a non-busy device */
		isdn_net_local *nlp = lp->next;
		while (lp->sav_skb) {
			if (lp == nlp)
				return 1;
			nlp = nd->queue = nd->queue->next;
		}
		lp = nlp;
	}
	ipt = ippp_table[lp->ppp_slot];
 
	lp->huptimer = 0;
 
	/*
	 * after this line .. requeueing in the device queue is no longer allowed!!!
	 */
 
	/* Pull off the fake header we stuck on earlier to keep
	 * the fragemntation code happy.
	 * this will break the ISDN_SYNCPPP_READDRESS hack a few lines
	 * above. So, enabling this is no longer allowed
	 */
	skb_pull(skb,IPPP_MAX_HEADER);
 
	if (ipt->debug & 0x4)
		printk(KERN_DEBUG "xmit skb, len %d\n", (int) skb->len);
 
#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;
 
		new_skb = dev_alloc_skb(skb->len);
		if (new_skb) {
			u_char *buf;
			int pktlen;
 
			new_skb->dev = skb->dev;
			SET_SKB_FREE(new_skb);
			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, FREE_WRITE);
				skb = new_skb;
			} else {
				dev_kfree_skb(new_skb, 0 /* FREE_WRITE */ );
			}
 
			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 or bundle compression
	 */
	skb = isdn_ppp_compress(skb,&proto,ipt,ipts,0);
 
	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++;
		nd->queue = nd->queue->next;
		if (ipt->mpppcfg & SC_OUT_SHORT_SEQ) {
			unsigned char *data = isdn_ppp_skb_push(&skb, 3);
			if(!data)
				return 0;
			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)
				return 0;
			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' compression 
	 */
	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)
			return 0;
		data[0] = proto & 0xff;
	}
	else {
		unsigned char *data = isdn_ppp_skb_push(&skb,2);
		if(!data)
			return 0;
		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)
			return 0;
		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);
	}
	if (isdn_net_send_skb(dev, lp, skb)) {
		if (lp->sav_skb) {	/* whole sav_skb processing with disabled IRQs ?? */
			printk(KERN_ERR "%s: whoops .. there is another stored skb!\n", dev->name);
			dev_kfree_skb(skb, FREE_WRITE);
		} else
			lp->sav_skb = skb;
	}
	return 0;
}
 
#ifdef CONFIG_ISDN_MPP
 
static void
isdn_ppp_free_sqqueue(isdn_net_dev * p)
{
	struct sqqueue *q = p->ib.sq;
 
	p->ib.sq = NULL;
	while (q) {
		struct sqqueue *qn = q->next;
		if (q->skb) {
			SET_SKB_FREE(q->skb);
			dev_kfree_skb(q->skb, 0 /* FREE_READ */ );
		}
		kfree(q);
		q = qn;
	}
 
}
 
static void 
isdn_ppp_free_mpqueue(isdn_net_dev * p)
{
	struct mpqueue *q = p->mp_last;
	p->mp_last = NULL;
 
	while (q) {
		struct mpqueue *ql = q->next;
		SET_SKB_FREE(q->skb);
		dev_kfree_skb(q->skb, 0 /* FREE_READ */ );
		kfree(q);
		q = ql;
	}
}
 
static int
isdn_ppp_bundle(struct ippp_struct *is, int unit)
{
	char ifn[IFNAMSIZ + 1];
	long flags;
	isdn_net_dev *p;
	isdn_net_local *lp,
	*nlp;
 
	sprintf(ifn, "ippp%d", unit);
	p = isdn_net_findif(ifn);
	if (!p)
		return -1;
 
	isdn_timer_ctrl(ISDN_TIMER_IPPP, 1);	/* enable timer for ippp/MP */
 
	save_flags(flags);
	cli();
 
	nlp = is->lp;
 
	lp = p->queue;
	p->ib.bundled = 1;
	nlp->last = lp->last;
	lp->last->next = nlp;
	lp->last = nlp;
	nlp->next = lp;
	p->queue = 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);
#if 0
	if (ippp_table[nlp->ppp_slot]->mpppcfg != ippp_table[lp->ppp_slot]->mpppcfg) {
		printk(KERN_WARNING "isdn_ppp_bundle: different MP options %04x and %04x\n",
		       ippp_table[nlp->ppp_slot]->mpppcfg, ippp_table[lp->ppp_slot]->mpppcfg);
	}
#endif
 
	restore_flags(flags);
	return 0;
}
 
 
static void
isdn_ppp_mask_queue(isdn_net_dev * dev, long mask)
{
	struct mpqueue *q = dev->mp_last;
	while (q) {
		q->sqno &= mask;
		q = q->next;
	}
}
 
static int
isdn_ppp_fill_mpqueue(isdn_net_dev * dev, struct sk_buff **skb, int BEbyte, long *sqnop, int min_sqno)
{
	struct mpqueue *qe,
	*q1,
	*q;
	long cnt,
	 flags;
	int pktlen,
	 sqno_end;
	int sqno = *sqnop;
 
	q1 = (struct mpqueue *) kmalloc(sizeof(struct mpqueue), GFP_ATOMIC);
	if (!q1) {
		printk(KERN_WARNING "isdn_ppp_fill_mpqueue: Can't alloc struct memory.\n");
		save_flags(flags);
		cli();
		isdn_ppp_cleanup_mpqueue(dev, min_sqno);
		restore_flags(flags);
		return -1;
	}
	q1->skb = *skb;
	q1->sqno = sqno;
	q1->BEbyte = BEbyte;
	q1->time = jiffies;
 
	save_flags(flags);
	cli();
 
	if (!(q = dev->mp_last)) {
		dev->mp_last = q1;
		q1->next = NULL;
		q1->last = NULL;
		isdn_ppp_cleanup_mpqueue(dev, min_sqno);	/* not necessary */
		restore_flags(flags);
		return -1;	/* -1 is not an error. Just says, that this fragment hasn't complete a full frame */
	}
	for (;;) {              /* the faster way would be to step from the queue-end to the start */
		if (sqno > q->sqno) {
			if (q->next) {
				q = q->next;
				continue;
			}
			q->next = q1;
			q1->next = NULL;
			q1->last = q;
			break;
		}
		if (sqno == q->sqno)
			printk(KERN_WARNING "isdn_fill_mpqueue: illegal sqno received!!\n");
		q1->last = q->last;
		q1->next = q;
		if (q->last) {
			q->last->next = q1;
		} else
			dev->mp_last = q1;
		q->last = q1;
		break;
	}
 
/* now we check whether we completed a packet with this fragment */
	pktlen = -q1->skb->len;
	q = q1;
	cnt = q1->sqno;
	while (!(q->BEbyte & MP_END_FRAG)) {
		cnt++;
		if (!(q->next) || q->next->sqno != cnt) {
			isdn_ppp_cleanup_mpqueue(dev, min_sqno);
			restore_flags(flags);
			return -1;
		}
		pktlen += q->skb->len;
		q = q->next;
	}
	pktlen += q->skb->len;
	qe = q;
 
	q = q1;
	cnt = q1->sqno;
	while (!(q->BEbyte & MP_BEGIN_FRAG)) {
		cnt--;
		if (!(q->last) || q->last->sqno != cnt) {
			isdn_ppp_cleanup_mpqueue(dev, min_sqno);
			restore_flags(flags);
			return -1;
		}
		pktlen += q->skb->len;
		q = q->last;
	}
	pktlen += q->skb->len;
 
	if (q->last)
		q->last->next = qe->next;
	else
		dev->mp_last = qe->next;
 
	if (qe->next)
		qe->next->last = q->last;
	qe->next = NULL;
	sqno_end = qe->sqno;
	*sqnop = q->sqno;
 
	isdn_ppp_cleanup_mpqueue(dev, min_sqno);
	restore_flags(flags);
 
	*skb = dev_alloc_skb(pktlen + 40);	/* not needed: +40 for VJ compression .. */
 
	if (!(*skb)) {
		while (q) {
			struct mpqueue *ql = q->next;
			SET_SKB_FREE(q->skb);
			dev_kfree_skb(q->skb, 0 /* FREE_READ */ );
			kfree(q);
			q = ql;
		}
		return -2;
	}
	cnt = 0;
	skb_put(*skb, pktlen);
	while (q) {
		struct mpqueue *ql = q->next;
		memcpy((*skb)->data + cnt, q->skb->data, q->skb->len);
		cnt += q->skb->len;
		SET_SKB_FREE(q->skb);
		dev_kfree_skb(q->skb, 0 /* FREE_READ */ );
		kfree(q);
		q = ql;
	}
 
	return sqno_end;
}
 
/*
 * check sq-queue, whether we have still buffered the next packet(s)
 * or packets with a sqno less or equal to min_sqno
 * net_dev: master netdevice , lp: 'real' local connection
 */
static void
isdn_ppp_cleanup_sqqueue(isdn_net_dev * net_dev, isdn_net_local * lp, long min_sqno)
{
	struct sqqueue *q;
 
	while ((q = net_dev->ib.sq) && (q->sqno_start == net_dev->ib.next_num || q->sqno_end <= min_sqno)) {
		int proto;
		if (q->sqno_start != net_dev->ib.next_num) {
			printk(KERN_DEBUG "ippp: MP, stepping over missing frame: %ld\n", net_dev->ib.next_num);
#ifdef CONFIG_ISDN_PPP_VJ
			slhc_toss(ippp_table[net_dev->local.ppp_slot]->slcomp);
#endif
		}
		proto = isdn_ppp_strip_proto(q->skb);
		isdn_ppp_push_higher(net_dev, lp, q->skb, proto);
		net_dev->ib.sq = q->next;
		net_dev->ib.next_num = q->sqno_end + 1;
		kfree(q);
	}
}
 
/*
 * remove stale packets from list
 */
static void
isdn_ppp_cleanup_mpqueue(isdn_net_dev * dev, long min_sqno)
{
#ifdef CONFIG_ISDN_PPP_VJ
	int toss = 0;
#endif
/* z.z einfaches aussortieren gammeliger pakete. Fuer die Zukunft:
   eventuell, solange vorne kein B-paket ist und sqno<=min_sqno: auch rauswerfen
   wenn sqno<min_sqno und Luecken vorhanden sind: auch weg (die koennen nicht mehr gefuellt werden)
   bei paketen groesser min_sqno: ueber mp_mrru: wenn summe ueber pktlen der rumhaengenden Pakete
   groesser als mrru ist: raus damit , Pakete muessen allerdings zusammenhaengen sonst koennte
   ja ein Paket mit B und eins mit E dazwischenpassen */
 
	struct mpqueue *ql,
	*q = dev->mp_last;
	while(q && (q->sqno < min_sqno) ) {
		if ( (q->BEbyte & MP_END_FRAG) || 
			 (q->next && (q->next->sqno <= min_sqno) && (q->next->BEbyte & MP_BEGIN_FRAG)) ) {
			printk(KERN_DEBUG "ippp: freeing stale packet(s), min_sq: %ld!\n",min_sqno);
			if ((dev->mp_last = q->next))
				q->next->last = NULL;
			while (q) {
				ql = q->last;
				SET_SKB_FREE(q->skb);
				printk(KERN_DEBUG "ippp, freeing packet with sqno: %ld\n",q->sqno);
				dev_kfree_skb(q->skb, 0 /* FREE_READ */ );
				kfree(q);
#ifdef CONFIG_ISDN_PPP_VJ
				toss = 1;
#endif
				q = ql;
			}
			q = dev->mp_last;
		} else
			q = q->next;
	}
#ifdef CONFIG_ISDN_PPP_VJ
	/* did we free a stale frame ? */
	if (toss)
		slhc_toss(ippp_table[dev->local.ppp_slot]->slcomp);
#endif
}
 
/*
 * a buffered packet timed-out?
 */
 
#endif
 
void
isdn_ppp_timer_timeout(void)
{
#ifdef CONFIG_ISDN_MPP
	isdn_net_dev *net_dev = dev->netdev;
	struct sqqueue *q,
	*ql = NULL,
	*qn;
 
	while (net_dev) {
		isdn_net_local *lp = &net_dev->local;
		if (net_dev->ib.modify || lp->master) {	/* interface locked or slave? */
			net_dev = net_dev->next;
			continue;
		}
		q = net_dev->ib.sq;
		while (q) {
			if (q->sqno_start == net_dev->ib.next_num || q->timer < jiffies) {
 
#ifdef CONFIG_ISDN_PPP_VJ
				/* did we step over a missing frame ? */
				if (q->sqno_start != net_dev->ib.next_num)
					slhc_toss(ippp_table[lp->ppp_slot]->slcomp);
#endif
 
				ql = net_dev->ib.sq;
				net_dev->ib.sq = q->next;
				net_dev->ib.next_num = q->sqno_end + 1;
				q->next = NULL;
				for (; ql;) {
					int proto = isdn_ppp_strip_proto(ql->skb);
					isdn_ppp_push_higher(net_dev, lp, ql->skb, proto);
					qn = ql->next;
					kfree(ql);
					ql = qn;
				}
				q = net_dev->ib.sq;
			} else
				q = q->next;
		}
		net_dev = net_dev->next;
	}
#endif
}
 
/*
 * network device ioctl handlers
 */
 
static int
isdn_ppp_dev_ioctl_stats(int slot, struct ifreq *ifr, struct device *dev)
{
	struct ppp_stats *res,
	 t;
	isdn_net_local *lp = (isdn_net_local *) dev->priv;
	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_ierrors = lp->stats.rx_errors;
		t.p.ppp_opackets = lp->stats.tx_packets;
		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
	}
	return copy_to_user(res, &t, sizeof(struct ppp_stats));
}
 
int
isdn_ppp_dev_ioctl(struct device *dev, struct ifreq *ifr, int cmd)
{
	int error;
	char *r;
	int len;
	isdn_net_local *lp = (isdn_net_local *) dev->priv;
 
#if 0
	printk(KERN_DEBUG "ippp, dev_ioctl: cmd %#08x , %d \n", cmd, lp->ppp_slot);
#endif
 
	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;
			error = copy_to_user(r, PPP_VERSION, len);
			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 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_force_dial_lp((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 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_hangup(sdev);
	return 0;
#else
	return -1;
#endif
}
 
/*
 * PPP compression stuff
 */
static struct sk_buff *isdn_ppp_decompress(struct sk_buff *skb,struct ippp_struct *is,struct ippp_struct *master)
{
#if 1
	printk(KERN_ERR "compression not included!\n");
	SET_SKB_FREE(skb);
	dev_kfree_skb(skb,FREE_WRITE);
	return NULL;
#else
	if(!master) {
		/* 
		 * single link compression 
		 */
		if(!is->link_compressor) {
			printk(KERN_ERR "ippp: no (link) compressor defined!\n");
			SET_SKB_FREE(skb);
			dev_kfree_skb(skb,FREE_WRITE);
			return NULL;
		}
		if(!is->link_decomp_stat) {
			printk(KERN_DEBUG "ippp: initialize link compressor\n");
		}
/*
		-> decompress link
*/
    }
	else {
		/*
		 * 'normal' or bundle-compression 
		 */
		if(!master->compressor) {
			printk(KERN_ERR "ippp: no (link) compressor defined!\n");
			SET_SKB_FREE(skb);
			dev_kfree_skb(skb,FREE_WRITE);
			return NULL;
		}
		if(!master->decomp_stat) {
#if 0
			master->decomp_stat = (master->compressor->decomp_alloc)( .. );
#endif
			printk(KERN_DEBUG "ippp: initialize compressor\n");
		}
	}
 
	return skb;
#endif
}
 
/*
 * 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)
{
#if 1	
	return skb_in;
#else
    int ret;
    int new_proto;
    struct isdn_ppp_compressor *compressor;
    void *stat;
    struct sk_buff *skb_out;
 
	if(type) { /* type=1 => Link compression */
		compressor = is->link_compressor;
		stat = is->link_comp_stat;
		new_proto = PPP_LINK_COMP;
	}
	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 "No compressor set!\n");
		return skb_in;
	}
	if(!stat) {
		/* init here ? */
		return skb_in;
	}
 
	skb_out = dev_alloc_skb(skb_in->len);
	if(!skb_out)
		return skb_in;
 
	ret = (compressor->compress)(stat,skb_in,skb_out,*proto);
	if(!ret) {
		SET_SKB_FREE(skb_out);
		dev_kfree_skb(skb_out,0);
		return skb_in;
	}
 
	dev_kfree_skb(skb_in,FREE_WRITE);
	*proto = new_proto;
	return skb_out;
#endif
 
}
 
/*
 * we received a CCP frame .. 
 * not a clean solution, but we SHOULD handle a few cased in the kernel
 */
static void isdn_ppp_receive_ccp(isdn_net_dev *net_dev, isdn_net_local *lp,
	 struct sk_buff *skb)
{
#if 0
	printk(KERN_DEBUG "isdn_ppp_receive_cpp: %02x %02x %02x %02x %02x %02x %02x %02x\n",
		skb->data[0],skb->data[1],skb->data[2],skb->data[3],
		skb->data[4],skb->data[5],skb->data[6],skb->data[7] );
#endif
}
 
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,int num)
{
	struct isdn_ppp_compressor *ipc = ipc_head;
 
	while(ipc) {
		if(ipc->num == num) {
			return 0;	
			is->compressor = ipc;
			is->link_compressor = ipc;
		}
		ipc = ipc->next;
	}
	return -EINVAL;
}
 
 
#if 0
static struct symbol_table isdn_ppp_syms =
{
#include <linux/symtab_begin.h>
	X(isdn_ppp_register_compressor),
	X(isdn_ppp_unregister_compressor),
#include <linux/symtab_end.h>
};
#endif
 
 
 
 
 

Go to most recent revision | Compare with Previous | Blame | View Log

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.