Re: BUG: Deadlock caused by bandwidth_mutex

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Sat, Apr 14, 2012 at 05:07:25PM -0400, Alan Stern wrote:
> On Fri, 13 Apr 2012, Alan Stern wrote:
> 
> > Sarah:
> > 
> > I just tracked this down.  When you do
> > 
> > 	echo 0 >/sys/.../bConfigurationValue
> > 
> > for a hub that has children, usb_disconnect() deadlocks when it tries
> > to acquire the bandwidth_mutex.  This is because the task already holds
> > the mutex -- it was acquired in usb_set_configuration() just before the
> > call to usb_disable_device().
> > 
> > That is, usb_disable_device() unbinds the hub driver, since its 
> > interface is going away.  The hub driver, in hub_quiesce(), disconnects 
> > all the child devices.
> > 
> > Any ideas on how to fix this?  Maybe usb_disable_device() should be 
> > responsible for acquiring the bandwidth_mutex on its own, after it 
> > unbinds the interfaces, instead of making the caller acquire the mutex.
> 
> In other words, something like this patch.  However I'm not sure how 
> well it would interact with your 
> fccf4e86200b8f5edd9a65da26f150e32ba79808 commit; it undoes several of 
> the changes you introduced.

I'm not sure what you mean by "undoes".  This patch just pushes the
bandwidth mutex acquirement into usb_disable_device() and shrinks the
mutex code coverage a bit, correct?  Assuming there are no unwanted race
conditions for bandwidth allocation/deallocation, the behavior should
still be basically the same, right?

The only issue I can see with pushing the mutex lock acquire into
usb_disable_device() is the following scenario:

1. Userspace (via the bConfiguration file) or a driver wants to switch a
configured device (device A) to a new configuration.

2. usb_disable_device() locks the mutex and removes the old
configuration from the xHCI internal structures.  It drops the mutex and
returns to usb_set_configuration().

3. A new USB device (device B) is connected, and the hub thread takes
the bandwidth mutex before usb_set_configuration() can re-acquire it.
It installs a configuration for device B that takes up the rest of the
bandwidth.

4. usb_set_configuration() re-acquires the bandwidth mutex and attempts
to install the requested new configuration for device A.  That fails
because there isn't enough bandwidth.

Now device A is in an odd state, because the old configuration is still
installed in the device.  We can't re-install the old configuration in
the xHCI host internal structures because there isn't enough bandwidth.

If the bandwidth mutex coverage stayed the same, device B would fail to
be enumerated, and device A would have either the new configuration or
the old configuration (depending on if there was enough room for the new
configuration), but it would still be usable.

I guess from a user's perspective, in either case, one of the devices is
just not going to work.  I think it would be better if the new device
was the one that stops working, but since there's race conditions in the
current code, I think it's OK that the old device could become unusable
and the user will need to unplug/replug it.  It's going to be very
confusing to users to have inconsistent behavior, but I think it's a
corner case that not many people will run into.

So my initial reaction to the patch is that it looks fine.  Did you have
any other specific concerns/scenarios that you wanted to run past me?

> Index: usb-3.4/drivers/usb/core/hub.c
> ===================================================================
> --- usb-3.4.orig/drivers/usb/core/hub.c
> +++ usb-3.4/drivers/usb/core/hub.c
> @@ -1667,7 +1667,6 @@ void usb_disconnect(struct usb_device **
>  {
>  	struct usb_device	*udev = *pdev;
>  	int			i;
> -	struct usb_hcd		*hcd = bus_to_hcd(udev->bus);
>  
>  	/* mark the device as inactive, so any further urb submissions for
>  	 * this device (and any of its children) will fail immediately.
> @@ -1690,9 +1689,7 @@ void usb_disconnect(struct usb_device **
>  	 * so that the hardware is now fully quiesced.
>  	 */
>  	dev_dbg (&udev->dev, "unregistering device\n");
> -	mutex_lock(hcd->bandwidth_mutex);
>  	usb_disable_device(udev, 0);
> -	mutex_unlock(hcd->bandwidth_mutex);
>  	usb_hcd_synchronize_unlinks(udev);
>  
>  	usb_remove_ep_devs(&udev->ep0);
> Index: usb-3.4/drivers/usb/core/message.c
> ===================================================================
> --- usb-3.4.orig/drivers/usb/core/message.c
> +++ usb-3.4/drivers/usb/core/message.c

You should also get rid of this comment above usb_disable_device():

/*
...
 * Must be called with hcd->bandwidth_mutex held.
 */
void usb_disable_device(struct usb_device *dev, int skip_ep0)
{
..
}

> @@ -1189,7 +1189,9 @@ void usb_disable_device(struct usb_devic
>  			usb_disable_endpoint(dev, i + USB_DIR_IN, false);
>  		}
>  		/* Remove endpoints from the host controller internal state */
> +		mutex_lock(hcd->bandwidth_mutex);
>  		usb_hcd_alloc_bandwidth(dev, NULL, NULL, NULL);
> +		mutex_unlock(hcd->bandwidth_mutex);
>  		/* Second pass: remove endpoint pointers */
>  	}
>  	for (i = skip_ep0; i < 16; ++i) {
> @@ -1749,7 +1751,6 @@ free_interfaces:
>  	/* if it's already configured, clear out old state first.
>  	 * getting rid of old interfaces means unbinding their drivers.
>  	 */
> -	mutex_lock(hcd->bandwidth_mutex);
>  	if (dev->state != USB_STATE_ADDRESS)
>  		usb_disable_device(dev, 1);	/* Skip ep0 */
>  
> @@ -1762,6 +1763,7 @@ free_interfaces:
>  	 * host controller will not allow submissions to dropped endpoints.  If
>  	 * this call fails, the device state is unchanged.
>  	 */
> +	mutex_lock(hcd->bandwidth_mutex);
>  	ret = usb_hcd_alloc_bandwidth(dev, cp, NULL, NULL);
>  	if (ret < 0) {
>  		mutex_unlock(hcd->bandwidth_mutex);
> 

Sarah Sharp
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux