Re: USB suspend and resume for PCI host controllers

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

 



On Monday, 8 of December 2008, Alan Stern wrote:
> Rafael:

Hi,
 
> Here's the current version of my patch.  All of the PCI stuff has been 
> moved to the suspend_late and resume_early routines.
> 
> Now maybe that's not the right thing to do, or at least not until some 
> further fixes have been added.  All I can tell you is that this looks 
> right to me, but it doesn't work.  When I tried it, all the I/O and 
> MMIO accesses after resume_early returned nothing but 0xffffffff.

Well, it would be good to understand why this happens.

> Here's a sample extracted from the log:
> 
> [  472.248029] ehci_hcd 0000:00:1d.7: resume from previous PCI D3: 0
> [  472.248139] ehci_hcd 0000:00:1d.7: restoring config space at offset 0xf (was 0x400, writing 0x0)
> [  472.248283] ehci_hcd 0000:00:1d.7: restoring config space at offset 0xd (was 0x50, writing 0x0)
> [  472.248430] ehci_hcd 0000:00:1d.7: restoring config space at offset 0xb (was 0x52478086, writing 0x0)
> [  472.248591] ehci_hcd 0000:00:1d.7: restoring config space at offset 0x2 (was 0xc032002, writing 0x0)
> [  472.248735] ehci_hcd 0000:00:1d.7: restoring config space at offset 0x1 (was 0x2900000, writing 0x0)
> [  472.248878] ehci_hcd 0000:00:1d.7: restoring config space at offset 0x0 (was 0x24cd8086, writing 0x0)
> [  472.249037] ehci_hcd 0000:00:1d.7: enabling device (0000 -> 0002)
> [  472.249136] ehci_hcd 0000:00:1d.7: PCI INT D -> GSI 23 (level, low) -> IRQ 23
> [  472.249237] ehci_hcd 0000:00:1d.7: setting latency timer to 64
> [  472.264026] ohci_hcd 0000:01:00.0: resume from previous PCI D3: 0
> [  472.264130] ohci_hcd 0000:01:00.0: restoring config space at offset 0xf (was 0x2a01010b, writing 0x0)
> [  472.264275] ohci_hcd 0000:01:00.0: restoring config space at offset 0xd (was 0x40, writing 0x0)
> [  472.264418] ohci_hcd 0000:01:00.0: restoring config space at offset 0xb (was 0x351033, writing 0x0)
> [  472.264571] ohci_hcd 0000:01:00.0: restoring config space at offset 0x4 (was 0xff8fd000, writing 0x0)
> [  472.264713] ohci_hcd 0000:01:00.0: restoring config space at offset 0x3 (was 0x802008, writing 0x0)
> [  472.264855] ohci_hcd 0000:01:00.0: restoring config space at offset 0x2 (was 0xc031043, writing 0x0)
> [  472.264997] ohci_hcd 0000:01:00.0: restoring config space at offset 0x1 (was 0x2100000, writing 0x0)
> [  472.265139] ohci_hcd 0000:01:00.0: restoring config space at offset 0x0 (was 0x351033, writing 0x0)

All of the above writes don't look good.  Only zeros were written, looks like
the saved state was bogus.

> [  472.265289] ohci_hcd 0000:01:00.0: enabling device (0000 -> 0002)
> [  472.265385] ohci_hcd 0000:01:00.0: PCI INT A -> GSI 21 (level, low) -> IRQ 21
> [  472.265484] ohci_hcd 0000:01:00.0: setting latency timer to 64
> ...
> [  472.350778] ehci_hcd 0000:00:1d.7: lost power, restarting
> [  472.350869] usb usb6: root hub lost power or was reset
> [  472.350964] ehci_hcd 0000:00:1d.7: reset command ffffffff park=3 ithresh=63 LReset IAAD Async Periodic period=?? R
> [  472.351120] ehci_hcd 0000:00:1d.7: debug port 1 IN USE
> [  472.351216] ehci_hcd 0000:00:1d.7: cache line size of 128 is not supported
> [  472.352143] ohci_hcd 0000:01:00.0: BIOS/SMM active, control ffffffff
> [  472.352237] ohci_hcd 0000:01:00.0: USB HC TakeOver from BIOS/SMM
> [  480.352022] ohci_hcd 0000:01:00.0: USB HC takeover failed!  (BIOS/SMM bug)
> [  480.368072] ohci_hcd 0000:01:00.0: USB HC reset timed out!
> [  480.368165] ohci_hcd 0000:01:00.0: can't restart, -1
> [  480.368253] usb usb9: root hub lost power or was reset
> [  480.368343] ohci_hcd 0000:01:00.0: already suspended
> 
> Alan Stern
> 
> 
> 
> Index: usb-2.6/drivers/usb/core/hcd.h
> ===================================================================
> --- usb-2.6.orig/drivers/usb/core/hcd.h
> +++ usb-2.6/drivers/usb/core/hcd.h
> @@ -256,7 +256,9 @@ extern int usb_hcd_pci_probe(struct pci_
>  extern void usb_hcd_pci_remove(struct pci_dev *dev);
>  
>  #ifdef CONFIG_PM
> -extern int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t state);
> +extern int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t msg);
> +extern int usb_hcd_pci_suspend_late(struct pci_dev *dev, pm_message_t msg);
> +extern int usb_hcd_pci_resume_early(struct pci_dev *dev);
>  extern int usb_hcd_pci_resume(struct pci_dev *dev);
>  #endif /* CONFIG_PM */
>  
> 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
> @@ -191,17 +191,15 @@ EXPORT_SYMBOL_GPL(usb_hcd_pci_remove);
>  /**
>   * usb_hcd_pci_suspend - power management suspend of a PCI-based HCD
>   * @dev: USB Host Controller being suspended
> - * @message: semantics in flux
> + * @message: Power Management message describing this state transition
>   *
> - * Store this function in the HCD's struct pci_driver as suspend().
> + * Store this function in the HCD's struct pci_driver as .suspend.
>   */
>  int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message)
>  {
> -	struct usb_hcd		*hcd;
> +	struct usb_hcd		*hcd = pci_get_drvdata(dev);
>  	int			retval = 0;
> -	int			has_pci_pm;
>  
> -	hcd = pci_get_drvdata(dev);
>  
>  	/* Root hub suspend should have stopped all downstream traffic,
>  	 * and all bus master traffic.  And done so for both the interface
> @@ -212,96 +210,86 @@ int usb_hcd_pci_suspend(struct pci_dev *
>  	 * otherwise the swsusp will save (and restore) garbage state.
>  	 */
>  	if (!(hcd->state == HC_STATE_SUSPENDED ||
> -			hcd->state == HC_STATE_HALT))
> -		return -EBUSY;
> +			hcd->state == HC_STATE_HALT)) {
> +		dev_warn(&dev->dev, "Root hub is not suspended\n");
> +		retval = -EBUSY;
>  
> -	if (hcd->driver->pci_suspend) {
> +	} else if (hcd->driver->pci_suspend) {
>  		retval = hcd->driver->pci_suspend(hcd, message);
>  		suspend_report_result(hcd->driver->pci_suspend, retval);
> -		if (retval)
> -			goto done;
>  	}
> -	synchronize_irq(dev->irq);
>  
> -	/* FIXME until the generic PM interfaces change a lot more, this
> -	 * can't use PCI D1 and D2 states.  For example, the confusion
> -	 * between messages and states will need to vanish, and messages
> -	 * will need to provide a target system state again.
> -	 *
> -	 * It'll be important to learn characteristics of the target state,
> -	 * especially on embedded hardware where the HCD will often be in
> -	 * charge of an external VBUS power supply and one or more clocks.
> -	 * Some target system states will leave them active; others won't.
> -	 * (With PCI, that's often handled by platform BIOS code.)
> -	 */
> +	if (retval == 0)
> +		synchronize_irq(dev->irq);
> +	return retval;
> +}
> +EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend);
> +
> +/**
> + * usb_hcd_pci_suspend_late - suspend a PCI-based HCD after IRQs are disabled
> + * @dev: USB Host Controller being suspended
> + * @message: Power Management message describing this state transition
> + *
> + * Store this function in the HCD's struct pci_driver as .suspend_late.
> + */
> +int usb_hcd_pci_suspend_late(struct pci_dev *dev, pm_message_t message)
> +{
> +	int			retval = 0;
> +	int			has_pci_pm;
> +	struct usb_hcd		*hcd = pci_get_drvdata(dev);
> +	int			wake;
> +
> +	/* We might already be suspended (runtime PM -- not yet written) */
> +	if (dev->current_state != PCI_D0)
> +		goto done;
> +
> +	/* Don't change state if we don't need to */
> +	if (message.event == PM_EVENT_FREEZE ||
> +			message.event == PM_EVENT_PRETHAW) {
> +		dev_dbg(&dev->dev, "--> no state change\n");
> +		goto done;
> +	}
>  
> -	/* even when the PCI layer rejects some of the PCI calls
> -	 * below, HCs can try global suspend and reduce DMA traffic.
> -	 * PM-sensitive HCDs may already have done this.
> -	 */
>  	has_pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM);
> +	if (!has_pci_pm) {
> +		dev_dbg(&dev->dev, "--> PCI D0/legacy\n");
> +		goto done;
> +	}
> +
> +	/* Ignore these return values.  We rely on pci code to
> +	 * reject requests the hardware can't implement, rather
> +	 * than coding the same thing.
> +	 */
> +	wake = (hcd->state == HC_STATE_SUSPENDED &&
> +			device_may_wakeup(&dev->dev));
> +	(void) pci_enable_wake(dev, PCI_D3hot, wake);
> +	(void) pci_enable_wake(dev, PCI_D3cold, wake);

I'm not sure if this is safe to call these with interrupts disabled.  Also,
there is pci_wake_from_d3() to call in such a context.

>  	/* Downstream ports from this root hub should already be quiesced, so
>  	 * there will be no DMA activity.  Now we can shut down the upstream
>  	 * link (except maybe for PME# resume signaling) and enter some PCI
>  	 * low power state, if the hardware allows.
>  	 */
> -	if (hcd->state == HC_STATE_SUSPENDED) {
> -
> -		/* no DMA or IRQs except when HC is active */
> -		if (dev->current_state == PCI_D0) {
> -			pci_save_state(dev);
> -			pci_disable_device(dev);
> -		}
> -
> -		if (message.event == PM_EVENT_FREEZE ||
> -				message.event == PM_EVENT_PRETHAW) {
> -			dev_dbg(hcd->self.controller, "--> no state change\n");
> -			goto done;
> -		}
> -
> -		if (!has_pci_pm) {
> -			dev_dbg(hcd->self.controller, "--> PCI D0/legacy\n");
> -			goto done;
> -		}
> +	pci_disable_device(dev);
>  
> -		/* NOTE:  dev->current_state becomes nonzero only here, and
> -		 * only for devices that support PCI PM.  Also, exiting
> -		 * PCI_D3 (but not PCI_D1 or PCI_D2) is allowed to reset
> -		 * some device state (e.g. as part of clock reinit).
> -		 */
> -		retval = pci_set_power_state(dev, PCI_D3hot);
> -		suspend_report_result(pci_set_power_state, retval);
> -		if (retval == 0) {
> -			int wake = device_can_wakeup(&hcd->self.root_hub->dev);
> -
> -			wake = wake && device_may_wakeup(hcd->self.controller);
> -
> -			dev_dbg(hcd->self.controller, "--> PCI D3%s\n",
> -					wake ? "/wakeup" : "");
> -
> -			/* Ignore these return values.  We rely on pci code to
> -			 * reject requests the hardware can't implement, rather
> -			 * than coding the same thing.
> -			 */
> -			(void) pci_enable_wake(dev, PCI_D3hot, wake);
> -			(void) pci_enable_wake(dev, PCI_D3cold, wake);
> -		} else {
> -			dev_dbg(&dev->dev, "PCI D3 suspend fail, %d\n",
> -					retval);
> -			(void) usb_hcd_pci_resume(dev);
> -		}
> -
> -	} else if (hcd->state != HC_STATE_HALT) {
> -		dev_dbg(hcd->self.controller, "hcd state %d; not suspended\n",
> -			hcd->state);
> -		WARN_ON(1);
> -		retval = -EINVAL;
> +	/* NOTE:  dev->current_state becomes nonzero only here, and
> +	 * only for devices that support PCI PM.  Also, exiting
> +	 * PCI_D3 (but not PCI_D1 or PCI_D2) is allowed to reset
> +	 * some device state (e.g. as part of clock reinit).
> +	 */
> +	retval = pci_set_power_state(dev, PCI_D3hot);

I'm not sure if it's safe to call this with interrupts disabled too.  It
invokes some ACPI callbacks that in turn use the AML interpreter, so I guess
it is not, in general.

> +	suspend_report_result(pci_set_power_state, retval);
> +	if (retval == 0) {
> +		dev_dbg(hcd->self.controller, "--> PCI D3%s\n",
> +				wake ? "/wakeup" : "");
> +	} else {
> +		dev_dbg(&dev->dev, "PCI D3 suspend fail, %d\n", retval);
> +		(void) usb_hcd_pci_resume(dev);
>  	}
>  
> -done:
> -	if (retval == 0) {
> + done:
>  #ifdef CONFIG_PPC_PMAC
> +	if (retval == 0) {
>  		/* Disable ASIC clocks for USB */
>  		if (machine_is(powermac)) {
>  			struct device_node	*of_node;
> @@ -311,30 +299,23 @@ done:
>  				pmac_call_feature(PMAC_FTR_USB_ENABLE,
>  							of_node, 0, 0);
>  		}
> -#endif
>  	}
> +#endif
>  
>  	return retval;
>  }
> -EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend);
> +EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend_late);

Well, it looks like you forgot to actually call pci_save_power_state(). :-)

Thanks,
Rafael
_______________________________________________
linux-pm mailing list
linux-pm@xxxxxxxxxxxxxxxxxxxxxxxxxx
https://lists.linux-foundation.org/mailman/listinfo/linux-pm

[Index of Archives]     [Linux ACPI]     [Netdev]     [Ethernet Bridging]     [Linux Wireless]     [CPU Freq]     [Kernel Newbies]     [Fedora Kernel]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux