/* Linux ISDN subsystem, functions for synchronous PPP (linklevel). * * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) * 1999-2002 by Kai Germaschewski <kai@germaschewski.name> * * This software may be used and distributed according to the terms * of the GNU General Public License, incorporated herein by reference. */ #include <linux/module.h> #include <linux/isdn.h> #include <linux/smp_lock.h> #include <linux/poll.h> #include <linux/ppp-comp.h> #include <linux/if_arp.h> #include "isdn_common.h" #include "isdn_net_lib.h" #include "isdn_ppp.h" #include "isdn_ppp_ccp.h" #include "isdn_ppp_vj.h" #include "isdn_ppp_mp.h" /* ====================================================================== */ #define IPPP_MAX_RQ_LEN 8 /* max #frames queued for ipppd to read */ static int isdn_ppp_set_compressor(isdn_net_dev *idev, struct isdn_ppp_comp_data *); /* ====================================================================== */ /* IPPPD handling */ /* ====================================================================== */ /* We use reference counting for struct ipppd. It is alloced on * open() on /dev/ipppX and saved into file->private, making for one * reference. release() will release this reference, after all other * references are gone, the destructor frees it. * * Another reference is taken by isdn_ppp_bind() and freed by * isdn_ppp_unbind(). The callbacks from isdn_net_lib.c happen only * between isdn_ppp_bind() and isdn_ppp_unbind(), i.e. access to * idev->ipppd is safe without further locking. */ #undef IPPPD_DEBUG #ifdef IPPPD_DEBUG #define ipppd_debug(i, fmt, arg...) \ printk(KERN_DEBUG "ipppd %p minor %d state %#x %s: " fmt "\n", (i), \ (i)->minor, (i)->state, __FUNCTION__ , ## arg) #else #define ipppd_debug(...) do { } while (0) #endif /* ipppd::flags */ enum { IPPPD_FL_HUP = 0x01, IPPPD_FL_WAKEUP = 0x02, }; /* ipppd::state */ enum { IPPPD_ST_OPEN, IPPPD_ST_ASSIGNED, IPPPD_ST_CONNECTED, }; struct ipppd { struct list_head ipppds; int state; int flags; struct sk_buff_head rq; wait_queue_head_t wq; struct isdn_net_dev_s *idev; int unit; int minor; unsigned long debug; atomic_t refcnt; }; /* ====================================================================== */ static spinlock_t ipppds_lock = SPIN_LOCK_UNLOCKED; static LIST_HEAD(ipppds); static void ipppd_destroy(struct ipppd *ipppd) { HERE; skb_queue_purge(&ipppd->rq); kfree(ipppd); } static inline struct ipppd * ipppd_get(struct ipppd *ipppd) { atomic_inc(&ipppd->refcnt); printk("%s: %d\n", __FUNCTION__, atomic_read(&ipppd->refcnt)); return ipppd; } static inline void ipppd_put(struct ipppd *ipppd) { printk("%s: %d\n", __FUNCTION__, atomic_read(&ipppd->refcnt)); if (atomic_dec_and_test(&ipppd->refcnt)) ipppd_destroy(ipppd); } /* ====================================================================== */ /* char dev ops */ /* --- open ------------------------------------------------------------- */ static int ipppd_open(struct inode *ino, struct file *file) { unsigned long flags; unsigned int minor = minor(ino->i_rdev) - ISDN_MINOR_PPP; struct ipppd *ipppd; ipppd = kmalloc(sizeof(*ipppd), GFP_KERNEL); if (!ipppd) return -ENOMEM; memset(ipppd, 0, sizeof(*ipppd)); atomic_set(&ipppd->refcnt, 0); /* file->private_data holds a reference */ file->private_data = ipppd_get(ipppd); ipppd->unit = -1; /* set by isdn_ppp_bind */ ipppd->minor = minor; ipppd->state = IPPPD_ST_OPEN; init_waitqueue_head(&ipppd->wq); skb_queue_head_init(&ipppd->rq); spin_lock_irqsave(&ipppds, flags); list_add(&ipppd->ipppds, &ipppds); spin_unlock_irqrestore(&ipppds, flags); ipppd_debug(ipppd, "minor %d", minor); return 0; } /* --- release --------------------------------------------------------- */ static int ipppd_release(struct inode *ino, struct file *file) { unsigned long flags; struct ipppd *ipppd = file->private_data; ipppd_debug(ipppd, ""); if (ipppd->state == IPPPD_ST_CONNECTED) isdn_net_hangup(ipppd->idev); spin_lock_irqsave(&ipppds, flags); list_del(&ipppd->ipppds); spin_unlock_irqrestore(&ipppds, flags); ipppd_put(ipppd); return 0; } /* --- read ------------------------------------------------------------- */ /* read() is always non blocking */ static ssize_t ipppd_read(struct file *file, char *buf, size_t count, loff_t *off) { struct ipppd *is; struct sk_buff *skb; int retval; if (off != &file->f_pos) return -ESPIPE; is = file->private_data; skb = skb_dequeue(&is->rq); if (!skb) { retval = -EAGAIN; goto out; } if (skb->len > count) { retval = -EMSGSIZE; goto out_free; } if (copy_to_user(buf, skb->data, skb->len)) { retval = -EFAULT; goto out_free; } retval = skb->len; out_free: dev_kfree_skb(skb); out: return retval; } /* --- write ------------------------------------------------------------ */ /* write() is always non blocking */ static ssize_t ipppd_write(struct file *file, const char *buf, size_t count, loff_t *off) { isdn_net_dev *idev; struct inl_ppp *inl_ppp; struct ind_ppp *ind_ppp; struct ipppd *ipppd; struct sk_buff *skb; char *p; int retval; u16 proto; if (off != &file->f_pos) return -ESPIPE; ipppd = file->private_data; ipppd_debug(ipppd, "count = %d", count); if (ipppd->state != IPPPD_ST_CONNECTED) { retval = -ENOTCONN; goto out; } idev = ipppd->idev; if (!idev) { isdn_BUG(); retval = -ENODEV; goto out; } ind_ppp = idev->ind_priv; inl_ppp = idev->mlp->inl_priv; /* Daemon needs to send at least full header, AC + proto */ if (count < 4) { retval = -EMSGSIZE; goto out; } skb = isdn_ppp_dev_alloc_skb(idev, count, GFP_KERNEL); if (!skb) { retval = -ENOMEM; goto out; } p = skb_put(skb, count); if (copy_from_user(p, buf, count)) { kfree_skb(skb); retval = -EFAULT; goto out; } /* Don't reset huptimer for LCP packets. (Echo requests). */ proto = PPP_PROTOCOL(p); if (proto != PPP_LCP) idev->huptimer = 0; /* Keeps CCP/compression states in sync */ switch (proto) { case PPP_CCP: ippp_ccp_send_ccp(inl_ppp->ccp, skb); break; case PPP_CCPFRAG: ippp_ccp_send_ccp(ind_ppp->ccp, skb); break; } /* FIXME: Somewhere we need protection against the * queue growing too large */ isdn_net_write_super(idev, skb); retval = count; out: return retval; } /* --- poll ------------------------------------------------------------- */ static unsigned int ipppd_poll(struct file *file, poll_table * wait) { unsigned int mask; struct ipppd *is; is = file->private_data; ipppd_debug(is, ""); /* just registers wait_queue hook. This doesn't really wait. */ poll_wait(file, &is->wq, wait); if (is->flags & IPPPD_FL_HUP) { mask = POLLHUP; goto out; } /* we're always ready to send .. */ mask = POLLOUT | POLLWRNORM; /* * if IPPP_FL_WAKEUP is set we return even if we have nothing to read */ if (!skb_queue_empty(&is->rq) || is->flags & IPPPD_FL_WAKEUP) { is->flags &= ~IPPPD_FL_WAKEUP; mask |= POLLIN | POLLRDNORM; } out: return mask; } /* --- ioctl ------------------------------------------------------------ */ /* get_arg .. ioctl helper */ static int get_arg(unsigned long arg, void *val, int len) { if (copy_from_user((void *) val, (void *) arg, len)) return -EFAULT; return 0; } /* set arg .. ioctl helper */ static int set_arg(unsigned long arg, void *val,int len) { if (copy_to_user((void *) arg, (void *) val, len)) return -EFAULT; return 0; } static int ipppd_ioctl(struct inode *ino, struct file *file, unsigned int cmd, unsigned long arg) { isdn_net_dev *idev; struct ind_ppp *ind_ppp = NULL; struct inl_ppp *inl_ppp = NULL; unsigned long val; int r; struct ipppd *is; struct isdn_ppp_comp_data data; unsigned int cfg; is = file->private_data; ipppd_debug(is, "cmd %#x", cmd); // FIXME that needs locking? idev = is->idev; if (idev) { ind_ppp = idev->ind_priv; inl_ppp = idev->mlp->inl_priv; } switch (cmd) { case PPPIOCGUNIT: /* get ppp/isdn unit number */ r = set_arg(arg, &is->unit, sizeof(is->unit)); break; case PPPIOCGDEBUG: r = set_arg(arg, &is->debug, sizeof(is->debug)); break; case PPPIOCSDEBUG: r = get_arg(arg, &val, sizeof(val)); if (r) break; is->debug = val; if (idev) { ind_ppp->debug = val; inl_ppp->debug = val; } break; case PPPIOCGCOMPRESSORS: { unsigned long protos[8]; ippp_ccp_get_compressors(protos); r = set_arg(arg, protos, sizeof(protos)); break; } default: r = -ENOTTY; break; } if (r != -ENOTTY) goto out; if (!idev) { r = -ENODEV; goto out; } switch (cmd) { case PPPIOCBUNDLE: r = get_arg(arg, &val, sizeof(val)); if (r) break; r = ippp_mp_bundle(idev, val); break; case PPPIOCGIFNAME: r = set_arg(arg, idev->name, strlen(idev->name)+1); break; case PPPIOCGMPFLAGS: /* get configuration flags */ r = set_arg(arg, &inl_ppp->mp_cfg, sizeof(inl_ppp->mp_cfg)); break; case PPPIOCSMPFLAGS: /* set configuration flags */ r = get_arg(arg, &val, sizeof(val)); if (r) break; inl_ppp->mp_cfg = val; break; case PPPIOCGFLAGS: /* get configuration flags */ cfg = ind_ppp->pppcfg | ippp_ccp_get_flags(ind_ppp->ccp); r = set_arg(arg, &cfg, sizeof(cfg)); break; case PPPIOCSFLAGS: /* set configuration flags */ r = get_arg(arg, &val, sizeof(val)); if (r) break; if ((val & SC_ENABLE_IP) && !(ind_ppp->pppcfg & SC_ENABLE_IP)) { ind_ppp->pppcfg = val; /* OK .. we are ready to send buffers */ isdn_net_online(idev); break; } ind_ppp->pppcfg = val; break; case PPPIOCGIDLE: /* get idle time information */ { struct ppp_idle pidle; pidle.xmit_idle = pidle.recv_idle = idev->huptimer; r = set_arg(arg, &pidle,sizeof(pidle)); break; } case PPPIOCSMRU: /* set receive unit size for PPP */ r = get_arg(arg, &val, sizeof(val)); if (r) break; r = ippp_ccp_set_mru(ind_ppp->ccp, val); break; case PPPIOCSMPMRU: break; case PPPIOCSMPMTU: break; case PPPIOCSMAXCID: /* set the maximum compression slot id */ r = get_arg(arg, &val, sizeof(val)); if (r) break; r = ippp_vj_set_maxcid(idev, val); break; case PPPIOCSCOMPRESSOR: r = get_arg(arg, &data, sizeof(data)); if (r) break; r = isdn_ppp_set_compressor(idev, &data); break; case PPPIOCGCALLINFO: { isdn_net_local *mlp; struct isdn_net_phone *phone; struct pppcallinfo pci; int i; memset(&pci, 0, sizeof(pci)); mlp = idev->mlp; strncpy(pci.local_num, mlp->msn, 63); i = 0; list_for_each_entry(phone, &mlp->phone[1], list) { if (i++ == idev->dial) { strncpy(pci.remote_num,phone->num,63); break; } } pci.charge_units = idev->charge; if (idev->outgoing) pci.calltype = CALLTYPE_OUTGOING; else pci.calltype = CALLTYPE_INCOMING; if (mlp->flags & ISDN_NET_CALLBACK) pci.calltype |= CALLTYPE_CALLBACK; r = set_arg(arg, &pci, sizeof(pci)); break; } default: r = -ENOTTY; break; } out: return r; } /* --- fops ------------------------------------------------------------- */ struct file_operations isdn_ppp_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .read = ipppd_read, .write = ipppd_write, .poll = ipppd_poll, .ioctl = ipppd_ioctl, .open = ipppd_open, .release = ipppd_release, }; /* --- ipppd_queue_read ------------------------------------------------- */ /* Queue packets for ipppd to read(). */ static int ipppd_queue_read(struct ipppd *is, u16 proto, unsigned char *buf, int len) { struct sk_buff *skb; unsigned char *p; int retval; if (is->state != IPPPD_ST_CONNECTED) { printk(KERN_DEBUG "ippp: device not connected.\n"); retval = -ENOTCONN; goto out; } if (skb_queue_len(&is->rq) > IPPP_MAX_RQ_LEN) { printk(KERN_WARNING "ippp: Queue is full\n"); retval = -EBUSY; goto out; } skb = dev_alloc_skb(len + 4); if (!skb) { printk(KERN_WARNING "ippp: Can't alloc buf\n"); retval = -ENOMEM; goto out; } p = skb_put(skb, 4); p += put_u8(p, PPP_ALLSTATIONS); p += put_u8(p, PPP_UI); p += put_u16(p, proto); memcpy(skb_put(skb, len), buf, len); skb_queue_tail(&is->rq, skb); wake_up(&is->wq); retval = len; out: return retval; } /* ====================================================================== */ /* interface to isdn_net_lib */ /* ====================================================================== */ /* Prototypes */ static int isdn_ppp_if_get_unit(char *namebuf); static void isdn_ppp_dev_xmit(void *priv, struct sk_buff *skb, u16 proto); static struct sk_buff * isdn_ppp_lp_alloc_skb(void *priv, int len, int gfp_mask); /* New CCP stuff */ static void isdn_ppp_dev_kick_up(void *priv); /* * frame log (debug) */ void isdn_ppp_frame_log(char *info, char *data, int len, int maxlen,int unit,int slot) { int cnt, j, i; char buf[80]; if (len < maxlen) maxlen = len; for (i = 0, cnt = 0; cnt < maxlen; i++) { for (j = 0; j < 16 && cnt < maxlen; j++, cnt++) sprintf(buf + j * 3, "%02x ", (unsigned char) data[cnt]); printk(KERN_DEBUG "[%d/%d].%s[%d]: %s\n",unit,slot, info, i, buf); } } void ippp_push_proto(struct ind_ppp *ind_ppp, struct sk_buff *skb, u16 proto) { if (skb_headroom(skb) < 2) { isdn_BUG(); return; } if ((ind_ppp->pppcfg & SC_COMP_PROT) && proto <= 0xff) put_u8(skb_push(skb, 1), proto); else put_u16(skb_push(skb, 2), proto); } static void ippp_push_ac(struct ind_ppp *ind_ppp, struct sk_buff *skb) { unsigned char *p; if (skb_headroom(skb) < 2) { isdn_BUG(); return; } if (ind_ppp->pppcfg & SC_COMP_AC) return; p = skb_push(skb, 2); p += put_u8(p, PPP_ALLSTATIONS); p += put_u8(p, PPP_UI); } /* * unbind isdn_net_local <=> ippp-device * note: it can happen, that we hangup/free the master before the slaves * in this case we bind another lp to the master device */ static void isdn_ppp_unbind(isdn_net_dev *idev) { struct ind_ppp *ind_ppp = idev->ind_priv; struct ipppd *is = ind_ppp->ipppd; if (!is) { isdn_BUG(); return; } ipppd_debug(is, ""); if (is->state != IPPPD_ST_ASSIGNED) isdn_BUG(); is->state = IPPPD_ST_OPEN; /* is->idev will be invalid shortly */ ippp_ccp_free(ind_ppp->ccp); is->idev = NULL; /* lose the reference we took on isdn_ppp_bind */ ipppd_put(is); ind_ppp->ipppd = NULL; kfree(ind_ppp); idev->ind_priv = NULL; return; } /* * bind isdn_net_local <=> ippp-device */ int isdn_ppp_bind(isdn_net_dev *idev) { struct ind_ppp *ind_ppp; int unit = 0; unsigned long flags; int retval = 0; struct ipppd *ipppd; if (idev->ind_priv) { isdn_BUG(); return -EIO; } ind_ppp = kmalloc(sizeof(struct ind_ppp), GFP_KERNEL); if (!ind_ppp) return -ENOMEM; spin_lock_irqsave(&ipppds_lock, flags); if (idev->pppbind < 0) { /* device bound to ippp device ? */ struct list_head *l; char exclusive[ISDN_MAX_CHANNELS]; /* exclusive flags */ memset(exclusive, 0, ISDN_MAX_CHANNELS); /* step through net devices to find exclusive minors */ list_for_each(l, &isdn_net_devs) { isdn_net_dev *p = list_entry(l, isdn_net_dev, global_list); if (p->pppbind >= 0 && p->pppbind < ISDN_MAX_CHANNELS) exclusive[p->pppbind] = 1; } /* * search a free device / slot */ list_for_each_entry(ipppd, &ipppds, ipppds) { if (!ipppd) continue; if (ipppd->state != IPPPD_ST_OPEN) continue; if (!exclusive[ipppd->minor]) break; goto found; } } else { list_for_each_entry(ipppd, &ipppds, ipppds) { if (!ipppd) continue; if (ipppd->state != IPPPD_ST_OPEN) continue; if (ipppd->minor == idev->pppbind) goto found; } } printk(KERN_INFO "isdn_ppp_bind: no ipppd\n"); retval = -ESRCH; goto err; found: unit = isdn_ppp_if_get_unit(idev->name); /* get unit number from interface name .. ugly! */ if (unit < 0) { printk(KERN_INFO "isdn_ppp_bind: illegal interface name %s.\n", idev->name); retval = -ENODEV; goto err; } ipppd->unit = unit; ipppd->state = IPPPD_ST_ASSIGNED; ipppd->idev = idev; /* we hold a reference until isdn_ppp_unbind() */ ipppd_get(ipppd); spin_unlock_irqrestore(&ipppds_lock, flags); idev->ind_priv = ind_ppp; ind_ppp->pppcfg = 0; /* config flags */ ind_ppp->ipppd = ipppd; ind_ppp->ccp = ippp_ccp_alloc(); if (!ind_ppp->ccp) { retval = -ENOMEM; goto out; } ind_ppp->ccp->proto = PPP_COMPFRAG; ind_ppp->ccp->priv = idev; ind_ppp->ccp->alloc_skb = isdn_ppp_dev_alloc_skb; ind_ppp->ccp->xmit = isdn_ppp_dev_xmit; ind_ppp->ccp->kick_up = isdn_ppp_dev_kick_up; retval = ippp_mp_bind(idev); if (retval) goto out; return 0; out: ipppd->state = IPPPD_ST_OPEN; ipppd_put(ipppd); ind_ppp->ipppd = NULL; kfree(ind_ppp); idev->ind_priv = NULL; return retval; err: spin_unlock_irqrestore(&ipppds_lock, flags); kfree(ind_ppp); return retval; } /* * kick the ipppd on the device * (wakes up daemon after B-channel connect) */ static void isdn_ppp_connected(isdn_net_dev *idev) { struct ind_ppp *ind_ppp = idev->ind_priv; struct ipppd *ipppd = ind_ppp->ipppd; ipppd_debug(ipppd, ""); ipppd->state = IPPPD_ST_CONNECTED; ipppd->flags |= IPPPD_FL_WAKEUP; wake_up(&ipppd->wq); } static void isdn_ppp_disconnected(isdn_net_dev *idev) { struct ind_ppp *ind_ppp = idev->ind_priv; struct ipppd *ipppd = ind_ppp->ipppd; ipppd_debug(ipppd, ""); if (ind_ppp->pppcfg & SC_ENABLE_IP) isdn_net_offline(idev); if (ipppd->state != IPPPD_ST_CONNECTED) isdn_BUG(); ipppd->state = IPPPD_ST_ASSIGNED; ipppd->flags |= IPPPD_FL_HUP; wake_up(&ipppd->wq); ippp_mp_disconnected(idev); } /* * init memory, structures etc. */ int isdn_ppp_init(void) { return 0; } void isdn_ppp_cleanup(void) { } /* * check for address/control field and skip if allowed * retval != 0 -> discard packet silently */ static int isdn_ppp_skip_ac(struct ind_ppp *ind_ppp, struct sk_buff *skb) { u8 val; if (skb->len < 1) return -EINVAL; get_u8(skb->data, &val); if (val != PPP_ALLSTATIONS) { /* if AC compression was not negotiated, but no AC present, discard packet */ if (ind_ppp->pppcfg & SC_REJ_COMP_AC) return -EINVAL; return 0; } if (skb->len < 2) return -EINVAL; get_u8(skb->data + 1, &val); if (val != PPP_UI) return -EINVAL; /* skip address/control (AC) field */ skb_pull(skb, 2); return 0; } /* * get the PPP protocol header and pull skb * retval < 0 -> discard packet silently */ int isdn_ppp_strip_proto(struct sk_buff *skb, u16 *proto) { u8 val; if (skb->len < 1) return -EINVAL; get_u8(skb->data, &val); if (val & 0x1) { /* protocol field is compressed */ *proto = val; skb_pull(skb, 1); } else { if (skb->len < 2) return -1; get_u16(skb->data, proto); skb_pull(skb, 2); } return 0; } /* * handler for incoming packets on a syncPPP interface */ static void isdn_ppp_receive(isdn_net_local *lp, isdn_net_dev *idev, struct sk_buff *skb) { struct ind_ppp *ind_ppp = idev->ind_priv; struct ipppd *is = ind_ppp->ipppd; u16 proto; if (!is) goto err; if (is->debug & 0x4) { printk(KERN_DEBUG "ippp_receive: is:%p lp:%p unit:%d len:%d\n", is, lp, is->unit, skb->len); isdn_ppp_frame_log("receive", skb->data, skb->len, 32,is->unit,-1); } if (isdn_ppp_skip_ac(ind_ppp, skb) < 0) goto err; if (isdn_ppp_strip_proto(skb, &proto)) goto err; ippp_mp_receive(idev, skb, proto); return; err: lp->stats.rx_dropped++; kfree_skb(skb); } /* * address/control and protocol have been stripped from the skb */ void ippp_receive(isdn_net_dev *idev, struct sk_buff *skb, u16 proto) { isdn_net_local *lp = idev->mlp; struct inl_ppp *inl_ppp = lp->inl_priv; struct ind_ppp *ind_ppp = idev->ind_priv; struct ipppd *is = ind_ppp->ipppd; if (is->debug & 0x10) { printk(KERN_DEBUG "push, skb %d %04x\n", (int) skb->len, proto); isdn_ppp_frame_log("rpush", skb->data, skb->len, 256, is->unit, -1); } /* all packets need to be passed through the compressor */ skb = ippp_ccp_decompress(inl_ppp->ccp, skb, &proto); if (!skb) /* decompression error */ goto error; switch (proto) { case PPP_IPX: /* untested */ if (is->debug & 0x20) printk(KERN_DEBUG "isdn_ppp: IPX\n"); isdn_netif_rx(idev, skb, htons(ETH_P_IPX)); break; case PPP_IP: if (is->debug & 0x20) printk(KERN_DEBUG "isdn_ppp: IP\n"); isdn_netif_rx(idev, skb, htons(ETH_P_IP)); break; case PPP_COMP: case PPP_COMPFRAG: printk(KERN_INFO "isdn_ppp: unexpected compressed frame dropped\n"); goto drop; case PPP_VJC_UNCOMP: case PPP_VJC_COMP: ippp_vj_decompress(idev, skb, proto); break; case PPP_CCPFRAG: ippp_ccp_receive_ccp(ind_ppp->ccp, skb); goto ccp; case PPP_CCP: ippp_ccp_receive_ccp(inl_ppp->ccp, skb); ccp: /* Dont pop up ResetReq/Ack stuff to the daemon any longer - the job is done already */ if(skb->data[0] == CCP_RESETREQ || skb->data[0] == CCP_RESETACK) goto free; /* fall through */ default: // FIXME use skb directly ipppd_queue_read(is, proto, skb->data, skb->len); goto free; } return; drop: lp->stats.rx_dropped++; free: kfree_skb(skb); return; error: lp->stats.rx_dropped++; } /* * 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!! */ static int isdn_ppp_start_xmit(struct sk_buff *skb, struct net_device *ndev) { isdn_net_local *mlp = ndev->priv; struct inl_ppp *inl_ppp = mlp->inl_priv; struct ind_ppp *ind_ppp; isdn_net_dev *idev = list_entry(mlp->online.next, isdn_net_dev, online); u16 proto = PPP_IP; /* 0x21 */ struct ipppd *ipppd; ndev->trans_start = jiffies; if (list_empty(&mlp->online)) return isdn_net_autodial(skb, ndev); switch (ntohs(skb->protocol)) { case ETH_P_IP: proto = PPP_IP; break; case ETH_P_IPX: proto = PPP_IPX; /* untested */ break; default: printk(KERN_INFO "isdn_ppp: skipped unsupported protocol: %#x.\n", skb->protocol); goto drop; } idev = isdn_net_get_xmit_dev(mlp); if (!idev) { printk(KERN_INFO "%s: IP frame delayed.\n", ndev->name); goto stop; } ind_ppp = idev->ind_priv; if (!(ind_ppp->pppcfg & SC_ENABLE_IP)) { /* PPP connected ? */ isdn_BUG(); goto stop; } ipppd = ind_ppp->ipppd; idev->huptimer = 0; if (ipppd->debug & 0x40) isdn_ppp_frame_log("xmit0", skb->data, skb->len, 256, ipppd->unit, -1); /* after this line,requeueing is no longer allowed! */ skb = ippp_vj_compress(idev, skb, &proto); /* normal (single link) or bundle compression */ skb = ippp_ccp_compress(inl_ppp->ccp, skb, &proto); if (ipppd->debug & 0x40) isdn_ppp_frame_log("xmit1", skb->data, skb->len, 32, ipppd->unit, -1); ippp_push_proto(ind_ppp, skb, proto); ippp_mp_xmit(idev, skb, proto); return 0; drop: kfree_skb(skb); mlp->stats.tx_dropped++; return 0; stop: netif_stop_queue(ndev); return 1; } void ippp_xmit(isdn_net_dev *idev, struct sk_buff *skb) { struct ind_ppp *ind_ppp = idev->ind_priv; struct ipppd *ipppd = ind_ppp->ipppd; ippp_push_ac(ind_ppp, skb); if (ipppd->debug & 0x40) { isdn_ppp_frame_log("xmit3", skb->data, skb->len, 32, ipppd->unit, -1); } isdn_net_writebuf_skb(idev, skb); } /* * network device ioctl handlers */ static int isdn_ppp_dev_ioctl_stats(struct ifreq *ifr, struct net_device *dev) { struct ppp_stats *res, t; isdn_net_local *lp = (isdn_net_local *) dev->priv; struct inl_ppp *inl_ppp = lp->inl_priv; struct slcompress *slcomp; 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_ibytes = lp->stats.rx_bytes; t.p.ppp_ierrors = lp->stats.rx_errors; t.p.ppp_opackets = lp->stats.tx_packets; t.p.ppp_obytes = lp->stats.tx_bytes; t.p.ppp_oerrors = lp->stats.tx_errors; #ifdef CONFIG_ISDN_PPP_VJ slcomp = inl_ppp->slcomp; if (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 } if( copy_to_user(res, &t, sizeof(struct ppp_stats))) return -EFAULT; return 0; } int isdn_ppp_dev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) { int error=0; char *r; int len; isdn_net_local *lp = (isdn_net_local *) dev->priv; 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; if (copy_to_user(r, PPP_VERSION, len)) error = -EFAULT; break; case SIOCGPPPSTATS: error = isdn_ppp_dev_ioctl_stats(ifr, dev); break; default: error = -EINVAL; break; } return error; } static int isdn_ppp_if_get_unit(char *name) { int len, i, unit = 0, deci; len = strlen(name); if (strncmp("ippp", name, 4) || len > 8) return -1; for (i = 0, deci = 1; i < len; i++, deci *= 10) { char a = name[len - i - 1]; if (a >= '0' && a <= '9') unit += (a - '0') * deci; else break; } if (!i || len - i != 4) unit = -1; return unit; } /* * PPP compression stuff */ /* Push an empty CCP Data Frame up to the daemon to wake it up and let it generate a CCP Reset-Request or tear down CCP altogether */ static void isdn_ppp_dev_kick_up(void *priv) { isdn_net_dev *idev = priv; struct ind_ppp *ind_ppp = idev->ind_priv; ipppd_queue_read(ind_ppp->ipppd, PPP_COMPFRAG, NULL, 0); } static void isdn_ppp_lp_kick_up(void *priv) { isdn_net_local *lp = priv; isdn_net_dev *idev; struct ind_ppp *ind_ppp; if (list_empty(&lp->online)) { isdn_BUG(); return; } idev = list_entry(lp->online.next, isdn_net_dev, online); ind_ppp = idev->ind_priv; ipppd_queue_read(ind_ppp->ipppd, PPP_COMP, NULL, 0); } /* Send a CCP Reset-Request or Reset-Ack directly from the kernel. */ static struct sk_buff * __isdn_ppp_alloc_skb(isdn_net_dev *idev, int len, unsigned int gfp_mask) { int hl = IPPP_MAX_HEADER + isdn_slot_hdrlen(idev->isdn_slot); struct sk_buff *skb; skb = alloc_skb(hl + len, gfp_mask); if (!skb) return NULL; skb_reserve(skb, hl); return skb; } struct sk_buff * isdn_ppp_dev_alloc_skb(void *priv, int len, int gfp_mask) { isdn_net_dev *idev = priv; return __isdn_ppp_alloc_skb(idev, len, gfp_mask); } static struct sk_buff * isdn_ppp_lp_alloc_skb(void *priv, int len, int gfp_mask) { isdn_net_local *lp = priv; isdn_net_dev *idev; if (list_empty(&lp->online)) { isdn_BUG(); return NULL; } idev = list_entry(lp->online.next, isdn_net_dev, online); return __isdn_ppp_alloc_skb(idev, len, gfp_mask); } static void isdn_ppp_dev_xmit(void *priv, struct sk_buff *skb, u16 proto) { isdn_net_dev *idev = priv; struct ind_ppp *ind_ppp = idev->ind_priv; ippp_push_proto(ind_ppp, skb, proto); ippp_push_ac(ind_ppp, skb); isdn_net_write_super(idev, skb); } static void isdn_ppp_lp_xmit(void *priv, struct sk_buff *skb, u16 proto) { isdn_net_local *lp = priv; isdn_net_dev *idev; struct ind_ppp *ind_ppp; if (list_empty(&lp->online)) { isdn_BUG(); return; } idev = list_entry(lp->online.next, isdn_net_dev, online); ind_ppp = idev->ind_priv; ippp_push_proto(ind_ppp, skb, proto); ippp_push_ac(ind_ppp, skb); isdn_net_write_super(idev, skb); } static int isdn_ppp_set_compressor(isdn_net_dev *idev, struct isdn_ppp_comp_data *data) { struct ippp_ccp *ccp; struct inl_ppp *inl_ppp = idev->mlp->inl_priv; struct ind_ppp *ind_ppp = idev->ind_priv; if (data->flags & IPPP_COMP_FLAG_LINK) ccp = ind_ppp->ccp; else ccp = inl_ppp->ccp; return ippp_ccp_set_compressor(ccp, ind_ppp->ipppd->unit, data); } // ISDN_NET_ENCAP_SYNCPPP // ====================================================================== static int isdn_ppp_open(isdn_net_local *lp) { struct inl_ppp *inl_ppp; inl_ppp = kmalloc(sizeof(*inl_ppp), GFP_KERNEL); if (!inl_ppp) return -ENOMEM; lp->inl_priv = inl_ppp; inl_ppp->slcomp = ippp_vj_alloc(); if (!inl_ppp->slcomp) goto err; inl_ppp->ccp = ippp_ccp_alloc(); if (!inl_ppp->ccp) goto err_vj; inl_ppp->ccp->proto = PPP_COMP; inl_ppp->ccp->priv = lp; inl_ppp->ccp->alloc_skb = isdn_ppp_lp_alloc_skb; inl_ppp->ccp->xmit = isdn_ppp_lp_xmit; inl_ppp->ccp->kick_up = isdn_ppp_lp_kick_up; return 0; err_vj: ippp_vj_free(inl_ppp->slcomp); err: kfree(inl_ppp); lp->inl_priv = NULL; return -ENOMEM; } static void isdn_ppp_close(isdn_net_local *lp) { struct inl_ppp *inl_ppp = lp->inl_priv; ippp_ccp_free(inl_ppp->ccp); ippp_vj_free(inl_ppp->slcomp); kfree(inl_ppp); lp->inl_priv = NULL; } struct isdn_netif_ops isdn_ppp_ops = { .hard_start_xmit = isdn_ppp_start_xmit, .do_ioctl = isdn_ppp_dev_ioctl, .flags = IFF_NOARP | IFF_POINTOPOINT, .type = ARPHRD_PPP, .receive = isdn_ppp_receive, .connected = isdn_ppp_connected, .disconnected = isdn_ppp_disconnected, .bind = isdn_ppp_bind, .unbind = isdn_ppp_unbind, .open = isdn_ppp_open, .close = isdn_ppp_close, };