Skip to content

Commit

Permalink
usbnet: fix cyclical race on disconnect with work queue
Browse files Browse the repository at this point in the history
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.
This is a design issue as old as the driver.

Signed-off-by: Oliver Neukum <oneukum@suse.com>
Fixes: 1da177e ("Linux-2.6.12-rc2")
CC: stable@vger.kernel.org
Link: https://patch.msgid.link/20240919123525.688065-1-oneukum@suse.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
  • Loading branch information
Oliver Neukum authored and Paolo Abeni committed Sep 26, 2024
1 parent b514c47 commit 04e9068
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 9 deletions.
37 changes: 28 additions & 9 deletions drivers/net/usb/usbnet.c
Original file line number Diff line number Diff line change
Expand Up @@ -464,10 +464,15 @@ 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);

Expand Down Expand Up @@ -535,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");
Expand Down Expand Up @@ -843,9 +849,18 @@ int usbnet_stop (struct net_device *net)

/* deferred work (timer, softirq, task) must also stop */
dev->flags = 0;
del_timer_sync (&dev->delay);
tasklet_kill (&dev->bh);
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);

Expand Down Expand Up @@ -1171,7 +1186,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);
}
}

Expand All @@ -1196,7 +1212,8 @@ usbnet_deferred_kevent (struct work_struct *work)
usb_autopm_put_interface(dev->intf);
fail_lowmem:
if (resched)
tasklet_schedule (&dev->bh);
if (!usbnet_going_away(dev))
tasklet_schedule(&dev->bh);
}
}

Expand Down Expand Up @@ -1559,6 +1576,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)) {
Expand Down Expand Up @@ -1606,6 +1624,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);

Expand Down
15 changes: 15 additions & 0 deletions include/linux/usb/usbnet.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,23 @@ 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)
{
return test_bit(EVENT_UNPLUG, &ubn->flags);
}

static inline void usbnet_mark_going_away(struct usbnet *ubn)
{
set_bit(EVENT_UNPLUG, &ubn->flags);
}

static inline struct usb_driver *driver_of(struct usb_interface *intf)
{
return to_usb_driver(intf->dev.driver);
Expand Down

0 comments on commit 04e9068

Please sign in to comment.