aboutsummaryrefslogtreecommitdiff
path: root/drivers/isdn/i4l/isdn_ppp.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/isdn/i4l/isdn_ppp.c
Linux-2.6.12-rc2v2.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!
Diffstat (limited to 'drivers/isdn/i4l/isdn_ppp.c')
-rw-r--r--drivers/isdn/i4l/isdn_ppp.c3020
1 files changed, 3020 insertions, 0 deletions
diff --git a/drivers/isdn/i4l/isdn_ppp.c b/drivers/isdn/i4l/isdn_ppp.c
new file mode 100644
index 00000000000..3c092117a8e
--- /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->hupt