/* $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 #define __NO_VERSION__ #include #include #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 sqnomp_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 }