diff options
Diffstat (limited to 'drivers/net/ppp/ppp_generic.c')
| -rw-r--r-- | drivers/net/ppp/ppp_generic.c | 274 |
1 files changed, 125 insertions, 149 deletions
diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c index c2024684b10d..b0d3bc49c685 100644 --- a/drivers/net/ppp/ppp_generic.c +++ b/drivers/net/ppp/ppp_generic.c @@ -134,7 +134,6 @@ struct ppp { int debug; /* debug flags 70 */ struct slcompress *vj; /* state for VJ header compression */ enum NPmode npmode[NUM_NP]; /* what to do with each net proto 78 */ - struct sk_buff *xmit_pending; /* a packet ready to go out 88 */ struct compressor *xcomp; /* transmit packet compressor 8c */ void *xc_state; /* its internal state 90 */ struct compressor *rcomp; /* receive decompressor 94 */ @@ -264,8 +263,8 @@ struct ppp_net { static int ppp_unattached_ioctl(struct net *net, struct ppp_file *pf, struct file *file, unsigned int cmd, unsigned long arg); static void ppp_xmit_process(struct ppp *ppp, struct sk_buff *skb); -static void ppp_send_frame(struct ppp *ppp, struct sk_buff *skb); -static void ppp_push(struct ppp *ppp); +static int ppp_prepare_tx_skb(struct ppp *ppp, struct sk_buff **pskb); +static int ppp_push(struct ppp *ppp, struct sk_buff *skb); static void ppp_channel_push(struct channel *pch); static void ppp_receive_frame(struct ppp *ppp, struct sk_buff *skb, struct channel *pch); @@ -287,12 +286,12 @@ static struct compressor *find_compressor(int type); static void ppp_get_stats(struct ppp *ppp, struct ppp_stats *st); static int ppp_create_interface(struct net *net, struct file *file, int *unit); static void init_ppp_file(struct ppp_file *pf, int kind); -static void ppp_destroy_interface(struct ppp *ppp); +static void ppp_release_interface(struct ppp *ppp); static struct ppp *ppp_find_unit(struct ppp_net *pn, int unit); static struct channel *ppp_find_channel(struct ppp_net *pn, int unit); static int ppp_connect_channel(struct channel *pch, int unit); static int ppp_disconnect_channel(struct channel *pch); -static void ppp_destroy_channel(struct channel *pch); +static void ppp_release_channel(struct channel *pch); static int unit_get(struct idr *p, void *ptr, int min); static int unit_set(struct idr *p, void *ptr, int n); static void unit_put(struct idr *p, int n); @@ -408,22 +407,18 @@ static int ppp_release(struct inode *unused, struct file *file) if (pf) { file->private_data = NULL; - if (pf->kind == INTERFACE) { + switch (pf->kind) { + case INTERFACE: ppp = PF_TO_PPP(pf); rtnl_lock(); if (file == ppp->owner) unregister_netdevice(ppp->dev); rtnl_unlock(); - } - if (refcount_dec_and_test(&pf->refcnt)) { - switch (pf->kind) { - case INTERFACE: - ppp_destroy_interface(PF_TO_PPP(pf)); - break; - case CHANNEL: - ppp_destroy_channel(PF_TO_CHANNEL(pf)); - break; - } + ppp_release_interface(ppp); + break; + case CHANNEL: + ppp_release_channel(PF_TO_CHANNEL(pf)); + break; } } return 0; @@ -676,8 +671,7 @@ err_unset: synchronize_rcu(); if (pchb) - if (refcount_dec_and_test(&pchb->file.refcnt)) - ppp_destroy_channel(pchb); + ppp_release_channel(pchb); return -EALREADY; } @@ -709,11 +703,9 @@ static int ppp_unbridge_channels(struct channel *pch) synchronize_rcu(); if (pchbb == pch) - if (refcount_dec_and_test(&pch->file.refcnt)) - ppp_destroy_channel(pch); + ppp_release_channel(pch); - if (refcount_dec_and_test(&pchb->file.refcnt)) - ppp_destroy_channel(pchb); + ppp_release_channel(pchb); return 0; } @@ -787,8 +779,7 @@ static long ppp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) break; err = ppp_bridge_channels(pch, pchb); /* Drop earlier refcount now bridge establishment is complete */ - if (refcount_dec_and_test(&pchb->file.refcnt)) - ppp_destroy_channel(pchb); + ppp_release_channel(pchb); break; case PPPIOCUNBRIDGECHAN: @@ -1588,8 +1579,7 @@ static void ppp_dev_priv_destructor(struct net_device *dev) struct ppp *ppp; ppp = netdev_priv(dev); - if (refcount_dec_and_test(&ppp->file.refcnt)) - ppp_destroy_interface(ppp); + ppp_release_interface(ppp); } static int ppp_fill_forward_path(struct net_device_path_ctx *ctx, @@ -1606,10 +1596,7 @@ static int ppp_fill_forward_path(struct net_device_path_ctx *ctx, if (!pch) return -ENODEV; - chan = READ_ONCE(pch->chan); - if (!chan) - return -ENODEV; - + chan = pch->chan; if (!chan->ops->fill_forward_path) return -EOPNOTSUPP; @@ -1654,26 +1641,44 @@ static void ppp_setup(struct net_device *dev) */ /* Called to do any work queued up on the transmit side that can now be done */ +static void ppp_xmit_flush(struct ppp *ppp) +{ + struct sk_buff *skb; + + while ((skb = skb_dequeue(&ppp->file.xq))) { + if (unlikely(!ppp_push(ppp, skb))) { + skb_queue_head(&ppp->file.xq, skb); + return; + } + } + /* If there's no work left to do, tell the core net code that we can + * accept some more. + */ + netif_wake_queue(ppp->dev); +} + static void __ppp_xmit_process(struct ppp *ppp, struct sk_buff *skb) { ppp_xmit_lock(ppp); - if (!ppp->closing) { - ppp_push(ppp); - - if (skb) + if (unlikely(ppp->closing)) { + kfree_skb(skb); + goto out; + } + if (unlikely(ppp_prepare_tx_skb(ppp, &skb))) + goto out; + /* Fastpath: No backlog, just send the new skb. */ + if (likely(skb_queue_empty(&ppp->file.xq))) { + if (unlikely(!ppp_push(ppp, skb))) { skb_queue_tail(&ppp->file.xq, skb); - while (!ppp->xmit_pending && - (skb = skb_dequeue(&ppp->file.xq))) - ppp_send_frame(ppp, skb); - /* If there's no work left to do, tell the core net - code that we can accept some more. */ - if (!ppp->xmit_pending && !skb_peek(&ppp->file.xq)) - netif_wake_queue(ppp->dev); - else netif_stop_queue(ppp->dev); - } else { - kfree_skb(skb); + } + goto out; } + + /* Slowpath: Enqueue the new skb and process backlog */ + skb_queue_tail(&ppp->file.xq, skb); + ppp_xmit_flush(ppp); +out: ppp_xmit_unlock(ppp); } @@ -1760,13 +1765,15 @@ pad_compress_skb(struct ppp *ppp, struct sk_buff *skb) } /* - * Compress and send a frame. - * The caller should have locked the xmit path, - * and xmit_pending should be 0. + * Compress and prepare to send a frame. + * The caller should have locked the xmit path. + * Returns 1 if the skb was consumed, 0 if it can be passed to ppp_push(). + * @pskb is updated if a compressor is in use. */ -static void -ppp_send_frame(struct ppp *ppp, struct sk_buff *skb) +static int +ppp_prepare_tx_skb(struct ppp *ppp, struct sk_buff **pskb) { + struct sk_buff *skb = *pskb; int proto = PPP_PROTO(skb); struct sk_buff *new_skb; int len; @@ -1787,7 +1794,7 @@ ppp_send_frame(struct ppp *ppp, struct sk_buff *skb) "PPP: outbound frame " "not passed\n"); kfree_skb(skb); - return; + return 1; } /* if this packet passes the active filter, record the time */ if (!(ppp->active_filter && @@ -1835,6 +1842,7 @@ ppp_send_frame(struct ppp *ppp, struct sk_buff *skb) } consume_skb(skb); skb = new_skb; + *pskb = skb; cp = skb_put(skb, len + 2); cp[0] = 0; cp[1] = proto; @@ -1861,6 +1869,7 @@ ppp_send_frame(struct ppp *ppp, struct sk_buff *skb) if (!new_skb) goto drop; skb = new_skb; + *pskb = skb; } /* @@ -1872,74 +1881,69 @@ ppp_send_frame(struct ppp *ppp, struct sk_buff *skb) goto drop; skb_queue_tail(&ppp->file.rq, skb); wake_up_interruptible(&ppp->file.rwait); - return; + return 1; } - ppp->xmit_pending = skb; - ppp_push(ppp); - return; + return 0; drop: kfree_skb(skb); ++ppp->dev->stats.tx_errors; + return 1; } /* - * Try to send the frame in xmit_pending. + * Try to send the frame. * The caller should have the xmit path locked. + * Returns 1 if the skb was consumed, 0 if not. */ -static void -ppp_push(struct ppp *ppp) +static int +ppp_push(struct ppp *ppp, struct sk_buff *skb) { struct list_head *list; struct channel *pch; - struct sk_buff *skb = ppp->xmit_pending; - - if (!skb) - return; list = &ppp->channels; if (list_empty(list)) { /* nowhere to send the packet, just drop it */ - ppp->xmit_pending = NULL; kfree_skb(skb); - return; + return 1; } if ((ppp->flags & SC_MULTILINK) == 0) { struct ppp_channel *chan; + int ret; /* not doing multilink: send it down the first channel */ list = list->next; pch = list_entry(list, struct channel, clist); spin_lock(&pch->downl); chan = pch->chan; - if (unlikely(!chan || (!chan->direct_xmit && skb_linearize(skb)))) { - /* channel got unregistered, or it requires a linear - * skb but linearization failed + if (unlikely(!chan->direct_xmit && skb_linearize(skb))) { + /* channel requires a linear skb but linearization + * failed */ kfree_skb(skb); - ppp->xmit_pending = NULL; + ret = 1; goto out; } - if (chan->ops->start_xmit(chan, skb)) - ppp->xmit_pending = NULL; + ret = chan->ops->start_xmit(chan, skb); out: spin_unlock(&pch->downl); - return; + return ret; } #ifdef CONFIG_PPP_MULTILINK /* Multilink: fragment the packet over as many links as can take the packet at the moment. */ if (!ppp_mp_explode(ppp, skb)) - return; + return 0; #endif /* CONFIG_PPP_MULTILINK */ - ppp->xmit_pending = NULL; kfree_skb(skb); + return 1; } #ifdef CONFIG_PPP_MULTILINK @@ -1978,28 +1982,23 @@ static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb) hdrlen = (ppp->flags & SC_MP_XSHORTSEQ)? MPHDRLEN_SSN: MPHDRLEN; i = 0; list_for_each_entry(pch, &ppp->channels, clist) { - if (pch->chan) { - pch->avail = 1; - navail++; - pch->speed = pch->chan->speed; - } else { - pch->avail = 0; - } - if (pch->avail) { - if (skb_queue_empty(&pch->file.xq) || - !pch->had_frag) { - if (pch->speed == 0) - nzero++; - else - totspeed += pch->speed; + pch->avail = 1; + navail++; + pch->speed = pch->chan->speed; - pch->avail = 2; - ++nfree; - ++totfree; - } - if (!pch->had_frag && i < ppp->nxchan) - ppp->nxchan = i; + if (skb_queue_empty(&pch->file.xq) || !pch->had_frag) { + if (pch->speed == 0) + nzero++; + else + totspeed += pch->speed; + + pch->avail = 2; + ++nfree; + ++totfree; } + if (!pch->had_frag && i < ppp->nxchan) + ppp->nxchan = i; + ++i; } /* @@ -2008,7 +2007,7 @@ static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb) * performance if we have a lot of channels. */ if (nfree == 0 || nfree < navail / 2) - return 0; /* can't take now, leave it in xmit_pending */ + return 0; /* can't take now, leave it in transmit queue */ /* Do protocol field compression */ if (skb_linearize(skb)) @@ -2058,25 +2057,7 @@ static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb) pch->avail = 1; } - /* check the channel's mtu and whether it is still attached. */ spin_lock(&pch->downl); - if (pch->chan == NULL) { - /* can't use this channel, it's being deregistered */ - if (pch->speed == 0) - nzero--; - else - totspeed -= pch->speed; - - spin_unlock(&pch->downl); - pch->avail = 0; - totlen = len; - totfree--; - nfree--; - if (--navail == 0) - break; - continue; - } - /* *if the channel speed is not set divide *the packet evenly among the free channels; @@ -2202,8 +2183,12 @@ static void __ppp_channel_push(struct channel *pch, struct ppp *ppp) spin_unlock(&pch->downl); /* see if there is anything from the attached unit to be sent */ if (skb_queue_empty(&pch->file.xq)) { - if (ppp) - __ppp_xmit_process(ppp, NULL); + if (ppp) { + ppp_xmit_lock(ppp); + if (!ppp->closing) + ppp_xmit_flush(ppp); + ppp_xmit_unlock(ppp); + } } } @@ -2366,12 +2351,10 @@ done: rcu_read_unlock_bh(); } -/* Put a 0-length skb in the receive queue as an error indication */ void -ppp_input_error(struct ppp_channel *chan, int code) +ppp_input_error(struct ppp_channel *chan) { struct channel *pch = chan->ppp; - struct sk_buff *skb; struct ppp *ppp; if (!pch) @@ -2380,12 +2363,9 @@ ppp_input_error(struct ppp_channel *chan, int code) rcu_read_lock_bh(); ppp = rcu_dereference_bh(pch->ppp); if (ppp) { - skb = alloc_skb(0, GFP_ATOMIC); - if (skb) { - skb->len = 0; /* probably unnecessary */ - skb->cb[0] = code; - ppp_do_recv(ppp, skb, pch); - } + ppp_recv_lock(ppp); + ppp_receive_error(ppp); + ppp_recv_unlock(ppp); } rcu_read_unlock_bh(); } @@ -2397,20 +2377,14 @@ ppp_input_error(struct ppp_channel *chan, int code) static void ppp_receive_frame(struct ppp *ppp, struct sk_buff *skb, struct channel *pch) { - /* note: a 0-length skb is used as an error indication */ - if (skb->len > 0) { - skb_checksum_complete_unset(skb); + skb_checksum_complete_unset(skb); #ifdef CONFIG_PPP_MULTILINK - /* XXX do channel-level decompression here */ - if (PPP_PROTO(skb) == PPP_MP) - ppp_receive_mp_frame(ppp, skb, pch); - else + /* XXX do channel-level decompression here */ + if (PPP_PROTO(skb) == PPP_MP) + ppp_receive_mp_frame(ppp, skb, pch); + else #endif /* CONFIG_PPP_MULTILINK */ - ppp_receive_nonmp_frame(ppp, skb); - } else { - kfree_skb(skb); - ppp_receive_error(ppp); - } + ppp_receive_nonmp_frame(ppp, skb); } static void @@ -2989,6 +2963,7 @@ int ppp_unit_number(struct ppp_channel *chan) /* * Return the PPP device interface name of a channel. + * Caller must hold RCU read lock. */ char *ppp_dev_name(struct ppp_channel *chan) { @@ -2997,11 +2972,9 @@ char *ppp_dev_name(struct ppp_channel *chan) struct ppp *ppp; if (pch) { - rcu_read_lock(); ppp = rcu_dereference(pch->ppp); if (ppp && ppp->dev) name = ppp->dev->name; - rcu_read_unlock(); } return name; } @@ -3026,12 +2999,12 @@ ppp_unregister_channel(struct ppp_channel *chan) * This ensures that we have returned from any calls into * the channel's start_xmit or ioctl routine before we proceed. */ + ppp_disconnect_channel(pch); down_write(&pch->chan_sem); spin_lock_bh(&pch->downl); - WRITE_ONCE(pch->chan, NULL); + pch->chan = NULL; spin_unlock_bh(&pch->downl); up_write(&pch->chan_sem); - ppp_disconnect_channel(pch); pn = ppp_pernet(pch->chan_net); spin_lock_bh(&pn->all_channels_lock); @@ -3043,8 +3016,7 @@ ppp_unregister_channel(struct ppp_channel *chan) pch->file.dead = 1; wake_up_interruptible(&pch->file.rwait); - if (refcount_dec_and_test(&pch->file.refcnt)) - ppp_destroy_channel(pch); + ppp_release_channel(pch); } /* @@ -3425,12 +3397,14 @@ init_ppp_file(struct ppp_file *pf, int kind) } /* - * Free the memory used by a ppp unit. This is only called once - * there are no channels connected to the unit and no file structs - * that reference the unit. + * Drop a reference to a ppp unit and free its memory if the refcount reaches + * zero. */ -static void ppp_destroy_interface(struct ppp *ppp) +static void ppp_release_interface(struct ppp *ppp) { + if (!refcount_dec_and_test(&ppp->file.refcnt)) + return; + atomic_dec(&ppp_unit_count); if (!ppp->file.dead || ppp->n_channels) { @@ -3463,7 +3437,6 @@ static void ppp_destroy_interface(struct ppp *ppp) } #endif /* CONFIG_PPP_FILTER */ - kfree_skb(ppp->xmit_pending); free_percpu(ppp->xmit_recursion); free_netdev(ppp->dev); @@ -3583,18 +3556,21 @@ ppp_disconnect_channel(struct channel *pch) wake_up_interruptible(&ppp->file.rwait); ppp_unlock(ppp); synchronize_net(); - if (refcount_dec_and_test(&ppp->file.refcnt)) - ppp_destroy_interface(ppp); + ppp_release_interface(ppp); err = 0; } return err; } /* - * Free up the resources used by a ppp channel. + * Drop a reference to a ppp channel and free its memory if the refcount reaches + * zero. */ -static void ppp_destroy_channel(struct channel *pch) +static void ppp_release_channel(struct channel *pch) { + if (!refcount_dec_and_test(&pch->file.refcnt)) + return; + put_net_track(pch->chan_net, &pch->ns_tracker); pch->chan_net = NULL; |
