diff mbox series

[net-next] usbnet: fix cyclical race on disconnect with work queue

Message ID 20240321124758.6302-1-oneukum@suse.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series [net-next] usbnet: fix cyclical race on disconnect with work queue | expand

Checks

Context Check Description
netdev/series_format success Single patches do not need cover letters
netdev/tree_selection success Clearly marked for net-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 955 this patch: 955
netdev/build_tools success Errors and warnings before: 0 this patch: 0
netdev/cc_maintainers fail 1 blamed authors not CCed: gregkh@linuxfoundation.org; 1 maintainers not CCed: gregkh@linuxfoundation.org
netdev/build_clang success Errors and warnings before: 956 this patch: 956
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success Fixes tag looks correct
netdev/build_allmodconfig_warn success Errors and warnings before: 972 this patch: 972
netdev/checkpatch warning CHECK: Unnecessary parentheses around '&dev->kevent' WARNING: Please use correct Fixes: style 'Fixes: <12 chars of sha1> ("<title line>")' - ie: 'Fixes: f29fc259976e ("[PATCH] USB: usbnet (1/9) clean up framing")' WARNING: line length of 108 exceeds 80 columns WARNING: line length of 96 exceeds 80 columns WARNING: memory barrier without comment WARNING: networking block comments don't use an empty /* line, use /* Comment... WARNING: space prohibited between function name and open parenthesis '('
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
netdev/contest success net-next-2024-03-21--15-00 (tests: 909)

Commit Message

Oliver Neukum March 21, 2024, 12:46 p.m. UTC
The work can submit URBs and the URBs can schedule the work.
This cycle needs to be broken, when a device is to be stopped.
Use a flag to do so.

Fixes: f29fc259976e9 ("[PATCH] USB: usbnet (1/9) clean up framing")
Reported-by: syzbot+9665bf55b1c828bbcd8a@syzkaller.appspotmail.com
Signed-off-by: Oliver Neukum <oneukum@suse.com>
---
 drivers/net/usb/usbnet.c   | 37 ++++++++++++++++++++++++++++---------
 include/linux/usb/usbnet.h | 18 ++++++++++++++++++
 2 files changed, 46 insertions(+), 9 deletions(-)

Comments

Greg KH March 21, 2024, 12:59 p.m. UTC | #1
On Thu, Mar 21, 2024 at 01:46:41PM +0100, Oliver Neukum wrote:
> The work can submit URBs and the URBs can schedule the work.
> This cycle needs to be broken, when a device is to be stopped.
> Use a flag to do so.
> 
> Fixes: f29fc259976e9 ("[PATCH] USB: usbnet (1/9) clean up framing")
> Reported-by: syzbot+9665bf55b1c828bbcd8a@syzkaller.appspotmail.com
> Signed-off-by: Oliver Neukum <oneukum@suse.com>
> ---
>  drivers/net/usb/usbnet.c   | 37 ++++++++++++++++++++++++++++---------
>  include/linux/usb/usbnet.h | 18 ++++++++++++++++++
>  2 files changed, 46 insertions(+), 9 deletions(-)
> 
Hi,

This is the friendly patch-bot of Greg Kroah-Hartman.  You have sent him
a patch that has triggered this response.  He used to manually respond
to these common problems, but in order to save his sanity (he kept
writing the same thing over and over, yet to different people), I was
created.  Hopefully you will not take offence and will fix the problem
in your patch and resubmit it so that it can be accepted into the Linux
kernel tree.

You are receiving this message because of the following common error(s)
as indicated below:

- You have marked a patch with a "Fixes:" tag for a commit that is in an
  older released kernel, yet you do not have a cc: stable line in the
  signed-off-by area at all, which means that the patch will not be
  applied to any older kernel releases.  To properly fix this, please
  follow the documented rules in the
  Documentation/process/stable-kernel-rules.rst file for how to resolve
  this.

If you wish to discuss this problem further, or you have questions about
how to resolve this issue, please feel free to respond to this email and
Greg will reply once he has dug out from the pending patches received
from other developers.

thanks,

greg k-h's patch email bot
Sai Krishna Gajula March 22, 2024, 5:43 p.m. UTC | #2
> -----Original Message-----
> From: Oliver Neukum <oneukum@suse.com>
> Sent: Thursday, March 21, 2024 6:17 PM
> To: davem@davemloft.net; edumazet@google.com; kuba@kernel.org;
> pabeni@redhat.com; netdev@vger.kernel.org; linux-usb@vger.kernel.org;
> linux-kernel@vger.kernel.org
> Cc: Oliver Neukum <oneukum@suse.com>;
> syzbot+9665bf55b1c828bbcd8a@syzkaller.appspotmail.com
> Subject: [PATCH net-next] usbnet: fix cyclical race on disconnect
> with work queue

This patch seems to be a fix, in that case the subject need to be with [PATCH net]

> 
> The work can submit URBs and the URBs can schedule the work.
> This cycle needs to be broken, when a device is to be stopped.
> Use a flag to do so.
> 
> Fixes: f29fc259976e9 ("[PATCH] USB: usbnet (1/9) clean up framing")

Please use correct Fixes: style 'Fixes: <12 chars of sha1> ("<title line>")' - ie: 'Fixes: f29fc259976e ("[PATCH] USB: usbnet (1/9) clean up framing")' 

> Reported-by: syzbot+9665bf55b1c828bbcd8a@syzkaller.appspotmail.com
> Signed-off-by: Oliver Neukum <oneukum@suse.com>
> ---
>  drivers/net/usb/usbnet.c   | 37 ++++++++++++++++++++++++++++---------
>  include/linux/usb/usbnet.h | 18 ++++++++++++++++++
>  2 files changed, 46 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c index
> e84efa661589..422d91635045 100644
> --- a/drivers/net/usb/usbnet.c
> +++ b/drivers/net/usb/usbnet.c
> @@ -467,10 +467,12 @@ static enum skb_state defer_bh(struct usbnet *dev,
> struct sk_buff *skb,  void usbnet_defer_kevent (struct usbnet *dev, int work)

space prohibited between function name and open parenthesis '('

> {
>  	set_bit (work, &dev->flags);
> -	if (!schedule_work (&dev->kevent))
> -		netdev_dbg(dev->net, "kevent %s may have been
> dropped\n", usbnet_event_names[work]);
> -	else
> -		netdev_dbg(dev->net, "kevent %s scheduled\n",
> usbnet_event_names[work]);
> +	if (!usbnet_going_away(dev)) {
> +		if (!schedule_work (&dev->kevent))
> +			netdev_dbg(dev->net, "kevent %s may have been
> dropped\n", usbnet_event_names[work]);
> +		else
> +			netdev_dbg(dev->net, "kevent %s scheduled\n",
> usbnet_event_names[work]);
> +	}
>  }
>  EXPORT_SYMBOL_GPL(usbnet_defer_kevent);
> 
> @@ -538,7 +540,8 @@ static int rx_submit (struct usbnet *dev, struct urb
> *urb, gfp_t flags)

space prohibited between function name and open parenthesis '(', where ever applicable.

>  			tasklet_schedule (&dev->bh);
>  			break;
>  		case 0:
> -			__usbnet_queue_skb(&dev->rxq, skb, rx_start);
> +			if (!usbnet_going_away(dev))
> +				__usbnet_queue_skb(&dev->rxq, skb,
> rx_start);
>  		}
>  	} else {
>  		netif_dbg(dev, ifdown, dev->net, "rx: stopped\n"); @@ -849,6
> +852,16 @@ int usbnet_stop (struct net_device *net)
>  	del_timer_sync (&dev->delay);
>  	tasklet_kill (&dev->bh);
>  	cancel_work_sync(&dev->kevent);
> +
> +	/*
> +	 * we have cyclic dependencies. Those calls are needed
> +	 * to break a cycle. We cannot fall into the gaps because
> +	 * we have a flag
> +	 */

Networking block comments don't use an empty /* line, use /* Comment...

> +	tasklet_kill (&dev->bh);
> +	del_timer_sync (&dev->delay);
> +	cancel_work_sync(&dev->kevent);
> +
>  	if (!pm)
>  		usb_autopm_put_interface(dev->intf);
> 
> @@ -1174,7 +1187,8 @@ usbnet_deferred_kevent (struct work_struct *work)
>  					   status);
>  		} else {
>  			clear_bit (EVENT_RX_HALT, &dev->flags);
> -			tasklet_schedule (&dev->bh);
> +			if (!usbnet_going_away(dev))
> +				tasklet_schedule (&dev->bh);
>  		}
>  	}
> 
> @@ -1196,10 +1210,13 @@ usbnet_deferred_kevent (struct work_struct
> *work)
>  			}
>  			if (rx_submit (dev, urb, GFP_KERNEL) == -ENOLINK)
>  				resched = 0;
> -			usb_autopm_put_interface(dev->intf);
>  fail_lowmem:
> -			if (resched)
> +			usb_autopm_put_interface(dev->intf);
> +			if (resched) {
> +				set_bit (EVENT_RX_MEMORY, &dev->flags);
> +
>  				tasklet_schedule (&dev->bh);
> +			}
>  		}
>  	}
> 
> @@ -1212,13 +1229,13 @@ usbnet_deferred_kevent (struct work_struct
> *work)
>  		if (status < 0)
>  			goto skip_reset;
>  		if(info->link_reset && (retval = info->link_reset(dev)) < 0) {
> -			usb_autopm_put_interface(dev->intf);
>  skip_reset:
>  			netdev_info(dev->net, "link reset failed (%d) usbnet
> usb-%s-%s, %s\n",
>  				    retval,
>  				    dev->udev->bus->bus_name,
>  				    dev->udev->devpath,
>  				    info->description);
> +			usb_autopm_put_interface(dev->intf);
>  		} else {
>  			usb_autopm_put_interface(dev->intf);
>  		}
> @@ -1562,6 +1579,7 @@ static void usbnet_bh (struct timer_list *t)
>  	} else if (netif_running (dev->net) &&
>  		   netif_device_present (dev->net) &&
>  		   netif_carrier_ok(dev->net) &&
> +		   !usbnet_going_away(dev) &&
>  		   !timer_pending(&dev->delay) &&
>  		   !test_bit(EVENT_RX_PAUSED, &dev->flags) &&
>  		   !test_bit(EVENT_RX_HALT, &dev->flags)) { @@ -1609,6
> +1627,7 @@ void usbnet_disconnect (struct usb_interface *intf)
>  	usb_set_intfdata(intf, NULL);
>  	if (!dev)
>  		return;
> +	usbnet_mark_going_away(dev);
> 
>  	xdev = interface_to_usbdev (intf);
> 
> diff --git a/include/linux/usb/usbnet.h b/include/linux/usb/usbnet.h index
> 9f08a584d707..d26599faab33 100644
> --- a/include/linux/usb/usbnet.h
> +++ b/include/linux/usb/usbnet.h
> @@ -76,8 +76,26 @@ struct usbnet {
>  #		define EVENT_LINK_CHANGE	11
>  #		define EVENT_SET_RX_MODE	12
>  #		define EVENT_NO_IP_ALIGN	13
> +/*
> + * this one is special, as it indicates that the device is going away
> + * there are cyclic dependencies between tasklet, timer and bh
> + * that must be broken
> + */

Networking block comments don't use an empty /* line, use /* Comment...

> +#		define EVENT_UNPLUG		31
>  };
> 
> +static inline bool usbnet_going_away(struct usbnet *ubn) {
> +	smp_mb__before_atomic();
> +	return test_bit(EVENT_UNPLUG, &ubn->flags); }
> +
> +static inline void usbnet_mark_going_away(struct usbnet *ubn) {
> +	set_bit(EVENT_UNPLUG, &ubn->flags);
> +	smp_mb__after_atomic();
> +}
> +
>  static inline struct usb_driver *driver_of(struct usb_interface *intf)  {
>  	return to_usb_driver(intf->dev.driver);
> --
> 2.44.0
>
Oliver Neukum March 27, 2024, 3:10 p.m. UTC | #3
On 3/22/24 18:43, Sai Krishna Gajula wrote:
> 
>> -----Original Message-----
>> From: Oliver Neukum <oneukum@suse.com>
>> Sent: Thursday, March 21, 2024 6:17 PM
>> To: davem@davemloft.net; edumazet@google.com; kuba@kernel.org;
>> pabeni@redhat.com; netdev@vger.kernel.org; linux-usb@vger.kernel.org;
>> linux-kernel@vger.kernel.org
>> Cc: Oliver Neukum <oneukum@suse.com>;
>> syzbot+9665bf55b1c828bbcd8a@syzkaller.appspotmail.com
>> Subject: [PATCH net-next] usbnet: fix cyclical race on disconnect
>> with work queue
> 
> This patch seems to be a fix, in that case the subject need to be with [PATCH net]

OK
> 
>>
>> The work can submit URBs and the URBs can schedule the work.
>> This cycle needs to be broken, when a device is to be stopped.
>> Use a flag to do so.
>>
>> Fixes: f29fc259976e9 ("[PATCH] USB: usbnet (1/9) clean up framing")
> 
> Please use correct Fixes: style 'Fixes: <12 chars of sha1> ("<title line>")' - ie: 'Fixes: f29fc259976e ("[PATCH] USB: usbnet (1/9) clean up framing")'

Ehm, what exactly did I do differently

>> --- a/drivers/net/usb/usbnet.c
>> +++ b/drivers/net/usb/usbnet.c
>> @@ -467,10 +467,12 @@ static enum skb_state defer_bh(struct usbnet *dev,
>> struct sk_buff *skb,  void usbnet_defer_kevent (struct usbnet *dev, int work)
> 
> space prohibited between function name and open parenthesis '('

I am sorry, but this is the context of the diff. You are not suggesting
to mix gratitious format changes into a bug fix, are you?

>> diff --git a/include/linux/usb/usbnet.h b/include/linux/usb/usbnet.h index
>> 9f08a584d707..d26599faab33 100644
>> --- a/include/linux/usb/usbnet.h
>> +++ b/include/linux/usb/usbnet.h
>> @@ -76,8 +76,26 @@ struct usbnet {
>>   #		define EVENT_LINK_CHANGE	11
>>   #		define EVENT_SET_RX_MODE	12
>>   #		define EVENT_NO_IP_ALIGN	13
>> +/*
>> + * this one is special, as it indicates that the device is going away
>> + * there are cyclic dependencies between tasklet, timer and bh
>> + * that must be broken
>> + */
> 
> Networking block comments don't use an empty /* line, use /* Comment...

OK

	Regards
		Oliver
Simon Horman March 28, 2024, 2:08 p.m. UTC | #4
On Wed, Mar 27, 2024 at 04:10:36PM +0100, Oliver Neukum wrote:
> 
> 
> On 3/22/24 18:43, Sai Krishna Gajula wrote:
> > 
> > > -----Original Message-----
> > > From: Oliver Neukum <oneukum@suse.com>
> > > Sent: Thursday, March 21, 2024 6:17 PM
> > > To: davem@davemloft.net; edumazet@google.com; kuba@kernel.org;
> > > pabeni@redhat.com; netdev@vger.kernel.org; linux-usb@vger.kernel.org;
> > > linux-kernel@vger.kernel.org
> > > Cc: Oliver Neukum <oneukum@suse.com>;
> > > syzbot+9665bf55b1c828bbcd8a@syzkaller.appspotmail.com
> > > Subject: [PATCH net-next] usbnet: fix cyclical race on disconnect
> > > with work queue
> > 
> > This patch seems to be a fix, in that case the subject need to be with [PATCH net]
> 
> OK
> > 
> > > 
> > > The work can submit URBs and the URBs can schedule the work.
> > > This cycle needs to be broken, when a device is to be stopped.
> > > Use a flag to do so.
> > > 
> > > Fixes: f29fc259976e9 ("[PATCH] USB: usbnet (1/9) clean up framing")
> > 
> > Please use correct Fixes: style 'Fixes: <12 chars of sha1> ("<title line>")' - ie: 'Fixes: f29fc259976e ("[PATCH] USB: usbnet (1/9) clean up framing")'
> 
> Ehm, what exactly did I do differently

I think the point being made is that the hash has 13 rather than 12
characters. But, IMHO, that is fine because my understanding is that the
requirement is that the hash is at least, not exactly, 12 characters long.

...
Sai Krishna Gajula March 31, 2024, 12:07 p.m. UTC | #5
> -----Original Message-----
> From: Oliver Neukum <oneukum@suse.com>
> Sent: Wednesday, March 27, 2024 8:41 PM
> To: Sai Krishna Gajula <saikrishnag@marvell.com>; Oliver Neukum
> <oneukum@suse.com>; davem@davemloft.net; edumazet@google.com;
> kuba@kernel.org; pabeni@redhat.com; netdev@vger.kernel.org; linux-
> usb@vger.kernel.org; linux-kernel@vger.kernel.org
> Cc: syzbot+9665bf55b1c828bbcd8a@syzkaller.appspotmail.com
> Subject: Re: [PATCH net-next] usbnet: fix cyclical race on
> disconnect with work queue
> 
> 
> On 3/22/24 18:43, Sai Krishna Gajula wrote:
> >
> >> -----Original Message-----
> >> From: Oliver Neukum <oneukum@suse.com>
> >> Sent: Thursday, March 21, 2024 6:17 PM
> >> To: davem@davemloft.net; edumazet@google.com; kuba@kernel.org;
> >> pabeni@redhat.com; netdev@vger.kernel.org; linux-usb@vger.kernel.org;
> >> linux-kernel@vger.kernel.org
> >> Cc: Oliver Neukum <oneukum@suse.com>;
> >> syzbot+9665bf55b1c828bbcd8a@syzkaller.appspotmail.com
> >> Subject: [PATCH net-next] usbnet: fix cyclical race on disconnect
> >> with work queue
> >
> > This patch seems to be a fix, in that case the subject need to be with
> > [PATCH net]
> 
> OK
> >
> >>
> >> The work can submit URBs and the URBs can schedule the work.
> >> This cycle needs to be broken, when a device is to be stopped.
> >> Use a flag to do so.
> >>
> >> Fixes: f29fc259976e9 ("[PATCH] USB: usbnet (1/9) clean up framing")
> >
> > Please use correct Fixes: style 'Fixes: <12 chars of sha1> ("<title line>")' - ie:
> 'Fixes: f29fc259976e ("[PATCH] USB: usbnet (1/9) clean up framing")'
> 
> Ehm, what exactly did I do differently

Checkpatch triggers warning if we give Fixes tag(f29fc259976e -9-) more than 12 chars. It is advisable to follow the upstreaming process steps to have uniformity across patches.

> 
> >> --- a/drivers/net/usb/usbnet.c
> >> +++ b/drivers/net/usb/usbnet.c
> >> @@ -467,10 +467,12 @@ static enum skb_state defer_bh(struct usbnet
> >> *dev, struct sk_buff *skb,  void usbnet_defer_kevent (struct usbnet
> >> *dev, int work)
> >
> > space prohibited between function name and open parenthesis '('
> 
> I am sorry, but this is the context of the diff. You are not suggesting to mix
> gratitious format changes into a bug fix, are you?

Checkpatch does a primary check and triggered warning if we use space between fn name and '('.  It is advisable to follow the upstreaming process steps(clean checkpatch output) to have uniformity across patches. Also check comments from Gregkh bot regarding this patch.

> 
> >> diff --git a/include/linux/usb/usbnet.h b/include/linux/usb/usbnet.h
> >> index
> >> 9f08a584d707..d26599faab33 100644
> >> --- a/include/linux/usb/usbnet.h
> >> +++ b/include/linux/usb/usbnet.h
> >> @@ -76,8 +76,26 @@ struct usbnet {
> >>   #		define EVENT_LINK_CHANGE	11
> >>   #		define EVENT_SET_RX_MODE	12
> >>   #		define EVENT_NO_IP_ALIGN	13
> >> +/*
> >> + * this one is special, as it indicates that the device is going
> >> +away
> >> + * there are cyclic dependencies between tasklet, timer and bh
> >> + * that must be broken
> >> + */
> >
> > Networking block comments don't use an empty /* line, use /* Comment...
> 
> OK
> 
> 	Regards
> 		Oliver
Oliver Neukum March 31, 2024, 5:03 p.m. UTC | #6
On 31.03.24 14:07, Sai Krishna Gajula wrote:
>>>> --- a/drivers/net/usb/usbnet.c
>>>> +++ b/drivers/net/usb/usbnet.c
>>>> @@ -467,10 +467,12 @@ static enum skb_state defer_bh(struct usbnet
>>>> *dev, struct sk_buff *skb,  void usbnet_defer_kevent (struct usbnet
>>>> *dev, int work)
>>>
>>> space prohibited between function name and open parenthesis '('
>>
>> I am sorry, but this is the context of the diff. You are not suggesting to mix
>> gratitious format changes into a bug fix, are you?
> 
> Checkpatch does a primary check and triggered warning if we use space between fn name and '('.  It is advisable to follow the upstreaming process steps(clean checkpatch output) to have uniformity across patches. Also check comments from Gregkh bot regarding this patch.

Hi,

at the risk of repeating myself:

This is a very old driver written long before these conventions.
I did not introduce these spaces. Nevertheless, a format change
has no place in a bug fix.

	Regards
		Oliver
diff mbox series

Patch

diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c
index e84efa661589..422d91635045 100644
--- a/drivers/net/usb/usbnet.c
+++ b/drivers/net/usb/usbnet.c
@@ -467,10 +467,12 @@  static enum skb_state defer_bh(struct usbnet *dev, struct sk_buff *skb,
 void usbnet_defer_kevent (struct usbnet *dev, int work)
 {
 	set_bit (work, &dev->flags);
-	if (!schedule_work (&dev->kevent))
-		netdev_dbg(dev->net, "kevent %s may have been dropped\n", usbnet_event_names[work]);
-	else
-		netdev_dbg(dev->net, "kevent %s scheduled\n", usbnet_event_names[work]);
+	if (!usbnet_going_away(dev)) {
+		if (!schedule_work (&dev->kevent))
+			netdev_dbg(dev->net, "kevent %s may have been dropped\n", usbnet_event_names[work]);
+		else
+			netdev_dbg(dev->net, "kevent %s scheduled\n", usbnet_event_names[work]);
+	}
 }
 EXPORT_SYMBOL_GPL(usbnet_defer_kevent);
 
@@ -538,7 +540,8 @@  static int rx_submit (struct usbnet *dev, struct urb *urb, gfp_t flags)
 			tasklet_schedule (&dev->bh);
 			break;
 		case 0:
-			__usbnet_queue_skb(&dev->rxq, skb, rx_start);
+			if (!usbnet_going_away(dev))
+				__usbnet_queue_skb(&dev->rxq, skb, rx_start);
 		}
 	} else {
 		netif_dbg(dev, ifdown, dev->net, "rx: stopped\n");
@@ -849,6 +852,16 @@  int usbnet_stop (struct net_device *net)
 	del_timer_sync (&dev->delay);
 	tasklet_kill (&dev->bh);
 	cancel_work_sync(&dev->kevent);
+
+	/*
+	 * we have cyclic dependencies. Those calls are needed
+	 * to break a cycle. We cannot fall into the gaps because
+	 * we have a flag
+	 */
+	tasklet_kill (&dev->bh);
+	del_timer_sync (&dev->delay);
+	cancel_work_sync(&dev->kevent);
+
 	if (!pm)
 		usb_autopm_put_interface(dev->intf);
 
@@ -1174,7 +1187,8 @@  usbnet_deferred_kevent (struct work_struct *work)
 					   status);
 		} else {
 			clear_bit (EVENT_RX_HALT, &dev->flags);
-			tasklet_schedule (&dev->bh);
+			if (!usbnet_going_away(dev))
+				tasklet_schedule (&dev->bh);
 		}
 	}
 
@@ -1196,10 +1210,13 @@  usbnet_deferred_kevent (struct work_struct *work)
 			}
 			if (rx_submit (dev, urb, GFP_KERNEL) == -ENOLINK)
 				resched = 0;
-			usb_autopm_put_interface(dev->intf);
 fail_lowmem:
-			if (resched)
+			usb_autopm_put_interface(dev->intf);
+			if (resched) {
+				set_bit (EVENT_RX_MEMORY, &dev->flags);
+
 				tasklet_schedule (&dev->bh);
+			}
 		}
 	}
 
@@ -1212,13 +1229,13 @@  usbnet_deferred_kevent (struct work_struct *work)
 		if (status < 0)
 			goto skip_reset;
 		if(info->link_reset && (retval = info->link_reset(dev)) < 0) {
-			usb_autopm_put_interface(dev->intf);
 skip_reset:
 			netdev_info(dev->net, "link reset failed (%d) usbnet usb-%s-%s, %s\n",
 				    retval,
 				    dev->udev->bus->bus_name,
 				    dev->udev->devpath,
 				    info->description);
+			usb_autopm_put_interface(dev->intf);
 		} else {
 			usb_autopm_put_interface(dev->intf);
 		}
@@ -1562,6 +1579,7 @@  static void usbnet_bh (struct timer_list *t)
 	} else if (netif_running (dev->net) &&
 		   netif_device_present (dev->net) &&
 		   netif_carrier_ok(dev->net) &&
+		   !usbnet_going_away(dev) &&
 		   !timer_pending(&dev->delay) &&
 		   !test_bit(EVENT_RX_PAUSED, &dev->flags) &&
 		   !test_bit(EVENT_RX_HALT, &dev->flags)) {
@@ -1609,6 +1627,7 @@  void usbnet_disconnect (struct usb_interface *intf)
 	usb_set_intfdata(intf, NULL);
 	if (!dev)
 		return;
+	usbnet_mark_going_away(dev);
 
 	xdev = interface_to_usbdev (intf);
 
diff --git a/include/linux/usb/usbnet.h b/include/linux/usb/usbnet.h
index 9f08a584d707..d26599faab33 100644
--- a/include/linux/usb/usbnet.h
+++ b/include/linux/usb/usbnet.h
@@ -76,8 +76,26 @@  struct usbnet {
 #		define EVENT_LINK_CHANGE	11
 #		define EVENT_SET_RX_MODE	12
 #		define EVENT_NO_IP_ALIGN	13
+/*
+ * this one is special, as it indicates that the device is going away
+ * there are cyclic dependencies between tasklet, timer and bh
+ * that must be broken
+ */
+#		define EVENT_UNPLUG		31
 };
 
+static inline bool usbnet_going_away(struct usbnet *ubn)
+{
+	smp_mb__before_atomic();
+	return test_bit(EVENT_UNPLUG, &ubn->flags);
+}
+
+static inline void usbnet_mark_going_away(struct usbnet *ubn)
+{
+	set_bit(EVENT_UNPLUG, &ubn->flags);
+	smp_mb__after_atomic();
+}
+
 static inline struct usb_driver *driver_of(struct usb_interface *intf)
 {
 	return to_usb_driver(intf->dev.driver);