patch-2.3.13 linux/drivers/net/ppp_generic.c
Next file: linux/drivers/net/ptifddi.c
Previous file: linux/drivers/net/ppp_async.c
Back to the patch index
Back to the overall index
- Lines: 1577
- Date:
Fri Aug 6 10:44:11 1999
- Orig file:
v2.3.12/linux/drivers/net/ppp_generic.c
- Orig date:
Wed Dec 31 16:00:00 1969
diff -u --recursive --new-file v2.3.12/linux/drivers/net/ppp_generic.c linux/drivers/net/ppp_generic.c
@@ -0,0 +1,1576 @@
+/*
+ * Generic PPP layer for Linux.
+ *
+ * Copyright 1999 Paul Mackerras.
+ *
+ * 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 of the License, or (at your option) any later version.
+ *
+ * The generic PPP layer handles the PPP network interfaces, the
+ * /dev/ppp device, packet and VJ compression, and multilink.
+ * It talks to PPP `channels' via the interface defined in
+ * include/linux/ppp_channel.h. Channels provide the basic means for
+ * sending and receiving PPP frames on some kind of communications
+ * channel.
+ *
+ * Part of the code in this driver was inspired by the old async-only
+ * PPP driver, written by Michael Callahan and Al Longyear, and
+ * subsequently hacked by Paul Mackerras.
+ *
+ * ==FILEVERSION 990806==
+ */
+
+/* $Id$ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/kmod.h>
+#include <linux/list.h>
+#include <linux/netdevice.h>
+#include <linux/poll.h>
+#include <linux/ppp_defs.h>
+#include <linux/if_ppp.h>
+#include <linux/ppp_channel.h>
+#include <linux/ppp-comp.h>
+#include <linux/skbuff.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_arp.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <net/slhc_vj.h>
+#include <asm/spinlock.h>
+
+#define PPP_VERSION "2.4.0"
+
+EXPORT_SYMBOL(ppp_register_channel);
+EXPORT_SYMBOL(ppp_unregister_channel);
+EXPORT_SYMBOL(ppp_input);
+EXPORT_SYMBOL(ppp_input_error);
+EXPORT_SYMBOL(ppp_output_wakeup);
+EXPORT_SYMBOL(ppp_register_compressor);
+EXPORT_SYMBOL(ppp_unregister_compressor);
+
+/*
+ * Network protocols we support.
+ */
+#define NP_IP 0 /* Internet Protocol V4 */
+#define NP_IPV6 1 /* Internet Protocol V6 */
+#define NP_IPX 2 /* IPX protocol */
+#define NP_AT 3 /* Appletalk protocol */
+#define NUM_NP 4 /* Number of NPs. */
+
+/*
+ * Data structure describing one ppp unit.
+ * A ppp unit corresponds to a ppp network interface device
+ * and represents a multilink bundle.
+ * It may have 0 or more ppp channels connected to it.
+ */
+struct ppp {
+ struct list_head list; /* link in list of ppp units */
+ int index; /* interface unit number */
+ char name[16]; /* unit name */
+ int refcnt; /* # open /dev/ppp attached */
+ unsigned long busy; /* lock and other bits */
+ struct list_head channels; /* list of attached channels */
+ int n_channels; /* how many channels are attached */
+ int mru; /* max receive unit */
+ unsigned int flags; /* control bits */
+ unsigned int xstate; /* transmit state bits */
+ unsigned int rstate; /* receive state bits */
+ int debug; /* debug flags */
+ struct slcompress *vj; /* state for VJ header compression */
+ struct sk_buff_head xq; /* pppd transmit queue */
+ struct sk_buff_head rq; /* receive queue for pppd */
+ wait_queue_head_t rwait; /* for poll on reading /dev/ppp */
+ enum NPmode npmode[NUM_NP]; /* what to do with each net proto */
+ struct sk_buff *xmit_pending; /* a packet ready to go out */
+ struct sk_buff_head recv_pending;/* pending input packets */
+ struct compressor *xcomp; /* transmit packet compressor */
+ void *xc_state; /* its internal state */
+ struct compressor *rcomp; /* receive decompressor */
+ void *rc_state; /* its internal state */
+ unsigned long last_xmit; /* jiffies when last pkt sent */
+ unsigned long last_recv; /* jiffies when last pkt rcvd */
+ struct device dev; /* network interface device */
+ struct net_device_stats stats; /* statistics */
+};
+
+static LIST_HEAD(all_ppp_units);
+static spinlock_t all_ppp_lock = SPIN_LOCK_UNLOCKED;
+
+/*
+ * Private data structure for each channel.
+ * Ultimately this will have multilink stuff etc. in it.
+ */
+struct channel {
+ struct list_head list; /* link in list of channels per unit */
+ struct ppp_channel *chan; /* public channel data structure */
+ int blocked; /* if channel refused last packet */
+ struct ppp *ppp; /* ppp unit we're connected to */
+};
+
+/* Bit numbers in busy */
+#define XMIT_BUSY 0
+#define RECV_BUSY 1
+#define XMIT_WAKEUP 2
+
+/*
+ * Bits in flags: SC_NO_TCP_CCID, SC_CCP_OPEN, SC_CCP_UP, SC_LOOP_TRAFFIC.
+ * Bits in rstate: SC_DECOMP_RUN, SC_DC_ERROR, SC_DC_FERROR.
+ * Bits in xstate: SC_COMP_RUN
+ */
+#define SC_FLAG_BITS (SC_NO_TCP_CCID|SC_CCP_OPEN|SC_CCP_UP|SC_LOOP_TRAFFIC)
+
+/* Get the PPP protocol number from a skb */
+#define PPP_PROTO(skb) (((skb)->data[0] << 8) + (skb)->data[1])
+
+/* We limit the length of ppp->rq to this (arbitrary) value */
+#define PPP_MAX_RQLEN 32
+
+/* Prototypes. */
+static void ppp_xmit_unlock(struct ppp *ppp);
+static void ppp_send_frame(struct ppp *ppp, struct sk_buff *skb);
+static void ppp_push(struct ppp *ppp);
+static void ppp_recv_unlock(struct ppp *ppp);
+static void ppp_receive_frame(struct ppp *ppp, struct sk_buff *skb);
+static struct sk_buff *ppp_decompress_frame(struct ppp *ppp,
+ struct sk_buff *skb);
+static int ppp_set_compress(struct ppp *ppp, unsigned long arg);
+static void ppp_ccp_peek(struct ppp *ppp, struct sk_buff *skb, int inbound);
+static void ppp_ccp_closed(struct ppp *ppp);
+static struct compressor *find_compressor(int type);
+static void ppp_get_stats(struct ppp *ppp, struct ppp_stats *st);
+static struct ppp *ppp_create_unit(int unit, int *retp);
+static struct ppp *ppp_find_unit(int unit);
+
+/* Translates a PPP protocol number to a NP index (NP == network protocol) */
+static inline int proto_to_npindex(int proto)
+{
+ switch (proto) {
+ case PPP_IP:
+ return NP_IP;
+ case PPP_IPV6:
+ return NP_IPV6;
+ case PPP_IPX:
+ return NP_IPX;
+ case PPP_AT:
+ return NP_AT;
+ }
+ return -EINVAL;
+}
+
+/* Translates an NP index into a PPP protocol number */
+static const int npindex_to_proto[NUM_NP] = {
+ PPP_IP,
+ PPP_IPV6,
+ PPP_IPX,
+ PPP_AT,
+};
+
+/* Translates an ethertype into an NP index */
+static inline int ethertype_to_npindex(int ethertype)
+{
+ switch (ethertype) {
+ case ETH_P_IP:
+ return NP_IP;
+ case ETH_P_IPV6:
+ return NP_IPV6;
+ case ETH_P_IPX:
+ return NP_IPX;
+ case ETH_P_PPPTALK:
+ case ETH_P_ATALK:
+ return NP_AT;
+ }
+ return -1;
+}
+
+/* Translates an NP index into an ethertype */
+static const int npindex_to_ethertype[NUM_NP] = {
+ ETH_P_IP,
+ ETH_P_IPV6,
+ ETH_P_IPX,
+ ETH_P_PPPTALK,
+};
+
+/*
+ * Routines for locking and unlocking the transmit and receive paths
+ * of each unit.
+ */
+static inline void
+lock_path(struct ppp *ppp, int bit)
+{
+ int timeout = 1000000;
+
+ do {
+ while (test_bit(bit, &ppp->busy)) {
+ mb();
+ if (--timeout == 0) {
+ printk(KERN_ERR "lock_path timeout ppp=%p bit=%x\n", ppp, bit);
+ return;
+ }
+ }
+ } while (test_and_set_bit(bit, &ppp->busy));
+ mb();
+}
+
+static inline int
+trylock_path(struct ppp *ppp, int bit)
+{
+ if (test_and_set_bit(bit, &ppp->busy))
+ return 0;
+ mb();
+ return 1;
+}
+
+static inline void
+unlock_path(struct ppp *ppp, int bit)
+{
+ mb();
+ clear_bit(bit, &ppp->busy);
+}
+
+#define lock_xmit_path(ppp) lock_path(ppp, XMIT_BUSY)
+#define trylock_xmit_path(ppp) trylock_path(ppp, XMIT_BUSY)
+#define unlock_xmit_path(ppp) unlock_path(ppp, XMIT_BUSY)
+#define lock_recv_path(ppp) lock_path(ppp, RECV_BUSY)
+#define trylock_recv_path(ppp) trylock_path(ppp, RECV_BUSY)
+#define unlock_recv_path(ppp) unlock_path(ppp, RECV_BUSY)
+
+static inline void
+free_skbs(struct sk_buff_head *head)
+{
+ struct sk_buff *skb;
+
+ while ((skb = skb_dequeue(head)) != 0)
+ kfree_skb(skb);
+}
+
+/*
+ * /dev/ppp device routines.
+ * The /dev/ppp device is used by pppd to control the ppp unit.
+ * It supports the read, write, ioctl and poll functions.
+ */
+static int ppp_open(struct inode *inode, struct file *file)
+{
+ /*
+ * This could (should?) be enforced by the permissions on /dev/ppp.
+ */
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static int ppp_release(struct inode *inode, struct file *file)
+{
+ struct ppp *ppp = (struct ppp *) file->private_data;
+ struct list_head *list, *next;
+ int ref;
+
+ if (ppp == 0)
+ goto out;
+ file->private_data = 0;
+ spin_lock(&all_ppp_lock);
+ ref = --ppp->refcnt;
+ if (ref == 0)
+ list_del(&ppp->list);
+ spin_unlock(&all_ppp_lock);
+ if (ref != 0)
+ goto out;
+
+ /* Last fd open to this ppp unit is being closed -
+ mark the interface down, free the ppp unit */
+ rtnl_lock();
+ dev_close(&ppp->dev);
+ rtnl_unlock();
+ for (list = ppp->channels.next; list != &ppp->channels; list = next) {
+ /* forcibly detach this channel */
+ struct channel *chan;
+ chan = list_entry(list, struct channel, list);
+ chan->chan->ppp = 0;
+ next = list->next;
+ kfree(chan);
+ }
+
+ /* Free up resources. */
+ ppp_ccp_closed(ppp);
+ lock_xmit_path(ppp);
+ lock_recv_path(ppp);
+ if (ppp->vj) {
+ slhc_free(ppp->vj);
+ ppp->vj = 0;
+ }
+ free_skbs(&ppp->xq);
+ free_skbs(&ppp->rq);
+ free_skbs(&ppp->recv_pending);
+ unregister_netdev(&ppp->dev);
+ kfree(ppp);
+
+ out:
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static ssize_t ppp_read(struct file *file, char *buf,
+ size_t count, loff_t *ppos)
+{
+ struct ppp *ppp = (struct ppp *) file->private_data;
+ DECLARE_WAITQUEUE(wait, current);
+ ssize_t ret;
+ struct sk_buff *skb = 0;
+
+ ret = -ENXIO;
+ if (ppp == 0)
+ goto out; /* not currently attached */
+
+ add_wait_queue(&ppp->rwait, &wait);
+ current->state = TASK_INTERRUPTIBLE;
+ for (;;) {
+ ret = -EAGAIN;
+ skb = skb_dequeue(&ppp->rq);
+ if (skb)
+ break;
+ if (file->f_flags & O_NONBLOCK)
+ break;
+ ret = -ERESTARTSYS;
+ if (signal_pending(current))
+ break;
+ schedule();
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&ppp->rwait, &wait);
+
+ if (skb == 0)
+ goto out;
+
+ ret = -EOVERFLOW;
+ if (skb->len > count)
+ goto outf;
+ ret = -EFAULT;
+ if (copy_to_user(buf, skb->data, skb->len))
+ goto outf;
+ ret = skb->len;
+
+ outf:
+ kfree_skb(skb);
+ out:
+ return ret;
+}
+
+static ssize_t ppp_write(struct file *file, const char *buf,
+ size_t count, loff_t *ppos)
+{
+ struct ppp *ppp = (struct ppp *) file->private_data;
+ struct sk_buff *skb;
+ ssize_t ret;
+
+ ret = -ENXIO;
+ if (ppp == 0)
+ goto out;
+
+ ret = -ENOMEM;
+ skb = alloc_skb(count + 2, GFP_KERNEL);
+ if (skb == 0)
+ goto out;
+ skb_reserve(skb, 2);
+ ret = -EFAULT;
+ if (copy_from_user(skb_put(skb, count), buf, count)) {
+ kfree_skb(skb);
+ goto out;
+ }
+
+ skb_queue_tail(&ppp->xq, skb);
+ if (trylock_xmit_path(ppp))
+ ppp_xmit_unlock(ppp);
+
+ ret = count;
+
+ out:
+ return ret;
+}
+
+static unsigned int ppp_poll(struct file *file, poll_table *wait)
+{
+ struct ppp *ppp = (struct ppp *) file->private_data;
+ unsigned int mask;
+
+ if (ppp == 0)
+ return 0;
+ poll_wait(file, &ppp->rwait, wait);
+ mask = POLLOUT | POLLWRNORM;
+ if (skb_peek(&ppp->rq) != 0)
+ mask |= POLLIN | POLLRDNORM;
+ return mask;
+}
+
+static int ppp_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct ppp *ppp = (struct ppp *) file->private_data;
+ int err, val, val2, i;
+ struct ppp_idle idle;
+ struct npioctl npi;
+
+ if (cmd == PPPIOCNEWUNIT) {
+ /* Create a new ppp unit */
+ int unit, ret;
+
+ if (ppp != 0)
+ return -EINVAL;
+ if (get_user(unit, (int *) arg))
+ return -EFAULT;
+ ppp = ppp_create_unit(unit, &ret);
+ if (ppp == 0)
+ return ret;
+ file->private_data = ppp;
+ if (put_user(ppp->index, (int *) arg))
+ return -EFAULT;
+ return 0;
+ }
+ if (cmd == PPPIOCATTACH) {
+ /* Attach to an existing ppp unit */
+ int unit;
+
+ if (ppp != 0)
+ return -EINVAL;
+ if (get_user(unit, (int *) arg))
+ return -EFAULT;
+ spin_lock(&all_ppp_lock);
+ ppp = ppp_find_unit(unit);
+ if (ppp != 0)
+ ++ppp->refcnt;
+ spin_unlock(&all_ppp_lock);
+ if (ppp == 0)
+ return -ENXIO;
+ file->private_data = ppp;
+ return 0;
+ }
+
+ if (ppp == 0)
+ return -ENXIO;
+ err = -EFAULT;
+ switch (cmd) {
+ case PPPIOCSMRU:
+ if (get_user(val, (int *) arg))
+ break;
+ ppp->mru = val;
+ err = 0;
+ break;
+
+ case PPPIOCSFLAGS:
+ if (get_user(val, (int *) arg))
+ break;
+ if (ppp->flags & ~val & SC_CCP_OPEN)
+ ppp_ccp_closed(ppp);
+ ppp->flags = val & SC_FLAG_BITS;
+ err = 0;
+ break;
+
+ case PPPIOCGFLAGS:
+ val = ppp->flags | ppp->xstate | ppp->rstate;
+ if (put_user(val, (int *) arg))
+ break;
+ err = 0;
+ break;
+
+ case PPPIOCSCOMPRESS:
+ err = ppp_set_compress(ppp, arg);
+ break;
+
+ case PPPIOCGUNIT:
+ if (put_user(ppp->index, (int *) arg))
+ break;
+ err = 0;
+ break;
+
+ case PPPIOCSDEBUG:
+ if (get_user(val, (int *) arg))
+ break;
+ ppp->debug = val;
+ err = 0;
+ break;
+
+ case PPPIOCGDEBUG:
+ if (put_user(ppp->debug, (int *) arg))
+ break;
+ err = 0;
+ break;
+
+ case PPPIOCGIDLE:
+ idle.xmit_idle = (jiffies - ppp->last_xmit) / HZ;
+ idle.recv_idle = (jiffies - ppp->last_recv) / HZ;
+ if (copy_to_user((void *) arg, &idle, sizeof(idle)))
+ break;
+ err = 0;
+ break;
+
+ case PPPIOCSMAXCID:
+ if (get_user(val, (int *) arg))
+ break;
+ val2 = 15;
+ if ((val >> 16) != 0) {
+ val2 = val >> 16;
+ val &= 0xffff;
+ }
+ lock_xmit_path(ppp);
+ lock_recv_path(ppp);
+ if (ppp->vj != 0)
+ slhc_free(ppp->vj);
+ ppp->vj = slhc_init(val2+1, val+1);
+ ppp_recv_unlock(ppp);
+ ppp_xmit_unlock(ppp);
+ err = -ENOMEM;
+ if (ppp->vj == 0) {
+ printk(KERN_ERR "PPP: no memory (VJ compressor)\n");
+ break;
+ }
+ err = 0;
+ break;
+
+ case PPPIOCGNPMODE:
+ case PPPIOCSNPMODE:
+ if (copy_from_user(&npi, (void *) arg, sizeof(npi)))
+ break;
+ err = proto_to_npindex(npi.protocol);
+ if (err < 0)
+ break;
+ i = err;
+ if (cmd == PPPIOCGNPMODE) {
+ err = -EFAULT;
+ npi.mode = ppp->npmode[i];
+ if (copy_to_user((void *) arg, &npi, sizeof(npi)))
+ break;
+ } else {
+ ppp->npmode[i] = npi.mode;
+ /* we may be able to transmit more packets now (??) */
+ mark_bh(NET_BH);
+ }
+ err = 0;
+ break;
+
+ default:
+ err = -ENOTTY;
+ }
+ return err;
+}
+
+static struct file_operations ppp_device_fops = {
+ NULL, /* seek */
+ ppp_read,
+ ppp_write,
+ NULL, /* readdir */
+ ppp_poll,
+ ppp_ioctl,
+ NULL, /* mmap */
+ ppp_open,
+ NULL, /* flush */
+ ppp_release
+};
+
+#define PPP_MAJOR 108
+
+/* Called at boot time if ppp is compiled into the kernel,
+ or at module load time (from init_module) if compiled as a module. */
+int
+ppp_init(struct device *dev)
+{
+ int err;
+#ifndef MODULE
+ extern struct compressor ppp_deflate, ppp_deflate_draft;
+ extern int ppp_async_init(void);
+#endif
+
+ printk(KERN_INFO "PPP generic driver version " PPP_VERSION "\n");
+ err = register_chrdev(PPP_MAJOR, "ppp", &ppp_device_fops);
+ if (err)
+ printk(KERN_ERR "failed to register PPP device (%d)\n", err);
+#ifndef MODULE
+#ifdef CONFIG_PPP_ASYNC
+ ppp_async_init();
+#endif
+#ifdef CONFIG_PPP_DEFLATE
+ if (ppp_register_compressor(&ppp_deflate) == 0)
+ printk(KERN_INFO "PPP Deflate compression module registered\n");
+ ppp_register_compressor(&ppp_deflate_draft);
+#endif
+#endif /* MODULE */
+
+ return -ENODEV;
+}
+
+/*
+ * Network interface unit routines.
+ */
+static int
+ppp_start_xmit(struct sk_buff *skb, struct device *dev)
+{
+ struct ppp *ppp = (struct ppp *) dev->priv;
+ int npi, proto;
+ unsigned char *pp;
+
+ if (skb == 0)
+ return 0;
+ /* can skb->data ever be 0? */
+
+ npi = ethertype_to_npindex(ntohs(skb->protocol));
+ if (npi < 0)
+ goto outf;
+
+ /* Drop, accept or reject the packet */
+ switch (ppp->npmode[npi]) {
+ case NPMODE_PASS:
+ break;
+ case NPMODE_QUEUE:
+ /* it would be nice to have a way to tell the network
+ system to queue this one up for later. */
+ goto outf;
+ case NPMODE_DROP:
+ case NPMODE_ERROR:
+ goto outf;
+ }
+
+ /* The transmit side of the ppp interface is serialized by
+ the XMIT_BUSY bit in ppp->busy. */
+ if (!trylock_xmit_path(ppp)) {
+ dev->tbusy = 1;
+ return 1;
+ }
+ if (ppp->xmit_pending)
+ ppp_push(ppp);
+ if (ppp->xmit_pending) {
+ dev->tbusy = 1;
+ ppp_xmit_unlock(ppp);
+ return 1;
+ }
+ dev->tbusy = 0;
+
+ /* Put the 2-byte PPP protocol number on the front,
+ making sure there is room for the address and control fields. */
+ if (skb_headroom(skb) < PPP_HDRLEN) {
+ struct sk_buff *ns;
+
+ ns = alloc_skb(skb->len + PPP_HDRLEN, GFP_ATOMIC);
+ if (ns == 0)
+ goto outnbusy;
+ skb_reserve(ns, PPP_HDRLEN);
+ memcpy(skb_put(ns, skb->len), skb->data, skb->len);
+ kfree_skb(skb);
+ skb = ns;
+ }
+ pp = skb_push(skb, 2);
+ proto = npindex_to_proto[npi];
+ pp[0] = proto >> 8;
+ pp[1] = proto;
+
+ ppp_send_frame(ppp, skb);
+ ppp_xmit_unlock(ppp);
+ return 0;
+
+ outnbusy:
+ ppp_xmit_unlock(ppp);
+
+ outf:
+ kfree_skb(skb);
+ return 0;
+}
+
+static struct net_device_stats *
+ppp_net_stats(struct device *dev)
+{
+ struct ppp *ppp = (struct ppp *) dev->priv;
+
+ return &ppp->stats;
+}
+
+static int
+ppp_net_ioctl(struct device *dev, struct ifreq *ifr, int cmd)
+{
+ struct ppp *ppp = dev->priv;
+ int err = -EFAULT;
+ void *addr = (void *) ifr->ifr_ifru.ifru_data;
+ struct ppp_stats stats;
+ struct ppp_comp_stats cstats;
+ char *vers;
+
+ switch (cmd) {
+ case SIOCGPPPSTATS:
+ ppp_get_stats(ppp, &stats);
+ if (copy_to_user(addr, &stats, sizeof(stats)))
+ break;
+ err = 0;
+ break;
+
+ case SIOCGPPPCSTATS:
+ memset(&cstats, 0, sizeof(cstats));
+ if (ppp->xc_state != 0)
+ ppp->xcomp->comp_stat(ppp->xc_state, &cstats.c);
+ if (ppp->rc_state != 0)
+ ppp->rcomp->decomp_stat(ppp->rc_state, &cstats.d);
+ if (copy_to_user(addr, &cstats, sizeof(cstats)))
+ break;
+ err = 0;
+ break;
+
+ case SIOCGPPPVER:
+ vers = PPP_VERSION;
+ if (copy_to_user(addr, vers, strlen(vers) + 1))
+ break;
+ err = 0;
+ break;
+
+ default:
+ err = -EINVAL;
+ }
+
+ return err;
+}
+
+int
+ppp_net_init(struct device *dev)
+{
+ dev->hard_header_len = PPP_HDRLEN;
+ dev->mtu = PPP_MTU;
+ dev->hard_start_xmit = ppp_start_xmit;
+ dev->get_stats = ppp_net_stats;
+ dev->do_ioctl = ppp_net_ioctl;
+ dev->addr_len = 0;
+ dev->tx_queue_len = 3;
+ dev->type = ARPHRD_PPP;
+ dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
+
+ dev_init_buffers(dev);
+ return 0;
+}
+
+/*
+ * Transmit-side routines.
+ */
+
+/*
+ * Called to unlock the transmit side of the ppp unit,
+ * making sure that any work queued up gets done.
+ */
+static void
+ppp_xmit_unlock(struct ppp *ppp)
+{
+ struct sk_buff *skb;
+
+ for (;;) {
+ if (test_and_clear_bit(XMIT_WAKEUP, &ppp->busy))
+ ppp_push(ppp);
+ while (ppp->xmit_pending == 0
+ && (skb = skb_dequeue(&ppp->xq)) != 0)
+ ppp_send_frame(ppp, skb);
+ unlock_xmit_path(ppp);
+ if (!(test_bit(XMIT_WAKEUP, &ppp->busy)
+ || (ppp->xmit_pending == 0 && skb_peek(&ppp->xq))))
+ break;
+ if (!trylock_xmit_path(ppp))
+ break;
+ }
+}
+
+/*
+ * Compress and send a frame.
+ * The caller should have locked the xmit path,
+ * and xmit_pending should be 0.
+ */
+static void
+ppp_send_frame(struct ppp *ppp, struct sk_buff *skb)
+{
+ int proto = PPP_PROTO(skb);
+ struct sk_buff *new_skb;
+ int len;
+ unsigned char *cp;
+
+ ++ppp->stats.tx_packets;
+ ppp->stats.tx_bytes += skb->len - 2;
+
+ switch (proto) {
+ case PPP_IP:
+ if (ppp->vj == 0 || (ppp->flags & SC_COMP_TCP) == 0)
+ break;
+ /* try to do VJ TCP header compression */
+ new_skb = alloc_skb(skb->len + 2, GFP_ATOMIC);
+ if (new_skb == 0) {
+ printk(KERN_ERR "PPP: no memory (VJ comp pkt)\n");
+ goto drop;
+ }
+ skb_reserve(new_skb, 2);
+ cp = skb->data + 2;
+ len = slhc_compress(ppp->vj, cp, skb->len - 2,
+ new_skb->data + 2, &cp,
+ !(ppp->flags & SC_NO_TCP_CCID));
+ if (cp == skb->data + 2) {
+ /* didn't compress */
+ kfree_skb(new_skb);
+ } else {
+ if (cp[0] & SL_TYPE_COMPRESSED_TCP) {
+ proto = PPP_VJC_COMP;
+ cp[0] &= ~SL_TYPE_COMPRESSED_TCP;
+ } else {
+ proto = PPP_VJC_UNCOMP;
+ cp[0] = skb->data[2];
+ }
+ kfree_skb(skb);
+ skb = new_skb;
+ cp = skb_put(skb, len + 2);
+ cp[0] = 0;
+ cp[1] = proto;
+ }
+ break;
+
+ case PPP_CCP:
+ /* peek at outbound CCP frames */
+ ppp_ccp_peek(ppp, skb, 0);
+ break;
+ }
+
+ /* try to do packet compression */
+ if ((ppp->xstate & SC_COMP_RUN) && ppp->xc_state != 0
+ && proto != PPP_LCP && proto != PPP_CCP) {
+ new_skb = alloc_skb(ppp->dev.mtu + PPP_HDRLEN, GFP_ATOMIC);
+ if (new_skb == 0) {
+ printk(KERN_ERR "PPP: no memory (comp pkt)\n");
+ goto drop;
+ }
+
+ /* compressor still expects A/C bytes in hdr */
+ len = ppp->xcomp->compress(ppp->xc_state, skb->data - 2,
+ new_skb->data, skb->len + 2,
+ ppp->dev.mtu + PPP_HDRLEN);
+ if (len > 0 && (ppp->flags & SC_CCP_UP)) {
+ kfree_skb(skb);
+ skb = new_skb;
+ skb_put(skb, len);
+ skb_pull(skb, 2); /* pull off A/C bytes */
+ } else {
+ /* didn't compress, or CCP not up yet */
+ kfree_skb(new_skb);
+ }
+ }
+
+ /*
+ * If we are waiting for traffic (demand dialling),
+ * queue it up for pppd to receive.
+ */
+ if (ppp->flags & SC_LOOP_TRAFFIC) {
+ if (ppp->rq.qlen > PPP_MAX_RQLEN)
+ goto drop;
+ skb_queue_tail(&ppp->rq, skb);
+ wake_up_interruptible(&ppp->rwait);
+ return;
+ }
+
+ ppp->xmit_pending = skb;
+ ppp_push(ppp);
+ return;
+
+ drop:
+ kfree_skb(skb);
+ ++ppp->stats.tx_errors;
+}
+
+/*
+ * Try to send the frame in xmit_pending.
+ * The caller should have the xmit path locked.
+ */
+static void
+ppp_push(struct ppp *ppp)
+{
+ struct list_head *list;
+ struct channel *chan;
+ struct sk_buff *skb = ppp->xmit_pending;
+
+ if (skb == 0)
+ return;
+
+ list = &ppp->channels;
+ if (list_empty(list)) {
+ /* nowhere to send the packet, just drop it */
+ ppp->xmit_pending = 0;
+ kfree_skb(skb);
+ return;
+ }
+
+ /* If we are doing multilink, decide which channel gets the
+ packet, and/or fragment the packet over several links. */
+ /* XXX for now, just take the first channel */
+ list = list->next;
+ chan = list_entry(list, struct channel, list);
+
+ if (chan->chan->ops->start_xmit(chan->chan, skb)) {
+ ppp->xmit_pending = 0;
+ chan->blocked = 0;
+ } else
+ chan->blocked = 1;
+}
+
+/*
+ * Receive-side routines.
+ */
+static inline void
+ppp_do_recv(struct ppp *ppp, struct sk_buff *skb)
+{
+ skb_queue_tail(&ppp->recv_pending, skb);
+ if (trylock_recv_path(ppp))
+ ppp_recv_unlock(ppp);
+}
+
+void
+ppp_input(struct ppp_channel *chan, struct sk_buff *skb)
+{
+ struct channel *pch = chan->ppp;
+
+ if (pch == 0 || skb->len == 0) {
+ kfree_skb(skb);
+ return;
+ }
+ ppp_do_recv(pch->ppp, skb);
+}
+
+/* Put a 0-length skb in the receive queue as an error indication */
+void
+ppp_input_error(struct ppp_channel *chan, int code)
+{
+ struct channel *pch = chan->ppp;
+ struct sk_buff *skb;
+
+ if (pch == 0)
+ return;
+ skb = alloc_skb(0, GFP_ATOMIC);
+ if (skb == 0)
+ return;
+ skb->len = 0; /* probably unnecessary */
+ skb->cb[0] = code;
+ ppp_do_recv(pch->ppp, skb);
+}
+
+static void
+ppp_recv_unlock(struct ppp *ppp)
+{
+ struct sk_buff *skb;
+
+ for (;;) {
+ while ((skb = skb_dequeue(&ppp->recv_pending)) != 0)
+ ppp_receive_frame(ppp, skb);
+ unlock_recv_path(ppp);
+ if (skb_peek(&ppp->recv_pending) == 0)
+ break;
+ if (!trylock_recv_path(ppp))
+ break;
+ }
+}
+
+static void
+ppp_receive_frame(struct ppp *ppp, struct sk_buff *skb)
+{
+ struct sk_buff *ns;
+ int proto, len, npi;
+
+ if (skb->len == 0) {
+ /* XXX should do something with code in skb->cb[0] */
+ goto err; /* error indication */
+ }
+
+ if (skb->len < 2) {
+ ++ppp->stats.rx_length_errors;
+ goto err;
+ }
+
+ /* Decompress the frame, if compressed. */
+ if (ppp->rc_state != 0 && (ppp->rstate & SC_DECOMP_RUN)
+ && (ppp->rstate & (SC_DC_FERROR | SC_DC_ERROR)) == 0)
+ skb = ppp_decompress_frame(ppp, skb);
+
+ proto = PPP_PROTO(skb);
+ switch (proto) {
+ case PPP_VJC_COMP:
+ /* decompress VJ compressed packets */
+ if (ppp->vj == 0 || (ppp->flags & SC_REJ_COMP_TCP))
+ goto err;
+ if (skb_tailroom(skb) < 124) {
+ /* copy to a new sk_buff with more tailroom */
+ ns = alloc_skb(skb->len + 128, GFP_ATOMIC);
+ if (ns == 0) {
+ printk(KERN_ERR"PPP: no memory (VJ decomp)\n");
+ goto err;
+ }
+ skb_reserve(ns, 2);
+ memcpy(skb_put(ns, skb->len), skb->data, skb->len);
+ kfree_skb(skb);
+ skb = ns;
+ }
+ len = slhc_uncompress(ppp->vj, skb->data + 2, skb->len - 2);
+ if (len <= 0) {
+ printk(KERN_ERR "PPP: VJ decompression error\n");
+ goto err;
+ }
+ len += 2;
+ if (len > skb->len)
+ skb_put(skb, len - skb->len);
+ else if (len < skb->len)
+ skb_trim(skb, len);
+ proto = PPP_IP;
+ break;
+
+ case PPP_VJC_UNCOMP:
+ if (ppp->vj == 0 || (ppp->flags & SC_REJ_COMP_TCP))
+ goto err;
+ if (slhc_remember(ppp->vj, skb->data + 2, skb->len - 2) <= 0) {
+ printk(KERN_ERR "PPP: VJ uncompressed error\n");
+ goto err;
+ }
+ proto = PPP_IP;
+ break;
+
+ case PPP_CCP:
+ ppp_ccp_peek(ppp, skb, 1);
+ break;
+ }
+
+ ++ppp->stats.rx_packets;
+ ppp->stats.rx_bytes += skb->len - 2;
+
+ npi = proto_to_npindex(proto);
+ if (npi < 0) {
+ /* control or unknown frame - pass it to pppd */
+ skb_queue_tail(&ppp->rq, skb);
+ /* limit queue length by dropping old frames */
+ while (ppp->rq.qlen > PPP_MAX_RQLEN) {
+ skb = skb_dequeue(&ppp->rq);
+ if (skb)
+ kfree_skb(skb);
+ }
+ /* wake up any process polling or blocking on read */
+ wake_up_interruptible(&ppp->rwait);
+
+ } else {
+ /* network protocol frame - give it to the kernel */
+ ppp->last_recv = jiffies;
+ if ((ppp->dev.flags & IFF_UP) == 0
+ || ppp->npmode[npi] != NPMODE_PASS) {
+ kfree_skb(skb);
+ } else {
+ skb_pull(skb, 2); /* chop off protocol */
+ skb->dev = &ppp->dev;
+ skb->protocol = htons(npindex_to_ethertype[npi]);
+ skb->mac.raw = skb->data;
+ netif_rx(skb);
+ }
+ }
+ return;
+
+ err:
+ ++ppp->stats.rx_errors;
+ if (ppp->vj != 0)
+ slhc_toss(ppp->vj);
+ kfree_skb(skb);
+}
+
+static struct sk_buff *
+ppp_decompress_frame(struct ppp *ppp, struct sk_buff *skb)
+{
+ int proto = PPP_PROTO(skb);
+ struct sk_buff *ns;
+ int len;
+
+ if (proto == PPP_COMP) {
+ ns = alloc_skb(ppp->mru + PPP_HDRLEN, GFP_ATOMIC);
+ if (ns == 0) {
+ printk(KERN_ERR "ppp_receive: no memory\n");
+ goto err;
+ }
+ /* the decompressor still expects the A/C bytes in the hdr */
+ len = ppp->rcomp->decompress(ppp->rc_state, skb->data - 2,
+ skb->len + 2, ns->data, ppp->mru + PPP_HDRLEN);
+ if (len < 0) {
+ /* Pass the compressed frame to pppd as an
+ error indication. */
+ if (len == DECOMP_FATALERROR)
+ ppp->rstate |= SC_DC_FERROR;
+ goto err;
+ }
+
+ kfree_skb(skb);
+ skb = ns;
+ skb_put(skb, len);
+ skb_pull(skb, 2); /* pull off the A/C bytes */
+
+ } else {
+ /* Uncompressed frame - pass to decompressor so it
+ can update its dictionary if necessary. */
+ if (ppp->rcomp->incomp)
+ ppp->rcomp->incomp(ppp->rc_state, skb->data - 2,
+ skb->len + 2);
+ }
+
+ return skb;
+
+ err:
+ ppp->rstate |= SC_DC_ERROR;
+ if (ppp->vj != 0)
+ slhc_toss(ppp->vj);
+ ++ppp->stats.rx_errors;
+ return skb;
+}
+
+/*
+ * Channel interface.
+ */
+
+/*
+ * Connect a channel to a given PPP unit.
+ * The channel MUST NOT be connected to a PPP unit already.
+ */
+int
+ppp_register_channel(struct ppp_channel *chan, int unit)
+{
+ struct ppp *ppp;
+ struct channel *pch;
+ int ret = -ENXIO;
+
+ spin_lock(&all_ppp_lock);
+ ppp = ppp_find_unit(unit);
+ if (ppp == 0)
+ goto out;
+ pch = kmalloc(sizeof(struct channel), GFP_ATOMIC);
+ ret = -ENOMEM;
+ if (pch == 0)
+ goto out;
+ memset(pch, 0, sizeof(struct channel));
+ pch->ppp = ppp;
+ pch->chan = chan;
+ list_add(&pch->list, &ppp->channels);
+ chan->ppp = pch;
+ ++ppp->n_channels;
+ ret = 0;
+ out:
+ spin_unlock(&all_ppp_lock);
+ return ret;
+}
+
+/*
+ * Disconnect a channel from its PPP unit.
+ */
+void
+ppp_unregister_channel(struct ppp_channel *chan)
+{
+ struct channel *pch;
+
+ spin_lock(&all_ppp_lock);
+ if ((pch = chan->ppp) != 0) {
+ chan->ppp = 0;
+ list_del(&pch->list);
+ --pch->ppp->n_channels;
+ kfree(pch);
+ }
+ spin_unlock(&all_ppp_lock);
+}
+
+/*
+ * Callback from a channel when it can accept more to transmit.
+ * This should ideally be called at BH level, not interrupt level.
+ */
+void
+ppp_output_wakeup(struct ppp_channel *chan)
+{
+ struct channel *pch = chan->ppp;
+ struct ppp *ppp;
+
+ if (pch == 0)
+ return;
+ ppp = pch->ppp;
+ pch->blocked = 0;
+ set_bit(XMIT_WAKEUP, &ppp->busy);
+ if (trylock_xmit_path(ppp))
+ ppp_xmit_unlock(ppp);
+ if (ppp->xmit_pending == 0) {
+ ppp->dev.tbusy = 0;
+ mark_bh(NET_BH);
+ }
+}
+
+/*
+ * Compression control.
+ */
+
+/* Process the PPPIOCSCOMPRESS ioctl. */
+static int
+ppp_set_compress(struct ppp *ppp, unsigned long arg)
+{
+ int err;
+ struct compressor *cp;
+ struct ppp_option_data data;
+ unsigned char ccp_option[CCP_MAX_OPTION_LENGTH];
+#ifdef CONFIG_KMOD
+ char modname[32];
+#endif
+
+ err = -EFAULT;
+ if (copy_from_user(&data, (void *) arg, sizeof(data))
+ || (data.length <= CCP_MAX_OPTION_LENGTH
+ && copy_from_user(ccp_option, data.ptr, data.length)))
+ goto out;
+ err = -EINVAL;
+ if (data.length > CCP_MAX_OPTION_LENGTH
+ || ccp_option[1] < 2 || ccp_option[1] > data.length)
+ goto out;
+
+ cp = find_compressor(ccp_option[0]);
+#ifdef CONFIG_KMOD
+ if (cp == 0) {
+ sprintf(modname, "ppp-compress-%d", ccp_option[0]);
+ request_module(modname);
+ cp = find_compressor(ccp_option[0]);
+ }
+#endif /* CONFIG_KMOD */
+ if (cp == 0)
+ goto out;
+
+ err = -ENOBUFS;
+ if (data.transmit) {
+ lock_xmit_path(ppp);
+ ppp->xstate &= ~SC_COMP_RUN;
+ if (ppp->xc_state != 0) {
+ ppp->xcomp->comp_free(ppp->xc_state);
+ ppp->xc_state = 0;
+ }
+
+ ppp->xcomp = cp;
+ ppp->xc_state = cp->comp_alloc(ccp_option, data.length);
+ ppp_xmit_unlock(ppp);
+ if (ppp->xc_state == 0)
+ goto out;
+
+ } else {
+ lock_recv_path(ppp);
+ ppp->rstate &= ~SC_DECOMP_RUN;
+ if (ppp->rc_state != 0) {
+ ppp->rcomp->decomp_free(ppp->rc_state);
+ ppp->rc_state = 0;
+ }
+
+ ppp->rcomp = cp;
+ ppp->rc_state = cp->decomp_alloc(ccp_option, data.length);
+ ppp_recv_unlock(ppp);
+ if (ppp->rc_state == 0)
+ goto out;
+ }
+ err = 0;
+
+ out:
+ return err;
+}
+
+/*
+ * Look at a CCP packet and update our state accordingly.
+ * We assume the caller has the xmit or recv path locked.
+ */
+static void
+ppp_ccp_peek(struct ppp *ppp, struct sk_buff *skb, int inbound)
+{
+ unsigned char *dp = skb->data + 2;
+ int len;
+
+ if (skb->len < CCP_HDRLEN + 2
+ || skb->len < (len = CCP_LENGTH(dp)) + 2)
+ return; /* too short */
+
+ switch (CCP_CODE(dp)) {
+ case CCP_CONFREQ:
+ case CCP_TERMREQ:
+ case CCP_TERMACK:
+ /*
+ * CCP is going down - disable compression.
+ */
+ if (inbound)
+ ppp->rstate &= ~SC_DECOMP_RUN;
+ else
+ ppp->xstate &= ~SC_COMP_RUN;
+ break;
+
+ case CCP_CONFACK:
+ if ((ppp->flags & (SC_CCP_OPEN | SC_CCP_UP)) != SC_CCP_OPEN)
+ break;
+ dp += CCP_HDRLEN;
+ len -= CCP_HDRLEN;
+ if (len < CCP_OPT_MINLEN || len < CCP_OPT_LENGTH(dp))
+ break;
+ if (inbound) {
+ /* we will start receiving compressed packets */
+ if (ppp->rc_state == 0)
+ break;
+ if (ppp->rcomp->decomp_init(ppp->rc_state, dp, len,
+ ppp->index, 0, ppp->mru, ppp->debug)) {
+ ppp->rstate |= SC_DECOMP_RUN;
+ ppp->rstate &= ~(SC_DC_ERROR | SC_DC_FERROR);
+ }
+ } else {
+ /* we will soon start sending compressed packets */
+ if (ppp->xc_state == 0)
+ break;
+ if (ppp->xcomp->comp_init(ppp->xc_state, dp, len,
+ ppp->index, 0, ppp->debug))
+ ppp->xstate |= SC_COMP_RUN;
+ }
+ break;
+
+ case CCP_RESETACK:
+ /* reset the [de]compressor */
+ if ((ppp->flags & SC_CCP_UP) == 0)
+ break;
+ if (inbound) {
+ if (ppp->rc_state && (ppp->rstate & SC_DECOMP_RUN)) {
+ ppp->rcomp->decomp_reset(ppp->rc_state);
+ ppp->rstate &= ~SC_DC_ERROR;
+ }
+ } else {
+ if (ppp->xc_state && (ppp->xstate & SC_COMP_RUN))
+ ppp->xcomp->comp_reset(ppp->xc_state);
+ }
+ break;
+ }
+}
+
+/* Free up compression resources. */
+static void
+ppp_ccp_closed(struct ppp *ppp)
+{
+ ppp->flags &= ~(SC_CCP_OPEN | SC_CCP_UP);
+
+ lock_xmit_path(ppp);
+ ppp->xstate &= ~SC_COMP_RUN;
+ if (ppp->xc_state) {
+ ppp->xcomp->comp_free(ppp->xc_state);
+ ppp->xc_state = 0;
+ }
+ ppp_xmit_unlock(ppp);
+
+ lock_recv_path(ppp);
+ ppp->xstate &= ~SC_DECOMP_RUN;
+ if (ppp->rc_state) {
+ ppp->rcomp->decomp_free(ppp->rc_state);
+ ppp->rc_state = 0;
+ }
+ ppp_recv_unlock(ppp);
+}
+
+/* List of compressors. */
+static LIST_HEAD(compressor_list);
+static spinlock_t compressor_list_lock = SPIN_LOCK_UNLOCKED;
+
+struct compressor_entry {
+ struct list_head list;
+ struct compressor *comp;
+};
+
+static struct compressor_entry *
+find_comp_entry(int proto)
+{
+ struct compressor_entry *ce;
+ struct list_head *list = &compressor_list;
+
+ while ((list = list->next) != &compressor_list) {
+ ce = list_entry(list, struct compressor_entry, list);
+ if (ce->comp->compress_proto == proto)
+ return ce;
+ }
+ return 0;
+}
+
+/* Register a compressor */
+int
+ppp_register_compressor(struct compressor *cp)
+{
+ struct compressor_entry *ce;
+ int ret;
+
+ spin_lock(&compressor_list_lock);
+ ret = -EEXIST;
+ if (find_comp_entry(cp->compress_proto) != 0)
+ goto out;
+ ret = -ENOMEM;
+ ce = kmalloc(sizeof(struct compressor_entry), GFP_KERNEL);
+ if (ce == 0)
+ goto out;
+ ret = 0;
+ ce->comp = cp;
+ list_add(&ce->list, &compressor_list);
+ out:
+ spin_unlock(&compressor_list_lock);
+ return ret;
+}
+
+/* Unregister a compressor */
+void
+ppp_unregister_compressor(struct compressor *cp)
+{
+ struct compressor_entry *ce;
+
+ spin_lock(&compressor_list_lock);
+ ce = find_comp_entry(cp->compress_proto);
+ if (ce != 0 && ce->comp == cp) {
+ list_del(&ce->list);
+ kfree(ce);
+ }
+ spin_unlock(&compressor_list_lock);
+}
+
+/* Find a compressor. */
+static struct compressor *
+find_compressor(int type)
+{
+ struct compressor_entry *ce;
+ struct compressor *cp = 0;
+
+ spin_lock(&compressor_list_lock);
+ ce = find_comp_entry(type);
+ if (ce != 0)
+ cp = ce->comp;
+ spin_unlock(&compressor_list_lock);
+ return cp;
+}
+
+/*
+ * Miscelleneous stuff.
+ */
+
+static void
+ppp_get_stats(struct ppp *ppp, struct ppp_stats *st)
+{
+ struct slcompress *vj = ppp->vj;
+
+ memset(st, 0, sizeof(*st));
+ st->p.ppp_ipackets = ppp->stats.rx_packets;
+ st->p.ppp_ierrors = ppp->stats.rx_errors;
+ st->p.ppp_ibytes = ppp->stats.rx_bytes;
+ st->p.ppp_opackets = ppp->stats.tx_packets;
+ st->p.ppp_oerrors = ppp->stats.tx_errors;
+ st->p.ppp_obytes = ppp->stats.tx_bytes;
+ if (vj == 0)
+ return;
+ st->vj.vjs_packets = vj->sls_o_compressed + vj->sls_o_uncompressed;
+ st->vj.vjs_compressed = vj->sls_o_compressed;
+ st->vj.vjs_searches = vj->sls_o_searches;
+ st->vj.vjs_misses = vj->sls_o_misses;
+ st->vj.vjs_errorin = vj->sls_i_error;
+ st->vj.vjs_tossed = vj->sls_i_tossed;
+ st->vj.vjs_uncompressedin = vj->sls_i_uncompressed;
+ st->vj.vjs_compressedin = vj->sls_i_compressed;
+}
+
+/*
+ * Stuff for handling the list of ppp units and for initialization.
+ */
+
+/*
+ * Create a new ppp unit. Fails if it can't allocate memory or
+ * if there is already a unit with the requested number.
+ * unit == -1 means allocate a new number.
+ */
+static struct ppp *
+ppp_create_unit(int unit, int *retp)
+{
+ struct ppp *ppp;
+ struct list_head *list;
+ int last_unit = -1;
+ int ret = -EEXIST;
+ int i;
+
+ spin_lock(&all_ppp_lock);
+ list = &all_ppp_units;
+ while ((list = list->next) != &all_ppp_units) {
+ ppp = list_entry(list, struct ppp, list);
+ if ((unit < 0 && ppp->index > last_unit + 1)
+ || (unit >= 0 && unit < ppp->index))
+ break;
+ if (unit == ppp->index)
+ goto out; /* unit already exists */
+ last_unit = ppp->index;
+ }
+ if (unit < 0)
+ unit = last_unit + 1;
+
+ /* Create a new ppp structure and link it before `list'. */
+ ret = -ENOMEM;
+ ppp = kmalloc(sizeof(struct ppp), GFP_KERNEL);
+ if (ppp == 0)
+ goto out;
+ memset(ppp, 0, sizeof(struct ppp));
+
+ ppp->index = unit;
+ sprintf(ppp->name, "ppp%d", unit);
+ ppp->mru = PPP_MRU;
+ skb_queue_head_init(&ppp->xq);
+ skb_queue_head_init(&ppp->rq);
+ init_waitqueue_head(&ppp->rwait);
+ ppp->refcnt = 1;
+ for (i = 0; i < NUM_NP; ++i)
+ ppp->npmode[i] = NPMODE_PASS;
+ INIT_LIST_HEAD(&ppp->channels);
+ skb_queue_head_init(&ppp->recv_pending);
+
+ ppp->dev.init = ppp_net_init;
+ ppp->dev.name = ppp->name;
+ ppp->dev.priv = ppp;
+
+ ret = register_netdev(&ppp->dev);
+ if (ret != 0) {
+ printk(KERN_ERR "PPP: couldn't register device (%d)\n", ret);
+ kfree(ppp);
+ goto out;
+ }
+
+ list_add(&ppp->list, list->prev);
+ out:
+ spin_unlock(&all_ppp_lock);
+ *retp = ret;
+ if (ret != 0)
+ ppp = 0;
+ return ppp;
+}
+
+/*
+ * Locate an existing ppp unit.
+ * The caller should have locked the all_ppp_lock.
+ */
+static struct ppp *
+ppp_find_unit(int unit)
+{
+ struct ppp *ppp;
+ struct list_head *list;
+
+ list = &all_ppp_units;
+ while ((list = list->next) != &all_ppp_units) {
+ ppp = list_entry(list, struct ppp, list);
+ if (ppp->index == unit)
+ return ppp;
+ }
+ return 0;
+}
+
+/*
+ * Module stuff.
+ */
+#ifdef MODULE
+int
+init_module(void)
+{
+ ppp_init(0);
+ return 0;
+}
+
+void
+cleanup_module(void)
+{
+ /* should never happen */
+ if (all_ppp_units.next != &all_ppp_units)
+ printk(KERN_ERR "PPP: removing module but units remain!\n");
+ if (unregister_chrdev(PPP_MAJOR, "ppp") != 0)
+ printk(KERN_ERR "PPP: failed to unregister PPP device\n");
+}
+#endif /* MODULE */
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)