diff options
Diffstat (limited to 'net/atm/pppoatm.c')
| -rw-r--r-- | net/atm/pppoatm.c | 170 | 
1 files changed, 154 insertions, 16 deletions
diff --git a/net/atm/pppoatm.c b/net/atm/pppoatm.c index e9aced0ec56..c4e09846d1d 100644 --- a/net/atm/pppoatm.c +++ b/net/atm/pppoatm.c @@ -37,13 +37,14 @@  #include <linux/module.h>  #include <linux/init.h> +#include <linux/interrupt.h>  #include <linux/skbuff.h>  #include <linux/slab.h>  #include <linux/atm.h>  #include <linux/atmdev.h>  #include <linux/capability.h>  #include <linux/ppp_defs.h> -#include <linux/if_ppp.h> +#include <linux/ppp-ioctl.h>  #include <linux/ppp_channel.h>  #include <linux/atmppp.h> @@ -59,14 +60,29 @@ struct pppoatm_vcc {  	struct atm_vcc	*atmvcc;	/* VCC descriptor */  	void (*old_push)(struct atm_vcc *, struct sk_buff *);  	void (*old_pop)(struct atm_vcc *, struct sk_buff *); +	void (*old_release_cb)(struct atm_vcc *); +	struct module *old_owner;  					/* keep old push/pop for detaching */  	enum pppoatm_encaps encaps; +	atomic_t inflight; +	unsigned long blocked;  	int flags;			/* SC_COMP_PROT - compress protocol */  	struct ppp_channel chan;	/* interface to generic ppp layer */  	struct tasklet_struct wakeup_tasklet;  };  /* + * We want to allow two packets in the queue. The one that's currently in + * flight, and *one* queued up ready for the ATM device to send immediately + * from its TX done IRQ. We want to be able to use atomic_inc_not_zero(), so + * inflight == -2 represents an empty queue, -1 one packet, and zero means + * there are two packets in the queue. + */ +#define NONE_INFLIGHT -2 + +#define BLOCKED 0 + +/*   * Header used for LLC Encapsulated PPP (4 bytes) followed by the LCP protocol   * ID (0xC021) used in autodetection   */ @@ -93,6 +109,24 @@ static void pppoatm_wakeup_sender(unsigned long arg)  	ppp_output_wakeup((struct ppp_channel *) arg);  } +static void pppoatm_release_cb(struct atm_vcc *atmvcc) +{ +	struct pppoatm_vcc *pvcc = atmvcc_to_pvcc(atmvcc); + +	/* +	 * As in pppoatm_pop(), it's safe to clear the BLOCKED bit here because +	 * the wakeup *can't* race with pppoatm_send(). They both hold the PPP +	 * channel's ->downl lock. And the potential race with *setting* it, +	 * which leads to the double-check dance in pppoatm_may_send(), doesn't +	 * exist here. In the sock_owned_by_user() case in pppoatm_send(), we +	 * set the BLOCKED bit while the socket is still locked. We know that +	 * ->release_cb() can't be called until that's done. +	 */ +	if (test_and_clear_bit(BLOCKED, &pvcc->blocked)) +		tasklet_schedule(&pvcc->wakeup_tasklet); +	if (pvcc->old_release_cb) +		pvcc->old_release_cb(atmvcc); +}  /*   * This gets called every time the ATM card has finished sending our   * skb.  The ->old_pop will take care up normal atm flow control, @@ -101,16 +135,30 @@ static void pppoatm_wakeup_sender(unsigned long arg)  static void pppoatm_pop(struct atm_vcc *atmvcc, struct sk_buff *skb)  {  	struct pppoatm_vcc *pvcc = atmvcc_to_pvcc(atmvcc); +  	pvcc->old_pop(atmvcc, skb); +	atomic_dec(&pvcc->inflight); +  	/* -	 * We don't really always want to do this since it's -	 * really inefficient - it would be much better if we could -	 * test if we had actually throttled the generic layer. -	 * Unfortunately then there would be a nasty SMP race where -	 * we could clear that flag just as we refuse another packet. -	 * For now we do the safe thing. +	 * We always used to run the wakeup tasklet unconditionally here, for +	 * fear of race conditions where we clear the BLOCKED flag just as we +	 * refuse another packet in pppoatm_send(). This was quite inefficient. +	 * +	 * In fact it's OK. The PPP core will only ever call pppoatm_send() +	 * while holding the channel->downl lock. And ppp_output_wakeup() as +	 * called by the tasklet will *also* grab that lock. So even if another +	 * CPU is in pppoatm_send() right now, the tasklet isn't going to race +	 * with it. The wakeup *will* happen after the other CPU is safely out +	 * of pppoatm_send() again. +	 * +	 * So if the CPU in pppoatm_send() has already set the BLOCKED bit and +	 * it about to return, that's fine. We trigger a wakeup which will +	 * happen later. And if the CPU in pppoatm_send() *hasn't* set the +	 * BLOCKED bit yet, that's fine too because of the double check in +	 * pppoatm_may_send() which is commented there.  	 */ -	tasklet_schedule(&pvcc->wakeup_tasklet); +	if (test_and_clear_bit(BLOCKED, &pvcc->blocked)) +		tasklet_schedule(&pvcc->wakeup_tasklet);  }  /* @@ -123,12 +171,11 @@ static void pppoatm_unassign_vcc(struct atm_vcc *atmvcc)  	pvcc = atmvcc_to_pvcc(atmvcc);  	atmvcc->push = pvcc->old_push;  	atmvcc->pop = pvcc->old_pop; +	atmvcc->release_cb = pvcc->old_release_cb;  	tasklet_kill(&pvcc->wakeup_tasklet);  	ppp_unregister_channel(&pvcc->chan);  	atmvcc->user_back = NULL;  	kfree(pvcc); -	/* Gee, I hope we have the big kernel lock here... */ -	module_put(THIS_MODULE);  }  /* Called when an AAL5 PDU comes in */ @@ -137,9 +184,13 @@ static void pppoatm_push(struct atm_vcc *atmvcc, struct sk_buff *skb)  	struct pppoatm_vcc *pvcc = atmvcc_to_pvcc(atmvcc);  	pr_debug("\n");  	if (skb == NULL) {			/* VCC was closed */ +		struct module *module; +  		pr_debug("removing ATMPPP VCC %p\n", pvcc); +		module = pvcc->old_owner;  		pppoatm_unassign_vcc(atmvcc);  		atmvcc->push(atmvcc, NULL);	/* Pass along bad news */ +		module_put(module);  		return;  	}  	atm_return(atmvcc, skb->truesize); @@ -183,6 +234,51 @@ error:  	ppp_input_error(&pvcc->chan, 0);  } +static int pppoatm_may_send(struct pppoatm_vcc *pvcc, int size) +{ +	/* +	 * It's not clear that we need to bother with using atm_may_send() +	 * to check we don't exceed sk->sk_sndbuf. If userspace sets a +	 * value of sk_sndbuf which is lower than the MTU, we're going to +	 * block for ever. But the code always did that before we introduced +	 * the packet count limit, so... +	 */ +	if (atm_may_send(pvcc->atmvcc, size) && +	    atomic_inc_not_zero_hint(&pvcc->inflight, NONE_INFLIGHT)) +		return 1; + +	/* +	 * We use test_and_set_bit() rather than set_bit() here because +	 * we need to ensure there's a memory barrier after it. The bit +	 * *must* be set before we do the atomic_inc() on pvcc->inflight. +	 * There's no smp_mb__after_set_bit(), so it's this or abuse +	 * smp_mb__after_atomic(). +	 */ +	test_and_set_bit(BLOCKED, &pvcc->blocked); + +	/* +	 * We may have raced with pppoatm_pop(). If it ran for the +	 * last packet in the queue, *just* before we set the BLOCKED +	 * bit, then it might never run again and the channel could +	 * remain permanently blocked. Cope with that race by checking +	 * *again*. If it did run in that window, we'll have space on +	 * the queue now and can return success. It's harmless to leave +	 * the BLOCKED flag set, since it's only used as a trigger to +	 * run the wakeup tasklet. Another wakeup will never hurt. +	 * If pppoatm_pop() is running but hasn't got as far as making +	 * space on the queue yet, then it hasn't checked the BLOCKED +	 * flag yet either, so we're safe in that case too. It'll issue +	 * an "immediate" wakeup... where "immediate" actually involves +	 * taking the PPP channel's ->downl lock, which is held by the +	 * code path that calls pppoatm_send(), and is thus going to +	 * wait for us to finish. +	 */ +	if (atm_may_send(pvcc->atmvcc, size) && +	    atomic_inc_not_zero(&pvcc->inflight)) +		return 1; + +	return 0; +}  /*   * Called by the ppp_generic.c to send a packet - returns true if packet   * was accepted.  If we return false, then it's our job to call @@ -196,33 +292,59 @@ error:  static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb)  {  	struct pppoatm_vcc *pvcc = chan_to_pvcc(chan); +	struct atm_vcc *vcc; +	int ret; +  	ATM_SKB(skb)->vcc = pvcc->atmvcc;  	pr_debug("(skb=0x%p, vcc=0x%p)\n", skb, pvcc->atmvcc);  	if (skb->data[0] == '\0' && (pvcc->flags & SC_COMP_PROT))  		(void) skb_pull(skb, 1); + +	vcc = ATM_SKB(skb)->vcc; +	bh_lock_sock(sk_atm(vcc)); +	if (sock_owned_by_user(sk_atm(vcc))) { +		/* +		 * Needs to happen (and be flushed, hence test_and_) before we unlock +		 * the socket. It needs to be seen by the time our ->release_cb gets +		 * called. +		 */ +		test_and_set_bit(BLOCKED, &pvcc->blocked); +		goto nospace; +	} +	if (test_bit(ATM_VF_RELEASED, &vcc->flags) || +	    test_bit(ATM_VF_CLOSE, &vcc->flags) || +	    !test_bit(ATM_VF_READY, &vcc->flags)) { +		bh_unlock_sock(sk_atm(vcc)); +		kfree_skb(skb); +		return DROP_PACKET; +	} +  	switch (pvcc->encaps) {		/* LLC encapsulation needed */  	case e_llc:  		if (skb_headroom(skb) < LLC_LEN) {  			struct sk_buff *n;  			n = skb_realloc_headroom(skb, LLC_LEN);  			if (n != NULL && -			    !atm_may_send(pvcc->atmvcc, n->truesize)) { +			    !pppoatm_may_send(pvcc, n->truesize)) {  				kfree_skb(n);  				goto nospace;  			} -			kfree_skb(skb); +			consume_skb(skb);  			skb = n; -			if (skb == NULL) +			if (skb == NULL) { +				bh_unlock_sock(sk_atm(vcc));  				return DROP_PACKET; -		} else if (!atm_may_send(pvcc->atmvcc, skb->truesize)) +			} +		} else if (!pppoatm_may_send(pvcc, skb->truesize))  			goto nospace;  		memcpy(skb_push(skb, LLC_LEN), pppllc, LLC_LEN);  		break;  	case e_vc: -		if (!atm_may_send(pvcc->atmvcc, skb->truesize)) +		if (!pppoatm_may_send(pvcc, skb->truesize))  			goto nospace;  		break;  	case e_autodetect: +		bh_unlock_sock(sk_atm(vcc));  		pr_debug("Trying to send without setting encaps!\n");  		kfree_skb(skb);  		return 1; @@ -232,9 +354,12 @@ static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb)  	ATM_SKB(skb)->atm_options = ATM_SKB(skb)->vcc->atm_options;  	pr_debug("atm_skb(%p)->vcc(%p)->dev(%p)\n",  		 skb, ATM_SKB(skb)->vcc, ATM_SKB(skb)->vcc->dev); -	return ATM_SKB(skb)->vcc->send(ATM_SKB(skb)->vcc, skb) +	ret = ATM_SKB(skb)->vcc->send(ATM_SKB(skb)->vcc, skb)  	    ? DROP_PACKET : 1; +	bh_unlock_sock(sk_atm(vcc)); +	return ret;  nospace: +	bh_unlock_sock(sk_atm(vcc));  	/*  	 * We don't have space to send this SKB now, but we might have  	 * already applied SC_COMP_PROT compression, so may need to undo @@ -284,8 +409,13 @@ static int pppoatm_assign_vcc(struct atm_vcc *atmvcc, void __user *arg)  	if (pvcc == NULL)  		return -ENOMEM;  	pvcc->atmvcc = atmvcc; + +	/* Maximum is zero, so that we can use atomic_inc_not_zero() */ +	atomic_set(&pvcc->inflight, NONE_INFLIGHT);  	pvcc->old_push = atmvcc->push;  	pvcc->old_pop = atmvcc->pop; +	pvcc->old_owner = atmvcc->owner; +	pvcc->old_release_cb = atmvcc->release_cb;  	pvcc->encaps = (enum pppoatm_encaps) be.encaps;  	pvcc->chan.private = pvcc;  	pvcc->chan.ops = &pppoatm_ops; @@ -301,7 +431,13 @@ static int pppoatm_assign_vcc(struct atm_vcc *atmvcc, void __user *arg)  	atmvcc->user_back = pvcc;  	atmvcc->push = pppoatm_push;  	atmvcc->pop = pppoatm_pop; +	atmvcc->release_cb = pppoatm_release_cb;  	__module_get(THIS_MODULE); +	atmvcc->owner = THIS_MODULE; + +	/* re-process everything received between connection setup and +	   backend setup */ +	vcc_process_recv_queue(atmvcc);  	return 0;  } @@ -326,6 +462,8 @@ static int pppoatm_ioctl(struct socket *sock, unsigned int cmd,  			return -ENOIOCTLCMD;  		if (!capable(CAP_NET_ADMIN))  			return -EPERM; +		if (sock->state != SS_CONNECTED) +			return -EINVAL;  		return pppoatm_assign_vcc(atmvcc, argp);  		}  	case PPPIOCGCHAN:  | 
