Re: [PATCH] USB: move usbcore away from hcd->state

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

 



Hi Alan,

I haven't had a chance to look this over too closely, but I really
appreciate the hcd->state cleanup.  I was actually just going to post
the updated USB 3.0 hub/split roothub patches today, so I'll have to
respin them on top of this patch later.

Sarah Sharp

On Mon, Mar 07, 2011 at 11:11:52AM -0500, Alan Stern wrote:
> The hcd->state variable is a disaster.  It's not clearly owned by
> either usbcore or the host controller drivers, and they both change it
> from time to time, potentially stepping on each other's toes.  It's
> not protected by any locks.  And there's no mechanism to prevent it
> from going through an invalid transition.
> 
> This patch (as1451) takes a first step toward fixing these problems.
> As it turns out, usbcore uses hcd->state for essentially only two
> things: checking whether the controller's root hub is running and
> checking whether the controller has died.  Therefore the patch adds
> two new atomic bitflags to the hcd structure, to store these pieces of
> information.  The new flags are used only by usbcore, and a private
> spinlock prevents invalid combinations (a dead controller's root hub
> cannot be running).
> 
> The patch does not change the places where usbcore sets hcd->state,
> since HCDs may depend on them.  Furthermore, there is one place in
> usb_hcd_irq() where usbcore still must use hcd->state: An HCD's
> interrupt handler can implicitly indicate that the controller died by
> setting hcd->state to HC_STATE_HALT.  Nevertheless, the new code is a
> big improvement over the current code.
> 
> The patch makes one other change.  The hcd_bus_suspend() and
> hcd_bus_resume() routines now check first whether the host controller
> has died; if it has then they return immediately without calling the
> HCD's bus_suspend or bus_resume methods.
> 
> This fixes the major problem reported in Bugzilla #29902: The system
> fails to suspend after a host controller dies during system resume.
> 
> Signed-off-by: Alan Stern <stern@xxxxxxxxxxxxxxxxxxx>
> Tested-by: Alex Terekhov <a.terekhov@xxxxxxxxx>
> CC: <stable@xxxxxxxxxx>
> 
> ---
> 
>  drivers/usb/core/hcd-pci.c |   13 ++++------
>  drivers/usb/core/hcd.c     |   55 +++++++++++++++++++++++++++++++++------------
>  include/linux/usb/hcd.h    |    4 +++
>  3 files changed, 51 insertions(+), 21 deletions(-)
> 
> Index: usb-2.6/include/linux/usb/hcd.h
> ===================================================================
> --- usb-2.6.orig/include/linux/usb/hcd.h
> +++ usb-2.6/include/linux/usb/hcd.h
> @@ -99,6 +99,8 @@ struct usb_hcd {
>  #define HCD_FLAG_POLL_RH		2	/* poll for rh status? */
>  #define HCD_FLAG_POLL_PENDING		3	/* status has changed? */
>  #define HCD_FLAG_WAKEUP_PENDING		4	/* root hub is resuming? */
> +#define HCD_FLAG_RH_RUNNING		5	/* root hub is running? */
> +#define HCD_FLAG_DEAD			6	/* controller has died? */
>  
>  	/* The flags can be tested using these macros; they are likely to
>  	 * be slightly faster than test_bit().
> @@ -108,6 +110,8 @@ struct usb_hcd {
>  #define HCD_POLL_RH(hcd)	((hcd)->flags & (1U << HCD_FLAG_POLL_RH))
>  #define HCD_POLL_PENDING(hcd)	((hcd)->flags & (1U << HCD_FLAG_POLL_PENDING))
>  #define HCD_WAKEUP_PENDING(hcd)	((hcd)->flags & (1U << HCD_FLAG_WAKEUP_PENDING))
> +#define HCD_RH_RUNNING(hcd)	((hcd)->flags & (1U << HCD_FLAG_RH_RUNNING))
> +#define HCD_DEAD(hcd)		((hcd)->flags & (1U << HCD_FLAG_DEAD))
>  
>  	/* Flags that get set only during HCD registration or removal. */
>  	unsigned		rh_registered:1;/* is root hub registered? */
> Index: usb-2.6/drivers/usb/core/hcd.c
> ===================================================================
> --- usb-2.6.orig/drivers/usb/core/hcd.c
> +++ usb-2.6/drivers/usb/core/hcd.c
> @@ -983,7 +983,7 @@ static int register_root_hub(struct usb_
>  		spin_unlock_irq (&hcd_root_hub_lock);
>  
>  		/* Did the HC die before the root hub was registered? */
> -		if (hcd->state == HC_STATE_HALT)
> +		if (HCD_DEAD(hcd) || hcd->state == HC_STATE_HALT)
>  			usb_hc_died (hcd);	/* This time clean up */
>  	}
>  
> @@ -1089,13 +1089,10 @@ int usb_hcd_link_urb_to_ep(struct usb_hc
>  	 * Check the host controller's state and add the URB to the
>  	 * endpoint's queue.
>  	 */
> -	switch (hcd->state) {
> -	case HC_STATE_RUNNING:
> -	case HC_STATE_RESUMING:
> +	if (HCD_RH_RUNNING(hcd)) {
>  		urb->unlinked = 0;
>  		list_add_tail(&urb->urb_list, &urb->ep->urb_list);
> -		break;
> -	default:
> +	} else {
>  		rc = -ESHUTDOWN;
>  		goto done;
>  	}
> @@ -1913,7 +1910,7 @@ int usb_hcd_get_frame_number (struct usb
>  {
>  	struct usb_hcd	*hcd = bus_to_hcd(udev->bus);
>  
> -	if (!HC_IS_RUNNING (hcd->state))
> +	if (!HCD_RH_RUNNING(hcd))
>  		return -ESHUTDOWN;
>  	return hcd->driver->get_frame_number (hcd);
>  }
> @@ -1930,9 +1927,15 @@ int hcd_bus_suspend(struct usb_device *r
>  
>  	dev_dbg(&rhdev->dev, "bus %s%s\n",
>  			(msg.event & PM_EVENT_AUTO ? "auto-" : ""), "suspend");
> +	if (HCD_DEAD(hcd)) {
> +		dev_dbg(&rhdev->dev, "skipped %s of dead bus\n", "suspend");
> +		return 0;
> +	}
> +
>  	if (!hcd->driver->bus_suspend) {
>  		status = -ENOENT;
>  	} else {
> +		clear_bit(HCD_FLAG_RH_RUNNING, &hcd->flags);
>  		hcd->state = HC_STATE_QUIESCING;
>  		status = hcd->driver->bus_suspend(hcd);
>  	}
> @@ -1940,7 +1943,12 @@ int hcd_bus_suspend(struct usb_device *r
>  		usb_set_device_state(rhdev, USB_STATE_SUSPENDED);
>  		hcd->state = HC_STATE_SUSPENDED;
>  	} else {
> -		hcd->state = old_state;
> +		spin_lock_irq(&hcd_root_hub_lock);
> +		if (!HCD_DEAD(hcd)) {
> +			set_bit(HCD_FLAG_RH_RUNNING, &hcd->flags);
> +			hcd->state = old_state;
> +		}
> +		spin_unlock_irq(&hcd_root_hub_lock);
>  		dev_dbg(&rhdev->dev, "bus %s fail, err %d\n",
>  				"suspend", status);
>  	}
> @@ -1955,9 +1963,13 @@ int hcd_bus_resume(struct usb_device *rh
>  
>  	dev_dbg(&rhdev->dev, "usb %s%s\n",
>  			(msg.event & PM_EVENT_AUTO ? "auto-" : ""), "resume");
> +	if (HCD_DEAD(hcd)) {
> +		dev_dbg(&rhdev->dev, "skipped %s of dead bus\n", "resume");
> +		return 0;
> +	}
>  	if (!hcd->driver->bus_resume)
>  		return -ENOENT;
> -	if (hcd->state == HC_STATE_RUNNING)
> +	if (HCD_RH_RUNNING(hcd))
>  		return 0;
>  
>  	hcd->state = HC_STATE_RESUMING;
> @@ -1966,10 +1978,15 @@ int hcd_bus_resume(struct usb_device *rh
>  	if (status == 0) {
>  		/* TRSMRCY = 10 msec */
>  		msleep(10);
> -		usb_set_device_state(rhdev, rhdev->actconfig
> -				? USB_STATE_CONFIGURED
> -				: USB_STATE_ADDRESS);
> -		hcd->state = HC_STATE_RUNNING;
> +		spin_lock_irq(&hcd_root_hub_lock);
> +		if (!HCD_DEAD(hcd)) {
> +			usb_set_device_state(rhdev, rhdev->actconfig
> +					? USB_STATE_CONFIGURED
> +					: USB_STATE_ADDRESS);
> +			set_bit(HCD_FLAG_RH_RUNNING, &hcd->flags);
> +			hcd->state = HC_STATE_RUNNING;
> +		}
> +		spin_unlock_irq(&hcd_root_hub_lock);
>  	} else {
>  		hcd->state = old_state;
>  		dev_dbg(&rhdev->dev, "bus %s fail, err %d\n",
> @@ -2080,7 +2097,7 @@ irqreturn_t usb_hcd_irq (int irq, void *
>  	 */
>  	local_irq_save(flags);
>  
> -	if (unlikely(hcd->state == HC_STATE_HALT || !HCD_HW_ACCESSIBLE(hcd))) {
> +	if (unlikely(HCD_DEAD(hcd) || !HCD_HW_ACCESSIBLE(hcd))) {
>  		rc = IRQ_NONE;
>  	} else if (hcd->driver->irq(hcd) == IRQ_NONE) {
>  		rc = IRQ_NONE;
> @@ -2114,6 +2131,8 @@ void usb_hc_died (struct usb_hcd *hcd)
>  	dev_err (hcd->self.controller, "HC died; cleaning up\n");
>  
>  	spin_lock_irqsave (&hcd_root_hub_lock, flags);
> +	clear_bit(HCD_FLAG_RH_RUNNING, &hcd->flags);
> +	set_bit(HCD_FLAG_DEAD, &hcd->flags);
>  	if (hcd->rh_registered) {
>  		clear_bit(HCD_FLAG_POLL_RH, &hcd->flags);
>  
> @@ -2256,6 +2275,12 @@ int usb_add_hcd(struct usb_hcd *hcd,
>  	 */
>  	device_init_wakeup(&rhdev->dev, 1);
>  
> +	/* HCD_FLAG_RH_RUNNING doesn't matter until the root hub is
> +	 * registered.  But since the controller can die at any time,
> +	 * let's initialize the flag before touching the hardware.
> +	 */
> +	set_bit(HCD_FLAG_RH_RUNNING, &hcd->flags);
> +
>  	/* "reset" is misnamed; its role is now one-time init. the controller
>  	 * should already have been reset (and boot firmware kicked off etc).
>  	 */
> @@ -2323,6 +2348,7 @@ int usb_add_hcd(struct usb_hcd *hcd,
>  	return retval;
>  
>  error_create_attr_group:
> +	clear_bit(HCD_FLAG_RH_RUNNING, &hcd->flags);
>  	if (HC_IS_RUNNING(hcd->state))
>  		hcd->state = HC_STATE_QUIESCING;
>  	spin_lock_irq(&hcd_root_hub_lock);
> @@ -2375,6 +2401,7 @@ void usb_remove_hcd(struct usb_hcd *hcd)
>  	usb_get_dev(rhdev);
>  	sysfs_remove_group(&rhdev->dev.kobj, &usb_bus_attr_group);
>  
> +	clear_bit(HCD_FLAG_RH_RUNNING, &hcd->flags);
>  	if (HC_IS_RUNNING (hcd->state))
>  		hcd->state = HC_STATE_QUIESCING;
>  
> Index: usb-2.6/drivers/usb/core/hcd-pci.c
> ===================================================================
> --- usb-2.6.orig/drivers/usb/core/hcd-pci.c
> +++ usb-2.6/drivers/usb/core/hcd-pci.c
> @@ -363,8 +363,7 @@ static int check_root_hub_suspended(stru
>  	struct pci_dev		*pci_dev = to_pci_dev(dev);
>  	struct usb_hcd		*hcd = pci_get_drvdata(pci_dev);
>  
> -	if (!(hcd->state == HC_STATE_SUSPENDED ||
> -			hcd->state == HC_STATE_HALT)) {
> +	if (HCD_RH_RUNNING(hcd)) {
>  		dev_warn(dev, "Root hub is not suspended\n");
>  		return -EBUSY;
>  	}
> @@ -386,7 +385,7 @@ static int suspend_common(struct device 
>  	if (retval)
>  		return retval;
>  
> -	if (hcd->driver->pci_suspend) {
> +	if (hcd->driver->pci_suspend && !HCD_DEAD(hcd)) {
>  		/* Optimization: Don't suspend if a root-hub wakeup is
>  		 * pending and it would cause the HCD to wake up anyway.
>  		 */
> @@ -427,7 +426,7 @@ static int resume_common(struct device *
>  	struct usb_hcd		*hcd = pci_get_drvdata(pci_dev);
>  	int			retval;
>  
> -	if (hcd->state != HC_STATE_SUSPENDED) {
> +	if (HCD_RH_RUNNING(hcd)) {
>  		dev_dbg(dev, "can't resume, not suspended!\n");
>  		return 0;
>  	}
> @@ -442,7 +441,7 @@ static int resume_common(struct device *
>  
>  	clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags);
>  
> -	if (hcd->driver->pci_resume) {
> +	if (hcd->driver->pci_resume && !HCD_DEAD(hcd)) {
>  		if (event != PM_EVENT_AUTO_RESUME)
>  			wait_for_companions(pci_dev, hcd);
>  
> @@ -475,10 +474,10 @@ static int hcd_pci_suspend_noirq(struct 
>  
>  	pci_save_state(pci_dev);
>  
> -	/* If the root hub is HALTed rather than SUSPENDed,
> +	/* If the root hub is dead rather than suspended,
>  	 * disallow remote wakeup.
>  	 */
> -	if (hcd->state == HC_STATE_HALT)
> +	if (HCD_DEAD(hcd))
>  		device_set_wakeup_enable(dev, 0);
>  	dev_dbg(dev, "wakeup: %d\n", device_may_wakeup(dev));
>  
> 
> --
> 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
--
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