diff options
Diffstat (limited to 'drivers/usb/core/hcd-pci.c')
| -rw-r--r-- | drivers/usb/core/hcd-pci.c | 305 | 
1 files changed, 191 insertions, 114 deletions
diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index 3799573bd38..82044b5d611 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -19,7 +19,6 @@  #include <linux/kernel.h>  #include <linux/module.h>  #include <linux/pci.h> -#include <linux/pm_runtime.h>  #include <linux/usb.h>  #include <linux/usb/hcd.h> @@ -38,119 +37,123 @@  /* PCI-based HCs are common, but plenty of non-PCI HCs are used too */ -#ifdef CONFIG_PM_SLEEP - -/* Coordinate handoffs between EHCI and companion controllers - * during system resume +/* + * Coordinate handoffs between EHCI and companion controllers + * during EHCI probing and system resume.   */ -static DEFINE_MUTEX(companions_mutex); +static DECLARE_RWSEM(companions_rwsem);  #define CL_UHCI		PCI_CLASS_SERIAL_USB_UHCI  #define CL_OHCI		PCI_CLASS_SERIAL_USB_OHCI  #define CL_EHCI		PCI_CLASS_SERIAL_USB_EHCI -enum companion_action { -	SET_HS_COMPANION, CLEAR_HS_COMPANION, WAIT_FOR_COMPANIONS -}; +static inline int is_ohci_or_uhci(struct pci_dev *pdev) +{ +	return pdev->class == CL_OHCI || pdev->class == CL_UHCI; +} + +typedef void (*companion_fn)(struct pci_dev *pdev, struct usb_hcd *hcd, +		struct pci_dev *companion, struct usb_hcd *companion_hcd); -static void companion_common(struct pci_dev *pdev, struct usb_hcd *hcd, -		enum companion_action action) +/* Iterate over PCI devices in the same slot as pdev and call fn for each */ +static void for_each_companion(struct pci_dev *pdev, struct usb_hcd *hcd, +		companion_fn fn)  {  	struct pci_dev		*companion;  	struct usb_hcd		*companion_hcd;  	unsigned int		slot = PCI_SLOT(pdev->devfn); -	/* Iterate through other PCI functions in the same slot. -	 * If pdev is OHCI or UHCI then we are looking for EHCI, and -	 * vice versa. +	/* +	 * Iterate through other PCI functions in the same slot. +	 * If the function's drvdata isn't set then it isn't bound to +	 * a USB host controller driver, so skip it.  	 */  	companion = NULL;  	for_each_pci_dev(companion) {  		if (companion->bus != pdev->bus ||  				PCI_SLOT(companion->devfn) != slot)  			continue; -  		companion_hcd = pci_get_drvdata(companion); -		if (!companion_hcd) +		if (!companion_hcd || !companion_hcd->self.root_hub)  			continue; - -		/* For SET_HS_COMPANION, store a pointer to the EHCI bus in -		 * the OHCI/UHCI companion bus structure. -		 * For CLEAR_HS_COMPANION, clear the pointer to the EHCI bus -		 * in the OHCI/UHCI companion bus structure. -		 * For WAIT_FOR_COMPANIONS, wait until the OHCI/UHCI -		 * companion controllers have fully resumed. -		 */ - -		if ((pdev->class == CL_OHCI || pdev->class == CL_UHCI) && -				companion->class == CL_EHCI) { -			/* action must be SET_HS_COMPANION */ -			dev_dbg(&companion->dev, "HS companion for %s\n", -					dev_name(&pdev->dev)); -			hcd->self.hs_companion = &companion_hcd->self; - -		} else if (pdev->class == CL_EHCI && -				(companion->class == CL_OHCI || -				companion->class == CL_UHCI)) { -			switch (action) { -			case SET_HS_COMPANION: -				dev_dbg(&pdev->dev, "HS companion for %s\n", -						dev_name(&companion->dev)); -				companion_hcd->self.hs_companion = &hcd->self; -				break; -			case CLEAR_HS_COMPANION: -				companion_hcd->self.hs_companion = NULL; -				break; -			case WAIT_FOR_COMPANIONS: -				device_pm_wait_for_dev(&pdev->dev, -						&companion->dev); -				break; -			} -		} +		fn(pdev, hcd, companion, companion_hcd);  	}  } -static void set_hs_companion(struct pci_dev *pdev, struct usb_hcd *hcd) +/* + * We're about to add an EHCI controller, which will unceremoniously grab + * all the port connections away from its companions.  To prevent annoying + * error messages, lock the companion's root hub and gracefully unconfigure + * it beforehand.  Leave it locked until the EHCI controller is all set. + */ +static void ehci_pre_add(struct pci_dev *pdev, struct usb_hcd *hcd, +		struct pci_dev *companion, struct usb_hcd *companion_hcd)  { -	mutex_lock(&companions_mutex); -	dev_set_drvdata(&pdev->dev, hcd); -	companion_common(pdev, hcd, SET_HS_COMPANION); -	mutex_unlock(&companions_mutex); +	struct usb_device *udev; + +	if (is_ohci_or_uhci(companion)) { +		udev = companion_hcd->self.root_hub; +		usb_lock_device(udev); +		usb_set_configuration(udev, 0); +	}  } -static void clear_hs_companion(struct pci_dev *pdev, struct usb_hcd *hcd) +/* + * Adding the EHCI controller has either succeeded or failed.  Set the + * companion pointer accordingly, and in either case, reconfigure and + * unlock the root hub. + */ +static void ehci_post_add(struct pci_dev *pdev, struct usb_hcd *hcd, +		struct pci_dev *companion, struct usb_hcd *companion_hcd)  { -	mutex_lock(&companions_mutex); -	dev_set_drvdata(&pdev->dev, NULL); +	struct usb_device *udev; -	/* If pdev is OHCI or UHCI, just clear its hs_companion pointer */ -	if (pdev->class == CL_OHCI || pdev->class == CL_UHCI) -		hcd->self.hs_companion = NULL; +	if (is_ohci_or_uhci(companion)) { +		if (dev_get_drvdata(&pdev->dev)) {	/* Succeeded */ +			dev_dbg(&pdev->dev, "HS companion for %s\n", +					dev_name(&companion->dev)); +			companion_hcd->self.hs_companion = &hcd->self; +		} +		udev = companion_hcd->self.root_hub; +		usb_set_configuration(udev, 1); +		usb_unlock_device(udev); +	} +} -	/* Otherwise search for companion buses and clear their pointers */ -	else -		companion_common(pdev, hcd, CLEAR_HS_COMPANION); -	mutex_unlock(&companions_mutex); +/* + * We just added a non-EHCI controller.  Find the EHCI controller to + * which it is a companion, and store a pointer to the bus structure. + */ +static void non_ehci_add(struct pci_dev *pdev, struct usb_hcd *hcd, +		struct pci_dev *companion, struct usb_hcd *companion_hcd) +{ +	if (is_ohci_or_uhci(pdev) && companion->class == CL_EHCI) { +		dev_dbg(&pdev->dev, "FS/LS companion for %s\n", +				dev_name(&companion->dev)); +		hcd->self.hs_companion = &companion_hcd->self; +	}  } -static void wait_for_companions(struct pci_dev *pdev, struct usb_hcd *hcd) +/* We are removing an EHCI controller.  Clear the companions' pointers. */ +static void ehci_remove(struct pci_dev *pdev, struct usb_hcd *hcd, +		struct pci_dev *companion, struct usb_hcd *companion_hcd)  { -	/* Only EHCI controllers need to wait. -	 * No locking is needed because a controller cannot be resumed -	 * while one of its companions is getting unbound. -	 */ -	if (pdev->class == CL_EHCI) -		companion_common(pdev, hcd, WAIT_FOR_COMPANIONS); +	if (is_ohci_or_uhci(companion)) +		companion_hcd->self.hs_companion = NULL;  } -#else /* !CONFIG_PM_SLEEP */ +#ifdef	CONFIG_PM -static inline void set_hs_companion(struct pci_dev *d, struct usb_hcd *h) {} -static inline void clear_hs_companion(struct pci_dev *d, struct usb_hcd *h) {} -static inline void wait_for_companions(struct pci_dev *d, struct usb_hcd *h) {} +/* An EHCI controller must wait for its companions before resuming. */ +static void ehci_wait_for_companions(struct pci_dev *pdev, struct usb_hcd *hcd, +		struct pci_dev *companion, struct usb_hcd *companion_hcd) +{ +	if (is_ohci_or_uhci(companion)) +		device_pm_wait_for_dev(&pdev->dev, &companion->dev); +} -#endif /* !CONFIG_PM_SLEEP */ +#endif	/* CONFIG_PM */  /*-------------------------------------------------------------------------*/ @@ -168,12 +171,15 @@ static inline void wait_for_companions(struct pci_dev *d, struct usb_hcd *h) {}   * through the hotplug entry's driver_data.   *   * Store this function in the HCD's struct pci_driver as probe(). + * + * Return: 0 if successful.   */  int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)  {  	struct hc_driver	*driver;  	struct usb_hcd		*hcd;  	int			retval; +	int			hcd_irq = 0;  	if (usb_disabled())  		return -ENODEV; @@ -186,22 +192,31 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)  	if (pci_enable_device(dev) < 0)  		return -ENODEV; -	dev->current_state = PCI_D0; - -	if (!dev->irq) { -		dev_err(&dev->dev, -			"Found HC with no IRQ.  Check BIOS/PCI %s setup!\n", -			pci_name(dev)); -		retval = -ENODEV; -		goto err1; + +	/* +	 * The xHCI driver has its own irq management +	 * make sure irq setup is not touched for xhci in generic hcd code +	 */ +	if ((driver->flags & HCD_MASK) != HCD_USB3) { +		if (!dev->irq) { +			dev_err(&dev->dev, +			"Found HC with no IRQ. Check BIOS/PCI %s setup!\n", +				pci_name(dev)); +			retval = -ENODEV; +			goto disable_pci; +		} +		hcd_irq = dev->irq;  	}  	hcd = usb_create_hcd(driver, &dev->dev, pci_name(dev));  	if (!hcd) {  		retval = -ENOMEM; -		goto err1; +		goto disable_pci;  	} +	hcd->amd_resume_bug = (usb_hcd_amd_remote_wakeup_quirk(dev) && +			driver->flags & (HCD_USB11 | HCD_USB3)) ? 1 : 0; +  	if (driver->flags & HCD_MEMORY) {  		/* EHCI, OHCI */  		hcd->rsrc_start = pci_resource_start(dev, 0); @@ -210,13 +225,13 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)  				driver->description)) {  			dev_dbg(&dev->dev, "controller already in use\n");  			retval = -EBUSY; -			goto err2; +			goto put_hcd;  		}  		hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len);  		if (hcd->regs == NULL) {  			dev_dbg(&dev->dev, "error mapping memory\n");  			retval = -EFAULT; -			goto err3; +			goto release_mem_region;  		}  	} else { @@ -237,32 +252,51 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)  		if (region == PCI_ROM_RESOURCE) {  			dev_dbg(&dev->dev, "no i/o regions available\n");  			retval = -EBUSY; -			goto err2; +			goto put_hcd;  		}  	}  	pci_set_master(dev); -	retval = usb_add_hcd(hcd, dev->irq, IRQF_DISABLED | IRQF_SHARED); +	/* Note: dev_set_drvdata must be called while holding the rwsem */ +	if (dev->class == CL_EHCI) { +		down_write(&companions_rwsem); +		dev_set_drvdata(&dev->dev, hcd); +		for_each_companion(dev, hcd, ehci_pre_add); +		retval = usb_add_hcd(hcd, hcd_irq, IRQF_SHARED); +		if (retval != 0) +			dev_set_drvdata(&dev->dev, NULL); +		for_each_companion(dev, hcd, ehci_post_add); +		up_write(&companions_rwsem); +	} else { +		down_read(&companions_rwsem); +		dev_set_drvdata(&dev->dev, hcd); +		retval = usb_add_hcd(hcd, hcd_irq, IRQF_SHARED); +		if (retval != 0) +			dev_set_drvdata(&dev->dev, NULL); +		else +			for_each_companion(dev, hcd, non_ehci_add); +		up_read(&companions_rwsem); +	} +  	if (retval != 0) -		goto err4; -	set_hs_companion(dev, hcd); +		goto unmap_registers; +	device_wakeup_enable(hcd->self.controller);  	if (pci_dev_run_wake(dev))  		pm_runtime_put_noidle(&dev->dev);  	return retval; - err4: +unmap_registers:  	if (driver->flags & HCD_MEMORY) {  		iounmap(hcd->regs); - err3: +release_mem_region:  		release_mem_region(hcd->rsrc_start, hcd->rsrc_len);  	} else  		release_region(hcd->rsrc_start, hcd->rsrc_len); - err2: -	clear_hs_companion(dev, hcd); +put_hcd:  	usb_put_hcd(hcd); - err1: +disable_pci:  	pci_disable_device(dev);  	dev_err(&dev->dev, "init %s fail, %d\n", pci_name(dev), retval);  	return retval; @@ -303,14 +337,29 @@ void usb_hcd_pci_remove(struct pci_dev *dev)  	usb_hcd_irq(0, hcd);  	local_irq_enable(); -	usb_remove_hcd(hcd); +	/* Note: dev_set_drvdata must be called while holding the rwsem */ +	if (dev->class == CL_EHCI) { +		down_write(&companions_rwsem); +		for_each_companion(dev, hcd, ehci_remove); +		usb_remove_hcd(hcd); +		dev_set_drvdata(&dev->dev, NULL); +		up_write(&companions_rwsem); +	} else { +		/* Not EHCI; just clear the companion pointer */ +		down_read(&companions_rwsem); +		hcd->self.hs_companion = NULL; +		usb_remove_hcd(hcd); +		dev_set_drvdata(&dev->dev, NULL); +		up_read(&companions_rwsem); +	} +  	if (hcd->driver->flags & HCD_MEMORY) {  		iounmap(hcd->regs);  		release_mem_region(hcd->rsrc_start, hcd->rsrc_len);  	} else {  		release_region(hcd->rsrc_start, hcd->rsrc_len);  	} -	clear_hs_companion(dev, hcd); +  	usb_put_hcd(hcd);  	pci_disable_device(dev);  } @@ -336,7 +385,7 @@ void usb_hcd_pci_shutdown(struct pci_dev *dev)  }  EXPORT_SYMBOL_GPL(usb_hcd_pci_shutdown); -#ifdef	CONFIG_PM_OPS +#ifdef	CONFIG_PM  #ifdef	CONFIG_PPC_PMAC  static void powermac_set_asic(struct pci_dev *pci_dev, int enable) @@ -364,14 +413,21 @@ static int check_root_hub_suspended(struct device *dev)  	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;  	} +	if (hcd->shared_hcd) { +		hcd = hcd->shared_hcd; +		if (HCD_RH_RUNNING(hcd)) { +			dev_warn(dev, "Secondary root hub is not suspended\n"); +			return -EBUSY; +		} +	}  	return 0;  } +#if defined(CONFIG_PM_SLEEP) || defined(CONFIG_PM_RUNTIME)  static int suspend_common(struct device *dev, bool do_wakeup)  {  	struct pci_dev		*pci_dev = to_pci_dev(dev); @@ -387,17 +443,22 @@ static int suspend_common(struct device *dev, bool do_wakeup)  	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.  		 */  		if (do_wakeup && HCD_WAKEUP_PENDING(hcd))  			return -EBUSY; +		if (do_wakeup && hcd->shared_hcd && +				HCD_WAKEUP_PENDING(hcd->shared_hcd)) +			return -EBUSY;  		retval = hcd->driver->pci_suspend(hcd, do_wakeup);  		suspend_report_result(hcd->driver->pci_suspend, retval);  		/* Check again in case wakeup raced with pci_suspend */ -		if (retval == 0 && do_wakeup && HCD_WAKEUP_PENDING(hcd)) { +		if ((retval == 0 && do_wakeup && HCD_WAKEUP_PENDING(hcd)) || +				(retval == 0 && do_wakeup && hcd->shared_hcd && +				 HCD_WAKEUP_PENDING(hcd->shared_hcd))) {  			if (hcd->driver->pci_resume)  				hcd->driver->pci_resume(hcd, false);  			retval = -EBUSY; @@ -406,7 +467,12 @@ static int suspend_common(struct device *dev, bool do_wakeup)  			return retval;  	} -	synchronize_irq(pci_dev->irq); +	/* If MSI-X is enabled, the driver will have synchronized all vectors +	 * in pci_suspend(). If MSI or legacy PCI is enabled, that will be +	 * synchronized here. +	 */ +	if (!hcd->msix_enabled) +		synchronize_irq(pci_dev->irq);  	/* Downstream ports from this root hub should already be quiesced, so  	 * there will be no DMA activity.  Now we can shut down the upstream @@ -423,7 +489,9 @@ static int resume_common(struct device *dev, int event)  	struct usb_hcd		*hcd = pci_get_drvdata(pci_dev);  	int			retval; -	if (hcd->state != HC_STATE_SUSPENDED) { +	if (HCD_RH_RUNNING(hcd) || +			(hcd->shared_hcd && +			 HCD_RH_RUNNING(hcd->shared_hcd))) {  		dev_dbg(dev, "can't resume, not suspended!\n");  		return 0;  	} @@ -436,21 +504,29 @@ static int resume_common(struct device *dev, int event)  	pci_set_master(pci_dev); -	clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags); +	if (hcd->driver->pci_resume && !HCD_DEAD(hcd)) { -	if (hcd->driver->pci_resume) { -		if (event != PM_EVENT_AUTO_RESUME) -			wait_for_companions(pci_dev, hcd); +		/* +		 * Only EHCI controllers have to wait for their companions. +		 * No locking is needed because PCI controller drivers do not +		 * get unbound during system resume. +		 */ +		if (pci_dev->class == CL_EHCI && event != PM_EVENT_AUTO_RESUME) +			for_each_companion(pci_dev, hcd, +					ehci_wait_for_companions);  		retval = hcd->driver->pci_resume(hcd,  				event == PM_EVENT_RESTORE);  		if (retval) {  			dev_err(dev, "PCI post-resume error %d!\n", retval); +			if (hcd->shared_hcd) +				usb_hc_died(hcd->shared_hcd);  			usb_hc_died(hcd);  		}  	}  	return retval;  } +#endif	/* SLEEP || RUNTIME */  #ifdef	CONFIG_PM_SLEEP @@ -471,10 +547,11 @@ static int hcd_pci_suspend_noirq(struct device *dev)  	pci_save_state(pci_dev); -	/* If the root hub is HALTed rather than SUSPENDed, -	 * disallow remote wakeup. +	/* If the root hub is dead rather than suspended, disallow remote +	 * wakeup.  usb_hc_died() should ensure that both hosts are marked as +	 * dying, so we only need to check the primary roothub.  	 */ -	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)); @@ -576,4 +653,4 @@ const struct dev_pm_ops usb_hcd_pci_pm_ops = {  };  EXPORT_SYMBOL_GPL(usb_hcd_pci_pm_ops); -#endif	/* CONFIG_PM_OPS */ +#endif	/* CONFIG_PM */  | 
