diff mbox

[3/5] implement .wakeup_event in PCI bus level

Message ID 1250666656.23178.118.camel@sli10-desk.sh.intel.com (mailing list archive)
State RFC, archived
Headers show

Commit Message

Shaohua Li Aug. 19, 2009, 7:24 a.m. UTC
Add an implementation how to detect wakeup event for PCI bus level. PCI device
can invoke PME and platform or PCIe native approach can collect the PME and
reports to OS. OS should identify exactly which device invokes PME as several
devices can share interrupts.

In platform approach (ACPI in this case), some BIOS give exact device which
invokes PME but others doesn't. 
In PCIe native approach, if PME source device is a pcie endpoint, the device
is the exact PME source. If the device is root port or pcie-to-pci bridge,
we need scan the hierarchy under the device.

To identify PME source, the patch does:
1. if the source is a pci device, the device is the only source for PME
2. if the source is a bridge, scan the hierarchy under the bridge. Several
devices under the bridge could be the source.


---
 drivers/pci/pci-driver.c |   90 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 90 insertions(+)



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

Comments

Rafael Wysocki Aug. 20, 2009, 8:15 p.m. UTC | #1
On Wednesday 19 August 2009, Shaohua Li wrote:
> Add an implementation how to detect wakeup event for PCI bus level. PCI device
> can invoke PME and platform or PCIe native approach can collect the PME and
> reports to OS. OS should identify exactly which device invokes PME as several
> devices can share interrupts.
> 
> In platform approach (ACPI in this case), some BIOS give exact device which
> invokes PME but others doesn't. 
> In PCIe native approach, if PME source device is a pcie endpoint, the device
> is the exact PME source. If the device is root port or pcie-to-pci bridge,
> we need scan the hierarchy under the device.
> 
> To identify PME source, the patch does:
> 1. if the source is a pci device, the device is the only source for PME
> 2. if the source is a bridge, scan the hierarchy under the bridge. Several
> devices under the bridge could be the source.
> 
> 
> ---
>  drivers/pci/pci-driver.c |   90 +++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 90 insertions(+)
> 
> Index: linux/drivers/pci/pci-driver.c
> ===================================================================
> --- linux.orig/drivers/pci/pci-driver.c	2009-08-19 13:43:18.000000000 +0800
> +++ linux/drivers/pci/pci-driver.c	2009-08-19 13:50:29.000000000 +0800
> @@ -570,6 +570,95 @@ static void pci_pm_complete(struct devic
>  		drv->pm->complete(dev);
>  }
>  
> +/*
> + * Called when dev is suspected to invoke a wakeup event
> + * */
> +static bool pci_handle_one_wakeup_event(struct pci_dev *pdev)
> +{
> +	int pme_pos = pdev->pm_cap;
> +	struct pci_driver *drv = pdev->driver;
> +	u16 pmcsr;
> +	bool spurious = false;
> +
> +	if (pme_pos == 0) {
> +		/*
> +		 * Some USB devices haven't PME, but have specific registers to
> +		 * control wakeup
> +		 */
> +		goto out;
> +	}
> +
> +	/* clear PME status and disable PME to avoid interrupt flood */
> +	pci_read_config_word(pdev, pme_pos + PCI_PM_CTRL, &pmcsr);
> +	if (!(pmcsr & PCI_PM_CTRL_PME_STATUS))
> +		return false;
> +	/* I see spurious PME here, just ignore it for now */
> +	if (!(pmcsr & PCI_PM_CTRL_PME_ENABLE))
> +		spurious = true;
> +	else
> +		pmcsr &= ~PCI_PM_CTRL_PME_ENABLE;
> +	pmcsr |= PCI_PM_CTRL_PME_STATUS;
> +	pci_write_config_word(pdev, pme_pos + PCI_PM_CTRL, pmcsr);
> +
> +	if (spurious)
> +		return false;
> +	return true;
> +out:
> +	if (drv && drv->driver.pm && drv->driver.pm->wakeup_event)
> +		return drv->driver.pm->wakeup_event(&pdev->dev);
> +	return false;
> +}
> +
> +static bool pci_pm_wakeup_event(struct device *dev)
> +{
> +	bool ret;
> +	struct pci_dev *tmp = NULL, *target = to_pci_dev(dev);
> +	int domain_nr, bus_start, bus_end;
> +
> +	/*
> +	 * @target could be a bridge or a device.
> +	 * PCIe native PME case:
> +	 *   @target is device - @target must be the exact device invoking PME
> +	 *   @target is a root port or pcie-pci bridge - should scan legacy pci
> +	 *	devices under the bridge
> +	 * ACPI GPE case:
> +	 *   @target is device - AML code could clear PME status before this
> +	 *	routine is called, so we can't detect if @target invokes PME.
> +	 *	Let's trust AML code
> +	 *   @target is bridge - scan devices under the bridge
> +	 * So: if target is device, trust the device invokes PME. If target is
> +	 * bridge, scan devices under the bridge and only trust device invokes
> +	 * PME which we can detect
> +	 **/
> +	ret = pci_handle_one_wakeup_event(target);
> +	if (!target->subordinate || (target->is_pcie &&
> +	    target->pcie_type != PCI_EXP_TYPE_ROOT_PORT &&
> +	    target->pcie_type != PCI_EXP_TYPE_PCI_BRIDGE)) {
> +		/* always trust the device invokes PME even we can't detect */
> +		device_receive_wakeup_event(&target->dev);

How's that thing defined?  We want to call pm_request_resume() at this point.

> +		return true;
> +	}
> +
> +	if (ret)
> +		device_receive_wakeup_event(&target->dev);
> +
> +	/* scan devices under the bridge */
> +	domain_nr = pci_domain_nr(target->bus);
> +	bus_start = target->subordinate->secondary;
> +	bus_end = target->subordinate->subordinate;
> +	while ((tmp = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, tmp)) != NULL) {
> +		if (pci_domain_nr(tmp->bus) == domain_nr &&
> +		   tmp->bus->number >= bus_start &&
> +		   tmp->bus->number <= bus_end) {
> +			if (pci_handle_one_wakeup_event(tmp)) {
> +				ret = true;
> +				device_receive_wakeup_event(&tmp->dev);

And here.

> +			}
> +		}
> +	}
> +	return ret;
> +}
> +
>  #ifdef CONFIG_SUSPEND
>  
>  static int pci_pm_suspend(struct device *dev)
> @@ -925,6 +1014,7 @@ struct dev_pm_ops pci_dev_pm_ops = {
>  	.thaw_noirq = pci_pm_thaw_noirq,
>  	.poweroff_noirq = pci_pm_poweroff_noirq,
>  	.restore_noirq = pci_pm_restore_noirq,
> +	.wakeup_event = pci_pm_wakeup_event,

Please don't do that.

>  };
>  
>  #define PCI_PM_OPS_PTR	(&pci_dev_pm_ops)

Best,
Rafael
--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

Index: linux/drivers/pci/pci-driver.c
===================================================================
--- linux.orig/drivers/pci/pci-driver.c	2009-08-19 13:43:18.000000000 +0800
+++ linux/drivers/pci/pci-driver.c	2009-08-19 13:50:29.000000000 +0800
@@ -570,6 +570,95 @@  static void pci_pm_complete(struct devic
 		drv->pm->complete(dev);
 }
 
+/*
+ * Called when dev is suspected to invoke a wakeup event
+ * */
+static bool pci_handle_one_wakeup_event(struct pci_dev *pdev)
+{
+	int pme_pos = pdev->pm_cap;
+	struct pci_driver *drv = pdev->driver;
+	u16 pmcsr;
+	bool spurious = false;
+
+	if (pme_pos == 0) {
+		/*
+		 * Some USB devices haven't PME, but have specific registers to
+		 * control wakeup
+		 */
+		goto out;
+	}
+
+	/* clear PME status and disable PME to avoid interrupt flood */
+	pci_read_config_word(pdev, pme_pos + PCI_PM_CTRL, &pmcsr);
+	if (!(pmcsr & PCI_PM_CTRL_PME_STATUS))
+		return false;
+	/* I see spurious PME here, just ignore it for now */
+	if (!(pmcsr & PCI_PM_CTRL_PME_ENABLE))
+		spurious = true;
+	else
+		pmcsr &= ~PCI_PM_CTRL_PME_ENABLE;
+	pmcsr |= PCI_PM_CTRL_PME_STATUS;
+	pci_write_config_word(pdev, pme_pos + PCI_PM_CTRL, pmcsr);
+
+	if (spurious)
+		return false;
+	return true;
+out:
+	if (drv && drv->driver.pm && drv->driver.pm->wakeup_event)
+		return drv->driver.pm->wakeup_event(&pdev->dev);
+	return false;
+}
+
+static bool pci_pm_wakeup_event(struct device *dev)
+{
+	bool ret;
+	struct pci_dev *tmp = NULL, *target = to_pci_dev(dev);
+	int domain_nr, bus_start, bus_end;
+
+	/*
+	 * @target could be a bridge or a device.
+	 * PCIe native PME case:
+	 *   @target is device - @target must be the exact device invoking PME
+	 *   @target is a root port or pcie-pci bridge - should scan legacy pci
+	 *	devices under the bridge
+	 * ACPI GPE case:
+	 *   @target is device - AML code could clear PME status before this
+	 *	routine is called, so we can't detect if @target invokes PME.
+	 *	Let's trust AML code
+	 *   @target is bridge - scan devices under the bridge
+	 * So: if target is device, trust the device invokes PME. If target is
+	 * bridge, scan devices under the bridge and only trust device invokes
+	 * PME which we can detect
+	 **/
+	ret = pci_handle_one_wakeup_event(target);
+	if (!target->subordinate || (target->is_pcie &&
+	    target->pcie_type != PCI_EXP_TYPE_ROOT_PORT &&
+	    target->pcie_type != PCI_EXP_TYPE_PCI_BRIDGE)) {
+		/* always trust the device invokes PME even we can't detect */
+		device_receive_wakeup_event(&target->dev);
+		return true;
+	}
+
+	if (ret)
+		device_receive_wakeup_event(&target->dev);
+
+	/* scan devices under the bridge */
+	domain_nr = pci_domain_nr(target->bus);
+	bus_start = target->subordinate->secondary;
+	bus_end = target->subordinate->subordinate;
+	while ((tmp = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, tmp)) != NULL) {
+		if (pci_domain_nr(tmp->bus) == domain_nr &&
+		   tmp->bus->number >= bus_start &&
+		   tmp->bus->number <= bus_end) {
+			if (pci_handle_one_wakeup_event(tmp)) {
+				ret = true;
+				device_receive_wakeup_event(&tmp->dev);
+			}
+		}
+	}
+	return ret;
+}
+
 #ifdef CONFIG_SUSPEND
 
 static int pci_pm_suspend(struct device *dev)
@@ -925,6 +1014,7 @@  struct dev_pm_ops pci_dev_pm_ops = {
 	.thaw_noirq = pci_pm_thaw_noirq,
 	.poweroff_noirq = pci_pm_poweroff_noirq,
 	.restore_noirq = pci_pm_restore_noirq,
+	.wakeup_event = pci_pm_wakeup_event,
 };
 
 #define PCI_PM_OPS_PTR	(&pci_dev_pm_ops)