1535 lines
39 KiB
C
1535 lines
39 KiB
C
/* $Id$
|
|
*
|
|
* 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$
|
|
* 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 */
|
|
|
|
#include <linux/config.h>
|
|
#define __NO_VERSION__
|
|
#include <linux/module.h>
|
|
#include <linux/isdn.h>
|
|
#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 minor);
|
|
static int isdn_ppp_closewait(int);
|
|
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);
|
|
|
|
#ifdef CONFIG_ISDN_MPP
|
|
static int isdn_ppp_bundle(int, int);
|
|
static void isdn_ppp_mask_queue(isdn_net_dev * dev, long mask);
|
|
static void isdn_ppp_cleanup_queue(isdn_net_dev * dev, long min);
|
|
static int isdn_ppp_fill_mpqueue(isdn_net_dev *, struct sk_buff **skb,
|
|
int BEbyte, int *sqno, int min_sqno);
|
|
#endif
|
|
|
|
char *isdn_ppp_revision = "$Revision$";
|
|
struct ippp_struct *ippp_table[ISDN_MAX_CHANNELS];
|
|
|
|
extern int isdn_net_force_dial_lp(isdn_net_local *);
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
isdn_net_local *master_lp=lp;
|
|
unsigned long flags;
|
|
|
|
if (lp->ppp_minor < 0)
|
|
return 0;
|
|
|
|
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
|
|
|
|
isdn_ppp_closewait(lp->ppp_minor); /* force wakeup on ippp device */
|
|
|
|
if(ippp_table[lp->ppp_minor]->debug & 0x1)
|
|
printk(KERN_DEBUG "isdn_ppp_free %d %lx %lx\n", lp->ppp_minor, (long) lp,(long) ippp_table[lp->ppp_minor]->lp);
|
|
|
|
ippp_table[lp->ppp_minor]->lp = NULL; /* link is down .. set lp to NULL */
|
|
lp->ppp_minor = -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;
|
|
char *name;
|
|
long flags;
|
|
|
|
if (lp->p_encap != ISDN_NET_ENCAP_SYNCPPP)
|
|
return 0;
|
|
|
|
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
|
|
*/
|
|
for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
|
|
if (ippp_table[i]->state == IPPP_OPEN && !exclusive[i]) { /* OPEN, but not connected! */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (ippp_table[lp->pppbind]->state == IPPP_OPEN) /* OPEN, but not connected! */
|
|
i = lp->pppbind;
|
|
else
|
|
i = ISDN_MAX_CHANNELS; /* trigger error */
|
|
}
|
|
|
|
if (i >= ISDN_MAX_CHANNELS) {
|
|
restore_flags(flags);
|
|
printk(KERN_WARNING "isdn_ppp_bind: Can't find usable ippp device.\n");
|
|
return -1;
|
|
}
|
|
lp->ppp_minor = i;
|
|
ippp_table[lp->ppp_minor]->lp = lp;
|
|
|
|
name = lp->name;
|
|
unit = isdn_ppp_if_get_unit(&name); /* get unit number from interface name .. ugly! */
|
|
ippp_table[lp->ppp_minor]->unit = unit;
|
|
|
|
ippp_table[lp->ppp_minor]->state = IPPP_OPEN | IPPP_CONNECT | IPPP_NOBLOCK;
|
|
|
|
restore_flags(flags);
|
|
|
|
/*
|
|
* kick the ipppd on the new device
|
|
*/
|
|
if (ippp_table[lp->ppp_minor]->wq)
|
|
wake_up_interruptible(&ippp_table[lp->ppp_minor]->wq);
|
|
|
|
return lp->ppp_minor;
|
|
}
|
|
|
|
/*
|
|
* 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 minor)
|
|
{
|
|
if (minor < 0 || minor >= ISDN_MAX_CHANNELS)
|
|
return 0;
|
|
|
|
if (ippp_table[minor]->state && ippp_table[minor]->wq)
|
|
wake_up_interruptible(&ippp_table[minor]->wq);
|
|
|
|
ippp_table[minor]->state = IPPP_CLOSEWAIT;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* isdn_ppp_open
|
|
*/
|
|
|
|
int isdn_ppp_open(int minor, struct file *file)
|
|
{
|
|
if(ippp_table[minor]->debug & 0x1)
|
|
printk(KERN_DEBUG "ippp, open, minor: %d state: %04x\n", minor,ippp_table[minor]->state);
|
|
if (ippp_table[minor]->state)
|
|
return -EBUSY;
|
|
|
|
ippp_table[minor]->lp = 0;
|
|
ippp_table[minor]->mp_seqno = 0; /* MP sequence number */
|
|
ippp_table[minor]->pppcfg = 0; /* ppp configuration */
|
|
ippp_table[minor]->mpppcfg = 0; /* mppp configuration */
|
|
ippp_table[minor]->range = 0x1000000; /* MP: 24 bit range */
|
|
ippp_table[minor]->last_link_seqno = -1; /* MP: maybe set to Bundle-MIN, when joining a bundle ?? */
|
|
ippp_table[minor]->unit = -1; /* set, when we have our interface */
|
|
ippp_table[minor]->mru = 1524; /* MRU, default 1524 */
|
|
ippp_table[minor]->maxcid = 16; /* VJ: maxcid */
|
|
ippp_table[minor]->tk = current;
|
|
ippp_table[minor]->wq = NULL; /* read() wait queue */
|
|
ippp_table[minor]->wq1 = NULL; /* select() wait queue */
|
|
ippp_table[minor]->first = ippp_table[minor]->rq + NUM_RCV_BUFFS - 1; /* receive queue */
|
|
ippp_table[minor]->last = ippp_table[minor]->rq;
|
|
#ifdef CONFIG_ISDN_PPP_VJ
|
|
/*
|
|
* VJ header compression init
|
|
*/
|
|
ippp_table[minor]->slcomp = slhc_init(16, 16); /* not necessary for 2. link in bundle */
|
|
#endif
|
|
|
|
ippp_table[minor]->state = IPPP_OPEN;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* release ippp device
|
|
*/
|
|
void isdn_ppp_release(int minor, struct file *file)
|
|
{
|
|
int i;
|
|
|
|
if (minor < 0 || minor >= ISDN_MAX_CHANNELS)
|
|
return;
|
|
|
|
if(ippp_table[minor]->debug & 0x1)
|
|
printk(KERN_DEBUG "ippp: release, minor: %d %lx\n", minor, (long) ippp_table[minor]->lp);
|
|
|
|
if (ippp_table[minor]->lp) { /* a lp address says: this link is still up */
|
|
isdn_net_dev *p = dev->netdev;
|
|
p = ippp_table[minor]->lp->netdev;
|
|
ippp_table[minor]->lp->ppp_minor = -1;
|
|
isdn_net_hangup(&p->dev); /* lp->ppp_minor==-1 => no calling of isdn_ppp_closewait() */
|
|
ippp_table[minor]->lp = NULL;
|
|
}
|
|
for (i = 0; i < NUM_RCV_BUFFS; i++) {
|
|
if (ippp_table[minor]->rq[i].buf) {
|
|
kfree(ippp_table[minor]->rq[i].buf);
|
|
ippp_table[minor]->rq[i].buf = NULL;
|
|
}
|
|
}
|
|
ippp_table[minor]->first = ippp_table[minor]->rq + NUM_RCV_BUFFS - 1; /* receive queue */
|
|
ippp_table[minor]->last = ippp_table[minor]->rq;
|
|
|
|
#ifdef CONFIG_ISDN_PPP_VJ
|
|
slhc_free(ippp_table[minor]->slcomp);
|
|
ippp_table[minor]->slcomp = NULL;
|
|
#endif
|
|
|
|
ippp_table[minor]->state = 0;
|
|
}
|
|
|
|
/*
|
|
* get_arg .. ioctl helper
|
|
*/
|
|
static int get_arg(void *b, unsigned long *val)
|
|
{
|
|
int r;
|
|
if ((r = verify_area(VERIFY_READ, (void *) b, sizeof(unsigned long))))
|
|
return r;
|
|
memcpy_fromfs((void *) val, b, sizeof(unsigned long));
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* set arg .. ioctl helper
|
|
*/
|
|
static int set_arg(void *b, unsigned long val)
|
|
{
|
|
int r;
|
|
if ((r = verify_area(VERIFY_WRITE, b, sizeof(unsigned long))))
|
|
return r;
|
|
memcpy_tofs(b, (void *) &val, sizeof(unsigned long));
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* ippp device ioctl
|
|
*/
|
|
int isdn_ppp_ioctl(int minor, struct file *file, unsigned int cmd, unsigned long arg)
|
|
{
|
|
unsigned long val;
|
|
int r;
|
|
|
|
if(ippp_table[minor]->debug & 0x1)
|
|
printk(KERN_DEBUG "isdn_ppp_ioctl: minor: %d cmd: %x state: %x\n",
|
|
minor,cmd,ippp_table[minor]->state);
|
|
|
|
if (!(ippp_table[minor]->state & IPPP_OPEN))
|
|
return -EINVAL;
|
|
|
|
switch (cmd) {
|
|
#if 0
|
|
case PPPIOCSINPSIG: /* obsolete: set input ready signal */
|
|
/* usual: sig = SIGIO *//* we always deliver a SIGIO */
|
|
break;
|
|
#endif
|
|
case PPPIOCBUNDLE:
|
|
#ifdef CONFIG_ISDN_MPP
|
|
if ((r = get_arg((void *) arg, &val)))
|
|
return r;
|
|
printk(KERN_DEBUG "iPPP-bundle: minor: %d, slave unit: %d, master unit: %d\n",
|
|
(int) minor, (int) ippp_table[minor]->unit, (int) val);
|
|
return isdn_ppp_bundle(minor, val);
|
|
#else
|
|
return -1;
|
|
#endif
|
|
break;
|
|
case PPPIOCGUNIT: /* get ppp/isdn unit number */
|
|
if ((r = set_arg((void *) arg, ippp_table[minor]->unit)))
|
|
return r;
|
|
break;
|
|
case PPPIOCGMPFLAGS: /* get configuration flags */
|
|
if ((r = set_arg((void *) arg, ippp_table[minor]->mpppcfg)))
|
|
return r;
|
|
break;
|
|
case PPPIOCSMPFLAGS: /* set configuration flags */
|
|
if ((r = get_arg((void *) arg, &val)))
|
|
return r;
|
|
ippp_table[minor]->mpppcfg = val;
|
|
break;
|
|
case PPPIOCGFLAGS: /* get configuration flags */
|
|
if ((r = set_arg((void *) arg, ippp_table[minor]->pppcfg)))
|
|
return r;
|
|
break;
|
|
case PPPIOCSFLAGS: /* set configuration flags */
|
|
if ((r = get_arg((void *) arg, &val))) {
|
|
return r;
|
|
}
|
|
if (val & SC_ENABLE_IP && !(ippp_table[minor]->pppcfg & SC_ENABLE_IP)) {
|
|
isdn_net_local *lp = ippp_table[minor]->lp;
|
|
lp->netdev->dev.tbusy = 0;
|
|
mark_bh(NET_BH); /* OK .. we are ready to send buffers */
|
|
}
|
|
ippp_table[minor]->pppcfg = val;
|
|
break;
|
|
#if 0
|
|
case PPPIOCGSTAT: /* read PPP statistic information */
|
|
break;
|
|
case PPPIOCGTIME: /* read time delta information */
|
|
break;
|
|
#endif
|
|
case PPPIOCSMRU: /* set receive unit size for PPP */
|
|
if ((r = get_arg((void *) arg, &val)))
|
|
return r;
|
|
ippp_table[minor]->mru = val;
|
|
break;
|
|
case PPPIOCSMPMRU:
|
|
break;
|
|
case PPPIOCSMPMTU:
|
|
break;
|
|
case PPPIOCSMAXCID: /* set the maximum compression slot id */
|
|
if ((r = get_arg((void *) arg, &val)))
|
|
return r;
|
|
val++;
|
|
if(ippp_table[minor]->maxcid != val) {
|
|
#ifdef CONFIG_ISDN_PPP_VJ
|
|
struct slcompress *sltmp;
|
|
#endif
|
|
if(ippp_table[minor]->debug & 0x1)
|
|
printk(KERN_DEBUG "ippp, ioctl: changed MAXCID to %ld\n",val);
|
|
ippp_table[minor]->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(ippp_table[minor]->slcomp)
|
|
slhc_free(ippp_table[minor]->slcomp);
|
|
ippp_table[minor]->slcomp = sltmp;
|
|
#endif
|
|
}
|
|
break;
|
|
case PPPIOCGDEBUG:
|
|
if ((r = set_arg((void *) arg, ippp_table[minor]->debug)))
|
|
return r;
|
|
break;
|
|
case PPPIOCSDEBUG:
|
|
if ((r = get_arg((void *) arg, &val)))
|
|
return r;
|
|
ippp_table[minor]->debug = val;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int isdn_ppp_select(int minor, struct file *file, int type, select_table * st)
|
|
{
|
|
struct ippp_buf_queue *bf, *bl;
|
|
unsigned long flags;
|
|
|
|
if(ippp_table[minor]->debug & 0x2)
|
|
printk(KERN_DEBUG "isdn_ppp_select: minor: %d, type: %d \n",minor,type);
|
|
|
|
if (!(ippp_table[minor]->state & IPPP_OPEN))
|
|
return -EINVAL;
|
|
|
|
switch (type) {
|
|
case SEL_IN:
|
|
save_flags(flags);
|
|
cli();
|
|
bl = ippp_table[minor]->last;
|
|
bf = ippp_table[minor]->first;
|
|
/*
|
|
* if IPPP_NOBLOCK is set we return even if we have nothing to read
|
|
*/
|
|
if (bf->next == bl && !(ippp_table[minor]->state & IPPP_NOBLOCK)) {
|
|
select_wait(&ippp_table[minor]->wq, st);
|
|
restore_flags(flags);
|
|
return 0;
|
|
}
|
|
ippp_table[minor]->state &= ~IPPP_NOBLOCK;
|
|
restore_flags(flags);
|
|
return 1;
|
|
case SEL_OUT:
|
|
/* we're always ready to send .. */
|
|
return 1;
|
|
case SEL_EX:
|
|
select_wait(&ippp_table[minor]->wq1, st);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* fill up isdn_ppp_read() queue ..
|
|
*/
|
|
|
|
static int isdn_ppp_fill_rq(unsigned char *buf, int len,int proto, int minor)
|
|
{
|
|
struct ippp_buf_queue *bf, *bl;
|
|
unsigned long flags;
|
|
unsigned char *nbuf;
|
|
|
|
if (minor < 0 || minor >= ISDN_MAX_CHANNELS) {
|
|
printk(KERN_WARNING "ippp: illegal minor.\n");
|
|
return 0;
|
|
}
|
|
if (!(ippp_table[minor]->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 = ippp_table[minor]->first;
|
|
bl = ippp_table[minor]->last;
|
|
|
|
if (bf == bl) {
|
|
printk(KERN_WARNING "ippp: Queue is full; discarding first buffer\n");
|
|
bf = bf->next;
|
|
kfree(bf->buf);
|
|
ippp_table[minor]->first = bf;
|
|
}
|
|
bl->buf = (char *) nbuf;
|
|
bl->len = len+4;
|
|
|
|
ippp_table[minor]->last = bl->next;
|
|
restore_flags(flags);
|
|
|
|
if (ippp_table[minor]->wq)
|
|
wake_up_interruptible(&ippp_table[minor]->wq);
|
|
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* read() .. non-blocking: ipppd calls it only after select()
|
|
* reports, that there is data
|
|
*/
|
|
|
|
int isdn_ppp_read(int minor, struct file *file, char *buf, int count)
|
|
{
|
|
struct ippp_struct *c = ippp_table[minor];
|
|
struct ippp_buf_queue *b;
|
|
int r;
|
|
unsigned long flags;
|
|
|
|
if (!(ippp_table[minor]->state & IPPP_OPEN))
|
|
return 0;
|
|
|
|
if ((r = verify_area(VERIFY_WRITE, (void *) buf, count)))
|
|
return r;
|
|
|
|
save_flags(flags);
|
|
cli();
|
|
|
|
b = c->first->next;
|
|
if (!b->buf) {
|
|
restore_flags(flags);
|
|
return -EAGAIN;
|
|
}
|
|
if (b->len < count)
|
|
count = b->len;
|
|
memcpy_tofs(buf, b->buf, count);
|
|
kfree(b->buf);
|
|
b->buf = NULL;
|
|
c->first = b;
|
|
restore_flags(flags);
|
|
|
|
return count;
|
|
}
|
|
|
|
/*
|
|
* ipppd wanna write a packet to the card .. non-blocking
|
|
*/
|
|
|
|
int isdn_ppp_write(int minor, struct file *file, const char *buf, int count)
|
|
{
|
|
isdn_net_local *lp;
|
|
|
|
if (!(ippp_table[minor]->state & IPPP_CONNECT))
|
|
return 0;
|
|
|
|
lp = ippp_table[minor]->lp;
|
|
|
|
/* -> push it directly to the lowlevel interface */
|
|
|
|
if (!lp)
|
|
printk(KERN_DEBUG "isdn_ppp_write: lp == NULL\n");
|
|
else {
|
|
lp->huptimer = 0;
|
|
if (lp->isdn_device < 0 || lp->isdn_channel < 0)
|
|
return 0;
|
|
|
|
if (dev->drv[lp->isdn_device]->running && lp->dialstate == 0 &&
|
|
(lp->flags & ISDN_NET_CONNECTED)) {
|
|
struct sk_buff *skb;
|
|
skb = dev_alloc_skb(count);
|
|
if(!skb) {
|
|
printk(KERN_WARNING "isdn_ppp_write: out of memory!\n");
|
|
return count;
|
|
}
|
|
skb->free = 1;
|
|
memcpy_fromfs(skb_put(skb, count), buf, count);
|
|
if(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!\n");
|
|
}
|
|
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]);
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
if(ippp_table[lp->ppp_minor]->debug & 0x4)
|
|
printk(KERN_DEBUG "recv skb, len: %ld\n",skb->len);
|
|
|
|
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 (ippp_table[lp->ppp_minor]->pppcfg & SC_REJ_COMP_AC) {
|
|
skb->free = 1;
|
|
dev_kfree_skb(skb,0 /* FREE_READ */ );
|
|
return; /* discard it silently */
|
|
}
|
|
|
|
#ifdef CONFIG_ISDN_MPP
|
|
if (!(ippp_table[lp->ppp_minor]->mpppcfg & SC_REJ_MP_PROT)) {
|
|
int proto;
|
|
int sqno_end;
|
|
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);
|
|
}
|
|
if (proto == PPP_MP) {
|
|
isdn_net_local *lpq;
|
|
int sqno, min_sqno, tseq;
|
|
u_char BEbyte = skb->data[0];
|
|
if(ippp_table[lp->ppp_minor]->debug & 0x8)
|
|
printk(KERN_DEBUG "recv: %d/%04x/%d -> %02x %02x %02x %02x %02x %02x\n", lp->ppp_minor, 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 (!(ippp_table[lp->ppp_minor]->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);
|
|
}
|
|
|
|
if ((tseq = ippp_table[lp->ppp_minor]->last_link_seqno) >= sqno) {
|
|
int range = ippp_table[lp->ppp_minor]->range;
|
|
if (tseq + 1024 < range + sqno) /* redundancy check .. not MP conform */
|
|
printk(KERN_WARNING "isdn_ppp_receive, MP, detected overflow with sqno: %d, last: %d !!!\n", sqno, tseq);
|
|
else {
|
|
sqno += range;
|
|
ippp_table[lp->ppp_minor]->last_link_seqno = sqno;
|
|
}
|
|
} else
|
|
ippp_table[lp->ppp_minor]->last_link_seqno = sqno;
|
|
|
|
for (min_sqno = 0, lpq = net_dev->queue;;) {
|
|
if (ippp_table[lpq->ppp_minor]->last_link_seqno > min_sqno)
|
|
min_sqno = ippp_table[lpq->ppp_minor]->last_link_seqno;
|
|
lpq = lpq->next;
|
|
if (lpq == net_dev->queue)
|
|
break;
|
|
}
|
|
if (min_sqno >= ippp_table[lpq->ppp_minor]->range) { /* OK, every link overflowed */
|
|
int mask = ippp_table[lpq->ppp_minor]->range - 1; /* range is a power of 2 */
|
|
isdn_ppp_cleanup_queue(net_dev, min_sqno);
|
|
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_minor]->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)) {
|
|
printk(KERN_DEBUG "ippp: trying ;) to fill mp_queue %d .. UNTESTED!!\n", lp->ppp_minor);
|
|
if ((sqno_end = isdn_ppp_fill_mpqueue(net_dev, &skb , BEbyte, &sqno, min_sqno)) < 0)
|
|
return; /* no packet complete */
|
|
} else
|
|
sqno_end = sqno;
|
|
|
|
/*
|
|
* 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");
|
|
skb->free = 1;
|
|
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;
|
|
}
|
|
}
|
|
net_dev->ib.modify = 0;
|
|
return;
|
|
} else {
|
|
/*
|
|
* packet was 'in order' .. push it higher
|
|
*/
|
|
struct sqqueue *q;
|
|
|
|
net_dev->ib.next_num = sqno_end + 1;
|
|
isdn_ppp_push_higher(net_dev, lp, skb, -1);
|
|
|
|
/*
|
|
* check queue, whether we have still buffered the next packet(s)
|
|
*/
|
|
while ((q = net_dev->ib.sq) && q->sqno_start == net_dev->ib.next_num) {
|
|
isdn_ppp_push_higher(net_dev, lp, q->skb, -1);
|
|
net_dev->ib.sq = q->next;
|
|
net_dev->ib.next_num = q->sqno_end + 1;
|
|
kfree(q);
|
|
}
|
|
}
|
|
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 , -1);
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
if (proto < 0) { /* MP, oder normales Paket bei REJ_MP, MP Pakete gehen bei REJ zum pppd */
|
|
if (skb->data[0] & 0x01) { /* is it odd? */
|
|
proto = (unsigned char) skb->data[0];
|
|
skb_pull(skb,1); /* protocol ID is only 8 bit */
|
|
} else {
|
|
proto = ((int) (unsigned char) skb->data[0] << 8) + (unsigned char) skb->data[1];
|
|
skb_pull(skb,2);
|
|
}
|
|
}
|
|
|
|
if(ippp_table[lp->ppp_minor]->debug & 0x10)
|
|
printk(KERN_DEBUG "push, skb %ld %04x\n",skb->len,proto);
|
|
|
|
switch (proto) {
|
|
case PPP_IPX: /* untested */
|
|
if(ippp_table[lp->ppp_minor]->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(ippp_table[lp->ppp_minor]->debug & 0x20)
|
|
printk(KERN_DEBUG "isdn_ppp: VJC_UNCOMP\n");
|
|
if(slhc_remember(ippp_table[net_dev->local.ppp_minor]->slcomp, skb->data, skb->len) <= 0) {
|
|
printk(KERN_WARNING "isdn_ppp: received illegal VJC_UNCOMP frame!\n");
|
|
net_dev->local.stats.rx_dropped++;
|
|
skb->free = 1;
|
|
dev_kfree_skb(skb,0 /* FREE_READ */ );
|
|
return;
|
|
}
|
|
#endif
|
|
case PPP_IP:
|
|
if(ippp_table[lp->ppp_minor]->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(ippp_table[lp->ppp_minor]->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);
|
|
|
|
skb_old->free = 1;
|
|
|
|
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_minor]->slcomp,
|
|
skb->data, skb_old->len);
|
|
dev_kfree_skb(skb_old,0 /* FREE_READ */ );
|
|
if(pkt_len < 0) {
|
|
skb->free = 1;
|
|
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++;
|
|
skb->free = 1;
|
|
dev_kfree_skb(skb,0 /* FREE_READ */ );
|
|
return;
|
|
#endif
|
|
break;
|
|
default:
|
|
isdn_ppp_fill_rq(skb->data, skb->len,proto, lp->ppp_minor); /* push data to pppd device */
|
|
skb->free = 1;
|
|
dev_kfree_skb(skb,0 /* FREE_READ */ );
|
|
return;
|
|
}
|
|
|
|
netif_rx(skb);
|
|
net_dev->local.stats.rx_packets++;
|
|
/* Reset hangup-timer */
|
|
lp->huptimer = 0;
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
int proto = PPP_IP; /* 0x21 */
|
|
struct ippp_struct *ipt,*ipts;
|
|
|
|
if(mdev)
|
|
mlp = (isdn_net_local *) (mdev->priv);
|
|
else
|
|
mlp = (isdn_net_local *) (dev->priv);
|
|
nd = mlp->netdev; /* get master lp */
|
|
ipts = ippp_table[mlp->ppp_minor];
|
|
|
|
if (!(ipts->pppcfg & SC_ENABLE_IP)) { /* PPP connected ? */
|
|
printk(KERN_INFO "%s: IP frame delayed.\n",dev->name);
|
|
return 1;
|
|
}
|
|
|
|
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_minor];
|
|
|
|
lp->huptimer = 0;
|
|
|
|
/* If packet is to be resent, it has already been processed and
|
|
* therefore its first bytes are already initialized. In this case
|
|
* send it immediately ...
|
|
*/
|
|
if (*((u32 *)skb->data) != 0) {
|
|
printk(KERN_ERR "%s: Whoops .. packet resend should no longer happen!\n",dev->name);
|
|
return (isdn_net_send_skb(dev , lp , skb));
|
|
}
|
|
|
|
/* ... else packet needs processing. */
|
|
|
|
if(ippp_table[lp->ppp_minor]->debug & 0x4)
|
|
printk(KERN_DEBUG "xmit skb, len %ld\n",skb->len);
|
|
|
|
#ifdef CONFIG_ISDN_PPP_VJ
|
|
if (ipts->pppcfg & SC_COMP_TCP) { /* ipts here? probably yes .. but check again */
|
|
struct sk_buff *new_skb;
|
|
int len = 4;
|
|
#ifdef CONFIG_ISDN_MPP
|
|
if (ipt->mpppcfg & SC_MP_PROT) /* sigh */ /* ipt or ipts ?? */
|
|
if (ipt->mpppcfg & SC_OUT_SHORT_SEQ)
|
|
len += 3;
|
|
else
|
|
len += 5;
|
|
#endif
|
|
new_skb = dev_alloc_skb(skb->len);
|
|
if(new_skb) {
|
|
u_char *buf;
|
|
int pktlen;
|
|
|
|
new_skb->dev = skb->dev;
|
|
new_skb->free = 1;
|
|
skb_put(new_skb,skb->len);
|
|
skb_pull(skb,len); /* pull PPP header */
|
|
skb_pull(new_skb,len); /* pull PPP header */
|
|
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) { /* copied to new buffer ??? (btw: WHY must slhc copy it?? *sigh*) */
|
|
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;
|
|
}
|
|
skb_push(skb,len);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if(ippp_table[lp->ppp_minor]->debug & 0x24)
|
|
printk(KERN_DEBUG "xmit2 skb, len %ld, proto %04x\n",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) {
|
|
/* skb_push(skb, 3); Done in isdn_net_header() */
|
|
mp_seqno &= 0xfff;
|
|
skb->data[4] = MP_BEGIN_FRAG | MP_END_FRAG | (mp_seqno >> 8); /* (B)egin & (E)ndbit .. */
|
|
skb->data[5] = mp_seqno & 0xff;
|
|
skb->data[6] = proto; /* PID compression */
|
|
} else {
|
|
/* skb_push(skb, 5); Done in isdn_net_header () */
|
|
skb->data[4] = MP_BEGIN_FRAG | MP_END_FRAG; /* (B)egin & (E)ndbit .. */
|
|
skb->data[5] = (mp_seqno >> 16) & 0xff; /* sequence number: 24bit */
|
|
skb->data[6] = (mp_seqno >> 8) & 0xff;
|
|
skb->data[7] = (mp_seqno >> 0) & 0xff;
|
|
skb->data[8] = proto; /* PID compression */
|
|
}
|
|
proto = PPP_MP; /* MP Protocol, 0x003d */
|
|
}
|
|
#endif
|
|
skb->data[0] = 0xff; /* All Stations */
|
|
skb->data[1] = 0x03; /* Unnumbered information */
|
|
skb->data[2] = proto >> 8;
|
|
skb->data[3] = proto & 0xff;
|
|
|
|
/* tx-stats are now updated via BSENT-callback */
|
|
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;
|
|
}
|
|
|
|
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) {
|
|
q->skb->free = 1;
|
|
dev_kfree_skb(q->skb,0 /* FREE_READ */ );
|
|
}
|
|
kfree(q);
|
|
q = qn;
|
|
}
|
|
|
|
}
|
|
|
|
void isdn_ppp_free_mpqueue(isdn_net_dev * p)
|
|
{
|
|
struct mpqueue *ql, *q = p->mp_last;
|
|
while (q) {
|
|
ql = q->next;
|
|
q->skb->free = 1;
|
|
dev_kfree_skb(q->skb,0 /* FREE_READ */ );
|
|
kfree(q);
|
|
q = ql;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_ISDN_MPP
|
|
|
|
static int isdn_ppp_bundle(int minor, 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 = ippp_table[minor]->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_minor]->unit = ippp_table[lp->ppp_minor]->unit;
|
|
/* maybe also SC_CCP stuff */
|
|
ippp_table[nlp->ppp_minor]->pppcfg |= ippp_table[lp->ppp_minor]->pppcfg &
|
|
(SC_ENABLE_IP | SC_NO_TCP_CCID | SC_REJ_COMP_TCP);
|
|
|
|
ippp_table[nlp->ppp_minor]->mpppcfg |= ippp_table[lp->ppp_minor]->mpppcfg &
|
|
(SC_MP_PROT | SC_REJ_MP_PROT | SC_OUT_SHORT_SEQ | SC_IN_SHORT_SEQ);
|
|
#if 0
|
|
if (ippp_table[nlp->ppp_minor]->mpppcfg != ippp_table[lp->ppp_minor]->mpppcfg) {
|
|
printk(KERN_WARNING "isdn_ppp_bundle: different MP options %04x and %04x\n",
|
|
ippp_table[nlp->ppp_minor]->mpppcfg, ippp_table[lp->ppp_minor]->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, int *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_KERNEL);
|
|
if (!q1) {
|
|
printk(KERN_WARNING "isdn_ppp_fill_mpqueue: Can't alloc struct memory.\n");
|
|
save_flags(flags);
|
|
cli();
|
|
isdn_ppp_cleanup_queue(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_queue(dev, min_sqno); /* not necessary */
|
|
restore_flags(flags);
|
|
return -1;
|
|
}
|
|
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_queue(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_queue(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_queue(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;
|
|
q->skb->free = 1;
|
|
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;
|
|
q->skb->free = 1;
|
|
dev_kfree_skb(q->skb,0 /* FREE_READ */ );
|
|
kfree(q);
|
|
q = ql;
|
|
}
|
|
|
|
return sqno_end;
|
|
}
|
|
|
|
/*
|
|
* remove stale packets from list
|
|
*/
|
|
|
|
static void isdn_ppp_cleanup_queue(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) {
|
|
if (q->sqno < min_sqno) {
|
|
if (q->BEbyte & MP_END_FRAG) {
|
|
printk(KERN_DEBUG "ippp: freeing stale packet!\n");
|
|
if ((dev->mp_last = q->next))
|
|
q->next->last = NULL;
|
|
while (q) {
|
|
ql = q->last;
|
|
q->skb->free = 1;
|
|
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;
|
|
} else
|
|
break;
|
|
}
|
|
#ifdef CONFIG_ISDN_PPP_VJ
|
|
/* did we free a stale frame ? */
|
|
if(toss)
|
|
slhc_toss(ippp_table[dev->local.ppp_minor]->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_minor]->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;) {
|
|
isdn_ppp_push_higher(net_dev, lp, ql->skb, -1);
|
|
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 minor,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(minor >= 0 && ippp_table[minor]->slcomp) {
|
|
struct slcompress *slcomp = ippp_table[minor]->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
|
|
}
|
|
memcpy_tofs (res, &t, sizeof (struct ppp_stats));
|
|
return 0;
|
|
|
|
}
|
|
|
|
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_minor);
|
|
#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 = verify_area(VERIFY_WRITE, r, len);
|
|
if (!error)
|
|
memcpy_tofs(r, PPP_VERSION, len);
|
|
break;
|
|
case SIOCGPPPSTATS:
|
|
error = isdn_ppp_dev_ioctl_stats (lp->ppp_minor, ifr, dev);
|
|
break;
|
|
default:
|
|
error = -EINVAL;
|
|
break;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
static int isdn_ppp_if_get_unit(char **namebuf)
|
|
{
|
|
char *name = *namebuf;
|
|
int len, i, unit = 0, deci;
|
|
|
|
len = strlen(name);
|
|
for (i = 0, deci = 1; i < len; i++, deci *= 10) {
|
|
if (name[len - 1 - i] >= '0' && name[len - 1 - i] <= '9')
|
|
unit += (name[len - 1 - i] - '0') * deci;
|
|
else
|
|
break;
|
|
}
|
|
if (!i)
|
|
unit = -1;
|
|
|
|
*namebuf = name + len - 1 - i;
|
|
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
|
|
}
|
|
|